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}