From 5e1dfff6a5c9918f2f58236f76b53df382c10b24 Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 13:57:49 +0200 Subject: [PATCH 1/8] Document counter and gauge --- core/include/prometheus/counter.h | 19 +++++++++++++++++++ core/include/prometheus/gauge.h | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/core/include/prometheus/counter.h b/core/include/prometheus/counter.h index 939fc26a..ae96204e 100644 --- a/core/include/prometheus/counter.h +++ b/core/include/prometheus/counter.h @@ -5,12 +5,30 @@ #include "prometheus/metric_type.h" namespace prometheus { + +/// \brief A counter metric to represent a monotonically increasing value. +/// +/// This class represents the metric type counter: +/// https://prometheus.io/docs/concepts/metric_types/#counter +/// +/// The value of the counter can only increase. Example of counters are: +/// - the number of requests served +/// - tasks completed +/// - errors +/// +/// Do not use a counter to expose a value that can decrease - instead use a +/// Gauge. class Counter { public: static const MetricType metric_type = MetricType::Counter; + /// \brief Increment the counter by 1. void Increment(); + + /// \brief Increment the counter by a given amount. void Increment(double); + + /// \brief Get the current value of the counter. double Value() const; ClientMetric Collect(); @@ -18,4 +36,5 @@ class Counter { private: Gauge gauge_; }; + } // namespace prometheus diff --git a/core/include/prometheus/gauge.h b/core/include/prometheus/gauge.h index 839acac5..bbb7e19e 100644 --- a/core/include/prometheus/gauge.h +++ b/core/include/prometheus/gauge.h @@ -7,18 +7,44 @@ namespace prometheus { +/// \brief A gauge metric to represent a value that can arbitrarily go up and +/// down. +/// +/// The class represents the metric type gauge: +/// https://prometheus.io/docs/concepts/metric_types/#gauge +/// +/// Gauges are typically used for measured values like temperatures or current +/// memory usage, but also "counts" that can go up and down, like the number of +/// running processes. class Gauge { public: static const MetricType metric_type = MetricType::Gauge; + /// \brief Create a gauge that starts at 0. Gauge(); + + /// \brief Create a gauge that starts at the given amount. Gauge(double); + + /// \brief Increment the gauge by 1. void Increment(); + + /// \brief Increment the gauge by the given amount. void Increment(double); + + /// \brief Decrement the gauge by 1. void Decrement(); + + /// \brief Decrement the gauge by the given amount. void Decrement(double); + + /// \brief Set the gauge to the given value. void Set(double); + + /// \brief Set the gauge to the current unixtime in seconds. void SetToCurrentTime(); + + /// \brief Get the current value of the gauge. double Value() const; ClientMetric Collect(); From 5ac32f36c351d8848a2a3300519a47d206fe8cc1 Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 14:34:23 +0200 Subject: [PATCH 2/8] Add test that counter cannot be decremented --- core/include/prometheus/counter.h | 2 ++ core/tests/counter_test.cc | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/core/include/prometheus/counter.h b/core/include/prometheus/counter.h index ae96204e..3a121889 100644 --- a/core/include/prometheus/counter.h +++ b/core/include/prometheus/counter.h @@ -26,6 +26,8 @@ class Counter { void Increment(); /// \brief Increment the counter by a given amount. + /// + /// The counter will not change if the given amount is negative. void Increment(double); /// \brief Get the current value of the counter. diff --git a/core/tests/counter_test.cc b/core/tests/counter_test.cc index a923a426..29beaf91 100644 --- a/core/tests/counter_test.cc +++ b/core/tests/counter_test.cc @@ -31,3 +31,10 @@ TEST_F(CounterTest, inc_multiple) { counter.Increment(5); EXPECT_EQ(counter.Value(), 7.0); } + +TEST_F(CounterTest, inc_negative_value) { + Counter counter; + counter.Increment(5.0); + counter.Increment(-5.0); + EXPECT_EQ(counter.Value(), 5.0); +} From e72703077eb086bac0b56a9bdf371628f34a8ba9 Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 14:37:11 +0200 Subject: [PATCH 3/8] Minor refactoring of gauge --- core/include/prometheus/gauge.h | 8 ++++---- core/src/gauge.cc | 17 +++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/include/prometheus/gauge.h b/core/include/prometheus/gauge.h index bbb7e19e..49d738ca 100644 --- a/core/include/prometheus/gauge.h +++ b/core/include/prometheus/gauge.h @@ -18,10 +18,10 @@ namespace prometheus { /// running processes. class Gauge { public: - static const MetricType metric_type = MetricType::Gauge; + static const MetricType metric_type{MetricType::Gauge}; /// \brief Create a gauge that starts at 0. - Gauge(); + Gauge() = default; /// \brief Create a gauge that starts at the given amount. Gauge(double); @@ -47,11 +47,11 @@ class Gauge { /// \brief Get the current value of the gauge. double Value() const; - ClientMetric Collect(); + ClientMetric Collect() const; private: void Change(double); - std::atomic value_; + std::atomic value_{0.0}; }; } // namespace prometheus diff --git a/core/src/gauge.cc b/core/src/gauge.cc index 78945034..4b16dd53 100644 --- a/core/src/gauge.cc +++ b/core/src/gauge.cc @@ -4,12 +4,12 @@ #include namespace prometheus { -Gauge::Gauge() : value_{0} {} -Gauge::Gauge(double value) : value_{value} {} +Gauge::Gauge(const double value) : value_{value} {} void Gauge::Increment() { Increment(1.0); } -void Gauge::Increment(double value) { + +void Gauge::Increment(const double value) { if (value < 0.0) { return; } @@ -18,31 +18,32 @@ void Gauge::Increment(double value) { void Gauge::Decrement() { Decrement(1.0); } -void Gauge::Decrement(double value) { +void Gauge::Decrement(const double value) { if (value < 0.0) { return; } Change(-1.0 * value); } -void Gauge::Set(double value) { value_.store(value); } +void Gauge::Set(const double value) { value_.store(value); } -void Gauge::Change(double value) { +void Gauge::Change(const double value) { auto current = value_.load(); while (!value_.compare_exchange_weak(current, current + value)) ; } void Gauge::SetToCurrentTime() { - auto time = std::time(nullptr); + const auto time = std::time(nullptr); Set(static_cast(time)); } double Gauge::Value() const { return value_; } -ClientMetric Gauge::Collect() { +ClientMetric Gauge::Collect() const { ClientMetric metric; metric.gauge.value = Value(); return metric; } + } // namespace prometheus From 2774d53f1dc962d141a9da4c521b3f15355da58e Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 14:39:28 +0200 Subject: [PATCH 4/8] Minor refactoring of counter --- core/include/prometheus/counter.h | 9 ++++++--- core/src/counter.cc | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/include/prometheus/counter.h b/core/include/prometheus/counter.h index 3a121889..c0ad712f 100644 --- a/core/include/prometheus/counter.h +++ b/core/include/prometheus/counter.h @@ -20,7 +20,10 @@ namespace prometheus { /// Gauge. class Counter { public: - static const MetricType metric_type = MetricType::Counter; + static const MetricType metric_type{MetricType::Counter}; + + /// \brief Create a counter that starts at 0. + Counter() = default; /// \brief Increment the counter by 1. void Increment(); @@ -33,10 +36,10 @@ class Counter { /// \brief Get the current value of the counter. double Value() const; - ClientMetric Collect(); + ClientMetric Collect() const; private: - Gauge gauge_; + Gauge gauge_{0.0}; }; } // namespace prometheus diff --git a/core/src/counter.cc b/core/src/counter.cc index 41f4e32d..fc5b6f30 100644 --- a/core/src/counter.cc +++ b/core/src/counter.cc @@ -4,13 +4,14 @@ namespace prometheus { void Counter::Increment() { gauge_.Increment(); } -void Counter::Increment(double val) { gauge_.Increment(val); } +void Counter::Increment(const double val) { gauge_.Increment(val); } double Counter::Value() const { return gauge_.Value(); } -ClientMetric Counter::Collect() { +ClientMetric Counter::Collect() const { ClientMetric metric; metric.counter.value = Value(); return metric; } -} + +} // namespace prometheus From 98a00e44ddc44f9d3ed23288120eb8f436252b48 Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 15:36:35 +0200 Subject: [PATCH 5/8] Document histogram --- core/include/prometheus/histogram.h | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/include/prometheus/histogram.h b/core/include/prometheus/histogram.h index 339ccbd8..6c417bc0 100644 --- a/core/include/prometheus/histogram.h +++ b/core/include/prometheus/histogram.h @@ -7,14 +7,44 @@ #include "prometheus/metric_type.h" namespace prometheus { + +/// \brief A histogram metric to represent aggregatable distributions of events. +/// +/// This class represents the metric type histogram: +/// https://prometheus.io/docs/concepts/metric_types/#histogram +/// +/// A histogram tracks the number of observations and the sum of the observed +/// values, allowing to calculate the average of the observed values. +/// +/// At its core a histogram has a counter per bucket. The sum of observations +/// also behaves like a counter. +/// +/// See https://prometheus.io/docs/practices/histograms/ for detailed +/// explanations of histogram usage and differences to summaries. class Histogram { public: using BucketBoundaries = std::vector; static const MetricType metric_type = MetricType::Histogram; + /// \brief Create a histogram with manually choosen buckets. + /// + /// The BucketBoundaries are a list of monotonically increasing values + /// representing the bucket boundaries. Each consecutive pair of values is + /// interpreted as a half-open interval [b_n, b_n+1) which defines one bucket. + /// + /// There is no limitation on how the buckets are divided, i.e, equal size, + /// exponential etc.. + /// + /// The bucket boundaries cannot be changed once the histogram is created. Histogram(const BucketBoundaries& buckets); + /// \brief Observe the given amount. + /// + /// The given amount selects the 'observed' bucket. The observed bucket is + /// chosen for which the given amount falls into the half-open interval [b_n, + /// b_n+1). The counter of the observed bucket is incremented. Also the total + /// sum of all observations is incremented. void Observe(double value); ClientMetric Collect(); @@ -24,4 +54,5 @@ class Histogram { std::vector bucket_counts_; Counter sum_; }; + } // namespace prometheus From 9c317ef150ca9f664b43da14c874ec543ee08660 Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 15:41:32 +0200 Subject: [PATCH 6/8] Minor refactoring of histogram --- core/include/prometheus/histogram.h | 4 ++-- core/src/histogram.cc | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/core/include/prometheus/histogram.h b/core/include/prometheus/histogram.h index 6c417bc0..2e78de55 100644 --- a/core/include/prometheus/histogram.h +++ b/core/include/prometheus/histogram.h @@ -25,7 +25,7 @@ class Histogram { public: using BucketBoundaries = std::vector; - static const MetricType metric_type = MetricType::Histogram; + static const MetricType metric_type{MetricType::Histogram}; /// \brief Create a histogram with manually choosen buckets. /// @@ -47,7 +47,7 @@ class Histogram { /// sum of all observations is incremented. void Observe(double value); - ClientMetric Collect(); + ClientMetric Collect() const; private: const BucketBoundaries bucket_boundaries_; diff --git a/core/src/histogram.cc b/core/src/histogram.cc index 4b1104bf..4135aef4 100644 --- a/core/src/histogram.cc +++ b/core/src/histogram.cc @@ -8,26 +8,27 @@ namespace prometheus { Histogram::Histogram(const BucketBoundaries& buckets) - : bucket_boundaries_(buckets), bucket_counts_(buckets.size() + 1) { + : bucket_boundaries_{buckets}, bucket_counts_{buckets.size() + 1}, sum_{} { assert(std::is_sorted(std::begin(bucket_boundaries_), std::end(bucket_boundaries_))); } -void Histogram::Observe(double value) { +void Histogram::Observe(const double value) { // TODO: determine bucket list size at which binary search would be faster - auto bucket_index = static_cast(std::distance( + const auto bucket_index = static_cast(std::distance( bucket_boundaries_.begin(), - std::find_if(bucket_boundaries_.begin(), bucket_boundaries_.end(), - [value](double boundary) { return boundary >= value; }))); + std::find_if( + std::begin(bucket_boundaries_), std::end(bucket_boundaries_), + [value](const double boundary) { return boundary >= value; }))); sum_.Increment(value); bucket_counts_[bucket_index].Increment(); } -ClientMetric Histogram::Collect() { +ClientMetric Histogram::Collect() const { auto metric = ClientMetric{}; auto cumulative_count = 0ULL; - for (std::size_t i = 0; i < bucket_counts_.size(); i++) { + for (std::size_t i{0}; i < bucket_counts_.size(); ++i) { cumulative_count += bucket_counts_[i].Value(); auto bucket = ClientMetric::Bucket{}; bucket.cumulative_count = cumulative_count; @@ -41,4 +42,5 @@ ClientMetric Histogram::Collect() { return metric; } + } // namespace prometheus From 81c46abebfe88d7d7d777272892cff97011dbb4c Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 18:53:40 +0200 Subject: [PATCH 7/8] Document summary --- core/include/prometheus/summary.h | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/core/include/prometheus/summary.h b/core/include/prometheus/summary.h index 98328f13..f1c4734b 100644 --- a/core/include/prometheus/summary.h +++ b/core/include/prometheus/summary.h @@ -12,16 +12,65 @@ namespace prometheus { +/// \brief A summary metric samples observations over a sliding window of time. +/// +/// This class represents the metric type summary: +/// https://prometheus.io/docs/instrumenting/writing_clientlibs/#summary +/// +/// A summary provides a total count of observations and a sum of all observed +/// values. In contrast to a histogram metric it also calculates configurable +/// Phi-quantiles over a sliding window of time. +/// +/// The essential difference between summaries and histograms is that summaries +/// calculate streaming Phi-quantiles on the client side and expose them +/// directly, while histograms expose bucketed observation counts and the +/// calculation of quantiles from the buckets of a histogram happens on the +/// server side: +/// https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile. +/// +/// Note that Phi designates the probability density function of the standard +/// Gaussian distribution. +/// +/// See https://prometheus.io/docs/practices/histograms/ for detailed +/// explanations of Phi-quantiles, summary usage, and differences to histograms. class Summary { public: using Quantiles = std::vector; static const MetricType metric_type = MetricType::Summary; + /// \brief Create a summary metric. + /// + /// \param quantiles A list of 'targeted' Phi-quantiles. A targeted + /// Phi-quantile is specified in the form of a Phi-quantile and tolerated + /// error. For example a Quantile{0.5, 0.1} means that the median (= 50th + /// percentile) should be returned with 10 percent error or a Quantile{0.2, + /// 0.05} means the 20th percentile with 5 percent tolerated error. Note that + /// percentiles and quantiles are the same concept, except percentiles are + /// expressed as percentages. The Phi-quantile must be in the interval [0, 1]. + /// Note that a lower tolerated error for a Phi-quantile results in higher + /// usage of resources (memory and cpu) to calculate the summary. + /// + /// The Phi-quantiles are calculated over a sliding window of time. The + /// sliding window of time is configured by max_age_seconds and age_buckets. + /// + /// \param max_age_seconds Set the duration of the time window, i.e., how long + /// observations are kept before they are discarded. The default value is 60 + /// seconds. + /// + /// \param age_buckets Set the number of buckets of the time window. It + /// determines the number of buckets used to exclude observations that + /// are older than max_age_seconds from the summary, e.g., if max_age_seconds + /// is 60 seconds and age_buckets is 5, buckets will be switched every 12 + /// seconds. The value is a trade-off between resources (memory and cpu for + /// maintaining the bucket) and how smooth the time window is moved. With only + /// one age bucket it effectively results in a complete reset of the summary + /// each time max_age_seconds has passed. The default value is 5. Summary(const Quantiles& quantiles, std::chrono::milliseconds max_age_seconds = std::chrono::seconds(60), int age_buckets = 5); + /// \brief Observe the given amount. void Observe(double value); ClientMetric Collect(); From 8fcf5b0431745d76597382af2e30c8aa979294af Mon Sep 17 00:00:00 2001 From: Jerry Crunchtime Date: Tue, 16 Oct 2018 18:59:01 +0200 Subject: [PATCH 8/8] Minor refactoring of summary --- core/include/prometheus/summary.h | 27 +++++++++++++-------------- core/src/summary.cc | 12 ++++++------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/core/include/prometheus/summary.h b/core/include/prometheus/summary.h index f1c4734b..29e06740 100644 --- a/core/include/prometheus/summary.h +++ b/core/include/prometheus/summary.h @@ -37,7 +37,7 @@ class Summary { public: using Quantiles = std::vector; - static const MetricType metric_type = MetricType::Summary; + static const MetricType metric_type{MetricType::Summary}; /// \brief Create a summary metric. /// @@ -52,22 +52,22 @@ class Summary { /// usage of resources (memory and cpu) to calculate the summary. /// /// The Phi-quantiles are calculated over a sliding window of time. The - /// sliding window of time is configured by max_age_seconds and age_buckets. + /// sliding window of time is configured by max_age and age_buckets. /// - /// \param max_age_seconds Set the duration of the time window, i.e., how long + /// \param max_age Set the duration of the time window, i.e., how long /// observations are kept before they are discarded. The default value is 60 /// seconds. /// /// \param age_buckets Set the number of buckets of the time window. It - /// determines the number of buckets used to exclude observations that - /// are older than max_age_seconds from the summary, e.g., if max_age_seconds - /// is 60 seconds and age_buckets is 5, buckets will be switched every 12 - /// seconds. The value is a trade-off between resources (memory and cpu for - /// maintaining the bucket) and how smooth the time window is moved. With only - /// one age bucket it effectively results in a complete reset of the summary - /// each time max_age_seconds has passed. The default value is 5. + /// determines the number of buckets used to exclude observations that are + /// older than max_age from the summary, e.g., if max_age is 60 seconds and + /// age_buckets is 5, buckets will be switched every 12 seconds. The value is + /// a trade-off between resources (memory and cpu for maintaining the bucket) + /// and how smooth the time window is moved. With only one age bucket it + /// effectively results in a complete reset of the summary each time max_age + /// has passed. The default value is 5. Summary(const Quantiles& quantiles, - std::chrono::milliseconds max_age_seconds = std::chrono::seconds(60), + std::chrono::milliseconds max_age = std::chrono::seconds{60}, int age_buckets = 5); /// \brief Observe the given amount. @@ -77,11 +77,10 @@ class Summary { private: const Quantiles quantiles_; - std::mutex mutex_; - - double count_; + std::uint64_t count_; double sum_; detail::TimeWindowQuantiles quantile_values_; }; + } // namespace prometheus diff --git a/core/src/summary.cc b/core/src/summary.cc index 9fea81da..2d913aa2 100644 --- a/core/src/summary.cc +++ b/core/src/summary.cc @@ -3,13 +3,13 @@ namespace prometheus { Summary::Summary(const Quantiles& quantiles, - std::chrono::milliseconds max_age_seconds, int age_buckets) - : quantiles_(quantiles), - count_(0), - sum_(0), - quantile_values_(quantiles_, max_age_seconds, age_buckets) {} + const std::chrono::milliseconds max_age, const int age_buckets) + : quantiles_{quantiles}, + count_{0}, + sum_{0}, + quantile_values_{quantiles_, max_age, age_buckets} {} -void Summary::Observe(double value) { +void Summary::Observe(const double value) { std::lock_guard lock(mutex_); count_ += 1;