pub struct HdrHistogram {
sub_count: u32,
sub_count_bits: u32,
counters: Vec<u64>,
total: u64,
high_index: usize,
}
impl HdrHistogram {
pub fn new(significant_digits: 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;
Self {
sub_count,
sub_count_bits,
counters: vec![0u64; sub_count as usize],
total: 0,
high_index: 0,
}
}
pub fn count(&self) -> u64 {
self.total
}
pub fn max(&self) -> u64 {
if self.total == 0 {
return 0;
}
value_from_index(self.high_index, self.sub_count_bits)
}
pub fn record(&mut self, value: u64) {
let idx = index_of(value, self.sub_count_bits) as usize;
if idx >= self.counters.len() {
self.counters.resize(idx + 1, 0);
}
self.counters[idx] += 1;
self.total += 1;
if idx > self.high_index {
self.high_index = idx;
}
}
pub fn record_with_expected_interval(&mut self, value: u64, expected_interval: u64) {
self.record(value);
if expected_interval == 0 || value <= expected_interval {
return;
}
let mut missing = value - expected_interval;
while missing >= expected_interval {
self.record(missing);
missing -= expected_interval;
}
}
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.counters.iter().take(self.high_index + 1).enumerate() {
cum += c;
if cum >= target {
return value_from_index(i, self.sub_count_bits);
}
}
value_from_index(self.high_index, self.sub_count_bits)
}
pub fn sub_count(&self) -> u32 {
self.sub_count
}
pub fn min(&self) -> u64 {
if self.total == 0 {
return 0;
}
for (i, &c) in self.counters.iter().take(self.high_index + 1).enumerate() {
if c > 0 {
return value_from_index(i, self.sub_count_bits);
}
}
0
}
pub fn mean(&self) -> f64 {
if self.total == 0 {
return 0.0;
}
let mut sum = 0f64;
for (i, &c) in self.counters.iter().take(self.high_index + 1).enumerate() {
if c > 0 {
sum += c as f64 * value_from_index(i, self.sub_count_bits) as f64;
}
}
sum / self.total as f64
}
pub fn count_at_value(&self, value: u64) -> u64 {
let idx = index_of(value, self.sub_count_bits) as usize;
self.counters.get(idx).copied().unwrap_or(0)
}
pub fn percentile_at_or_below_value(&self, value: u64) -> f64 {
if self.total == 0 {
return 0.0;
}
let idx = index_of(value, self.sub_count_bits) as usize;
let end = (idx + 1).min(self.high_index + 1).min(self.counters.len());
let cum: u64 = self.counters[..end].iter().sum();
cum as f64 / self.total as f64
}
pub fn footprint_bytes(&self) -> usize {
self.counters.len() * size_of::<u64>()
}
pub fn reset(&mut self) {
self.counters.fill(0);
self.total = 0;
self.high_index = 0;
}
#[cfg(feature = "iterators")]
#[inline]
pub(crate) fn sub_count_bits(&self) -> u32 {
self.sub_count_bits
}
#[cfg(feature = "iterators")]
#[inline]
pub(crate) fn counters(&self) -> &[u64] {
&self.counters
}
#[cfg(feature = "iterators")]
#[inline]
pub(crate) fn high_index(&self) -> usize {
self.high_index
}
#[cfg(feature = "merge")]
pub(crate) fn add_counts_from(&mut self, other: &HdrHistogram) -> Result<(), &'static str> {
if self.sub_count_bits != other.sub_count_bits {
return Err("significant-digit mismatch");
}
if other.high_index >= self.counters.len() {
self.counters.resize(other.high_index + 1, 0);
}
for (i, &c) in other.counters.iter().enumerate() {
if c == 0 {
continue;
}
self.counters[i] += c;
if i > self.high_index {
self.high_index = i;
}
}
self.total += other.total;
Ok(())
}
}
pub(crate) fn index_of(value: u64, sub_count_bits: u32) -> u32 {
let sub_mask = (1u64 << sub_count_bits) - 1;
if value <= sub_mask {
return value as u32;
}
let bits = 64 - value.leading_zeros();
let major = bits - sub_count_bits;
let sub = ((value >> (major - 1)) & sub_mask) as u32;
(major << sub_count_bits) | sub
}
pub(crate) fn value_from_index(idx: usize, sub_count_bits: u32) -> u64 {
let sub_count = 1u64 << sub_count_bits;
let sub_mask = sub_count - 1;
let idx = idx as u64;
if idx < sub_count {
return idx;
}
let major = idx >> sub_count_bits;
let sub = idx & sub_mask;
(sub | sub_count) << (major - 1)
}
#[cfg(test)]
#[path = "hdr_tests.rs"]
mod hdr_tests;
#[cfg(test)]
#[path = "sample_app_tests.rs"]
mod sample_app_tests;
#[cfg(feature = "harness")]
pub mod growth;
#[cfg(feature = "harness")]
pub mod recipe;
#[cfg(any(
feature = "dual-recorder",
feature = "concurrent-writes",
feature = "merge",
feature = "decay",
feature = "value-tagging",
feature = "iterators",
))]
pub mod features;
#[cfg(feature = "concurrent-writes")]
pub use features::concurrent_writes::ConcurrentHdrHistogram;
#[cfg(feature = "decay")]
pub use features::decay::{Clock, DecayingHdrHistogram, ManualClock};
#[cfg(feature = "dual-recorder")]
pub use features::dual_recorder::DualRecorder;
#[cfg(feature = "iterators")]
pub use features::iterators::{HdrLinearIter, HdrLogarithmicIter, HdrPercentileIter, IterEntry};
#[cfg(feature = "merge")]
pub use features::merge::merge;
#[cfg(feature = "value-tagging")]
pub use features::value_tagging::TaggedHdrHistogram;