use std::{num::NonZeroU32, time::Duration};
use metrics_util::Quantile;
use quanta::Instant;
use crate::summary::{
DEFAULT_QUANTILES,
simple::SimpleSummary,
traits::{NonConcurrentSummaryProvider, Summary},
};
pub const DEFAULT_SUMMARY_BUCKET_DURATION: Duration = Duration::from_secs(20);
pub const DEFAULT_SUMMARY_BUCKET_COUNT: NonZeroU32 = NonZeroU32::new(3).unwrap();
pub type RollingSummary = metrics_exporter_prometheus::Distribution;
pub struct RollingSummarySnapshot {
count: usize,
inner: SimpleSummary,
}
impl Summary for RollingSummarySnapshot {
fn sample_sum(&self) -> f64 {
self.inner.sample_sum()
}
fn sample_count(&self) -> u64 {
self.count as u64
}
fn quantile(&self, val: f64) -> Option<f64> {
self.inner.quantile(val)
}
}
#[derive(Clone)]
pub struct RollingSummaryOpts {
pub quantiles: Vec<Quantile>,
pub duration: Duration,
pub max_buckets_count: NonZeroU32,
}
impl RollingSummaryOpts {
pub fn with_quantiles(self, quantiles: &[f64]) -> Self {
Self {
quantiles: quantiles.iter().map(|quantile| Quantile::new(*quantile)).collect(),
..self
}
}
}
impl Default for RollingSummaryOpts {
fn default() -> Self {
Self {
quantiles: DEFAULT_QUANTILES.iter().map(|quantile| Quantile::new(*quantile)).collect(),
duration: DEFAULT_SUMMARY_BUCKET_DURATION,
max_buckets_count: DEFAULT_SUMMARY_BUCKET_COUNT,
}
}
}
impl NonConcurrentSummaryProvider for RollingSummary {
type Opts = RollingSummaryOpts;
type Summary = RollingSummarySnapshot;
fn new_provider(opts: &Self::Opts) -> Self {
let distribution = metrics_exporter_prometheus::DistributionBuilder::new(
opts.quantiles.clone(),
Some(opts.duration),
None,
Some(opts.max_buckets_count),
None,
)
.get_distribution("name not relevant");
assert!(
matches!(distribution, RollingSummary::Summary(..)),
"DistributionBuilder didn't build a Summary!"
);
distribution
}
fn observe(&mut self, sample: f64) {
let now = Instant::now();
self.record_samples(&[(sample, now)]);
}
fn snapshot(&self) -> RollingSummarySnapshot {
match self {
RollingSummary::Summary(summary, _, sum) => {
let count = summary.count();
let snapshot = summary.snapshot(Instant::now());
let inner = SimpleSummary { inner: snapshot, sum: *sum };
RollingSummarySnapshot { inner, count }
}
_ => unreachable!("Distribution forced to be a Summary"),
}
}
}