use crate::{index_of, value_from_index};
pub trait Clock {
fn now_ns(&self) -> u64;
}
pub struct ManualClock {
pub now: std::cell::Cell<u64>,
}
impl ManualClock {
pub fn new() -> Self {
Self {
now: std::cell::Cell::new(0),
}
}
pub fn advance_ns(&self, dt: u64) {
self.now.set(self.now.get() + dt);
}
}
impl Default for ManualClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for ManualClock {
fn now_ns(&self) -> u64 {
self.now.get()
}
}
impl<C: Clock + ?Sized> Clock for &C {
fn now_ns(&self) -> u64 {
(*self).now_ns()
}
}
pub struct DecayingHdrHistogram<C: Clock> {
sub_count_bits: u32,
counters: Vec<f64>,
high_index: usize,
last_decay_ns: u64,
halflife_ns: u64,
clock: C,
}
impl<C: Clock> DecayingHdrHistogram<C> {
pub fn new(significant_digits: u32, halflife_ns: u64, clock: C) -> Self {
let sig = significant_digits.clamp(1, 5);
let target = 2u32 * 10u32.pow(sig);
let sub_count_bits = (32 - target.leading_zeros()).max(1);
let sub_count = 1u32 << sub_count_bits;
let last = clock.now_ns();
Self {
sub_count_bits,
counters: vec![0.0; sub_count as usize],
high_index: 0,
last_decay_ns: last,
halflife_ns: halflife_ns.max(1),
clock,
}
}
pub fn record(&mut self, value: u64) {
self.decay_to_now();
let idx = index_of(value, self.sub_count_bits) as usize;
if idx >= self.counters.len() {
self.counters.resize(idx + 1, 0.0);
}
self.counters[idx] += 1.0;
if idx > self.high_index {
self.high_index = idx;
}
}
pub fn count(&self) -> f64 {
let factor = self.peek_factor();
self.counters.iter().sum::<f64>() * factor
}
pub fn max(&self) -> u64 {
for i in (0..=self.high_index.min(self.counters.len() - 1)).rev() {
if self.counters[i] > 1e-9 {
return value_from_index(i, self.sub_count_bits);
}
}
0
}
pub fn value_at_percentile(&self, q: f64) -> u64 {
let total = self.count();
if total <= 0.0 {
return 0;
}
let target = (q.clamp(0.0, 1.0) * total).max(f64::MIN_POSITIVE);
let factor = self.peek_factor();
let mut cum = 0.0;
let end = (self.high_index + 1).min(self.counters.len());
for i in 0..end {
cum += self.counters[i] * factor;
if cum >= target {
return value_from_index(i, self.sub_count_bits);
}
}
value_from_index(self.high_index, self.sub_count_bits)
}
pub fn halflife_ns(&self) -> u64 {
self.halflife_ns
}
fn peek_factor(&self) -> f64 {
let now = self.clock.now_ns();
let dt = now.saturating_sub(self.last_decay_ns);
if dt == 0 {
return 1.0;
}
(-(dt as f64 / self.halflife_ns as f64) * std::f64::consts::LN_2).exp()
}
fn decay_to_now(&mut self) {
let factor = self.peek_factor();
if factor < 1.0 {
for c in self.counters.iter_mut() {
*c *= factor;
}
self.last_decay_ns = self.clock.now_ns();
} else if factor == 1.0 && self.last_decay_ns == 0 {
self.last_decay_ns = self.clock.now_ns();
}
}
}
#[cfg(test)]
#[path = "decay_tests.rs"]
mod tests;