use crate::Clock;
use exponential_decay_histogram::ExponentialDecayHistogram;
use parking_lot::Mutex;
use std::sync::Arc;
pub trait Reservoir: 'static + Sync + Send {
fn update(&self, value: i64);
fn snapshot(&self) -> Box<dyn Snapshot>;
}
pub trait Snapshot: 'static + Sync + Send {
fn value(&self, quantile: f64) -> f64;
fn max(&self) -> i64;
fn min(&self) -> i64;
fn mean(&self) -> f64;
fn stddev(&self) -> f64;
}
pub struct ExponentiallyDecayingReservoir {
histogram: Mutex<ExponentialDecayHistogram>,
clock: Arc<dyn Clock>,
}
impl Default for ExponentiallyDecayingReservoir {
fn default() -> Self {
Self::new()
}
}
impl ExponentiallyDecayingReservoir {
pub fn new() -> Self {
Self::new_with(crate::SYSTEM_CLOCK.clone())
}
pub fn new_with(clock: Arc<dyn Clock>) -> Self {
ExponentiallyDecayingReservoir {
histogram: Mutex::new(ExponentialDecayHistogram::builder().at(clock.now()).build()),
clock,
}
}
}
impl Reservoir for ExponentiallyDecayingReservoir {
fn update(&self, value: i64) {
self.histogram.lock().update_at(self.clock.now(), value);
}
fn snapshot(&self) -> Box<dyn Snapshot> {
Box::new(self.histogram.lock().snapshot())
}
}
impl Snapshot for exponential_decay_histogram::Snapshot {
fn value(&self, quantile: f64) -> f64 {
self.value(quantile) as f64
}
fn max(&self) -> i64 {
self.max()
}
fn min(&self) -> i64 {
self.min()
}
fn mean(&self) -> f64 {
self.mean()
}
fn stddev(&self) -> f64 {
self.stddev()
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod test {
use crate::{ExponentiallyDecayingReservoir, Reservoir};
#[test]
fn exponential_basic() {
let reservoir = ExponentiallyDecayingReservoir::new();
for _ in 0..15 {
reservoir.update(0);
}
for _ in 0..5 {
reservoir.update(5);
}
let snapshot = reservoir.snapshot();
assert_eq!(snapshot.value(0.5), 0.);
assert_eq!(snapshot.value(0.8), 5.);
assert_eq!(snapshot.max(), 5);
assert_eq!(snapshot.min(), 0);
assert_eq!(snapshot.mean(), 1.25);
assert!((snapshot.stddev() - 2.165).abs() < 0.0001);
}
}