Skip to main content

subms_hdr_histogram/features/
iterators.rs

1//! Explicit iterators over the histogram in different orders.
2//!
3//! - **Linear**: every populated bucket in value order. Each step
4//!   yields one bucket entry.
5//! - **Logarithmic**: bucket boundaries aligned to powers of two
6//!   (each step doubles the upper bound). Each step yields the sum
7//!   of counts in the half-open band `[lo, hi)`.
8//! - **Percentile**: yields buckets at evenly-spaced percentile
9//!   thresholds. Caller picks the step size (e.g. 1.0 for 100
10//!   percentiles, 0.1 for 1000).
11//!
12//! All three are zero-allocation after construction (they hold a
13//! reference to the histogram and a small per-iterator cursor).
14
15use crate::{HdrHistogram, value_from_index};
16
17/// One step of histogram iteration.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct IterEntry {
20    /// Lower bound (inclusive) of the value band.
21    pub value_lo: u64,
22    /// Upper bound (exclusive) of the value band. `u64::MAX` for the
23    /// final open band.
24    pub value_hi: u64,
25    /// Count of records in this band.
26    pub count: u64,
27    /// Cumulative count from the start of iteration through this band.
28    pub cumulative: u64,
29}
30
31/// Walks every populated bucket in value order.
32pub struct HdrLinearIter<'a> {
33    counters: &'a [u64],
34    sub_count_bits: u32,
35    high_index: usize,
36    idx: usize,
37    cumulative: u64,
38}
39
40impl<'a> HdrLinearIter<'a> {
41    pub(crate) fn new(h: &'a HdrHistogram) -> Self {
42        Self {
43            counters: h.counters(),
44            sub_count_bits: h.sub_count_bits(),
45            high_index: h.high_index(),
46            idx: 0,
47            cumulative: 0,
48        }
49    }
50}
51
52impl<'a> Iterator for HdrLinearIter<'a> {
53    type Item = IterEntry;
54    fn next(&mut self) -> Option<Self::Item> {
55        let bits = self.sub_count_bits;
56        let end = (self.high_index + 1).min(self.counters.len());
57        while self.idx < end {
58            let i = self.idx;
59            self.idx += 1;
60            let c = self.counters[i];
61            if c == 0 {
62                continue;
63            }
64            let lo = value_from_index(i, bits);
65            let hi = value_from_index(i + 1, bits);
66            self.cumulative += c;
67            return Some(IterEntry {
68                value_lo: lo,
69                value_hi: hi,
70                count: c,
71                cumulative: self.cumulative,
72            });
73        }
74        None
75    }
76}
77
78/// Walks the histogram in powers-of-two bands. Each step yields the
79/// sum of counts in `[2^k, 2^(k+1))`.
80pub struct HdrLogarithmicIter<'a> {
81    counters: &'a [u64],
82    sub_count_bits: u32,
83    high_index: usize,
84    /// Current lower bound of the band (a power of two), starting at 1.
85    lo: u64,
86    cumulative: u64,
87    /// True once we've yielded the band that covers `high_index`.
88    done: bool,
89}
90
91impl<'a> HdrLogarithmicIter<'a> {
92    pub(crate) fn new(h: &'a HdrHistogram) -> Self {
93        Self {
94            counters: h.counters(),
95            sub_count_bits: h.sub_count_bits(),
96            high_index: h.high_index(),
97            lo: 1,
98            cumulative: 0,
99            done: false,
100        }
101    }
102}
103
104impl<'a> Iterator for HdrLogarithmicIter<'a> {
105    type Item = IterEntry;
106    fn next(&mut self) -> Option<Self::Item> {
107        if self.done {
108            return None;
109        }
110        let bits = self.sub_count_bits;
111        let hi = self.lo.saturating_mul(2);
112        // Sum every populated bucket whose lower bound falls in [lo, hi).
113        let mut count = 0u64;
114        let end = (self.high_index + 1).min(self.counters.len());
115        for i in 0..end {
116            let v = value_from_index(i, bits);
117            if v >= self.lo && v < hi {
118                count += self.counters[i];
119            }
120        }
121        self.cumulative += count;
122        let entry = IterEntry {
123            value_lo: self.lo,
124            value_hi: hi,
125            count,
126            cumulative: self.cumulative,
127        };
128        // Walk past the high bucket and then stop.
129        let high_val = value_from_index(self.high_index, bits);
130        if hi > high_val {
131            self.done = true;
132        }
133        self.lo = hi;
134        Some(entry)
135    }
136}
137
138/// Walks the histogram at evenly-spaced percentile thresholds.
139/// `step_percent` of 1.0 yields ~100 entries; 0.1 yields ~1000.
140pub struct HdrPercentileIter<'a> {
141    counters: &'a [u64],
142    sub_count_bits: u32,
143    high_index: usize,
144    total: u64,
145    /// Step in percent (e.g. 1.0 for 1%).
146    step_pct: f64,
147    /// Next percentile threshold to emit, in percent.
148    next_pct: f64,
149    /// Cursor through the counter array.
150    idx: usize,
151    /// Cumulative count so far.
152    cum: u64,
153}
154
155impl<'a> HdrPercentileIter<'a> {
156    pub(crate) fn new(h: &'a HdrHistogram, step_percent: f64) -> Self {
157        Self {
158            counters: h.counters(),
159            sub_count_bits: h.sub_count_bits(),
160            high_index: h.high_index(),
161            total: h.count(),
162            step_pct: step_percent.max(f64::MIN_POSITIVE),
163            next_pct: step_percent.max(f64::MIN_POSITIVE),
164            idx: 0,
165            cum: 0,
166        }
167    }
168}
169
170impl<'a> Iterator for HdrPercentileIter<'a> {
171    type Item = IterEntry;
172    fn next(&mut self) -> Option<Self::Item> {
173        if self.total == 0 || self.next_pct > 100.0 + 1e-9 {
174            return None;
175        }
176        let bits = self.sub_count_bits;
177        let end = (self.high_index + 1).min(self.counters.len());
178        let target = ((self.next_pct / 100.0) * self.total as f64) as u64;
179        // Advance until cumulative count crosses target.
180        while self.idx < end {
181            self.cum += self.counters[self.idx];
182            if self.cum >= target {
183                let lo = value_from_index(self.idx, bits);
184                let hi = value_from_index(self.idx + 1, bits);
185                let pct_now = self.next_pct;
186                self.next_pct += self.step_pct;
187                // Don't advance idx here - the next percentile may
188                // also land in this same bucket and should report
189                // the same band.
190                return Some(IterEntry {
191                    value_lo: lo,
192                    value_hi: hi,
193                    count: self.counters[self.idx],
194                    cumulative: self
195                        .cum
196                        .min(self.total)
197                        .max((pct_now / 100.0 * self.total as f64) as u64),
198                });
199            }
200            self.idx += 1;
201        }
202        None
203    }
204}
205
206impl HdrHistogram {
207    pub fn iter_linear(&self) -> HdrLinearIter<'_> {
208        HdrLinearIter::new(self)
209    }
210
211    pub fn iter_logarithmic(&self) -> HdrLogarithmicIter<'_> {
212        HdrLogarithmicIter::new(self)
213    }
214
215    pub fn iter_percentiles(&self, step_percent: f64) -> HdrPercentileIter<'_> {
216        HdrPercentileIter::new(self, step_percent)
217    }
218}
219
220#[cfg(test)]
221#[path = "iterators_tests.rs"]
222mod tests;