From e6f1992d9f7f9bd3a301a05c5781f6cb78a3d2d3 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Tue, 29 Mar 2022 23:09:54 +1100 Subject: [PATCH] Allow summary metrics to use custom time source --- core/include/prometheus/counter.h | 9 ++++ .../prometheus/detail/time_window_quantiles.h | 7 +-- core/include/prometheus/family.h | 4 +- core/include/prometheus/gauge.h | 9 ++++ core/include/prometheus/histogram.h | 11 +++- core/include/prometheus/summary.h | 50 +++++++++++++++++++ core/src/counter.cc | 6 +++ core/src/detail/time_window_quantiles.cc | 18 ++++--- core/src/family.cc | 9 ++-- core/src/gauge.cc | 6 +++ core/src/histogram.cc | 6 +++ core/src/summary.cc | 22 ++++++-- 12 files changed, 137 insertions(+), 20 deletions(-) diff --git a/core/include/prometheus/counter.h b/core/include/prometheus/counter.h index f8fc593f..c93aadb8 100644 --- a/core/include/prometheus/counter.h +++ b/core/include/prometheus/counter.h @@ -48,6 +48,15 @@ class PROMETHEUS_CPP_CORE_EXPORT Counter { /// Collect is called by the Registry when collecting metrics. ClientMetric Collect() const; + /// \brief Get the current value of the counter. + /// + /// \param time The time of when this function was called. + /// + /// \return The current value. + /// + /// Collect is called by the Registry when collecting metrics. + ClientMetric Collect(const std::chrono::system_clock::time_point& time) const; + /// \brief Check if the counter has expired. /// /// Expires is called by the Registry when collecting metrics. diff --git a/core/include/prometheus/detail/time_window_quantiles.h b/core/include/prometheus/detail/time_window_quantiles.h index 8b0b8182..e153a153 100644 --- a/core/include/prometheus/detail/time_window_quantiles.h +++ b/core/include/prometheus/detail/time_window_quantiles.h @@ -17,13 +17,14 @@ class PROMETHEUS_CPP_CORE_EXPORT TimeWindowQuantiles { public: TimeWindowQuantiles(const std::vector& quantiles, + const Clock::time_point& creation_time, Clock::duration max_age_seconds, int age_buckets); - double get(double q) const; - void insert(double value); + double get(double q, const Clock::time_point& time) const; + void insert(double value, const Clock::time_point& time); private: - CKMSQuantiles& rotate() const; + CKMSQuantiles& rotate(const Clock::time_point& time) const; const std::vector& quantiles_; mutable std::vector ckms_quantiles_; diff --git a/core/include/prometheus/family.h b/core/include/prometheus/family.h index 8ad48d83..7b9560c5 100644 --- a/core/include/prometheus/family.h +++ b/core/include/prometheus/family.h @@ -169,7 +169,9 @@ class PROMETHEUS_CPP_CORE_EXPORT Family : public Collectable { std::chrono::seconds ttl_{std::chrono::seconds::max()}; mutable std::mutex mutex_; - ClientMetric CollectMetric(const Labels& labels, T* metric) const; + ClientMetric CollectMetric( + const Labels& labels, T* metric, + const std::chrono::system_clock::time_point& time) const; T& Add(const Labels& labels, std::unique_ptr object); }; diff --git a/core/include/prometheus/gauge.h b/core/include/prometheus/gauge.h index bc6df38d..b0de3f78 100644 --- a/core/include/prometheus/gauge.h +++ b/core/include/prometheus/gauge.h @@ -58,6 +58,15 @@ class PROMETHEUS_CPP_CORE_EXPORT Gauge { /// Collect is called by the Registry when collecting metrics. ClientMetric Collect() const; + /// \brief Get the current value of the gauge. + /// + /// \param time The time of when this function was called. + /// + /// \return The current value. + /// + /// Collect is called by the Registry when collecting metrics. + ClientMetric Collect(const std::chrono::system_clock::time_point& time) const; + /// \brief Check if the gauge has expired. /// /// Expires is called by the Registry when collecting metrics. diff --git a/core/include/prometheus/histogram.h b/core/include/prometheus/histogram.h index 30fe914b..9b38857a 100644 --- a/core/include/prometheus/histogram.h +++ b/core/include/prometheus/histogram.h @@ -63,11 +63,20 @@ class PROMETHEUS_CPP_CORE_EXPORT Histogram { void ObserveMultiple(const std::vector& bucket_increments, const double sum_of_values); - /// \brief Get the current value of the counter. + /// \brief Get the current value of the histogram. /// /// Collect is called by the Registry when collecting metrics. ClientMetric Collect() const; + /// \brief Get the current value of the histogram. + /// + /// \param time The time of when this function was called. + /// + /// \return The current value. + /// + /// Collect is called by the Registry when collecting metrics. + ClientMetric Collect(const std::chrono::system_clock::time_point& time) const; + /// \brief Check if the histogram has expired. /// /// Expires is called by the Registry when collecting metrics. diff --git a/core/include/prometheus/summary.h b/core/include/prometheus/summary.h index 8de46a90..79c0f1ba 100644 --- a/core/include/prometheus/summary.h +++ b/core/include/prometheus/summary.h @@ -75,14 +75,64 @@ class PROMETHEUS_CPP_CORE_EXPORT Summary { std::chrono::milliseconds max_age = std::chrono::seconds{60}, int age_buckets = 5); + /// \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 and age_buckets. + /// + /// \param creation_time The time of when this constructor was called. + /// + /// \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 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, + const std::chrono::system_clock::time_point& creation_time, + std::chrono::milliseconds max_age = std::chrono::seconds{60}, + int age_buckets = 5); + /// \brief Observe the given amount. void Observe(double value); + /// \brief Observe the given amount. + /// + /// \param quantiles The given amount. + /// + /// \param time The time of when this function was called. + void Observe(double value, const std::chrono::system_clock::time_point& time); + /// \brief Get the current value of the summary. /// /// Collect is called by the Registry when collecting metrics. ClientMetric Collect() const; + /// \brief Get the current value of the summary. + /// + /// \param time The time of when this function was called. + /// + /// \return The current value. + /// + /// Collect is called by the Registry when collecting metrics. + ClientMetric Collect(const std::chrono::system_clock::time_point& time) const; + /// \brief Check if the summary has expired. /// /// Expires is called by the Registry when collecting metrics. diff --git a/core/src/counter.cc b/core/src/counter.cc index 89c4ceac..4ffb8458 100644 --- a/core/src/counter.cc +++ b/core/src/counter.cc @@ -19,6 +19,12 @@ ClientMetric Counter::Collect() const { return metric; } +ClientMetric Counter::Collect( + const std::chrono::system_clock::time_point& time) const { + (void)time; + return Collect(); +} + bool Counter::Expired(const std::chrono::system_clock::time_point& time, const std::chrono::seconds& ttl) { (void)time; diff --git a/core/src/detail/time_window_quantiles.cc b/core/src/detail/time_window_quantiles.cc index 57f1e1c6..4f690ce1 100644 --- a/core/src/detail/time_window_quantiles.cc +++ b/core/src/detail/time_window_quantiles.cc @@ -8,27 +8,29 @@ namespace detail { TimeWindowQuantiles::TimeWindowQuantiles( const std::vector& quantiles, - const Clock::duration max_age, const int age_buckets) + const Clock::time_point& creation_time, const Clock::duration max_age, + const int age_buckets) : quantiles_(quantiles), ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)), current_bucket_(0), - last_rotation_(Clock::now()), + last_rotation_(creation_time), rotation_interval_(max_age / age_buckets) {} -double TimeWindowQuantiles::get(double q) const { - CKMSQuantiles& current_bucket = rotate(); +double TimeWindowQuantiles::get(double q, const Clock::time_point& time) const { + CKMSQuantiles& current_bucket = rotate(time); return current_bucket.get(q); } -void TimeWindowQuantiles::insert(double value) { - rotate(); +void TimeWindowQuantiles::insert(double value, const Clock::time_point& time) { + rotate(time); for (auto& bucket : ckms_quantiles_) { bucket.insert(value); } } -CKMSQuantiles& TimeWindowQuantiles::rotate() const { - auto delta = Clock::now() - last_rotation_; +CKMSQuantiles& TimeWindowQuantiles::rotate( + const Clock::time_point& time) const { + auto delta = time - last_rotation_; while (delta > rotation_interval_) { ckms_quantiles_[current_bucket_].reset(); diff --git a/core/src/family.cc b/core/src/family.cc index 49a71a2f..3a2c6197 100644 --- a/core/src/family.cc +++ b/core/src/family.cc @@ -109,16 +109,17 @@ std::vector Family::Collect( for (const auto& m : metrics_) { if (!m.second->Expired(time, ttl_)) { family.metric.push_back( - std::move(CollectMetric(m.first, m.second.get()))); + std::move(CollectMetric(m.first, m.second.get(), time))); } } return {family}; } template -ClientMetric Family::CollectMetric(const Labels& metric_labels, - T* metric) const { - auto collected = metric->Collect(); +ClientMetric Family::CollectMetric( + const Labels& metric_labels, T* metric, + const std::chrono::system_clock::time_point& time) const { + auto collected = metric->Collect(time); collected.label.reserve(constant_labels_.size() + metric_labels.size()); const auto add_label = [&collected](const std::pair& label_pair) { diff --git a/core/src/gauge.cc b/core/src/gauge.cc index e2f2aa92..57219b76 100644 --- a/core/src/gauge.cc +++ b/core/src/gauge.cc @@ -41,6 +41,12 @@ ClientMetric Gauge::Collect() const { return metric; } +ClientMetric Gauge::Collect( + const std::chrono::system_clock::time_point& time) const { + (void)time; + return Collect(); +} + bool Gauge::Expired(const std::chrono::system_clock::time_point& time, const std::chrono::seconds& ttl) const { return std::chrono::duration_cast(time - diff --git a/core/src/histogram.cc b/core/src/histogram.cc index f6446547..c2138329 100644 --- a/core/src/histogram.cc +++ b/core/src/histogram.cc @@ -80,6 +80,12 @@ ClientMetric Histogram::Collect() const { return metric; } +ClientMetric Histogram::Collect( + const std::chrono::system_clock::time_point& time) const { + (void)time; + return Collect(); +} + bool Histogram::Expired(const std::chrono::system_clock::time_point& time, const std::chrono::seconds& ttl) { (void)time; diff --git a/core/src/summary.cc b/core/src/summary.cc index 743ecf19..a86c7a81 100644 --- a/core/src/summary.cc +++ b/core/src/summary.cc @@ -6,20 +6,36 @@ namespace prometheus { Summary::Summary(const Quantiles& quantiles, const std::chrono::milliseconds max_age, const int age_buckets) + : Summary(quantiles, std::chrono::system_clock::now(), max_age, + age_buckets) {} + +Summary::Summary(const Quantiles& quantiles, + const std::chrono::system_clock::time_point& creation_time, + const std::chrono::milliseconds max_age, const int age_buckets) : quantiles_{quantiles}, count_{0}, sum_{0}, - quantile_values_{quantiles_, max_age, age_buckets} {} + quantile_values_{quantiles_, creation_time, max_age, age_buckets} {} void Summary::Observe(const double value) { + Observe(value, std::chrono::system_clock::now()); +} + +void Summary::Observe(const double value, + const std::chrono::system_clock::time_point& time) { std::lock_guard lock(mutex_); count_ += 1; sum_ += value; - quantile_values_.insert(value); + quantile_values_.insert(value, time); } ClientMetric Summary::Collect() const { + return Collect(std::chrono::system_clock::now()); +} + +ClientMetric Summary::Collect( + const std::chrono::system_clock::time_point& time) const { auto metric = ClientMetric{}; std::lock_guard lock(mutex_); @@ -28,7 +44,7 @@ ClientMetric Summary::Collect() const { for (const auto& quantile : quantiles_) { auto metricQuantile = ClientMetric::Quantile{}; metricQuantile.quantile = quantile.quantile; - metricQuantile.value = quantile_values_.get(quantile.quantile); + metricQuantile.value = quantile_values_.get(quantile.quantile, time); metric.summary.quantile.push_back(std::move(metricQuantile)); } metric.summary.sample_count = count_;