1use crate::{Bucket, Config, Error, SparseHistogram};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Histogram {
7 pub(crate) config: Config,
8 pub(crate) buckets: Box<[u64]>,
9}
10
11impl Histogram {
12 pub fn new(grouping_power: u8, max_value_power: u8) -> Result<Self, Error> {
15 let config = Config::new(grouping_power, max_value_power)?;
16
17 Ok(Self::with_config(&config))
18 }
19
20 pub fn with_config(config: &Config) -> Self {
22 let buckets: Box<[u64]> = vec![0; config.total_buckets()].into();
23
24 Self {
25 config: *config,
26 buckets,
27 }
28 }
29
30 pub fn from_buckets(
33 grouping_power: u8,
34 max_value_power: u8,
35 buckets: Vec<u64>,
36 ) -> Result<Self, Error> {
37 let config = Config::new(grouping_power, max_value_power)?;
38
39 if config.total_buckets() != buckets.len() {
40 return Err(Error::IncompatibleParameters);
41 }
42
43 Ok(Self {
44 config,
45 buckets: buckets.into(),
46 })
47 }
48
49 pub fn increment(&mut self, value: u64) -> Result<(), Error> {
52 self.add(value, 1)
53 }
54
55 pub fn add(&mut self, value: u64, count: u64) -> Result<(), Error> {
58 let index = self.config.value_to_index(value)?;
59 self.buckets[index] = self.buckets[index].wrapping_add(count);
60 Ok(())
61 }
62
63 pub fn as_slice(&self) -> &[u64] {
65 &self.buckets
66 }
67
68 pub fn as_mut_slice(&mut self) -> &mut [u64] {
70 &mut self.buckets
71 }
72
73 pub fn percentiles(&self, percentiles: &[f64]) -> Result<Option<Vec<(f64, Bucket)>>, Error> {
80 let total_count: u128 = self.buckets.iter().map(|v| *v as u128).sum();
82
83 let mut percentiles = percentiles.to_vec();
85 percentiles.sort_by(|a, b| a.partial_cmp(b).unwrap());
86
87 for percentile in &percentiles {
89 if !(0.0..=100.0).contains(percentile) {
90 return Err(Error::InvalidPercentile);
91 }
92 }
93
94 if total_count == 0 {
96 return Ok(None);
97 }
98
99 let mut bucket_idx = 0;
100 let mut partial_sum = self.buckets[bucket_idx] as u128;
101
102 let result: Vec<(f64, Bucket)> = percentiles
103 .iter()
104 .filter_map(|percentile| {
105 let count =
108 std::cmp::max(1, (percentile / 100.0 * total_count as f64).ceil() as u128);
109
110 loop {
111 if partial_sum >= count {
113 return Some((
114 *percentile,
115 Bucket {
116 count: self.buckets[bucket_idx],
117 range: self.config.index_to_range(bucket_idx),
118 },
119 ));
120 }
121
122 if bucket_idx == (self.buckets.len() - 1) {
124 break;
125 }
126
127 bucket_idx += 1;
129 partial_sum += self.buckets[bucket_idx] as u128;
130 }
131
132 None
133 })
134 .collect();
135
136 Ok(Some(result))
137 }
138
139 pub fn percentile(&self, percentile: f64) -> Result<Option<Bucket>, Error> {
144 self.percentiles(&[percentile])
145 .map(|v| v.map(|x| x.first().unwrap().1.clone()))
146 }
147
148 pub fn downsample(&self, grouping_power: u8) -> Result<Histogram, Error> {
162 if grouping_power >= self.config.grouping_power() {
163 return Err(Error::MaxPowerTooLow);
164 }
165
166 let mut histogram = Histogram::new(grouping_power, self.config.max_value_power())?;
167 for (i, n) in self.as_slice().iter().enumerate() {
168 if *n != 0 {
170 let val = self.config.index_to_lower_bound(i);
171 histogram.add(val, *n)?;
172 }
173 }
174
175 Ok(histogram)
176 }
177
178 pub fn checked_add(&self, other: &Histogram) -> Result<Histogram, Error> {
184 if self.config != other.config {
185 return Err(Error::IncompatibleParameters);
186 }
187
188 let mut result = self.clone();
189
190 for (this, other) in result.buckets.iter_mut().zip(other.buckets.iter()) {
191 *this = this.checked_add(*other).ok_or(Error::Overflow)?;
192 }
193
194 Ok(result)
195 }
196
197 pub fn wrapping_add(&self, other: &Histogram) -> Result<Histogram, Error> {
202 if self.config != other.config {
203 return Err(Error::IncompatibleParameters);
204 }
205
206 let mut result = self.clone();
207
208 for (this, other) in result.buckets.iter_mut().zip(other.buckets.iter()) {
209 *this = this.wrapping_add(*other);
210 }
211
212 Ok(result)
213 }
214
215 pub fn checked_sub(&self, other: &Histogram) -> Result<Histogram, Error> {
221 if self.config != other.config {
222 return Err(Error::IncompatibleParameters);
223 }
224
225 let mut result = self.clone();
226
227 for (this, other) in result.buckets.iter_mut().zip(other.buckets.iter()) {
228 *this = this.checked_sub(*other).ok_or(Error::Overflow)?;
229 }
230
231 Ok(result)
232 }
233
234 pub fn wrapping_sub(&self, other: &Histogram) -> Result<Histogram, Error> {
239 if self.config != other.config {
240 return Err(Error::IncompatibleParameters);
241 }
242
243 let mut result = self.clone();
244
245 for (this, other) in result.buckets.iter_mut().zip(other.buckets.iter()) {
246 *this = this.wrapping_sub(*other);
247 }
248
249 Ok(result)
250 }
251
252 pub fn iter(&self) -> Iter {
254 Iter {
255 index: 0,
256 histogram: self,
257 }
258 }
259
260 pub fn config(&self) -> Config {
262 self.config
263 }
264}
265
266impl<'a> IntoIterator for &'a Histogram {
267 type Item = Bucket;
268 type IntoIter = Iter<'a>;
269
270 fn into_iter(self) -> Self::IntoIter {
271 Iter {
272 index: 0,
273 histogram: self,
274 }
275 }
276}
277
278pub struct Iter<'a> {
280 index: usize,
281 histogram: &'a Histogram,
282}
283
284impl Iterator for Iter<'_> {
285 type Item = Bucket;
286
287 fn next(&mut self) -> Option<<Self as std::iter::Iterator>::Item> {
288 if self.index >= self.histogram.buckets.len() {
289 return None;
290 }
291
292 let bucket = Bucket {
293 count: self.histogram.buckets[self.index],
294 range: self.histogram.config.index_to_range(self.index),
295 };
296
297 self.index += 1;
298
299 Some(bucket)
300 }
301}
302
303impl From<&SparseHistogram> for Histogram {
304 fn from(other: &SparseHistogram) -> Self {
305 let mut histogram = Histogram::with_config(&other.config);
306
307 for (index, count) in other.index.iter().zip(other.count.iter()) {
308 histogram.buckets[*index] = *count;
309 }
310
311 histogram
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use rand::Rng;
319
320 #[cfg(target_pointer_width = "64")]
321 #[test]
322 fn size() {
323 assert_eq!(std::mem::size_of::<Histogram>(), 48);
324 }
325
326 #[test]
327 fn percentiles() {
329 let mut histogram = Histogram::new(7, 64).unwrap();
330
331 assert_eq!(histogram.percentile(50.0).unwrap(), None);
332 assert_eq!(
333 histogram.percentiles(&[50.0, 90.0, 99.0, 99.9]).unwrap(),
334 None
335 );
336
337 for i in 0..=100 {
338 let _ = histogram.increment(i);
339 assert_eq!(
340 histogram.percentile(0.0),
341 Ok(Some(Bucket {
342 count: 1,
343 range: 0..=0,
344 }))
345 );
346 assert_eq!(
347 histogram.percentile(100.0),
348 Ok(Some(Bucket {
349 count: 1,
350 range: i..=i,
351 }))
352 );
353 }
354 assert_eq!(histogram.percentile(0.0).map(|b| b.unwrap().end()), Ok(0));
355 assert_eq!(histogram.percentile(25.0).map(|b| b.unwrap().end()), Ok(25));
356 assert_eq!(histogram.percentile(50.0).map(|b| b.unwrap().end()), Ok(50));
357 assert_eq!(histogram.percentile(75.0).map(|b| b.unwrap().end()), Ok(75));
358 assert_eq!(histogram.percentile(90.0).map(|b| b.unwrap().end()), Ok(90));
359 assert_eq!(histogram.percentile(99.0).map(|b| b.unwrap().end()), Ok(99));
360 assert_eq!(
361 histogram.percentile(99.9).map(|b| b.unwrap().end()),
362 Ok(100)
363 );
364
365 assert_eq!(histogram.percentile(-1.0), Err(Error::InvalidPercentile));
366 assert_eq!(histogram.percentile(101.0), Err(Error::InvalidPercentile));
367
368 let percentiles: Vec<(f64, u64)> = histogram
369 .percentiles(&[50.0, 90.0, 99.0, 99.9])
370 .unwrap()
371 .unwrap()
372 .iter()
373 .map(|(p, b)| (*p, b.end()))
374 .collect();
375
376 assert_eq!(
377 percentiles,
378 vec![(50.0, 50), (90.0, 90), (99.0, 99), (99.9, 100)]
379 );
380
381 let _ = histogram.increment(1024);
382 assert_eq!(
383 histogram.percentile(99.9),
384 Ok(Some(Bucket {
385 count: 1,
386 range: 1024..=1031,
387 }))
388 );
389 }
390
391 #[test]
392 fn min() {
394 let mut histogram = Histogram::new(7, 64).unwrap();
395
396 assert_eq!(histogram.percentile(0.0).unwrap(), None);
397
398 let _ = histogram.increment(10);
399 assert_eq!(histogram.percentile(0.0).map(|b| b.unwrap().end()), Ok(10));
400
401 let _ = histogram.increment(4);
402 assert_eq!(histogram.percentile(0.0).map(|b| b.unwrap().end()), Ok(4));
403 }
404
405 #[test]
406 #[ignore = "this test is flaky (see issue #100)"]
407 fn downsample() {
409 let mut histogram = Histogram::new(8, 32).unwrap();
410 let mut vals: Vec<u64> = Vec::with_capacity(10000);
411 let mut rng = rand::thread_rng();
412
413 for _ in 0..vals.capacity() {
415 let v: u64 = rng.gen_range(1..2_u64.pow(histogram.config.max_value_power() as u32));
416 vals.push(v);
417 let _ = histogram.increment(v);
418 }
419 vals.sort();
420
421 let mut percentiles: Vec<f64> = Vec::with_capacity(109);
423 for i in 20..99 {
424 percentiles.push(i as f64);
425 }
426 let mut tail = vec![
427 99.1, 99.2, 99.3, 99.4, 99.5, 99.6, 99.7, 99.8, 99.9, 99.99, 100.0,
428 ];
429 percentiles.append(&mut tail);
430
431 let h = histogram.clone();
433 let grouping_power = histogram.config.grouping_power();
434 for factor in 1..grouping_power {
435 let error = histogram.config.error();
436
437 for p in &percentiles {
438 let v = vals[((*p / 100.0 * (vals.len() as f64)) as usize) - 1];
439
440 let vhist = histogram.percentile(*p).unwrap().unwrap().end();
442 let e = (v.abs_diff(vhist) as f64) * 100.0 / (v as f64);
443 assert!(e < error);
444 }
445
446 histogram = h.downsample(grouping_power - factor).unwrap();
447 }
448 }
449
450 fn build_histograms() -> (Histogram, Histogram, Histogram, Histogram) {
454 let mut h1 = Histogram::new(1, 3).unwrap();
455 let mut h2 = Histogram::new(1, 3).unwrap();
456 let mut h3 = Histogram::new(1, 3).unwrap();
457 let h4 = Histogram::new(7, 32).unwrap();
458
459 for i in 0..h1.config().total_buckets() {
460 h1.as_mut_slice()[i] = 1;
461 h2.as_mut_slice()[i] = 1;
462 h3.as_mut_slice()[i] = u64::MAX;
463 }
464
465 (h1, h2, h3, h4)
466 }
467
468 #[test]
469 fn checked_add() {
471 let (h, h_good, h_overflow, h_mismatch) = build_histograms();
472
473 assert_eq!(
474 h.checked_add(&h_mismatch),
475 Err(Error::IncompatibleParameters)
476 );
477
478 let r = h.checked_add(&h_good).unwrap();
479 assert_eq!(r.as_slice(), &[2, 2, 2, 2, 2, 2]);
480
481 assert_eq!(h.checked_add(&h_overflow), Err(Error::Overflow));
482 }
483
484 #[test]
485 fn wrapping_add() {
487 let (h, h_good, h_overflow, h_mismatch) = build_histograms();
488
489 assert_eq!(
490 h.wrapping_add(&h_mismatch),
491 Err(Error::IncompatibleParameters)
492 );
493
494 let r = h.wrapping_add(&h_good).unwrap();
495 assert_eq!(r.as_slice(), &[2, 2, 2, 2, 2, 2]);
496
497 let r = h.wrapping_add(&h_overflow).unwrap();
498 assert_eq!(r.as_slice(), &[0, 0, 0, 0, 0, 0]);
499 }
500
501 #[test]
502 fn checked_sub() {
504 let (h, h_good, h_overflow, h_mismatch) = build_histograms();
505
506 assert_eq!(
507 h.checked_sub(&h_mismatch),
508 Err(Error::IncompatibleParameters)
509 );
510
511 let r = h.checked_sub(&h_good).unwrap();
512 assert_eq!(r.as_slice(), &[0, 0, 0, 0, 0, 0]);
513
514 assert_eq!(h.checked_add(&h_overflow), Err(Error::Overflow));
515 }
516
517 #[test]
518 fn wrapping_sub() {
520 let (h, h_good, h_overflow, h_mismatch) = build_histograms();
521
522 assert_eq!(
523 h.wrapping_sub(&h_mismatch),
524 Err(Error::IncompatibleParameters)
525 );
526
527 let r = h.wrapping_sub(&h_good).unwrap();
528 assert_eq!(r.as_slice(), &[0, 0, 0, 0, 0, 0]);
529
530 let r = h.wrapping_sub(&h_overflow).unwrap();
531 assert_eq!(r.as_slice(), &[2, 2, 2, 2, 2, 2]);
532 }
533
534 #[test]
535 fn from_buckets() {
537 let mut histogram = Histogram::new(8, 32).unwrap();
538 for i in 0..=100 {
539 let _ = histogram.increment(i);
540 }
541
542 let buckets = histogram.as_slice();
543 let constructed = Histogram::from_buckets(8, 32, buckets.to_vec()).unwrap();
544
545 assert!(constructed == histogram);
546 }
547}