From 2ce3bb011e515d70a21558986f2c0e06d53dcec0 Mon Sep 17 00:00:00 2001 From: Johnny Jazeix Date: Wed, 8 Jun 2022 18:59:41 +0200 Subject: [PATCH] feat(core): Add Info metric --- core/CMakeLists.txt | 1 + core/benchmarks/CMakeLists.txt | 1 + core/benchmarks/info_bench.cc | 20 ++++++++ core/include/prometheus/client_metric.h | 7 +++ core/include/prometheus/family.h | 1 + core/include/prometheus/info.h | 68 +++++++++++++++++++++++++ core/include/prometheus/metric_type.h | 1 + core/include/prometheus/registry.h | 4 +- core/src/detail/builder.cc | 3 ++ core/src/family.cc | 2 + core/src/info.cc | 11 ++++ core/src/registry.cc | 27 ++++++++-- core/src/text_serializer.cc | 15 ++++++ core/tests/builder_test.cc | 9 ++++ core/tests/check_label_name_test.cc | 2 +- core/tests/registry_test.cc | 16 ++++++ core/tests/text_serializer_test.cc | 9 ++++ pull/tests/integration/sample_server.cc | 6 +++ 18 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 core/benchmarks/info_bench.cc create mode 100644 core/include/prometheus/info.h create mode 100644 core/src/info.cc diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6706a6a4..fe0d7d41 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(core src/family.cc src/gauge.cc src/histogram.cc + src/info.cc src/registry.cc src/serializer.cc src/summary.cc diff --git a/core/benchmarks/CMakeLists.txt b/core/benchmarks/CMakeLists.txt index c0d2dd09..c1294d5a 100644 --- a/core/benchmarks/CMakeLists.txt +++ b/core/benchmarks/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(benchmarks counter_bench.cc gauge_bench.cc histogram_bench.cc + info_bench.cc registry_bench.cc summary_bench.cc ) diff --git a/core/benchmarks/info_bench.cc b/core/benchmarks/info_bench.cc new file mode 100644 index 00000000..f9e5e16c --- /dev/null +++ b/core/benchmarks/info_bench.cc @@ -0,0 +1,20 @@ +#include + +#include "prometheus/family.h" +#include "prometheus/info.h" +#include "prometheus/registry.h" + +static void BM_Info_Collect(benchmark::State& state) { + using prometheus::BuildInfo; + using prometheus::Info; + using prometheus::Registry; + Registry registry; + auto& info_family = + BuildInfo().Name("benchmark_info").Help("").Register(registry); + auto& info = info_family.Add({}); + + while (state.KeepRunning()) { + benchmark::DoNotOptimize(info.Collect()); + }; +} +BENCHMARK(BM_Info_Collect); diff --git a/core/include/prometheus/client_metric.h b/core/include/prometheus/client_metric.h index b224b80c..df0ca8d3 100644 --- a/core/include/prometheus/client_metric.h +++ b/core/include/prometheus/client_metric.h @@ -40,6 +40,13 @@ struct PROMETHEUS_CPP_CORE_EXPORT ClientMetric { }; Gauge gauge; + // Info + + struct Info { + double value = 1.0; + }; + Info info; + // Summary struct Quantile { diff --git a/core/include/prometheus/family.h b/core/include/prometheus/family.h index c5d725e4..0bc07232 100644 --- a/core/include/prometheus/family.h +++ b/core/include/prometheus/family.h @@ -16,6 +16,7 @@ // IWYU pragma: no_include "prometheus/counter.h" // IWYU pragma: no_include "prometheus/gauge.h" // IWYU pragma: no_include "prometheus/histogram.h" +// IWYU pragma: no_include "prometheus/info.h" // IWYU pragma: no_include "prometheus/summary.h" namespace prometheus { diff --git a/core/include/prometheus/info.h b/core/include/prometheus/info.h new file mode 100644 index 00000000..f54dbf05 --- /dev/null +++ b/core/include/prometheus/info.h @@ -0,0 +1,68 @@ +#pragma once + +#include "prometheus/client_metric.h" +#include "prometheus/detail/builder.h" // IWYU pragma: export +#include "prometheus/detail/core_export.h" +#include "prometheus/metric_type.h" + +namespace prometheus { + +/// \brief A info metric to represent textual information which should not +/// change during process lifetime. +/// +/// This class represents the metric type info: +/// https://github.com/OpenObservability/OpenMetrics/blob/98ae26c87b1c3bcf937909a880b32c8be643cc9b/specification/OpenMetrics.md#info + +/// Prometheus does not provide this type directly, it is used by emulating a +/// gauge with value 1: https://prometheus.io/docs/concepts/metric_types/#gauge +/// +/// The value of the info cannot change. Example of infos are: +/// - the application's version +/// - revision control commit +/// - version of the compiler +/// +/// The class is thread-safe. No concurrent call to any API of this type causes +/// a data race. +class PROMETHEUS_CPP_CORE_EXPORT Info { + public: + static const MetricType metric_type{MetricType::Info}; + + /// \brief Create a info. + Info() = default; + + /// \brief Get the current value of the info. + /// + /// Collect is called by the Registry when collecting metrics. + ClientMetric Collect() const; +}; + +/// \brief Return a builder to configure and register a Info metric. +/// +/// @copydetails Family<>::Family() +/// +/// Example usage: +/// +/// \code +/// auto registry = std::make_shared(); +/// auto& info_family = prometheus::BuildInfo() +/// .Name("some_name") +/// .Help("Additional description.") +/// .Labels({{"key", "value"}}) +/// .Register(*registry); +/// +/// ... +/// \endcode +/// +/// \return An object of unspecified type T, i.e., an implementation detail +/// except that it has the following members: +/// +/// - Name(const std::string&) to set the metric name, +/// - Help(const std::string&) to set an additional description. +/// - Labels(const Labels&) to assign a set of +/// key-value pairs (= labels) to the metric. +/// +/// To finish the configuration of the Info metric, register it with +/// Register(Registry&). +PROMETHEUS_CPP_CORE_EXPORT detail::Builder BuildInfo(); + +} // namespace prometheus diff --git a/core/include/prometheus/metric_type.h b/core/include/prometheus/metric_type.h index bd5c77f0..ec69fb2f 100644 --- a/core/include/prometheus/metric_type.h +++ b/core/include/prometheus/metric_type.h @@ -8,6 +8,7 @@ enum class MetricType { Summary, Untyped, Histogram, + Info, }; } // namespace prometheus diff --git a/core/include/prometheus/registry.h b/core/include/prometheus/registry.h index 51aeef5d..6603f6c6 100644 --- a/core/include/prometheus/registry.h +++ b/core/include/prometheus/registry.h @@ -16,6 +16,7 @@ namespace prometheus { class Counter; class Gauge; class Histogram; +class Info; class Summary; namespace detail { @@ -33,7 +34,7 @@ class Builder; // IWYU pragma: keep /// that returns zero or more metrics and their samples. The metrics are /// represented by the class Family<>, which implements the Collectable /// interface. A new metric is registered with BuildCounter(), BuildGauge(), -/// BuildHistogram() or BuildSummary(). +/// BuildHistogram(), BuildInfo() or BuildSummary(). /// /// The class is thread-safe. No concurrent call to any API of this type causes /// a data race. @@ -111,6 +112,7 @@ class PROMETHEUS_CPP_CORE_EXPORT Registry : public Collectable { std::vector>> counters_; std::vector>> gauges_; std::vector>> histograms_; + std::vector>> infos_; std::vector>> summaries_; mutable std::mutex mutex_; }; diff --git a/core/src/detail/builder.cc b/core/src/detail/builder.cc index a1880448..e66dde06 100644 --- a/core/src/detail/builder.cc +++ b/core/src/detail/builder.cc @@ -4,6 +4,7 @@ #include "prometheus/detail/core_export.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/registry.h" #include "prometheus/summary.h" @@ -37,6 +38,7 @@ Family& Builder::Register(Registry& registry) { template class PROMETHEUS_CPP_CORE_EXPORT Builder; template class PROMETHEUS_CPP_CORE_EXPORT Builder; template class PROMETHEUS_CPP_CORE_EXPORT Builder; +template class PROMETHEUS_CPP_CORE_EXPORT Builder; template class PROMETHEUS_CPP_CORE_EXPORT Builder; } // namespace detail @@ -44,6 +46,7 @@ template class PROMETHEUS_CPP_CORE_EXPORT Builder; detail::Builder BuildCounter() { return {}; } detail::Builder BuildGauge() { return {}; } detail::Builder BuildHistogram() { return {}; } +detail::Builder BuildInfo() { return {}; } detail::Builder BuildSummary() { return {}; } } // namespace prometheus diff --git a/core/src/family.cc b/core/src/family.cc index 46781a62..f087586b 100644 --- a/core/src/family.cc +++ b/core/src/family.cc @@ -11,6 +11,7 @@ #include "prometheus/counter.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/summary.h" namespace prometheus { @@ -124,6 +125,7 @@ ClientMetric Family::CollectMetric(const Labels& metric_labels, template class PROMETHEUS_CPP_CORE_EXPORT Family; template class PROMETHEUS_CPP_CORE_EXPORT Family; template class PROMETHEUS_CPP_CORE_EXPORT Family; +template class PROMETHEUS_CPP_CORE_EXPORT Family; template class PROMETHEUS_CPP_CORE_EXPORT Family; } // namespace prometheus diff --git a/core/src/info.cc b/core/src/info.cc new file mode 100644 index 00000000..ea430382 --- /dev/null +++ b/core/src/info.cc @@ -0,0 +1,11 @@ +#include "prometheus/info.h" + +namespace prometheus { + +ClientMetric Info::Collect() const { + ClientMetric metric; + metric.info.value = 1; + return metric; +} + +} // namespace prometheus diff --git a/core/src/registry.cc b/core/src/registry.cc index ff8d7ca9..267bf92b 100644 --- a/core/src/registry.cc +++ b/core/src/registry.cc @@ -9,6 +9,7 @@ #include "prometheus/counter.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/summary.h" namespace prometheus { @@ -49,6 +50,7 @@ std::vector Registry::Collect() const { CollectAll(results, counters_); CollectAll(results, gauges_); CollectAll(results, histograms_); + CollectAll(results, infos_); CollectAll(results, summaries_); return results; @@ -69,6 +71,11 @@ std::vector>>& Registry::GetFamilies() { return histograms_; } +template <> +std::vector>>& Registry::GetFamilies() { + return infos_; +} + template <> std::vector>>& Registry::GetFamilies() { return summaries_; @@ -76,22 +83,27 @@ std::vector>>& Registry::GetFamilies() { template <> bool Registry::NameExistsInOtherType(const std::string& name) const { - return FamilyNameExists(name, gauges_, histograms_, summaries_); + return FamilyNameExists(name, gauges_, histograms_, infos_, summaries_); } template <> bool Registry::NameExistsInOtherType(const std::string& name) const { - return FamilyNameExists(name, counters_, histograms_, summaries_); + return FamilyNameExists(name, counters_, histograms_, infos_, summaries_); } template <> bool Registry::NameExistsInOtherType(const std::string& name) const { - return FamilyNameExists(name, counters_, gauges_, summaries_); + return FamilyNameExists(name, counters_, gauges_, infos_, summaries_); +} + +template <> +bool Registry::NameExistsInOtherType(const std::string& name) const { + return FamilyNameExists(name, counters_, gauges_, histograms_, summaries_); } template <> bool Registry::NameExistsInOtherType(const std::string& name) const { - return FamilyNameExists(name, counters_, gauges_, histograms_); + return FamilyNameExists(name, counters_, gauges_, histograms_, infos_); } template @@ -143,6 +155,10 @@ template Family& Registry::Add(const std::string& name, const std::string& help, const Labels& labels); +template Family& Registry::Add(const std::string& name, + const std::string& help, + const Labels& labels); + template Family& Registry::Add(const std::string& name, const std::string& help, const Labels& labels); @@ -181,4 +197,7 @@ Registry::Remove(const Family& family); template bool PROMETHEUS_CPP_CORE_EXPORT Registry::Remove(const Family& family); +template bool PROMETHEUS_CPP_CORE_EXPORT +Registry::Remove(const Family& family); + } // namespace prometheus diff --git a/core/src/text_serializer.cc b/core/src/text_serializer.cc index d26d3e58..0656b505 100644 --- a/core/src/text_serializer.cc +++ b/core/src/text_serializer.cc @@ -96,6 +96,13 @@ void SerializeGauge(std::ostream& out, const MetricFamily& family, WriteTail(out, metric); } +void SerializeInfo(std::ostream& out, const MetricFamily& family, + const ClientMetric& metric) { + WriteHead(out, family, metric, "_info"); + WriteValue(out, metric.info.value); + WriteTail(out, metric); +} + void SerializeSummary(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) { auto& sum = metric.summary; @@ -164,6 +171,14 @@ void SerializeFamily(std::ostream& out, const MetricFamily& family) { SerializeGauge(out, family, metric); } break; + // info is not handled by prometheus, we use gauge as workaround + // (https://github.com/OpenObservability/OpenMetrics/blob/98ae26c87b1c3bcf937909a880b32c8be643cc9b/specification/OpenMetrics.md#info-1) + case MetricType::Info: + out << "# TYPE " << family.name << " gauge\n"; + for (auto& metric : family.metric) { + SerializeInfo(out, family, metric); + } + break; case MetricType::Summary: out << "# TYPE " << family.name << " summary\n"; for (auto& metric : family.metric) { diff --git a/core/tests/builder_test.cc b/core/tests/builder_test.cc index a92adf19..6fdffafa 100644 --- a/core/tests/builder_test.cc +++ b/core/tests/builder_test.cc @@ -14,6 +14,7 @@ #include "prometheus/family.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/labels.h" #include "prometheus/registry.h" #include "prometheus/summary.h" @@ -92,6 +93,14 @@ TEST_F(BuilderTest, build_histogram) { verifyCollectedLabels(); } +TEST_F(BuilderTest, build_info) { + auto& family = + BuildInfo().Name(name).Help(help).Labels(const_labels).Register(registry); + family.Add(more_labels); + + verifyCollectedLabels(); +} + TEST_F(BuilderTest, build_summary) { auto& family = BuildSummary() .Name(name) diff --git a/core/tests/check_label_name_test.cc b/core/tests/check_label_name_test.cc index bc739ddb..c4b8b430 100644 --- a/core/tests/check_label_name_test.cc +++ b/core/tests/check_label_name_test.cc @@ -43,7 +43,7 @@ TEST_P(CheckLabelNameTest, reject_quantile_for_histogram) { INSTANTIATE_TEST_SUITE_P(AllMetricTypes, CheckLabelNameTest, testing::Values(MetricType::Counter, MetricType::Gauge, MetricType::Histogram, - MetricType::Summary, + MetricType::Info, MetricType::Summary, MetricType::Untyped)); } // namespace diff --git a/core/tests/registry_test.cc b/core/tests/registry_test.cc index 994491e4..45b98ff2 100644 --- a/core/tests/registry_test.cc +++ b/core/tests/registry_test.cc @@ -8,6 +8,7 @@ #include "prometheus/counter.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/summary.h" namespace prometheus { @@ -62,6 +63,7 @@ TEST(RegistryTest, reject_different_type_than_counter) { EXPECT_NO_THROW(BuildCounter().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildInfo().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry)); } @@ -72,6 +74,7 @@ TEST(RegistryTest, reject_different_type_than_gauge) { EXPECT_NO_THROW(BuildGauge().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildInfo().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry)); } @@ -80,11 +83,23 @@ TEST(RegistryTest, reject_different_type_than_histogram) { Registry registry{}; EXPECT_NO_THROW(BuildHistogram().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildInfo().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry)); } +TEST(RegistryTest, reject_different_type_than_info) { + const auto same_name = std::string{"same_name"}; + Registry registry{}; + + EXPECT_NO_THROW(BuildInfo().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry)); +} + TEST(RegistryTest, reject_different_type_than_summary) { const auto same_name = std::string{"same_name"}; Registry registry{}; @@ -93,6 +108,7 @@ TEST(RegistryTest, reject_different_type_than_summary) { EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry)); EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry)); + EXPECT_ANY_THROW(BuildInfo().Name(same_name).Register(registry)); } TEST(RegistryTest, throw_for_same_family_name) { diff --git a/core/tests/text_serializer_test.cc b/core/tests/text_serializer_test.cc index 0ef68a0d..470583c4 100644 --- a/core/tests/text_serializer_test.cc +++ b/core/tests/text_serializer_test.cc @@ -9,6 +9,7 @@ #include "prometheus/client_metric.h" #include "prometheus/histogram.h" +#include "prometheus/info.h" #include "prometheus/metric_family.h" #include "prometheus/metric_type.h" #include "prometheus/summary.h" @@ -107,6 +108,14 @@ TEST_F(TextSerializerTest, shouldSerializeHistogram) { testing::HasSubstr(name + "_bucket{le=\"+Inf\"} 2\n")); } +TEST_F(TextSerializerTest, shouldSerializeInfo) { + Info info; + metric = info.Collect(); + + const auto serialized = Serialize(MetricType::Info); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_info 1")); +} + TEST_F(TextSerializerTest, shouldSerializeSummary) { Summary summary{Summary::Quantiles{{0.5, 0.05}}}; summary.Observe(0); diff --git a/pull/tests/integration/sample_server.cc b/pull/tests/integration/sample_server.cc index f78a89e0..600958ac 100644 --- a/pull/tests/integration/sample_server.cc +++ b/pull/tests/integration/sample_server.cc @@ -9,6 +9,7 @@ #include "prometheus/counter.h" #include "prometheus/exposer.h" #include "prometheus/family.h" +#include "prometheus/info.h" #include "prometheus/registry.h" int main() { @@ -49,6 +50,11 @@ int main() { .Help("Number of HTTP requests") .Register(*registry); + auto& version_info = BuildInfo() + .Name("versions") + .Help("Static info about the library") + .Register(*registry); + version_info.Add({{"prometheus", "1.0"}}); // ask the exposer to scrape the registry on incoming HTTP requests exposer.RegisterCollectable(registry);