use crate::{index_of, value_from_index};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
pub struct ConcurrentHdrHistogram {
sub_count: u32,
sub_count_bits: u32,
counters: Vec<AtomicU64>,
total: AtomicU64,
high_index: AtomicUsize,
}
impl ConcurrentHdrHistogram {
pub fn new(significant_digits: u32) -> Self {
Self::with_majors(significant_digits, 32)
}
pub fn with_majors(significant_digits: u32, majors: u32) -> 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 majors = majors.max(1);
let total_buckets = (sub_count as usize) * (majors as usize);
let mut counters = Vec::with_capacity(total_buckets);
for _ in 0..total_buckets {
counters.push(AtomicU64::new(0));
}
Self {
sub_count,
sub_count_bits,
counters,
total: AtomicU64::new(0),
high_index: AtomicUsize::new(0),
}
}
pub fn sub_count(&self) -> u32 {
self.sub_count
}
pub fn sub_count_bits(&self) -> u32 {
self.sub_count_bits
}
pub fn count(&self) -> u64 {
self.total.load(Ordering::Relaxed)
}
pub fn max(&self) -> u64 {
if self.count() == 0 {
return 0;
}
value_from_index(self.high_index.load(Ordering::Relaxed), self.sub_count_bits)
}
pub fn record(&self, value: u64) {
let raw = index_of(value, self.sub_count_bits) as usize;
let idx = raw.min(self.counters.len() - 1);
self.counters[idx].fetch_add(1, Ordering::Relaxed);
self.total.fetch_add(1, Ordering::Relaxed);
let mut cur = self.high_index.load(Ordering::Relaxed);
while idx > cur {
match self.high_index.compare_exchange_weak(
cur,
idx,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(seen) => cur = seen,
}
}
}
pub fn value_at_percentile(&self, q: f64) -> u64 {
let total = self.count();
if total == 0 {
return 0;
}
let target = ((q.clamp(0.0, 1.0) * total as f64) as u64).max(1);
let high = self.high_index.load(Ordering::Relaxed);
let mut cum = 0u64;
let end = (high + 1).min(self.counters.len());
for i in 0..end {
cum += self.counters[i].load(Ordering::Relaxed);
if cum >= target {
return value_from_index(i, self.sub_count_bits);
}
}
value_from_index(high, self.sub_count_bits)
}
pub fn drain_snapshot(&self) -> Snapshot {
let mut counts = Vec::with_capacity(self.counters.len());
let high = self.high_index.load(Ordering::Relaxed);
let len = (high + 1).min(self.counters.len());
let mut total = 0u64;
for i in 0..len {
let v = self.counters[i].swap(0, Ordering::AcqRel);
counts.push(v);
total += v;
}
self.total.fetch_sub(total, Ordering::AcqRel);
self.high_index.store(0, Ordering::Relaxed);
Snapshot {
sub_count_bits: self.sub_count_bits,
counts,
total,
}
}
}
pub struct Snapshot {
sub_count_bits: u32,
counts: Vec<u64>,
total: u64,
}
impl Snapshot {
pub fn count(&self) -> u64 {
self.total
}
pub fn max(&self) -> u64 {
if self.total == 0 {
return 0;
}
for i in (0..self.counts.len()).rev() {
if self.counts[i] > 0 {
return value_from_index(i, self.sub_count_bits);
}
}
0
}
pub fn value_at_percentile(&self, q: f64) -> u64 {
if self.total == 0 {
return 0;
}
let target = ((q.clamp(0.0, 1.0) * self.total as f64) as u64).max(1);
let mut cum = 0u64;
for (i, &c) in self.counts.iter().enumerate() {
cum += c;
if cum >= target {
return value_from_index(i, self.sub_count_bits);
}
}
0
}
}
#[cfg(test)]
#[path = "concurrent_writes_tests.rs"]
mod tests;