From cc282f998555bcdfafea2749cef132d794aece9a Mon Sep 17 00:00:00 2001 From: James Harrison <00jamesh@gmail.com> Date: Thu, 30 Apr 2020 14:25:42 +0100 Subject: [PATCH 1/2] Add support for serving multiple paths Allows users to specify multiple Endpoints, and register Collectables to each Endpoint. E.g., /metrics and /metricsHighCardinality Can both be served by the same MultiExposer, but may expose different Collectables. This may be useful if, for example, one wants to scrape groups of metrics with different frequencies. Each Endpoint must have a unique URI. --- pull/CMakeLists.txt | 1 + pull/include/prometheus/endpoint.h | 34 +++++++++++ pull/include/prometheus/exposer.h | 43 ++++++++++---- pull/src/endpoint.cc | 28 +++++++++ pull/src/exposer.cc | 48 ++++++++++------ pull/tests/integration/BUILD.bazel | 6 ++ pull/tests/integration/CMakeLists.txt | 10 ++++ pull/tests/integration/sample_server_multi.cc | 57 +++++++++++++++++++ 8 files changed, 199 insertions(+), 28 deletions(-) create mode 100644 pull/include/prometheus/endpoint.h create mode 100644 pull/src/endpoint.cc create mode 100644 pull/tests/integration/sample_server_multi.cc diff --git a/pull/CMakeLists.txt b/pull/CMakeLists.txt index d278ba99..cb8f1688 100644 --- a/pull/CMakeLists.txt +++ b/pull/CMakeLists.txt @@ -14,6 +14,7 @@ if(ENABLE_COMPRESSION) endif() add_library(pull + src/endpoint.cc src/exposer.cc src/handler.cc src/handler.h diff --git a/pull/include/prometheus/endpoint.h b/pull/include/prometheus/endpoint.h new file mode 100644 index 00000000..b3f8b900 --- /dev/null +++ b/pull/include/prometheus/endpoint.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "prometheus/collectable.h" +#include "prometheus/detail/pull_export.h" +#include "prometheus/registry.h" + +namespace prometheus { + +namespace detail { +class MetricsHandler; +} // namespace detail + +class PROMETHEUS_CPP_PULL_EXPORT Endpoint { + public: + explicit Endpoint(std::string uri); + ~Endpoint(); + + void RegisterCollectable(const std::weak_ptr& collectable); + + detail::MetricsHandler* getMetricsHandler() const; + + const std::string& getURI() const; + + private: + std::vector> collectables_; + // registry for "meta" metrics about the endpoint itself + std::shared_ptr endpoint_registry_; + std::unique_ptr metrics_handler_; + std::string uri_; +}; + +} // namespace prometheus diff --git a/pull/include/prometheus/exposer.h b/pull/include/prometheus/exposer.h index f58cfb3d..26369cbb 100644 --- a/pull/include/prometheus/exposer.h +++ b/pull/include/prometheus/exposer.h @@ -18,24 +18,45 @@ namespace detail { class MetricsHandler; } // namespace detail -class PROMETHEUS_CPP_PULL_EXPORT Exposer { +class Endpoint; + +/** + * Exposer capable of serving different groups of Collectables + * on different paths. + */ +class PROMETHEUS_CPP_PULL_EXPORT MultiExposer { + public: + MultiExposer(const std::string& bind_address, + std::vector> endpoints, + const std::size_t num_threads = 2); + + MultiExposer(std::vector options, + std::vector> endpoints); + + virtual ~MultiExposer(); + + std::vector GetListeningPorts() const; + + protected: + std::unique_ptr server_; + std::vector> endpoints_; +}; + +/** + * Exposer serving a group of Collectables on a single path. + * + * Provides a simpler interface than directly using a MultiExposer with + * a single Endpoint. + */ +class PROMETHEUS_CPP_PULL_EXPORT Exposer : public MultiExposer { public: explicit Exposer(const std::string& bind_address, const std::string& uri = std::string("/metrics"), const std::size_t num_threads = 2); explicit Exposer(std::vector options, const std::string& uri = std::string("/metrics")); - ~Exposer(); - void RegisterCollectable(const std::weak_ptr& collectable); - std::vector GetListeningPorts() const; - - private: - std::unique_ptr server_; - std::vector> collectables_; - std::shared_ptr exposer_registry_; - std::unique_ptr metrics_handler_; - std::string uri_; + void RegisterCollectable(const std::weak_ptr& collectable); }; } // namespace prometheus diff --git a/pull/src/endpoint.cc b/pull/src/endpoint.cc new file mode 100644 index 00000000..902e79b3 --- /dev/null +++ b/pull/src/endpoint.cc @@ -0,0 +1,28 @@ +#include "prometheus/endpoint.h" + +#include "handler.h" + +namespace prometheus { + +Endpoint::Endpoint(std::string uri) + : endpoint_registry_(std::make_shared()), + metrics_handler_( + new detail::MetricsHandler{collectables_, *endpoint_registry_}), + uri_(std::move(uri)) { + RegisterCollectable(endpoint_registry_); +} + +Endpoint::~Endpoint() = default; + +void Endpoint::RegisterCollectable( + const std::weak_ptr& collectable) { + collectables_.push_back(collectable); +} + +detail::MetricsHandler* Endpoint::getMetricsHandler() const { + return metrics_handler_.get(); +} + +const std::string& Endpoint::getURI() const { return uri_; } + +} // namespace prometheus diff --git a/pull/src/exposer.cc b/pull/src/exposer.cc index 7e97b14e..250e9acc 100644 --- a/pull/src/exposer.cc +++ b/pull/src/exposer.cc @@ -6,38 +6,52 @@ #include "prometheus/client_metric.h" #include "prometheus/detail/future_std.h" +#include "prometheus/endpoint.h" #include "CivetServer.h" #include "handler.h" namespace prometheus { -Exposer::Exposer(const std::string& bind_address, const std::string& uri, - const std::size_t num_threads) - : Exposer( +MultiExposer::MultiExposer(const std::string& bind_address, + std::vector> endpoints, + const std::size_t num_threads) + : MultiExposer( std::vector{"listening_ports", bind_address, "num_threads", std::to_string(num_threads)}, - uri) {} + std::move(endpoints)) {} -Exposer::Exposer(std::vector options, const std::string& uri) +MultiExposer::MultiExposer(std::vector options, + std::vector> endpoints) : server_(detail::make_unique(std::move(options))), - exposer_registry_(std::make_shared()), - metrics_handler_( - new detail::MetricsHandler{collectables_, *exposer_registry_}), - uri_(uri) { - RegisterCollectable(exposer_registry_); - server_->addHandler(uri, metrics_handler_.get()); + endpoints_(std::move(endpoints)) { + for (const auto& endpoint : endpoints_) { + server_->addHandler(endpoint->getURI(), endpoint->getMetricsHandler()); + } } -Exposer::~Exposer() { server_->removeHandler(uri_); } - -void Exposer::RegisterCollectable( - const std::weak_ptr& collectable) { - collectables_.push_back(collectable); +MultiExposer::~MultiExposer() { + for (const auto& endpoint : endpoints_) { + server_->removeHandler(endpoint->getURI()); + } } -std::vector Exposer::GetListeningPorts() const { +std::vector MultiExposer::GetListeningPorts() const { return server_->getListeningPorts(); } +Exposer::Exposer(const std::string& bind_address, const std::string& uri, + const std::size_t num_threads) + : MultiExposer(bind_address, {std::make_shared(uri)}, + num_threads) {} + +Exposer::Exposer(std::vector options, const std::string& uri) + : MultiExposer(std::move(options), {std::make_shared(uri)}) {} + +void Exposer::RegisterCollectable( + const std::weak_ptr& collectable) { + // Exposer is guaranteed to have a single Endpoint. + endpoints_.at(0)->RegisterCollectable(collectable); +} + } // namespace prometheus diff --git a/pull/tests/integration/BUILD.bazel b/pull/tests/integration/BUILD.bazel index 4104a6a5..2e172af6 100644 --- a/pull/tests/integration/BUILD.bazel +++ b/pull/tests/integration/BUILD.bazel @@ -4,6 +4,12 @@ cc_binary( deps = ["//pull"], ) +cc_binary( + name = "sample-server_multi", + srcs = ["sample_server_multi.cc"], + deps = ["//pull"], +) + sh_test( name = "scrape-test", size = "small", diff --git a/pull/tests/integration/CMakeLists.txt b/pull/tests/integration/CMakeLists.txt index 1ba22cdc..ecf64aa1 100644 --- a/pull/tests/integration/CMakeLists.txt +++ b/pull/tests/integration/CMakeLists.txt @@ -7,3 +7,13 @@ target_link_libraries(sample_server PRIVATE ${PROJECT_NAME}::pull ) + + +add_executable(sample_server_multi + sample_server_multi.cc + ) + +target_link_libraries(sample_server_multi + PRIVATE + ${PROJECT_NAME}::pull + ) diff --git a/pull/tests/integration/sample_server_multi.cc b/pull/tests/integration/sample_server_multi.cc new file mode 100644 index 00000000..25de39eb --- /dev/null +++ b/pull/tests/integration/sample_server_multi.cc @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include +#include +#include + +int main() { + using namespace prometheus; + + auto endpointA = std::make_shared("/metricsA"); + auto endpointB = std::make_shared("/metricsB"); + + auto registryA = std::make_shared(); + + // add a new counter family to the registry (families combine values with the + // same name, but distinct label dimensions) + auto& counter_familyA = BuildCounter() + .Name("time_running_seconds_total") + .Help("How many seconds is this server running?") + .Labels({{"label", "foo"}}) + .Register(*registryA); + + // add a counter to the metric family + auto& seconds_counterA = counter_familyA.Add( + {{"another_label", "bar"}, {"yet_another_label", "baz"}}); + + // ask the exposer to scrape registryA on incoming scrapes for "/metricsA" + endpointA->RegisterCollectable(registryA); + + auto registryB = std::make_shared(); + + auto& counter_familyB = BuildCounter() + .Name("other_time_running_seconds_total") + .Help("How many seconds has something else been running?") + .Labels({{"label", "not_foo"}}) + .Register(*registryB); + + auto& seconds_counterB = counter_familyB.Add( + {{"another_label", "not_bar"}, {"yet_another_label", "not_baz"}}); + + // This endpoint exposes registryB. + endpointB->RegisterCollectable(registryB); + + // create an http server running on port 8080 + MultiExposer exposer{"127.0.0.1:8080", {endpointA, endpointB}, 1}; + + for (;;) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + // increment the counters by one (second) + seconds_counterA.Increment(); + seconds_counterB.Increment(); + } + return 0; +} From 9fc89d4abe8accb851ff4e7418771b948c2a6514 Mon Sep 17 00:00:00 2001 From: Gregor Jasny Date: Mon, 25 May 2020 18:12:52 +0200 Subject: [PATCH 2/2] Extend existing exposer interface --- pull/CMakeLists.txt | 1 + pull/include/prometheus/exposer.h | 46 ++++---------- pull/src/endpoint.cc | 21 ++++--- pull/{include/prometheus => src}/endpoint.h | 17 ++--- pull/src/exposer.cc | 63 ++++++++----------- pull/src/handler.cc | 5 +- pull/tests/integration/CMakeLists.txt | 11 ++-- pull/tests/integration/sample_server.cc | 10 +-- pull/tests/integration/sample_server_multi.cc | 27 ++++---- 9 files changed, 84 insertions(+), 117 deletions(-) rename pull/{include/prometheus => src}/endpoint.h (74%) diff --git a/pull/CMakeLists.txt b/pull/CMakeLists.txt index cb8f1688..bfdf1351 100644 --- a/pull/CMakeLists.txt +++ b/pull/CMakeLists.txt @@ -15,6 +15,7 @@ endif() add_library(pull src/endpoint.cc + src/endpoint.h src/exposer.cc src/handler.cc src/handler.h diff --git a/pull/include/prometheus/exposer.h b/pull/include/prometheus/exposer.h index 26369cbb..f8122f01 100644 --- a/pull/include/prometheus/exposer.h +++ b/pull/include/prometheus/exposer.h @@ -15,48 +15,26 @@ class CivetServer; namespace prometheus { namespace detail { +class Endpoint; class MetricsHandler; } // namespace detail -class Endpoint; - -/** - * Exposer capable of serving different groups of Collectables - * on different paths. - */ -class PROMETHEUS_CPP_PULL_EXPORT MultiExposer { +class PROMETHEUS_CPP_PULL_EXPORT Exposer { public: - MultiExposer(const std::string& bind_address, - std::vector> endpoints, - const std::size_t num_threads = 2); - - MultiExposer(std::vector options, - std::vector> endpoints); - - virtual ~MultiExposer(); + explicit Exposer(const std::string& bind_address, + const std::size_t num_threads = 2); + explicit Exposer(std::vector options); + ~Exposer(); + void RegisterCollectable(const std::weak_ptr& collectable, + const std::string& uri = std::string("/metrics")); std::vector GetListeningPorts() const; - protected: - std::unique_ptr server_; - std::vector> endpoints_; -}; + private: + detail::Endpoint& GetEndpointForUri(const std::string& uri); -/** - * Exposer serving a group of Collectables on a single path. - * - * Provides a simpler interface than directly using a MultiExposer with - * a single Endpoint. - */ -class PROMETHEUS_CPP_PULL_EXPORT Exposer : public MultiExposer { - public: - explicit Exposer(const std::string& bind_address, - const std::string& uri = std::string("/metrics"), - const std::size_t num_threads = 2); - explicit Exposer(std::vector options, - const std::string& uri = std::string("/metrics")); - - void RegisterCollectable(const std::weak_ptr& collectable); + std::unique_ptr server_; + std::vector> endpoints_; }; } // namespace prometheus diff --git a/pull/src/endpoint.cc b/pull/src/endpoint.cc index 902e79b3..82d7d812 100644 --- a/pull/src/endpoint.cc +++ b/pull/src/endpoint.cc @@ -1,28 +1,29 @@ -#include "prometheus/endpoint.h" +#include "endpoint.h" #include "handler.h" +#include "prometheus/detail/future_std.h" namespace prometheus { +namespace detail { -Endpoint::Endpoint(std::string uri) - : endpoint_registry_(std::make_shared()), +Endpoint::Endpoint(CivetServer& server, std::string uri) + : server_(server), + uri_(std::move(uri)), + endpoint_registry_(std::make_shared()), metrics_handler_( - new detail::MetricsHandler{collectables_, *endpoint_registry_}), - uri_(std::move(uri)) { + make_unique(collectables_, *endpoint_registry_)) { RegisterCollectable(endpoint_registry_); + server_.addHandler(uri_, metrics_handler_.get()); } -Endpoint::~Endpoint() = default; +Endpoint::~Endpoint() { server_.removeHandler(uri_); } void Endpoint::RegisterCollectable( const std::weak_ptr& collectable) { collectables_.push_back(collectable); } -detail::MetricsHandler* Endpoint::getMetricsHandler() const { - return metrics_handler_.get(); -} - const std::string& Endpoint::getURI() const { return uri_; } +} // namespace detail } // namespace prometheus diff --git a/pull/include/prometheus/endpoint.h b/pull/src/endpoint.h similarity index 74% rename from pull/include/prometheus/endpoint.h rename to pull/src/endpoint.h index b3f8b900..397642d3 100644 --- a/pull/include/prometheus/endpoint.h +++ b/pull/src/endpoint.h @@ -1,34 +1,35 @@ #pragma once +#include #include +#include #include "prometheus/collectable.h" -#include "prometheus/detail/pull_export.h" #include "prometheus/registry.h" -namespace prometheus { +class CivetServer; +namespace prometheus { namespace detail { class MetricsHandler; -} // namespace detail -class PROMETHEUS_CPP_PULL_EXPORT Endpoint { +class Endpoint { public: - explicit Endpoint(std::string uri); + explicit Endpoint(CivetServer& server, std::string uri); ~Endpoint(); void RegisterCollectable(const std::weak_ptr& collectable); - detail::MetricsHandler* getMetricsHandler() const; - const std::string& getURI() const; private: + CivetServer& server_; + const std::string uri_; std::vector> collectables_; // registry for "meta" metrics about the endpoint itself std::shared_ptr endpoint_registry_; std::unique_ptr metrics_handler_; - std::string uri_; }; +} // namespace detail } // namespace prometheus diff --git a/pull/src/exposer.cc b/pull/src/exposer.cc index 250e9acc..27cf16c8 100644 --- a/pull/src/exposer.cc +++ b/pull/src/exposer.cc @@ -4,54 +4,45 @@ #include #include -#include "prometheus/client_metric.h" -#include "prometheus/detail/future_std.h" -#include "prometheus/endpoint.h" - #include "CivetServer.h" +#include "endpoint.h" #include "handler.h" +#include "prometheus/client_metric.h" +#include "prometheus/detail/future_std.h" namespace prometheus { -MultiExposer::MultiExposer(const std::string& bind_address, - std::vector> endpoints, - const std::size_t num_threads) - : MultiExposer( - std::vector{"listening_ports", bind_address, - "num_threads", std::to_string(num_threads)}, - std::move(endpoints)) {} - -MultiExposer::MultiExposer(std::vector options, - std::vector> endpoints) - : server_(detail::make_unique(std::move(options))), - endpoints_(std::move(endpoints)) { - for (const auto& endpoint : endpoints_) { - server_->addHandler(endpoint->getURI(), endpoint->getMetricsHandler()); - } -} +Exposer::Exposer(const std::string& bind_address, const std::size_t num_threads) + : Exposer(std::vector{"listening_ports", bind_address, + "num_threads", + std::to_string(num_threads)}) {} -MultiExposer::~MultiExposer() { - for (const auto& endpoint : endpoints_) { - server_->removeHandler(endpoint->getURI()); - } +Exposer::Exposer(std::vector options) + : server_(detail::make_unique(std::move(options))) {} + +Exposer::~Exposer() = default; + +void Exposer::RegisterCollectable(const std::weak_ptr& collectable, + const std::string& uri) { + auto& endpoint = GetEndpointForUri(uri); + endpoint.RegisterCollectable(collectable); } -std::vector MultiExposer::GetListeningPorts() const { +std::vector Exposer::GetListeningPorts() const { return server_->getListeningPorts(); } -Exposer::Exposer(const std::string& bind_address, const std::string& uri, - const std::size_t num_threads) - : MultiExposer(bind_address, {std::make_shared(uri)}, - num_threads) {} - -Exposer::Exposer(std::vector options, const std::string& uri) - : MultiExposer(std::move(options), {std::make_shared(uri)}) {} +detail::Endpoint& Exposer::GetEndpointForUri(const std::string& uri) { + auto sameUri = [uri](const std::unique_ptr& endpoint) { + return endpoint->getURI() == uri; + }; + auto it = std::find_if(std::begin(endpoints_), std::end(endpoints_), sameUri); + if (it != std::end(endpoints_)) { + return *it->get(); + } -void Exposer::RegisterCollectable( - const std::weak_ptr& collectable) { - // Exposer is guaranteed to have a single Endpoint. - endpoints_.at(0)->RegisterCollectable(collectable); + endpoints_.emplace_back(detail::make_unique(*server_, uri)); + return *endpoints_.back().get(); } } // namespace prometheus diff --git a/pull/src/handler.cc b/pull/src/handler.cc index 9ba95442..eaad7420 100644 --- a/pull/src/handler.cc +++ b/pull/src/handler.cc @@ -1,9 +1,10 @@ #include "handler.h" -#include "prometheus/counter.h" -#include "prometheus/summary.h" #include +#include "prometheus/counter.h" +#include "prometheus/summary.h" + #ifdef HAVE_ZLIB #include #endif diff --git a/pull/tests/integration/CMakeLists.txt b/pull/tests/integration/CMakeLists.txt index ecf64aa1..82e41d08 100644 --- a/pull/tests/integration/CMakeLists.txt +++ b/pull/tests/integration/CMakeLists.txt @@ -8,12 +8,11 @@ target_link_libraries(sample_server ${PROJECT_NAME}::pull ) - add_executable(sample_server_multi - sample_server_multi.cc - ) + sample_server_multi.cc +) target_link_libraries(sample_server_multi - PRIVATE - ${PROJECT_NAME}::pull - ) + PRIVATE + ${PROJECT_NAME}::pull +) diff --git a/pull/tests/integration/sample_server.cc b/pull/tests/integration/sample_server.cc index ab8c3df8..72512fa2 100644 --- a/pull/tests/integration/sample_server.cc +++ b/pull/tests/integration/sample_server.cc @@ -1,18 +1,18 @@ +#include +#include +#include + #include #include #include #include #include -#include -#include -#include - int main() { using namespace prometheus; // create an http server running on port 8080 - Exposer exposer{"127.0.0.1:8080", "/metrics", 1}; + Exposer exposer{"127.0.0.1:8080", 1}; // create a metrics registry with component=main labels applied to all its // metrics diff --git a/pull/tests/integration/sample_server_multi.cc b/pull/tests/integration/sample_server_multi.cc index 25de39eb..4a86171a 100644 --- a/pull/tests/integration/sample_server_multi.cc +++ b/pull/tests/integration/sample_server_multi.cc @@ -1,5 +1,4 @@ #include -#include #include #include @@ -10,8 +9,8 @@ int main() { using namespace prometheus; - auto endpointA = std::make_shared("/metricsA"); - auto endpointB = std::make_shared("/metricsB"); + // create an http server running on port 8080 + Exposer exposer{"127.0.0.1:8080", 1}; auto registryA = std::make_shared(); @@ -20,7 +19,6 @@ int main() { auto& counter_familyA = BuildCounter() .Name("time_running_seconds_total") .Help("How many seconds is this server running?") - .Labels({{"label", "foo"}}) .Register(*registryA); // add a counter to the metric family @@ -28,30 +26,27 @@ int main() { {{"another_label", "bar"}, {"yet_another_label", "baz"}}); // ask the exposer to scrape registryA on incoming scrapes for "/metricsA" - endpointA->RegisterCollectable(registryA); + exposer.RegisterCollectable(registryA, "/metricsA"); auto registryB = std::make_shared(); - auto& counter_familyB = BuildCounter() - .Name("other_time_running_seconds_total") - .Help("How many seconds has something else been running?") - .Labels({{"label", "not_foo"}}) - .Register(*registryB); + auto& counter_familyB = + BuildCounter() + .Name("other_time_running_seconds_total") + .Help("How many seconds has something else been running?") + .Register(*registryB); auto& seconds_counterB = counter_familyB.Add( {{"another_label", "not_bar"}, {"yet_another_label", "not_baz"}}); // This endpoint exposes registryB. - endpointB->RegisterCollectable(registryB); - - // create an http server running on port 8080 - MultiExposer exposer{"127.0.0.1:8080", {endpointA, endpointB}, 1}; + exposer.RegisterCollectable(registryB, "/metricsB"); for (;;) { std::this_thread::sleep_for(std::chrono::seconds(1)); // increment the counters by one (second) - seconds_counterA.Increment(); - seconds_counterB.Increment(); + seconds_counterA.Increment(1.0); + seconds_counterB.Increment(1.5); } return 0; }