use core::sync::atomic::{AtomicU64, Ordering};
use crate::Op;
pub const BUCKETS: usize = 26;
pub const BUCKET_BOUNDS_US: [u64; BUCKETS] = [
1,
2,
4,
8,
16,
32,
64,
128,
256,
512,
1_024,
2_048,
4_096,
8_192,
16_384,
32_768,
65_536,
131_072,
262_144,
524_288,
1_048_576,
2_097_152,
4_194_304,
8_388_608,
16_777_216,
u64::MAX,
];
static HISTOGRAMS: [[AtomicU64; BUCKETS]; Op::COUNT] =
[const { [const { AtomicU64::new(0) }; BUCKETS] }; Op::COUNT];
static OP_COUNT: [AtomicU64; Op::COUNT] = [const { AtomicU64::new(0) }; Op::COUNT];
static OP_SUM_US: [AtomicU64; Op::COUNT] = [const { AtomicU64::new(0) }; Op::COUNT];
#[inline(always)]
pub fn bucket_idx(us: u64) -> usize {
if us == 0 {
return 0;
}
if us >= BUCKET_BOUNDS_US[BUCKETS - 2] {
return BUCKETS - 1;
}
let lz = us.leading_zeros() as usize;
64 - lz
}
#[inline(always)]
pub fn observe_us(op: Op, us: u64) {
let b = bucket_idx(us);
HISTOGRAMS[op as usize][b].fetch_add(1, Ordering::Relaxed);
OP_COUNT[op as usize].fetch_add(1, Ordering::Relaxed);
OP_SUM_US[op as usize].fetch_add(us, Ordering::Relaxed);
}
pub fn bucket(op: Op, idx: usize) -> u64 {
HISTOGRAMS[op as usize][idx].load(Ordering::Relaxed)
}
pub fn count(op: Op) -> u64 {
OP_COUNT[op as usize].load(Ordering::Relaxed)
}
pub fn sum_us(op: Op) -> u64 {
OP_SUM_US[op as usize].load(Ordering::Relaxed)
}
pub fn quantile_us(op: Op, q: f64) -> Option<u64> {
let total = count(op);
if total == 0 {
return None;
}
let target = ((total as f64) * q).ceil() as u64;
let mut running: u64 = 0;
for i in 0..BUCKETS {
running += bucket(op, i);
if running >= target {
if i == 0 {
return Some(0);
}
if i == BUCKETS - 1 {
return Some(BUCKET_BOUNDS_US[BUCKETS - 2]);
}
let lo = BUCKET_BOUNDS_US[i - 1];
let hi = BUCKET_BOUNDS_US[i];
return Some((lo + hi) / 2);
}
}
Some(BUCKET_BOUNDS_US[BUCKETS - 2])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bucket_idx_endpoints() {
assert_eq!(bucket_idx(0), 0);
assert_eq!(bucket_idx(1), 1);
assert_eq!(bucket_idx(2), 2);
assert_eq!(bucket_idx(3), 2);
assert_eq!(bucket_idx(4), 3);
assert!(bucket_idx(10_000_000) < BUCKETS - 1);
assert_eq!(bucket_idx(16_777_216), BUCKETS - 1);
assert_eq!(bucket_idx(u64::MAX), BUCKETS - 1);
}
}