use crate::{HdrHistogram, value_from_index};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IterEntry {
pub value_lo: u64,
pub value_hi: u64,
pub count: u64,
pub cumulative: u64,
}
pub struct HdrLinearIter<'a> {
counters: &'a [u64],
sub_count_bits: u32,
high_index: usize,
idx: usize,
cumulative: u64,
}
impl<'a> HdrLinearIter<'a> {
pub(crate) fn new(h: &'a HdrHistogram) -> Self {
Self {
counters: h.counters(),
sub_count_bits: h.sub_count_bits(),
high_index: h.high_index(),
idx: 0,
cumulative: 0,
}
}
}
impl<'a> Iterator for HdrLinearIter<'a> {
type Item = IterEntry;
fn next(&mut self) -> Option<Self::Item> {
let bits = self.sub_count_bits;
let end = (self.high_index + 1).min(self.counters.len());
while self.idx < end {
let i = self.idx;
self.idx += 1;
let c = self.counters[i];
if c == 0 {
continue;
}
let lo = value_from_index(i, bits);
let hi = value_from_index(i + 1, bits);
self.cumulative += c;
return Some(IterEntry {
value_lo: lo,
value_hi: hi,
count: c,
cumulative: self.cumulative,
});
}
None
}
}
pub struct HdrLogarithmicIter<'a> {
counters: &'a [u64],
sub_count_bits: u32,
high_index: usize,
lo: u64,
cumulative: u64,
done: bool,
}
impl<'a> HdrLogarithmicIter<'a> {
pub(crate) fn new(h: &'a HdrHistogram) -> Self {
Self {
counters: h.counters(),
sub_count_bits: h.sub_count_bits(),
high_index: h.high_index(),
lo: 1,
cumulative: 0,
done: false,
}
}
}
impl<'a> Iterator for HdrLogarithmicIter<'a> {
type Item = IterEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let bits = self.sub_count_bits;
let hi = self.lo.saturating_mul(2);
let mut count = 0u64;
let end = (self.high_index + 1).min(self.counters.len());
for i in 0..end {
let v = value_from_index(i, bits);
if v >= self.lo && v < hi {
count += self.counters[i];
}
}
self.cumulative += count;
let entry = IterEntry {
value_lo: self.lo,
value_hi: hi,
count,
cumulative: self.cumulative,
};
let high_val = value_from_index(self.high_index, bits);
if hi > high_val {
self.done = true;
}
self.lo = hi;
Some(entry)
}
}
pub struct HdrPercentileIter<'a> {
counters: &'a [u64],
sub_count_bits: u32,
high_index: usize,
total: u64,
step_pct: f64,
next_pct: f64,
idx: usize,
cum: u64,
}
impl<'a> HdrPercentileIter<'a> {
pub(crate) fn new(h: &'a HdrHistogram, step_percent: f64) -> Self {
Self {
counters: h.counters(),
sub_count_bits: h.sub_count_bits(),
high_index: h.high_index(),
total: h.count(),
step_pct: step_percent.max(f64::MIN_POSITIVE),
next_pct: step_percent.max(f64::MIN_POSITIVE),
idx: 0,
cum: 0,
}
}
}
impl<'a> Iterator for HdrPercentileIter<'a> {
type Item = IterEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.total == 0 || self.next_pct > 100.0 + 1e-9 {
return None;
}
let bits = self.sub_count_bits;
let end = (self.high_index + 1).min(self.counters.len());
let target = ((self.next_pct / 100.0) * self.total as f64) as u64;
while self.idx < end {
self.cum += self.counters[self.idx];
if self.cum >= target {
let lo = value_from_index(self.idx, bits);
let hi = value_from_index(self.idx + 1, bits);
let pct_now = self.next_pct;
self.next_pct += self.step_pct;
return Some(IterEntry {
value_lo: lo,
value_hi: hi,
count: self.counters[self.idx],
cumulative: self
.cum
.min(self.total)
.max((pct_now / 100.0 * self.total as f64) as u64),
});
}
self.idx += 1;
}
None
}
}
impl HdrHistogram {
pub fn iter_linear(&self) -> HdrLinearIter<'_> {
HdrLinearIter::new(self)
}
pub fn iter_logarithmic(&self) -> HdrLogarithmicIter<'_> {
HdrLogarithmicIter::new(self)
}
pub fn iter_percentiles(&self, step_percent: f64) -> HdrPercentileIter<'_> {
HdrPercentileIter::new(self, step_percent)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_visits_only_populated_buckets() {
let mut h = HdrHistogram::new(3);
for v in [10u64, 100, 1000] {
h.record(v);
}
let entries: Vec<IterEntry> = h.iter_linear().collect();
assert_eq!(entries.len(), 3, "exactly the three populated buckets");
for w in entries.windows(2) {
assert!(w[0].value_lo < w[1].value_lo, "value order");
}
assert_eq!(entries.last().unwrap().cumulative, 3);
}
#[test]
fn linear_on_empty_yields_nothing() {
let h = HdrHistogram::new(3);
let entries: Vec<IterEntry> = h.iter_linear().collect();
assert!(entries.is_empty());
}
#[test]
fn logarithmic_bands_double() {
let mut h = HdrHistogram::new(3);
for v in [1u64, 3, 7, 15, 31, 100, 1000] {
h.record(v);
}
let entries: Vec<IterEntry> = h.iter_logarithmic().collect();
assert!(!entries.is_empty());
for w in entries.windows(2) {
assert_eq!(w[1].value_lo, w[0].value_hi, "abutting bands");
assert_eq!(w[0].value_hi, w[0].value_lo * 2, "powers of two");
}
let total: u64 = entries.iter().map(|e| e.count).sum();
assert_eq!(total, 7, "every record covered by some band");
}
#[test]
fn logarithmic_covers_high_bucket() {
let mut h = HdrHistogram::new(3);
h.record(1);
h.record(1_000_000);
let entries: Vec<IterEntry> = h.iter_logarithmic().collect();
let total: u64 = entries.iter().map(|e| e.count).sum();
assert_eq!(total, 2);
}
#[test]
fn percentile_emits_roughly_step_entries() {
let mut h = HdrHistogram::new(3);
for v in 1u64..=1000 {
h.record(v);
}
let entries: Vec<IterEntry> = h.iter_percentiles(10.0).collect();
assert!(
(8..=12).contains(&entries.len()),
"got {} percentile entries",
entries.len()
);
for w in entries.windows(2) {
assert!(w[0].cumulative <= w[1].cumulative);
}
}
#[test]
fn percentile_on_empty_yields_nothing() {
let h = HdrHistogram::new(3);
let entries: Vec<IterEntry> = h.iter_percentiles(1.0).collect();
assert!(entries.is_empty());
}
#[test]
fn linear_and_percentile_share_population() {
let mut h = HdrHistogram::new(3);
for v in 1u64..=100 {
h.record(v);
}
let linear_total: u64 = h.iter_linear().map(|e| e.count).sum();
assert_eq!(linear_total, 100);
let last_pct = h.iter_percentiles(1.0).last();
assert!(last_pct.is_some());
}
}