use crate::util::atomic_f64::AtomicF64;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::time::{Duration, Instant};
const DECAYS_PER_HALF_LIFE: u64 = 10;
fn decay_factor() -> f64 {
0.5f64.powf(1. / DECAYS_PER_HALF_LIFE as f64)
}
pub struct CoarseExponentialDecayReservoir {
value: AtomicF64,
start: Instant,
last_decay_nanos: AtomicU64,
decay_interval_nanos: u64,
}
impl CoarseExponentialDecayReservoir {
pub fn new(half_life: Duration) -> CoarseExponentialDecayReservoir {
CoarseExponentialDecayReservoir {
value: AtomicF64::new(0.),
start: Instant::now(),
last_decay_nanos: AtomicU64::new(0),
decay_interval_nanos: half_life.as_nanos() as u64 / DECAYS_PER_HALF_LIFE,
}
}
pub fn update(&self, updates: f64) {
self.decay_if_necessary();
self.value.fetch_add(updates, Ordering::SeqCst);
}
pub fn get(&self) -> f64 {
self.decay_if_necessary();
self.value.load(Ordering::SeqCst)
}
fn decay_if_necessary(&self) {
let now_nanos = self.start.elapsed().as_nanos() as u64;
let last_decay_nanos_snapshot = self.last_decay_nanos.load(Ordering::SeqCst);
let nanos_since_last_decay = now_nanos - last_decay_nanos_snapshot;
let decays = nanos_since_last_decay / self.decay_interval_nanos;
if decays == 0 {
return;
}
if self
.last_decay_nanos
.compare_exchange(
last_decay_nanos_snapshot,
last_decay_nanos_snapshot + decays * self.decay_interval_nanos,
Ordering::SeqCst,
Ordering::SeqCst,
)
.is_err()
{
return;
}
self.decay(decays as i32);
}
fn decay(&self, decays: i32) {
let _ = self
.value
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| {
Some(old * decay_factor().powi(decays))
});
}
}