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