xref: /aosp_15_r20/external/crosvm/metrics/src/local_stats.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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[0], &[0, 9, 5]);
575         bucket_check(&histogram.buckets[1], &[20, 50]);
576         bucket_check(&histogram.buckets[2], &[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[1], &[4, 9, 5]);
596         bucket_check(&histogram.buckets[2], &[20, 50]);
597         bucket_check(&histogram.buckets[3], &[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     #[cfg(feature = "experimental")]
604     #[derive(Clone, Debug, PartialEq)]
605     struct MyDetails(u64, u64);
606 
607     #[cfg(feature = "experimental")]
608     impl Details<u64> for MyDetails {
value(&self) -> u64609         fn value(&self) -> u64 {
610             self.1 - self.0
611         }
612     }
613 
614     #[cfg(feature = "experimental")]
test_detailed_values() -> Vec<MyDetails>615     fn test_detailed_values() -> Vec<MyDetails> {
616         vec![
617             MyDetails(0, 4),
618             MyDetails(1, 21),
619             MyDetails(2, 201),
620             MyDetails(3, 53),
621             MyDetails(10, 19),
622             MyDetails(5, 10),
623             MyDetails(120, 240),
624         ]
625     }
626 
627     #[cfg(feature = "experimental")]
628     #[test]
histogram_with_details()629     fn histogram_with_details() {
630         let mut histogram =
631             DetailedHistogram::new_with_details(&[0..10, 10..100, 100..200]).unwrap();
632 
633         let mut simple_stats = SimpleStat::default();
634         assert_eq!(histogram.simple_stat(), simple_stats);
635 
636         let values = test_detailed_values();
637 
638         for v in &values {
639             simple_stats.add(v.value());
640             histogram.add(v.clone()).unwrap();
641         }
642 
643         bucket_check(histogram.buckets[0], &[4, 9, 5]);
644         bucket_check(histogram.buckets[1], &[20, 50]);
645         bucket_check(histogram.buckets[2], &[199, 120]);
646         assert_eq!(histogram.buckets.len(), 3);
647         assert_eq!(histogram.simple_stat(), simple_stats);
648         assert_eq!(histogram.values, Some(values));
649     }
650 
651     #[cfg(feature = "experimental")]
652     #[test]
histogram_with_details_empty_first_last_buckets()653     fn histogram_with_details_empty_first_last_buckets() {
654         let mut histogram =
655             DetailedHistogram::new_with_details(&[0..4, 4..10, 10..100, 100..200, 200..300])
656                 .unwrap();
657 
658         let mut simple_stats = SimpleStat::default();
659         assert_eq!(histogram.simple_stat(), simple_stats);
660         let values = test_detailed_values();
661 
662         for v in &values {
663             simple_stats.add(v.value());
664             histogram.add(v.clone()).unwrap();
665         }
666 
667         bucket_check(histogram.buckets[0], &[]);
668         bucket_check(histogram.buckets[4], &[]);
669         bucket_check(histogram.buckets[1], &[4, 9, 5]);
670         bucket_check(histogram.buckets[2], &[20, 50]);
671         bucket_check(histogram.buckets[3], &[199, 120]);
672         assert_eq!(histogram.buckets.len(), 5);
673         assert_eq!(histogram.simple_stat(), simple_stats);
674         assert_eq!(histogram.values, Some(values));
675     }
676 
677     #[test]
histogram_debug_fmt()678     fn histogram_debug_fmt() {
679         let range = 0..200;
680         let mut histogram = Histogram::new(&[range]).unwrap();
681 
682         let mut simple_stats = SimpleStat::default();
683         assert_eq!(histogram.simple_stat(), simple_stats);
684         let values = [0, 20, 199];
685 
686         for v in values {
687             histogram.add(v).unwrap();
688             simple_stats.add(v);
689         }
690         assert_eq!(
691             format!("{:#?}", histogram),
692             r#"DetailedHistogram {
693     simple_stats: SimpleStat {
694         count: 3,
695         sum: 219,
696         min: 0,
697         max: 199,
698         average: 73,
699     },
700     buckets: [
701         Bucket {
702             simple_stat: SimpleStat {
703                 count: 3,
704                 sum: 219,
705                 min: 0,
706                 max: 199,
707                 average: 73,
708             },
709             range: 0..200,
710         },
711     ],
712 }"#
713         );
714     }
715 
716     #[cfg(feature = "experimental")]
717     #[test]
detailed_histogram_debug_fmt()718     fn detailed_histogram_debug_fmt() {
719         let mut histogram = DetailedHistogram::new_with_details(&[0..200]).unwrap();
720 
721         let mut simple_stats = SimpleStat::default();
722         assert_eq!(histogram.simple_stat(), simple_stats);
723         let values = test_detailed_values();
724 
725         for v in &values {
726             histogram.add(v.clone()).unwrap();
727             simple_stats.add(v.value());
728         }
729         assert_eq!(
730             format!("{:#?}", histogram),
731             r#"DetailedHistogram {
732     simple_stats: SimpleStat {
733         count: 7,
734         sum: 407,
735         min: 4,
736         max: 199,
737         average: 58,
738     },
739     median: 20,
740     std_dev: 69.03297053153779,
741     values: [
742         MyDetails(
743             0,
744             4,
745         ),
746         MyDetails(
747             1,
748             21,
749         ),
750         MyDetails(
751             2,
752             201,
753         ),
754         MyDetails(
755             3,
756             53,
757         ),
758         MyDetails(
759             10,
760             19,
761         ),
762         MyDetails(
763             5,
764             10,
765         ),
766         MyDetails(
767             120,
768             240,
769         ),
770     ],
771     buckets: [
772         Bucket {
773             simple_stat: SimpleStat {
774                 count: 7,
775                 sum: 407,
776                 min: 4,
777                 max: 199,
778                 average: 58,
779             },
780             range: 0..200,
781         },
782     ],
783 }"#
784         );
785     }
786 
787     #[test]
add_on_drop()788     fn add_on_drop() {
789         let range = 0..u64::MAX;
790         let histogram = Arc::new(Mutex::new(DetailedHistogram::new(&[range]).unwrap()));
791 
792         {
793             let _ = timed_scope(histogram.clone());
794         }
795 
796         assert_eq!(histogram.lock().count(), 1);
797         assert!(histogram.lock().sum().unwrap() > 1);
798     }
799 
800     #[test]
disk_io_stat()801     fn disk_io_stat() {
802         #[derive(Debug)]
803         struct DiskIOStats {
804             read: BytesLatencyStats,
805             write: BytesLatencyStats,
806         }
807 
808         #[derive(Copy, Clone, Debug)]
809         enum DiskOperationType {
810             Read,
811             Write,
812         }
813 
814         impl GetStatsForOp<DiskOperationType> for DiskIOStats {
815             fn get_stats_for_op(&mut self, op: DiskOperationType) -> &mut BytesLatencyStats {
816                 match op {
817                     DiskOperationType::Read => &mut self.read,
818                     DiskOperationType::Write => &mut self.write,
819                 }
820             }
821         }
822 
823         let stats = Arc::new(Mutex::new(DiskIOStats {
824             read: BytesLatencyStats::new_with_buckets(
825                 &[0..100, 100..u64::MAX],
826                 &[0..100, 100..u64::MAX],
827             ),
828             write: BytesLatencyStats::new_with_buckets(
829                 &[0..100, 100..u64::MAX],
830                 &[0..100, 100..u64::MAX],
831             ),
832         }));
833 
834         {
835             let _ =
836                 collect_scoped_byte_latency_stat(stats.clone(), 100..1000, DiskOperationType::Read);
837             std::thread::sleep(Duration::from_millis(10));
838         }
839         assert_eq!(stats.lock().read.latency.count(), 1);
840         assert_eq!(stats.lock().read.bytes_transferred.sum(), Some(900));
841         assert_eq!(stats.lock().write.latency.count(), 0);
842 
843         {
844             let _ = collect_scoped_byte_latency_stat(
845                 stats.clone(),
846                 200..1000,
847                 DiskOperationType::Write,
848             );
849             std::thread::sleep(Duration::from_millis(10));
850         }
851         assert_eq!(stats.lock().write.latency.count(), 1);
852         assert_eq!(stats.lock().write.bytes_transferred.sum(), Some(800));
853         assert_eq!(stats.lock().read.latency.count(), 1);
854         assert_eq!(stats.lock().read.bytes_transferred.sum(), Some(900));
855     }
856 }
857