From a2c5a8a5ff6cf639d1c3f03619f075f208bb4aa7 Mon Sep 17 00:00:00 2001 From: Christopher Parratto Date: Thu, 29 Feb 2024 19:47:57 -0600 Subject: [PATCH] Rolling summary configuration (#444) --- metrics-exporter-prometheus/src/builder.rs | 31 +++++++++++++++++++ metrics-exporter-prometheus/src/common.rs | 4 +++ .../src/distribution.rs | 31 ++++++++++++++++--- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/metrics-exporter-prometheus/src/builder.rs b/metrics-exporter-prometheus/src/builder.rs index 6d02bb4a..49c22c4f 100644 --- a/metrics-exporter-prometheus/src/builder.rs +++ b/metrics-exporter-prometheus/src/builder.rs @@ -5,6 +5,7 @@ use std::convert::TryFrom; use std::future::Future; #[cfg(feature = "http-listener")] use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::num::NonZeroU32; #[cfg(any(feature = "http-listener", feature = "push-gateway"))] use std::pin::Pin; use std::sync::RwLock; @@ -96,6 +97,8 @@ pub struct PrometheusBuilder { #[cfg(feature = "http-listener")] allowed_addresses: Option>, quantiles: Vec, + bucket_duration: Option, + bucket_count: Option, buckets: Option>, bucket_overrides: Option>>, idle_timeout: Option, @@ -120,6 +123,8 @@ impl PrometheusBuilder { #[cfg(feature = "http-listener")] allowed_addresses: None, quantiles, + bucket_duration: None, + bucket_count: None, buckets: None, bucket_overrides: None, idle_timeout: None, @@ -239,6 +244,30 @@ impl PrometheusBuilder { Ok(self) } + /// Sets the default bucket duration for rolling summaries + /// + /// Buckets will be cleared after this interval expires + /// + /// ## Errors + /// + /// If `value` less than 1 error will be thrown + pub fn set_bucket_duration(mut self, value: Duration) -> Result { + if value.is_zero() { + return Err(BuildError::ZeroBucketDuration); + } + + self.bucket_duration = Some(value); + Ok(self) + } + + /// Sets the default bucket count for rolling summaries + /// + /// Count number buckets are created to store summary information + pub fn set_bucket_count(mut self, count: NonZeroU32) -> Self { + self.bucket_count = Some(count); + self + } + /// Sets the buckets to use when rendering histograms. /// /// Buckets values represent the higher bound of each buckets. If buckets are set, then all @@ -536,7 +565,9 @@ impl PrometheusBuilder { distributions: RwLock::new(HashMap::new()), distribution_builder: DistributionBuilder::new( self.quantiles, + self.bucket_duration, self.buckets, + self.bucket_count, self.bucket_overrides, ), descriptions: RwLock::new(HashMap::new()), diff --git a/metrics-exporter-prometheus/src/common.rs b/metrics-exporter-prometheus/src/common.rs index b61873fb..94ff6ab6 100644 --- a/metrics-exporter-prometheus/src/common.rs +++ b/metrics-exporter-prometheus/src/common.rs @@ -74,6 +74,10 @@ pub enum BuildError { /// Bucket bounds or quantiles were empty. #[error("bucket bounds/quantiles cannot be empty")] EmptyBucketsOrQuantiles, + + /// Bucket duration cannot be zero + #[error("bucket durations cannot be set to zero")] + ZeroBucketDuration, } pub struct Snapshot { diff --git a/metrics-exporter-prometheus/src/distribution.rs b/metrics-exporter-prometheus/src/distribution.rs index 24475d82..83a3b4d1 100644 --- a/metrics-exporter-prometheus/src/distribution.rs +++ b/metrics-exporter-prometheus/src/distribution.rs @@ -8,6 +8,9 @@ use crate::common::Matcher; use metrics_util::{Histogram, Quantile, Summary}; +const DEFAULT_SUMMARY_BUCKET_COUNT: u32 = 3; +const DEFAULT_SUMMARY_BUCKET_DURATION: u64 = 20; + /// Distribution type. #[derive(Clone)] pub enum Distribution { @@ -34,9 +37,12 @@ impl Distribution { } /// Creates a summary distribution. - pub fn new_summary(quantiles: Arc>) -> Distribution { - let summary = RollingSummary::default(); - Distribution::Summary(summary, quantiles, 0.0) + pub fn new_summary( + quantiles: Arc>, + bucket_duration: Duration, + bucket_count: NonZeroU32, + ) -> Distribution { + Distribution::Summary(RollingSummary::new(bucket_count, bucket_duration), quantiles, 0.0) } /// Records the given `samples` in the current distribution. @@ -60,6 +66,8 @@ impl Distribution { pub struct DistributionBuilder { quantiles: Arc>, buckets: Option>, + bucket_duration: Option, + bucket_count: Option, bucket_overrides: Option)>>, } @@ -67,12 +75,16 @@ impl DistributionBuilder { /// Creates a new instance of `DistributionBuilder`. pub fn new( quantiles: Vec, + bucket_duration: Option, buckets: Option>, + bucket_count: Option, bucket_overrides: Option>>, ) -> DistributionBuilder { DistributionBuilder { quantiles: Arc::new(quantiles), + bucket_duration, buckets, + bucket_count, bucket_overrides: bucket_overrides.map(|entries| { let mut matchers = entries.into_iter().collect::>(); matchers.sort_by(|a, b| a.0.cmp(&b.0)); @@ -95,7 +107,13 @@ impl DistributionBuilder { return Distribution::new_histogram(buckets); } - Distribution::new_summary(self.quantiles.clone()) + let b_duration = self + .bucket_duration + .map_or(Duration::from_secs(DEFAULT_SUMMARY_BUCKET_DURATION), |d| d); + let b_count = + self.bucket_count.map_or(NonZeroU32::new(DEFAULT_SUMMARY_BUCKET_COUNT).unwrap(), |c| c); + + Distribution::new_summary(self.quantiles.clone(), b_duration, b_count) } /// Returns the distribution type for the given metric key. @@ -142,7 +160,10 @@ pub struct RollingSummary { impl Default for RollingSummary { fn default() -> Self { - RollingSummary::new(NonZeroU32::new(3).unwrap(), Duration::from_secs(20)) + RollingSummary::new( + NonZeroU32::new(DEFAULT_SUMMARY_BUCKET_COUNT).unwrap(), + Duration::from_secs(20), + ) } }