pub(super) const BUCKETS: usize = 65;
pub(super) const SLOTS: usize = BUCKETS + 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Histogram {
GetMicros,
WriteMicros,
}
impl Histogram {
pub const ALL: [Self; 2] = [Self::GetMicros, Self::WriteMicros];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::GetMicros => "get.micros",
Self::WriteMicros => "write.micros",
}
}
pub(super) const fn index(self) -> usize {
match self {
Self::GetMicros => 0,
Self::WriteMicros => 1,
}
}
pub(super) const fn base(self) -> usize {
match self {
Self::GetMicros => 0,
Self::WriteMicros => SLOTS,
}
}
}
pub(super) fn bucket(micros: u64) -> usize {
usize::try_from(u64::BITS.saturating_sub(micros.leading_zeros())).unwrap_or(BUCKETS)
}
fn upper(bucket: usize) -> u64 {
match u32::try_from(bucket) {
Ok(0) => 0,
Ok(bits) if bits < u64::BITS => (1_u64 << bits).saturating_sub(1),
_ => u64::MAX,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HistogramSnapshot {
pub(super) buckets: [u64; BUCKETS],
pub(super) sum: u64,
}
impl Default for HistogramSnapshot {
fn default() -> Self {
Self {
buckets: [0; BUCKETS],
sum: 0,
}
}
}
impl HistogramSnapshot {
#[must_use]
pub fn count(&self) -> u64 {
self.buckets
.iter()
.fold(0, |total, count| total.wrapping_add(*count))
}
#[must_use]
pub const fn sum_micros(&self) -> u64 {
self.sum
}
#[must_use]
pub const fn bucket_counts(&self) -> &[u64] {
&self.buckets
}
#[must_use]
pub fn quantile(&self, numerator: u64, denominator: u64) -> u64 {
let count = u128::from(self.count());
let Some(rank) = (count.saturating_mul(u128::from(numerator.min(denominator))))
.checked_div(u128::from(denominator))
else {
return 0;
};
let rank = rank.max(1);
let mut seen = 0_u128;
for (bucket, values) in self.buckets.iter().enumerate() {
seen = seen.saturating_add(u128::from(*values));
if seen >= rank {
return upper(bucket);
}
}
0
}
}