diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c97f25..a0bea81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,12 +60,20 @@ file(GLOB_RECURSE ALL_EXECUTABLES "*_main.cc") list(REMOVE_ITEM ALL_LIBRARY_SRCS ${ALL_TESTS}) list(REMOVE_ITEM ALL_LIBRARY_SRCS ${ALL_EXECUTABLES}) file(GLOB_RECURSE ALL_GRPC_FILES "cartographer_grpc/*") +file(GLOB_RECURSE ALL_PROMETHEUS_FILES "cartographer_grpc/metrics/prometheus/*") +list(REMOVE_ITEM ALL_GRPC_FILES ${ALL_PROMETHEUS_FILES}) if (NOT ${BUILD_GRPC}) list(REMOVE_ITEM ALL_LIBRARY_HDRS ${ALL_GRPC_FILES}) list(REMOVE_ITEM ALL_LIBRARY_SRCS ${ALL_GRPC_FILES}) list(REMOVE_ITEM ALL_TESTS ${ALL_GRPC_FILES}) list(REMOVE_ITEM ALL_EXECUTABLES ${ALL_GRPC_FILES}) endif() +if (NOT ${BUILD_PROMETHEUS}) + list(REMOVE_ITEM ALL_LIBRARY_HDRS ${ALL_PROMETHEUS_FILES}) + list(REMOVE_ITEM ALL_LIBRARY_SRCS ${ALL_PROMETHEUS_FILES}) + list(REMOVE_ITEM ALL_TESTS ${ALL_PROMETHEUS_FILES}) + list(REMOVE_ITEM ALL_EXECUTABLES ${ALL_PROMETHEUS_FILES}) +endif() set(INSTALL_SOURCE_HDRS ${ALL_LIBRARY_HDRS}) file(GLOB_RECURSE INTERNAL_HDRS "cartographer/internal/*.h" "cartographer_grpc/internal/*.h") diff --git a/cartographer_grpc/metrics/prometheus/family_factory.cc b/cartographer_grpc/metrics/prometheus/family_factory.cc new file mode 100644 index 0000000..c389818 --- /dev/null +++ b/cartographer_grpc/metrics/prometheus/family_factory.cc @@ -0,0 +1,182 @@ +/* + * Copyright 2018 The Cartographer Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cartographer_grpc/metrics/prometheus/family_factory.h" + +#include "cartographer/common/make_unique.h" +#include "prometheus/counter.h" +#include "prometheus/family.h" +#include "prometheus/gauge.h" +#include "prometheus/histogram.h" + +namespace cartographer_grpc { +namespace metrics { +namespace prometheus { + +namespace { + +using BucketBoundaries = cartographer::metrics::Histogram::BucketBoundaries; + +class Counter : public cartographer::metrics::Counter { + public: + explicit Counter(::prometheus::Counter* prometheus) + : prometheus_(prometheus) {} + + void Increment() override { prometheus_->Increment(); } + void Increment(double by_value) override { prometheus_->Increment(by_value); } + + private: + ::prometheus::Counter* prometheus_; +}; + +class CounterFamily + : public cartographer::metrics::Family { + public: + explicit CounterFamily( + ::prometheus::Family<::prometheus::Counter>* prometheus) + : prometheus_(prometheus) {} + + Counter* Add(const std::map& labels) override { + ::prometheus::Counter* counter = &prometheus_->Add(labels); + auto wrapper = cartographer::common::make_unique(counter); + auto* ptr = wrapper.get(); + wrappers_.emplace_back(std::move(wrapper)); + return ptr; + } + + private: + ::prometheus::Family<::prometheus::Counter>* prometheus_; + std::vector> wrappers_; +}; + +class Gauge : public cartographer::metrics::Gauge { + public: + explicit Gauge(::prometheus::Gauge* prometheus) : prometheus_(prometheus) {} + + void Decrement() override { prometheus_->Decrement(); } + void Decrement(double by_value) override { prometheus_->Decrement(by_value); } + void Increment() override { prometheus_->Increment(); } + void Increment(double by_value) override { prometheus_->Increment(by_value); } + void Set(double value) override { prometheus_->Set(value); } + + private: + ::prometheus::Gauge* prometheus_; +}; + +class GaugeFamily + : public cartographer::metrics::Family { + public: + explicit GaugeFamily(::prometheus::Family<::prometheus::Gauge>* prometheus) + : prometheus_(prometheus) {} + + Gauge* Add(const std::map& labels) override { + ::prometheus::Gauge* gauge = &prometheus_->Add(labels); + auto wrapper = cartographer::common::make_unique(gauge); + auto* ptr = wrapper.get(); + wrappers_.emplace_back(std::move(wrapper)); + return ptr; + } + + private: + ::prometheus::Family<::prometheus::Gauge>* prometheus_; + std::vector> wrappers_; +}; + +class Histogram : public cartographer::metrics::Histogram { + public: + explicit Histogram(::prometheus::Histogram* prometheus) + : prometheus_(prometheus) {} + + void Observe(double value) override { prometheus_->Observe(value); } + + private: + ::prometheus::Histogram* prometheus_; +}; + +class HistogramFamily + : public cartographer::metrics::Family { + public: + HistogramFamily(::prometheus::Family<::prometheus::Histogram>* prometheus, + const BucketBoundaries& boundaries) + : prometheus_(prometheus), boundaries_(boundaries) {} + + Histogram* Add(const std::map& labels) override { + ::prometheus::Histogram* histogram = &prometheus_->Add(labels, boundaries_); + auto wrapper = cartographer::common::make_unique(histogram); + auto* ptr = wrapper.get(); + wrappers_.emplace_back(std::move(wrapper)); + return ptr; + } + + private: + ::prometheus::Family<::prometheus::Histogram>* prometheus_; + std::vector> wrappers_; + const BucketBoundaries boundaries_; +}; + +} // namespace + +FamilyFactory::FamilyFactory() + : registry_(std::make_shared<::prometheus::Registry>()) {} + +cartographer::metrics::Family* +FamilyFactory::NewCounterFamily(const std::string& name, + const std::string& description) { + auto& family = ::prometheus::BuildCounter() + .Name(name) + .Help(description) + .Register(*registry_); + auto wrapper = cartographer::common::make_unique(&family); + auto* ptr = wrapper.get(); + counters_.emplace_back(std::move(wrapper)); + return ptr; +} + +cartographer::metrics::Family* +FamilyFactory::NewGaugeFamily(const std::string& name, + const std::string& description) { + auto& family = ::prometheus::BuildGauge() + .Name(name) + .Help(description) + .Register(*registry_); + auto wrapper = cartographer::common::make_unique(&family); + auto* ptr = wrapper.get(); + gauges_.emplace_back(std::move(wrapper)); + return ptr; +} + +cartographer::metrics::Family* +FamilyFactory::NewHistogramFamily(const std::string& name, + const std::string& description, + const BucketBoundaries& boundaries) { + auto& family = ::prometheus::BuildHistogram() + .Name(name) + .Help(description) + .Register(*registry_); + auto wrapper = + cartographer::common::make_unique(&family, boundaries); + auto* ptr = wrapper.get(); + histograms_.emplace_back(std::move(wrapper)); + return ptr; +} + +std::weak_ptr<::prometheus::Collectable> FamilyFactory::GetCollectable() const { + return registry_; +} + +} // namespace prometheus +} // namespace metrics +} // namespace cartographer_grpc diff --git a/cartographer_grpc/metrics/prometheus/family_factory.h b/cartographer_grpc/metrics/prometheus/family_factory.h new file mode 100644 index 0000000..4626c90 --- /dev/null +++ b/cartographer_grpc/metrics/prometheus/family_factory.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 The Cartographer Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CARTOGRAPHER_GRPC_METRICS_PROMETHEUS_FAMILY_FACTORY_H_ +#define CARTOGRAPHER_GRPC_METRICS_PROMETHEUS_FAMILY_FACTORY_H_ + +#include +#include + +#include "cartographer/metrics/family_factory.h" +#include "prometheus/registry.h" + +namespace cartographer_grpc { +namespace metrics { +namespace prometheus { + +class FamilyFactory : public cartographer::metrics::FamilyFactory { + public: + FamilyFactory(); + + cartographer::metrics::Family* + NewCounterFamily(const std::string& name, + const std::string& description) override; + cartographer::metrics::Family* NewGaugeFamily( + const std::string& name, const std::string& description) override; + cartographer::metrics::Family* + NewHistogramFamily(const std::string& name, const std::string& description, + const cartographer::metrics::Histogram::BucketBoundaries& + boundaries) override; + + std::weak_ptr<::prometheus::Collectable> GetCollectable() const; + + private: + std::vector>> + counters_; + std::vector>> + gauges_; + std::vector>> + histograms_; + std::shared_ptr<::prometheus::Registry> registry_; +}; + +} // namespace prometheus +} // namespace metrics +} // namespace cartographer_grpc + +#endif // CARTOGRAPHER_GRPC_METRICS_PROMETHEUS_FAMILY_FACTORY_H_ diff --git a/cartographer_grpc/metrics/prometheus/metrics_test.cc b/cartographer_grpc/metrics/prometheus/metrics_test.cc new file mode 100644 index 0000000..62dd51d --- /dev/null +++ b/cartographer_grpc/metrics/prometheus/metrics_test.cc @@ -0,0 +1,137 @@ +/* + * Copyright 2018 The Cartographer Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cartographer/metrics/family_factory.h" +#include "cartographer/metrics/register.h" +#include "cartographer_grpc/metrics/prometheus/family_factory.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "prometheus/exposer.h" + +namespace cartographer_grpc { +namespace metrics { +namespace prometheus { +namespace { + +static auto* kCounter = cartographer::metrics::Counter::Null(); +static auto* kGauge = cartographer::metrics::Gauge::Null(); +static auto* kScoresMetric = cartographer::metrics::Histogram::Null(); + +const char kLabelKey[] = "kind"; +const char kLabelValue[] = "score"; +const std::array kObserveScores = {{-1, 0.11, 0.2, 0.5, 2}}; + +class Algorithm { + public: + static void RegisterMetrics(cartographer::metrics::FamilyFactory* factory) { + auto boundaries = cartographer::metrics::Histogram::FixedWidth(0.05, 20); + auto* scores_family = factory->NewHistogramFamily( + "/algorithm/scores", "Scores achieved", boundaries); + kScoresMetric = scores_family->Add({{kLabelKey, kLabelValue}}); + } + void Run() { + for (double score : kObserveScores) { + kScoresMetric->Observe(score); + } + } +}; + +TEST(MetricsTest, CollectCounter) { + FamilyFactory factory; + auto* counter_family = factory.NewCounterFamily("/test/hits", "Hits"); + kCounter = counter_family->Add({{kLabelKey, kLabelValue}}); + kCounter->Increment(); + kCounter->Increment(5); + double expected_value = 1 + 5; + std::vector<::io::prometheus::client::MetricFamily> collected; + { + std::shared_ptr<::prometheus::Collectable> collectable; + CHECK(collectable = factory.GetCollectable().lock()); + collected = collectable->Collect(); + } + ASSERT_EQ(collected.size(), 1); + ASSERT_EQ(collected[0].metric_size(), 1); + EXPECT_THAT( + collected[0].metric(0).label(), + testing::AllOf( + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::name, kLabelKey)), + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::value, kLabelValue)))); + EXPECT_THAT(collected[0].metric(0).counter().value(), + testing::DoubleEq(expected_value)); +} + +TEST(MetricsTest, CollectGauge) { + FamilyFactory factory; + auto* gauge_family = + factory.NewGaugeFamily("/test/queue/length", "Length of some queue"); + kGauge = gauge_family->Add({{kLabelKey, kLabelValue}}); + kGauge->Increment(); + kGauge->Increment(5); + kGauge->Decrement(); + kGauge->Decrement(2); + double expected_value = 1 + 5 - 1 - 2; + std::vector<::io::prometheus::client::MetricFamily> collected; + { + std::shared_ptr<::prometheus::Collectable> collectable; + CHECK(collectable = factory.GetCollectable().lock()); + collected = collectable->Collect(); + } + ASSERT_EQ(collected.size(), 1); + ASSERT_EQ(collected[0].metric_size(), 1); + EXPECT_THAT( + collected[0].metric(0).label(), + testing::AllOf( + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::name, kLabelKey)), + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::value, kLabelValue)))); + EXPECT_THAT(collected[0].metric(0).gauge().value(), + testing::DoubleEq(expected_value)); +} + +TEST(MetricsTest, CollectHistogram) { + FamilyFactory registry; + Algorithm::RegisterMetrics(®istry); + + Algorithm algorithm; + algorithm.Run(); + std::vector<::io::prometheus::client::MetricFamily> collected; + { + std::shared_ptr<::prometheus::Collectable> collectable; + CHECK(collectable = registry.GetCollectable().lock()); + collected = collectable->Collect(); + } + ASSERT_EQ(collected.size(), 1); + ASSERT_EQ(collected[0].metric_size(), 1); + EXPECT_THAT( + collected[0].metric(0).label(), + testing::AllOf( + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::name, kLabelKey)), + testing::ElementsAre(testing::Property( + &io::prometheus::client::LabelPair::value, kLabelValue)))); + EXPECT_THAT(collected[0].metric(0).histogram().sample_count(), + testing::Eq(kObserveScores.size())); + EXPECT_EQ(collected[0].metric(0).histogram().bucket(0).cumulative_count(), 1); +} + +} // namespace +} // namespace prometheus +} // namespace metrics +} // namespace cartographer_grpc