tracing_calltree/
stats.rs1use std::time::Duration;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize))]
5#[non_exhaustive]
6pub struct TimingStats {
7 pub samples: usize,
8 pub min: Duration,
9 pub max: Duration,
10 pub mean: Duration,
11 pub p95: Duration,
12}
13
14impl TimingStats {
15 pub(crate) fn from_nanos(values: impl IntoIterator<Item = u64>) -> Self {
16 let mut sorted: Vec<u64> = values.into_iter().collect();
17
18 if sorted.is_empty() {
19 return Self {
20 samples: 0,
21 min: Duration::ZERO,
22 max: Duration::ZERO,
23 mean: Duration::ZERO,
24 p95: Duration::ZERO,
25 };
26 }
27
28 sorted.sort_unstable();
29
30 let samples = sorted.len();
31 let min = sorted[0];
32 let max = sorted[samples - 1];
33 let sum = sorted
34 .iter()
35 .fold(0_u128, |acc, value| acc + u128::from(*value));
36 let mean = (sum / samples as u128).min(u64::MAX as u128) as u64;
37 let rank = ((samples * 95).div_ceil(100)).saturating_sub(1);
38 let p95 = sorted[rank];
39
40 Self {
41 samples,
42 min: Duration::from_nanos(min),
43 max: Duration::from_nanos(max),
44 mean: Duration::from_nanos(mean),
45 p95: Duration::from_nanos(p95),
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::TimingStats;
53 use std::time::Duration;
54
55 #[test]
56 fn calculates_exact_nearest_rank_statistics() {
57 let stats = TimingStats::from_nanos(1..=100);
58
59 assert_eq!(stats.samples, 100);
60 assert_eq!(stats.min, Duration::from_nanos(1));
61 assert_eq!(stats.max, Duration::from_nanos(100));
62 assert_eq!(stats.mean, Duration::from_nanos(50));
63 assert_eq!(stats.p95, Duration::from_nanos(95));
64 }
65
66 #[test]
67 fn empty_input_returns_zeroed_stats() {
68 let stats = TimingStats::from_nanos(std::iter::empty());
69
70 assert_eq!(stats.samples, 0);
71 assert_eq!(stats.min, Duration::ZERO);
72 assert_eq!(stats.max, Duration::ZERO);
73 assert_eq!(stats.mean, Duration::ZERO);
74 assert_eq!(stats.p95, Duration::ZERO);
75 }
76}