• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! This module contains stats that useful on the system. Local stats may not a be in
6 //! state to be consumed by metris reporter or it might not be efficient to report
7 //! metrics in the current state to the backend.
8 
9 use std::fmt;
10 use std::fmt::Debug;
11 use std::ops::Add;
12 use std::ops::Div;
13 use std::ops::Range;
14 use std::ops::Sub;
15 use std::sync::Arc;
16 use std::time::Instant;
17 
18 use anyhow::anyhow;
19 use anyhow::Result;
20 use base::info;
21 use sync::Mutex;
22 
23 pub trait Limits {
absolute_min() -> Self24     fn absolute_min() -> Self;
absolute_max() -> Self25     fn absolute_max() -> Self;
26 }
27 
28 impl Limits for u64 {
absolute_min() -> Self29     fn absolute_min() -> Self {
30         u64::MIN
31     }
32 
absolute_max() -> Self33     fn absolute_max() -> Self {
34         u64::MAX
35     }
36 }
37 
38 // Aggregate information about a collection that does require large memory footprint.
39 pub trait SummaryStats<T> {
40     /// Count of data points that tracked.
count(&self) -> u6441     fn count(&self) -> u64;
42 
43     /// Sum of all data points.
44     /// Returns None if count is zero.
sum(&self) -> Option<T>45     fn sum(&self) -> Option<T>;
46 
47     /// Minimum value of data points.
48     /// Returns None if count is zero.
min(&self) -> Option<T>49     fn min(&self) -> Option<T>;
50 
51     /// Maximum value of data points.
52     /// Returns None if count is zero.
max(&self) -> Option<T>53     fn max(&self) -> Option<T>;
54 
55     /// Average value of data points.
56     /// Returns None if count is zero.
average(&self) -> Option<T>57     fn average(&self) -> Option<T>;
58 }
59 
60 pub trait NumberType:
61     Limits + Div<u64, Output = Self> + Add<Output = Self> + Clone + Ord + PartialOrd + Debug + Sub<Self>
62 {
as_f64(&self) -> f6463     fn as_f64(&self) -> f64;
64 }
65 
66 impl NumberType for u64 {
as_f64(&self) -> f6467     fn as_f64(&self) -> f64 {
68         *self as f64
69     }
70 }
71 
72 /// Light weight stat struct that helps you get aggregate stats like min, max, average, count and
73 /// sum.
74 /// Median and standard deviation are intentionally excluded to keep the structure light weight.
75 #[derive(Eq, PartialEq)]
76 pub struct SimpleStat<T: NumberType> {
77     count: u64,
78     sum: T,
79     min: T,
80     max: T,
81 }
82 
83 /// A helper trait that can be associated with information that is tracked with a histogram.
84 /// For example, if histogram is tracking latencies and for debugging reasons, if we want to track
85 /// size of IO along with latency, this trait makes that posssible.
86 pub trait Details<T: NumberType>: Debug {
87     /// Returns a value that is being traked by the histogram.
value(&self) -> T88     fn value(&self) -> T;
89 }
90 
91 impl<T: NumberType> Details<T> for T {
value(&self) -> T92     fn value(&self) -> T {
93         self.clone()
94     }
95 }
96 
97 impl Details<u64> for Range<u64> {
value(&self) -> u6498     fn value(&self) -> u64 {
99         self.end - self.start
100     }
101 }
102 
103 impl<T: NumberType> Debug for SimpleStat<T> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result104     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105         if self.count == 0 {
106             f.debug_struct("SimpleStat")
107                 .field("count", &self.count)
108                 .finish()
109         } else {
110             f.debug_struct("SimpleStat")
111                 .field("count", &self.count)
112                 .field("sum", &self.sum)
113                 .field("min", &self.min)
114                 .field("max", &self.max)
115                 .field("average", &self.average().unwrap())
116                 .finish()
117         }
118     }
119 }
120 
121 impl<T: NumberType> SimpleStat<T> {
add(&mut self, value: T)122     pub fn add(&mut self, value: T) {
123         self.count += 1;
124         self.sum = self.sum.clone() + value.clone();
125         if self.max < value {
126             self.max = value.clone();
127         }
128         if self.min > value {
129             self.min = value;
130         }
131     }
132 }
133 
134 impl<T: NumberType> Default for SimpleStat<T> {
default() -> Self135     fn default() -> Self {
136         Self {
137             count: 0,
138             sum: T::absolute_min(),
139             min: T::absolute_max(),
140             max: T::absolute_min(),
141         }
142     }
143 }
144 
145 impl<T: NumberType> SummaryStats<T> for SimpleStat<T> {
count(&self) -> u64146     fn count(&self) -> u64 {
147         self.count
148     }
149 
sum(&self) -> Option<T>150     fn sum(&self) -> Option<T> {
151         if self.count == 0 {
152             return None;
153         }
154         Some(self.sum.clone())
155     }
156 
min(&self) -> Option<T>157     fn min(&self) -> Option<T> {
158         if self.count == 0 {
159             return None;
160         }
161         Some(self.min.clone())
162     }
163 
max(&self) -> Option<T>164     fn max(&self) -> Option<T> {
165         if self.count == 0 {
166             return None;
167         }
168         Some(self.max.clone())
169     }
170 
average(&self) -> Option<T>171     fn average(&self) -> Option<T> {
172         if self.count == 0 {
173             return None;
174         }
175         Some(self.sum.clone() / self.count)
176     }
177 }
178 
179 /// Computes and returns median of `values`.
180 /// This is an expensive function as it sorts values to get the median.
median<T: NumberType, D: Details<T>>(values: &[D]) -> T181 fn median<T: NumberType, D: Details<T>>(values: &[D]) -> T {
182     let mut sorted: Vec<T> = values.iter().map(|v| v.value()).collect();
183     sorted.sort();
184     sorted.get(sorted.len() / 2).unwrap().clone()
185 }
186 
187 /// Computes and returns standard deviation of `values`.
stddev<T: NumberType, D: Details<T>>(values: &[D], simple_stat: &SimpleStat<T>) -> f64188 fn stddev<T: NumberType, D: Details<T>>(values: &[D], simple_stat: &SimpleStat<T>) -> f64 {
189     let avg = simple_stat.sum().unwrap().as_f64() / simple_stat.count() as f64;
190     (values
191         .iter()
192         .map(|value| {
193             let diff = avg - (value.value().as_f64());
194             diff * diff
195         })
196         .sum::<f64>()
197         / simple_stat.count as f64)
198         .sqrt()
199 }
200 
201 /// Buckets of an histogram.
202 #[derive(Debug)]
203 struct Bucket<T: NumberType> {
204     simple_stat: SimpleStat<T>,
205     range: Range<T>,
206 }
207 
208 impl<T: NumberType> Bucket<T> {
new(range: Range<T>) -> Self209     fn new(range: Range<T>) -> Self {
210         Self {
211             simple_stat: SimpleStat::default(),
212             range,
213         }
214     }
215 
add(&mut self, value: T)216     fn add(&mut self, value: T) {
217         self.simple_stat.add(value);
218     }
219 }
220 
221 /// A histogram that optionally holds details about each added value. These values let
222 /// us compute standard deviation and median.
223 pub struct DetailedHistogram<T: NumberType, D: Details<T>> {
224     buckets: Vec<Bucket<T>>,
225     values: Option<Vec<D>>,
226 }
227 
228 impl<T: NumberType, D: Details<T>> Debug for DetailedHistogram<T, D> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result229     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230         let mut dbg = f.debug_struct("DetailedHistogram");
231         let simple_stat = self.simple_stat();
232         dbg.field("simple_stats", &simple_stat);
233         if simple_stat.count > 0 {
234             if let Some(values) = &self.values {
235                 dbg.field("median", &median(values));
236                 dbg.field("std_dev", &stddev(values, &simple_stat));
237                 dbg.field("values", values);
238             }
239         }
240         dbg.field("buckets", &self.buckets);
241         dbg.finish()
242     }
243 }
244 
245 impl<T: NumberType, D: Details<T>> DetailedHistogram<T, D> {
new_internal(ranges: &[Range<T>], details: bool) -> Result<Self>246     fn new_internal(ranges: &[Range<T>], details: bool) -> Result<Self> {
247         let mut last = T::absolute_min();
248         let mut buckets = vec![];
249         for r in ranges {
250             if r.start > r.end {
251                 return Err(anyhow!("invalid range {:?}", r));
252             }
253 
254             if r.start < last {
255                 return Err(anyhow!("Ranges overlap {:?} ", r));
256             }
257             last = r.end.clone();
258             buckets.push(Bucket::new(r.clone()));
259         }
260         let values = if details { Some(vec![]) } else { None };
261 
262         Ok(Self { buckets, values })
263     }
264 
265     /// Creates an histogram with given ranges of buckets.
new(ranges: &[Range<T>]) -> Result<Self>266     pub fn new(ranges: &[Range<T>]) -> Result<Self> {
267         Self::new_internal(ranges, false)
268     }
269 
270     /// Creating a histogram that maintains details about all the events can
271     /// get expensive if the events are frequent. Hence this feature is for
272     /// debug builds only.
273     #[cfg(feature = "experimental")]
new_with_details(ranges: &[Range<T>]) -> Result<Self>274     pub fn new_with_details(ranges: &[Range<T>]) -> Result<Self> {
275         Self::new_internal(ranges, true)
276     }
277 
278     /// Adds a value to histogram.
add(&mut self, value: D) -> Result<()>279     pub fn add(&mut self, value: D) -> Result<()> {
280         for b in &mut self.buckets {
281             if value.value() >= b.range.start && value.value() < b.range.end {
282                 b.add(value.value());
283                 if let Some(values) = &mut self.values {
284                     values.push(value);
285                 }
286                 return Ok(());
287             }
288         }
289         Err(anyhow!(
290             "value does not fit in any buckets: {:?}",
291             value.value()
292         ))
293     }
294 
295     /// Returns simple stat for the histogram.
simple_stat(&self) -> SimpleStat<T>296     pub fn simple_stat(&self) -> SimpleStat<T> {
297         let count = self.count();
298         if count == 0 {
299             SimpleStat::default()
300         } else {
301             SimpleStat {
302                 count: self.count(),
303                 sum: self.sum().unwrap(),
304                 min: self.min().unwrap(),
305                 max: self.max().unwrap(),
306             }
307         }
308     }
309 }
310 
311 impl<T: NumberType, D: Details<T>> SummaryStats<T> for DetailedHistogram<T, D> {
count(&self) -> u64312     fn count(&self) -> u64 {
313         let mut count = 0;
314         for b in &self.buckets {
315             count += b.simple_stat.count();
316         }
317         count
318     }
319 
sum(&self) -> Option<T>320     fn sum(&self) -> Option<T> {
321         let mut sum = T::absolute_min();
322         let mut ret = None;
323         for b in &self.buckets {
324             if let Some(v) = b.simple_stat.sum() {
325                 sum = sum.clone() + v;
326                 ret = Some(sum.clone())
327             }
328         }
329         ret
330     }
331 
min(&self) -> Option<T>332     fn min(&self) -> Option<T> {
333         for b in &self.buckets {
334             let min = b.simple_stat.min();
335             if min.is_some() {
336                 return min;
337             }
338         }
339         None
340     }
341 
max(&self) -> Option<T>342     fn max(&self) -> Option<T> {
343         for b in self.buckets.iter().rev() {
344             let max = b.simple_stat.max();
345             if max.is_some() {
346                 return max;
347             }
348         }
349         None
350     }
351 
average(&self) -> Option<T>352     fn average(&self) -> Option<T> {
353         let mut count = 0;
354         let mut sum = T::absolute_min();
355         for b in &self.buckets {
356             if b.simple_stat.count != 0 {
357                 sum = sum + b.simple_stat.sum().unwrap();
358                 count += b.simple_stat.count();
359             }
360         }
361         if count != 0 {
362             Some(sum / count)
363         } else {
364             None
365         }
366     }
367 }
368 
369 /// A helper type alias for Histogram that doesn't store details.
370 /// The structure can be used in production without much memory penalty.
371 pub type Histogram<T> = DetailedHistogram<T, T>;
372 
373 /// A helper struct that makes it easy to get time spent in a scope.
374 pub struct CallOnDrop<V, F: ?Sized + Fn(&V)> {
375     init_value: V,
376     update_value: F,
377 }
378 
379 impl<V, F: Fn(&V)> CallOnDrop<V, F> {
new(init_value: V, update_value: F) -> Self380     pub fn new(init_value: V, update_value: F) -> Self {
381         Self {
382             init_value,
383             update_value,
384         }
385     }
386 }
387 
388 impl<V, F: ?Sized + Fn(&V)> Drop for CallOnDrop<V, F> {
drop(&mut self)389     fn drop(&mut self) {
390         let f = &(self.update_value);
391         f(&self.init_value);
392     }
393 }
394 
timed_scope( histogram: Arc<Mutex<DetailedHistogram<u64, u64>>>, ) -> CallOnDrop< (Arc<Mutex<DetailedHistogram<u64, u64>>>, Instant), fn(&(Arc<Mutex<DetailedHistogram<u64, u64>>>, Instant)), >395 pub fn timed_scope(
396     histogram: Arc<Mutex<DetailedHistogram<u64, u64>>>,
397 ) -> CallOnDrop<
398     (Arc<Mutex<DetailedHistogram<u64, u64>>>, Instant),
399     fn(&(Arc<Mutex<DetailedHistogram<u64, u64>>>, Instant)),
400 > {
401     CallOnDrop::new((histogram, Instant::now()), |(histogram, x)| {
402         if histogram.lock().add(x.elapsed().as_nanos() as u64).is_err() {
403             info!("Error adding timed scope stat");
404         }
405     })
406 }
407 
408 /// A helper struct to collect metrics for byte transferred and latency.
409 #[derive(Debug)]
410 pub struct BytesLatencyStats {
411     /// Collects latency related metrics. The unit, u64, is large enough to hold nano-second
412     /// granularity.
413     pub latency: DetailedHistogram<u64, u64>,
414     /// Collects bytes transferred metrics. The unit, u64, is large enough to hold byte level
415     /// offset and length.
416     pub bytes_transferred: DetailedHistogram<u64, Range<u64>>,
417 }
418 
419 impl BytesLatencyStats {
new_with_buckets(latency_buckets: &[Range<u64>], bytes_buckets: &[Range<u64>]) -> Self420     pub fn new_with_buckets(latency_buckets: &[Range<u64>], bytes_buckets: &[Range<u64>]) -> Self {
421         Self {
422             latency: DetailedHistogram::new(latency_buckets).unwrap(),
423             bytes_transferred: DetailedHistogram::new(bytes_buckets).unwrap(),
424         }
425     }
426 }
427 
428 pub trait GetStatsForOp<OperationType> {
get_stats_for_op(&mut self, op: OperationType) -> &mut BytesLatencyStats429     fn get_stats_for_op(&mut self, op: OperationType) -> &mut BytesLatencyStats;
430 }
431 
432 /// A generic struct that temporarily holds reference of a `Stats` to update details for an
433 /// operation of type `OperationType` when the instance of `OpInfo` is dropped.
434 #[cfg(any(test, feature = "collect"))]
435 pub struct OpInfo<Stats, OperationType> {
436     stats: Arc<Mutex<Stats>>,
437     io_range: Range<u64>,
438     operation: OperationType,
439     start_time: Instant,
440 }
441 
442 /// Helper routine to collect byte latency stat.
443 ///
444 /// The mutex protecting `Stats` is not held across operation but is only held to
445 /// update the stats atomically. The order of events is
446 ///   # get `start_time`
447 ///   # caller performs the operation like read(), write(), etc.
448 ///   # hold the stats lock
449 ///   # update the stats
450 ///   # drop the lock
451 #[cfg(any(test, feature = "collect"))]
collect_scoped_byte_latency_stat< Stats: GetStatsForOp<OperationType> + Debug, OperationType: Copy + Clone + Debug, >( stats: Arc<Mutex<Stats>>, io_range: Range<u64>, operation: OperationType, ) -> CallOnDrop<OpInfo<Stats, OperationType>, fn(&OpInfo<Stats, OperationType>)>452 pub fn collect_scoped_byte_latency_stat<
453     Stats: GetStatsForOp<OperationType> + Debug,
454     OperationType: Copy + Clone + Debug,
455 >(
456     stats: Arc<Mutex<Stats>>,
457     io_range: Range<u64>,
458     operation: OperationType,
459 ) -> CallOnDrop<OpInfo<Stats, OperationType>, fn(&OpInfo<Stats, OperationType>)> {
460     let info = OpInfo {
461         stats,
462         io_range,
463         operation,
464         start_time: Instant::now(),
465     };
466     CallOnDrop::new(info, |info| {
467         let mut stats = info.stats.lock();
468         let op_stats = stats.get_stats_for_op(info.operation);
469 
470         if op_stats
471             .latency
472             .add(info.start_time.elapsed().as_nanos() as u64)
473             .is_err()
474         {
475             info!("Error adding disk IO latency stat");
476         }
477 
478         if op_stats
479             .bytes_transferred
480             .add(info.io_range.clone())
481             .is_err()
482         {
483             info!("Error adding disk IO bytes transferred stat");
484         }
485     })
486 }
487 
488 #[cfg(all(not(test), not(feature = "collect")))]
489 pub struct OpInfo {}
490 
491 #[cfg(all(not(test), not(feature = "collect")))]
collect_scoped_byte_latency_stat< Stats: GetStatsForOp<OperationType> + Debug, OperationType: Copy + Clone + Debug, >( _stats: Arc<Mutex<Stats>>, _io_range: Range<u64>, _operation: OperationType, ) -> OpInfo492 pub fn collect_scoped_byte_latency_stat<
493     Stats: GetStatsForOp<OperationType> + Debug,
494     OperationType: Copy + Clone + Debug,
495 >(
496     _stats: Arc<Mutex<Stats>>,
497     _io_range: Range<u64>,
498     _operation: OperationType,
499 ) -> OpInfo {
500     OpInfo {}
501 }
502 
503 #[cfg(test)]
504 mod tests {
505 
506     use std::time::Duration;
507 
508     use super::*;
509 
510     #[test]
simple_stat_init()511     fn simple_stat_init() {
512         let x = SimpleStat::<u64>::default();
513         assert_eq!(x.count, 0);
514         assert_eq!(x.max(), None);
515         assert_eq!(x.min(), None);
516         assert_eq!(x.average(), None);
517         assert_eq!(x.sum(), None);
518     }
519 
520     #[test]
simple_stat_updates()521     fn simple_stat_updates() {
522         let mut x = SimpleStat::<u64>::default();
523         x.add(10);
524         assert_eq!(x.count, 1);
525         assert_eq!(x.max(), Some(10));
526         assert_eq!(x.min(), Some(10));
527         assert_eq!(x.average(), Some(10));
528         assert_eq!(x.sum(), Some(10));
529         x.add(2);
530         assert_eq!(x.count, 2);
531         assert_eq!(x.max(), Some(10));
532         assert_eq!(x.min(), Some(2));
533         assert_eq!(x.average(), Some(6));
534         assert_eq!(x.sum(), Some(12));
535         x.add(1);
536         assert_eq!(x.count, 3);
537         assert_eq!(x.max(), Some(10));
538         assert_eq!(x.min(), Some(1));
539         assert_eq!(x.average(), Some(4));
540         assert_eq!(x.sum(), Some(13));
541         x.add(0);
542         assert_eq!(x.count, 4);
543         assert_eq!(x.max(), Some(10));
544         assert_eq!(x.min(), Some(0));
545         assert_eq!(x.average(), Some(3));
546         assert_eq!(x.sum(), Some(13));
547     }
548 
bucket_check(bucket: &Bucket<u64>, values: &[u64])549     fn bucket_check(bucket: &Bucket<u64>, values: &[u64]) {
550         let mut stats = SimpleStat::default();
551         for v in values {
552             stats.add(*v);
553         }
554         assert_eq!(bucket.simple_stat.count(), stats.count());
555         assert_eq!(bucket.simple_stat.sum(), stats.sum());
556         assert_eq!(bucket.simple_stat.min(), stats.min());
557         assert_eq!(bucket.simple_stat.max(), stats.max());
558         assert_eq!(bucket.simple_stat.average(), stats.average());
559     }
560 
561     #[test]
histogram_without_details()562     fn histogram_without_details() {
563         let mut histogram = Histogram::new(&[0..10, 10..100, 100..200]).unwrap();
564 
565         let mut simple_stats = SimpleStat::default();
566         assert_eq!(histogram.simple_stat(), simple_stats);
567         let values = [0, 20, 199, 50, 9, 5, 120];
568 
569         for v in values {
570             histogram.add(v).unwrap();
571             simple_stats.add(v);
572         }
573 
574         bucket_check(histogram.buckets.get(0).unwrap(), &[0, 9, 5]);
575         bucket_check(histogram.buckets.get(1).unwrap(), &[20, 50]);
576         bucket_check(histogram.buckets.get(2).unwrap(), &[199, 120]);
577         assert_eq!(histogram.buckets.len(), 3);
578         assert_eq!(histogram.simple_stat(), simple_stats);
579         assert_eq!(histogram.values, None);
580     }
581 
582     #[test]
histogram_without_details_empty_first_last_buckets()583     fn histogram_without_details_empty_first_last_buckets() {
584         let mut histogram = Histogram::new(&[0..4, 4..10, 10..100, 100..200, 200..300]).unwrap();
585 
586         let mut simple_stats = SimpleStat::default();
587         assert_eq!(histogram.simple_stat(), simple_stats);
588         let values = [4, 20, 199, 50, 9, 5, 120];
589 
590         for v in values {
591             histogram.add(v).unwrap();
592             simple_stats.add(v);
593         }
594 
595         bucket_check(histogram.buckets.get(1).unwrap(), &[4, 9, 5]);
596         bucket_check(histogram.buckets.get(2).unwrap(), &[20, 50]);
597         bucket_check(histogram.buckets.get(3).unwrap(), &[199, 120]);
598         assert_eq!(histogram.buckets.len(), 5);
599         assert_eq!(histogram.simple_stat(), simple_stats);
600         assert_eq!(histogram.values, None);
601     }
602 
603     #[derive(Clone, Debug, PartialEq)]
604     struct MyDetails(u64, u64);
605     impl Details<u64> for MyDetails {
value(&self) -> u64606         fn value(&self) -> u64 {
607             self.1 - self.0
608         }
609     }
610 
611     #[cfg(feature = "experimental")]
test_detailed_values() -> Vec<MyDetails>612     fn test_detailed_values() -> Vec<MyDetails> {
613         vec![
614             MyDetails(0, 4),
615             MyDetails(1, 21),
616             MyDetails(2, 201),
617             MyDetails(3, 53),
618             MyDetails(10, 19),
619             MyDetails(5, 10),
620             MyDetails(120, 240),
621         ]
622     }
623 
624     #[cfg(feature = "experimental")]
625     #[test]
histogram_with_details()626     fn histogram_with_details() {
627         let mut histogram =
628             DetailedHistogram::new_with_details(&[0..10, 10..100, 100..200]).unwrap();
629 
630         let mut simple_stats = SimpleStat::default();
631         assert_eq!(histogram.simple_stat(), simple_stats);
632 
633         let values = test_detailed_values();
634 
635         for v in &values {
636             simple_stats.add(v.value());
637             histogram.add(v.clone()).unwrap();
638         }
639 
640         bucket_check(histogram.buckets.get(0).unwrap(), &[4, 9, 5]);
641         bucket_check(histogram.buckets.get(1).unwrap(), &[20, 50]);
642         bucket_check(histogram.buckets.get(2).unwrap(), &[199, 120]);
643         assert_eq!(histogram.buckets.len(), 3);
644         assert_eq!(histogram.simple_stat(), simple_stats);
645         assert_eq!(histogram.values, Some(values));
646     }
647 
648     #[cfg(feature = "experimental")]
649     #[test]
histogram_with_details_empty_first_last_buckets()650     fn histogram_with_details_empty_first_last_buckets() {
651         let mut histogram =
652             DetailedHistogram::new_with_details(&[0..4, 4..10, 10..100, 100..200, 200..300])
653                 .unwrap();
654 
655         let mut simple_stats = SimpleStat::default();
656         assert_eq!(histogram.simple_stat(), simple_stats);
657         let values = test_detailed_values();
658 
659         for v in &values {
660             simple_stats.add(v.value());
661             histogram.add(v.clone()).unwrap();
662         }
663 
664         bucket_check(histogram.buckets.get(0).unwrap(), &[]);
665         bucket_check(histogram.buckets.get(4).unwrap(), &[]);
666         bucket_check(histogram.buckets.get(1).unwrap(), &[4, 9, 5]);
667         bucket_check(histogram.buckets.get(2).unwrap(), &[20, 50]);
668         bucket_check(histogram.buckets.get(3).unwrap(), &[199, 120]);
669         assert_eq!(histogram.buckets.len(), 5);
670         assert_eq!(histogram.simple_stat(), simple_stats);
671         assert_eq!(histogram.values, Some(values));
672     }
673 
674     #[test]
histogram_debug_fmt()675     fn histogram_debug_fmt() {
676         let range = 0..200;
677         let mut histogram = Histogram::new(&[range]).unwrap();
678 
679         let mut simple_stats = SimpleStat::default();
680         assert_eq!(histogram.simple_stat(), simple_stats);
681         let values = [0, 20, 199];
682 
683         for v in values {
684             histogram.add(v).unwrap();
685             simple_stats.add(v);
686         }
687         assert_eq!(
688             format!("{:#?}", histogram),
689             r#"DetailedHistogram {
690     simple_stats: SimpleStat {
691         count: 3,
692         sum: 219,
693         min: 0,
694         max: 199,
695         average: 73,
696     },
697     buckets: [
698         Bucket {
699             simple_stat: SimpleStat {
700                 count: 3,
701                 sum: 219,
702                 min: 0,
703                 max: 199,
704                 average: 73,
705             },
706             range: 0..200,
707         },
708     ],
709 }"#
710         );
711     }
712 
713     #[cfg(feature = "experimental")]
714     #[test]
detailed_histogram_debug_fmt()715     fn detailed_histogram_debug_fmt() {
716         let mut histogram = DetailedHistogram::new_with_details(&[0..200]).unwrap();
717 
718         let mut simple_stats = SimpleStat::default();
719         assert_eq!(histogram.simple_stat(), simple_stats);
720         let values = test_detailed_values();
721 
722         for v in &values {
723             histogram.add(v.clone()).unwrap();
724             simple_stats.add(v.value());
725         }
726         assert_eq!(
727             format!("{:#?}", histogram),
728             r#"DetailedHistogram {
729     simple_stats: SimpleStat {
730         count: 7,
731         sum: 407,
732         min: 4,
733         max: 199,
734         average: 58,
735     },
736     median: 20,
737     std_dev: 69.03297053153779,
738     values: [
739         MyDetails(
740             0,
741             4,
742         ),
743         MyDetails(
744             1,
745             21,
746         ),
747         MyDetails(
748             2,
749             201,
750         ),
751         MyDetails(
752             3,
753             53,
754         ),
755         MyDetails(
756             10,
757             19,
758         ),
759         MyDetails(
760             5,
761             10,
762         ),
763         MyDetails(
764             120,
765             240,
766         ),
767     ],
768     buckets: [
769         Bucket {
770             simple_stat: SimpleStat {
771                 count: 7,
772                 sum: 407,
773                 min: 4,
774                 max: 199,
775                 average: 58,
776             },
777             range: 0..200,
778         },
779     ],
780 }"#
781         );
782     }
783 
784     #[test]
add_on_drop()785     fn add_on_drop() {
786         let range = 0..u64::MAX;
787         let histogram = Arc::new(Mutex::new(DetailedHistogram::new(&[range]).unwrap()));
788 
789         {
790             let _ = timed_scope(histogram.clone());
791         }
792 
793         assert_eq!(histogram.lock().count(), 1);
794         assert!(histogram.lock().sum().unwrap() > 1);
795     }
796 
797     #[test]
disk_io_stat()798     fn disk_io_stat() {
799         #[derive(Debug)]
800         struct DiskIOStats {
801             read: BytesLatencyStats,
802             write: BytesLatencyStats,
803         }
804 
805         #[derive(Copy, Clone, Debug)]
806         enum DiskOperationType {
807             Read,
808             Write,
809         }
810 
811         impl GetStatsForOp<DiskOperationType> for DiskIOStats {
812             fn get_stats_for_op(&mut self, op: DiskOperationType) -> &mut BytesLatencyStats {
813                 match op {
814                     DiskOperationType::Read => &mut self.read,
815                     DiskOperationType::Write => &mut self.write,
816                 }
817             }
818         }
819 
820         let stats = Arc::new(Mutex::new(DiskIOStats {
821             read: BytesLatencyStats::new_with_buckets(
822                 &[0..100, 100..u64::MAX],
823                 &[0..100, 100..u64::MAX],
824             ),
825             write: BytesLatencyStats::new_with_buckets(
826                 &[0..100, 100..u64::MAX],
827                 &[0..100, 100..u64::MAX],
828             ),
829         }));
830 
831         {
832             let _ =
833                 collect_scoped_byte_latency_stat(stats.clone(), 100..1000, DiskOperationType::Read);
834             std::thread::sleep(Duration::from_millis(10));
835         }
836         assert_eq!(stats.lock().read.latency.count(), 1);
837         assert_eq!(stats.lock().read.bytes_transferred.sum(), Some(900));
838         assert_eq!(stats.lock().write.latency.count(), 0);
839 
840         {
841             let _ = collect_scoped_byte_latency_stat(
842                 stats.clone(),
843                 200..1000,
844                 DiskOperationType::Write,
845             );
846             std::thread::sleep(Duration::from_millis(10));
847         }
848         assert_eq!(stats.lock().write.latency.count(), 1);
849         assert_eq!(stats.lock().write.bytes_transferred.sum(), Some(800));
850         assert_eq!(stats.lock().read.latency.count(), 1);
851         assert_eq!(stats.lock().read.bytes_transferred.sum(), Some(900));
852     }
853 }
854