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