hdrhistogram/iterators/
mod.rs

1use crate::core::counter::Counter;
2use crate::Histogram;
3
4/// An iterator that iterates over histogram quantiles.
5pub mod quantile;
6
7/// An iterator that iterates linearly over histogram values.
8pub mod linear;
9
10/// An iterator that iterates logarithmically over histogram values.
11pub mod log;
12
13/// An iterator that iterates over recorded histogram values.
14pub mod recorded;
15
16/// An iterator that iterates over histogram values.
17pub mod all;
18
19/// Extra information about the picked point in the histogram provided by the picker.
20pub struct PickMetadata {
21    /// Supply the quantile iterated to in the last `pick()`, if available. If `None` is provided,
22    /// the quantile of the current value will be used instead. Probably only useful for the
23    /// quantile iterator.
24    quantile_iterated_to: Option<f64>,
25
26    /// Supply the value iterated to in the last `pick()`, if the picker can supply a more useful
27    /// value than the largest value represented by the bucket.
28    value_iterated_to: Option<u64>,
29}
30
31impl PickMetadata {
32    fn new(quantile_iterated_to: Option<f64>, value_iterated_to: Option<u64>) -> PickMetadata {
33        PickMetadata {
34            quantile_iterated_to,
35            value_iterated_to,
36        }
37    }
38}
39
40/// A trait for designing an subset iterator over values in a `Histogram`.
41pub trait PickyIterator<T: Counter> {
42    /// Return `Some` if an `IterationValue` should be emitted at this point.
43    ///
44    /// `index` is a valid index in the relevant histogram.
45    ///
46    /// This will be called with the same index until it returns `None`. This enables modes of
47    /// iteration that pick different values represented by the same bucket, for instance.
48    fn pick(
49        &mut self,
50        index: usize,
51        total_count_to_index: u64,
52        count_at_index: T,
53    ) -> Option<PickMetadata>;
54
55    /// Should we keep iterating even though the last index with non-zero count has already been
56    /// picked at least once?
57    ///
58    /// This will be called on every iteration once the last index with non-zero count has been
59    /// picked, even if the index was not advanced in the last iteration (because `pick()` returned
60    /// `Some`).
61    fn more(&mut self, index_to_pick: usize) -> bool;
62}
63
64/// `HistogramIterator` provides a base iterator for a `Histogram`.
65///
66/// It will iterate over all discrete values until there are no more recorded values (i.e., *not*
67/// necessarily until all bins have been exhausted). To facilitate the development of more
68/// sophisticated iterators, a *picker* is also provided, which is allowed to only select some bins
69/// that should be yielded. The picker may also extend the iteration to include a suffix of empty
70/// bins.
71pub struct HistogramIterator<'a, T: 'a + Counter, P: PickyIterator<T>> {
72    hist: &'a Histogram<T>,
73    total_count_to_index: u64,
74    count_since_last_iteration: u64,
75    count_at_index: T,
76    current_index: usize,
77    last_picked_index: usize,
78    max_value_index: usize,
79    fresh: bool,
80    ended: bool,
81    picker: P,
82}
83
84/// The value emitted at each step when iterating over a `Histogram`.
85#[derive(Debug, PartialEq)]
86pub struct IterationValue<T: Counter> {
87    value_iterated_to: u64,
88    quantile: f64,
89    quantile_iterated_to: f64,
90    count_at_value: T,
91    count_since_last_iteration: u64,
92}
93
94impl<T: Counter> IterationValue<T> {
95    /// Create a new IterationValue.
96    pub fn new(
97        value_iterated_to: u64,
98        quantile: f64,
99        quantile_iterated_to: f64,
100        count_at_value: T,
101        count_since_last_iteration: u64,
102    ) -> IterationValue<T> {
103        IterationValue {
104            value_iterated_to,
105            quantile,
106            quantile_iterated_to,
107            count_at_value,
108            count_since_last_iteration,
109        }
110    }
111
112    /// The value iterated to. Some iterators provide a specific value inside the bucket, while
113    /// others just use the highest value in the bucket.
114    pub fn value_iterated_to(&self) -> u64 {
115        self.value_iterated_to
116    }
117
118    /// Percent of recorded values that are at or below the current bucket.
119    /// This is simply the quantile multiplied by 100.0, so if you care about maintaining the best
120    /// floating-point precision, use `quantile()` instead.
121    pub fn percentile(&self) -> f64 {
122        self.quantile * 100.0
123    }
124
125    /// Quantile of recorded values that are at or below the current bucket.
126    pub fn quantile(&self) -> f64 {
127        self.quantile
128    }
129
130    /// Quantile iterated to, which may be different than `quantile()` when an iterator provides
131    /// information about the specific quantile it's iterating to.
132    pub fn quantile_iterated_to(&self) -> f64 {
133        self.quantile_iterated_to
134    }
135
136    /// Recorded count for values equivalent to `value`
137    pub fn count_at_value(&self) -> T {
138        self.count_at_value
139    }
140
141    /// Number of values traversed since the last iteration step
142    pub fn count_since_last_iteration(&self) -> u64 {
143        self.count_since_last_iteration
144    }
145}
146
147impl<'a, T: Counter, P: PickyIterator<T>> HistogramIterator<'a, T, P> {
148    fn new(h: &'a Histogram<T>, picker: P) -> HistogramIterator<'a, T, P> {
149        HistogramIterator {
150            hist: h,
151            total_count_to_index: 0,
152            count_since_last_iteration: 0,
153            count_at_index: T::zero(),
154            current_index: 0,
155            last_picked_index: 0,
156            max_value_index: h.index_for(h.max()).expect("Either 0 or an existing index"),
157            picker,
158            fresh: true,
159            ended: false,
160        }
161    }
162}
163
164impl<'a, T: 'a, P> Iterator for HistogramIterator<'a, T, P>
165where
166    T: Counter,
167    P: PickyIterator<T>,
168{
169    type Item = IterationValue<T>;
170    fn next(&mut self) -> Option<Self::Item> {
171        // here's the deal: we are iterating over all the indices in the histogram's .count array.
172        // however, most of those values (especially towards the end) will be zeros, which the
173        // original HdrHistogram implementation doesn't yield (probably with good reason -- there
174        // could be a lot of them!). so, what we do instead is iterate over indicies until we reach
175        // the total *count*. After that, we iterate only until .more() returns false, at which
176        // point we stop completely.
177
178        // rust doesn't support tail call optimization, so we'd run out of stack if we simply
179        // called self.next() again at the bottom. instead, we loop when we would have yielded None
180        // unless we have ended.
181        while !self.ended {
182            // have we reached the end?
183            if self.current_index == self.hist.distinct_values() {
184                self.ended = true;
185                return None;
186            }
187
188            // Have we already picked the index with the last non-zero count in the histogram?
189            if self.last_picked_index >= self.max_value_index {
190                // is the picker done?
191                if !self.picker.more(self.current_index) {
192                    self.ended = true;
193                    return None;
194                }
195            } else {
196                // nope -- alright, let's keep iterating
197                assert!(self.current_index < self.hist.distinct_values());
198
199                if self.fresh {
200                    // at a new index, and not past the max, so there's nonzero counts to add
201                    self.count_at_index = self
202                        .hist
203                        .count_at_index(self.current_index)
204                        .expect("Already checked that current_index is < counts len");
205
206                    self.total_count_to_index = self
207                        .total_count_to_index
208                        .saturating_add(self.count_at_index.as_u64());
209                    self.count_since_last_iteration = self
210                        .count_since_last_iteration
211                        .saturating_add(self.count_at_index.as_u64());
212
213                    // make sure we don't add this index again
214                    self.fresh = false;
215                }
216            }
217
218            // figure out if picker thinks we should yield this value
219            if let Some(metadata) = self.picker.pick(
220                self.current_index,
221                self.total_count_to_index,
222                self.count_at_index,
223            ) {
224                let quantile = self.total_count_to_index as f64 / self.hist.len() as f64;
225                let val = IterationValue {
226                    value_iterated_to: metadata.value_iterated_to.unwrap_or_else(|| {
227                        self.hist
228                            .highest_equivalent(self.hist.value_for(self.current_index))
229                    }),
230                    quantile,
231                    quantile_iterated_to: metadata.quantile_iterated_to.unwrap_or(quantile),
232                    count_at_value: self
233                        .hist
234                        .count_at_index(self.current_index)
235                        .expect("current index cannot exceed counts length"),
236                    count_since_last_iteration: self.count_since_last_iteration,
237                };
238
239                // Note that we *don't* increment self.current_index here. The picker will be
240                // exposed to the same value again after yielding. This is to allow a picker to
241                // pick multiple times at the same index. An example of this is how the linear
242                // picker may be using a step size smaller than the bucket size, so it should
243                // step multiple times without advancing the index.
244
245                self.count_since_last_iteration = 0;
246                self.last_picked_index = self.current_index;
247                return Some(val);
248            }
249
250            // check the next entry
251            self.current_index += 1;
252            self.fresh = true;
253        }
254        None
255    }
256}