fluxbench_stats/
percentiles.rs1#[derive(Debug, Clone)]
8pub struct Percentiles {
9 pub p50: f64,
11 pub p75: f64,
13 pub p90: f64,
15 pub p95: f64,
17 pub p99: f64,
19 pub p999: f64,
21}
22
23pub fn compute_percentile(samples: &[f64], percentile: f64) -> f64 {
38 if samples.is_empty() {
39 return 0.0;
40 }
41
42 if samples.len() == 1 {
43 return samples[0];
44 }
45
46 let mut sorted = samples.to_vec();
47 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
48
49 let n = sorted.len();
50 let p = percentile / 100.0;
51
52 let rank = p * (n - 1) as f64;
54 let lower_idx = rank.floor() as usize;
55 let upper_idx = (lower_idx + 1).min(n - 1);
56 let fraction = rank - lower_idx as f64;
57
58 sorted[lower_idx] + fraction * (sorted[upper_idx] - sorted[lower_idx])
59}
60
61pub fn compute_percentiles(samples: &[f64]) -> Percentiles {
63 Percentiles {
64 p50: compute_percentile(samples, 50.0),
65 p75: compute_percentile(samples, 75.0),
66 p90: compute_percentile(samples, 90.0),
67 p95: compute_percentile(samples, 95.0),
68 p99: compute_percentile(samples, 99.0),
69 p999: compute_percentile(samples, 99.9),
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_median() {
79 let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0];
80 let p50 = compute_percentile(&samples, 50.0);
81 assert!((p50 - 3.0).abs() < 0.01);
82 }
83
84 #[test]
85 fn test_quartiles() {
86 let samples: Vec<f64> = (1..=100).map(|x| x as f64).collect();
87 let p25 = compute_percentile(&samples, 25.0);
88 let p75 = compute_percentile(&samples, 75.0);
89
90 assert!((p25 - 25.75).abs() < 1.0);
91 assert!((p75 - 75.25).abs() < 1.0);
92 }
93
94 #[test]
95 fn test_extreme_percentiles() {
96 let samples: Vec<f64> = (1..=1000).map(|x| x as f64).collect();
97 let p99 = compute_percentile(&samples, 99.0);
98 let p999 = compute_percentile(&samples, 99.9);
99
100 assert!(p99 > 985.0 && p99 < 995.0);
101 assert!(p999 > 998.0 && p999 <= 1000.0);
102 }
103
104 #[test]
105 fn test_single_sample() {
106 let samples = vec![42.0];
107 let p50 = compute_percentile(&samples, 50.0);
108 assert!((p50 - 42.0).abs() < f64::EPSILON);
109 }
110
111 #[test]
112 fn test_empty_samples() {
113 let samples: Vec<f64> = Vec::new();
114 let p50 = compute_percentile(&samples, 50.0);
115 assert!((p50 - 0.0).abs() < f64::EPSILON);
116 }
117
118 #[test]
119 fn test_compute_all_percentiles() {
120 let samples: Vec<f64> = (1..=100).map(|x| x as f64).collect();
121 let percentiles = compute_percentiles(&samples);
122
123 assert!(percentiles.p50 > 49.0 && percentiles.p50 < 51.0);
124 assert!(percentiles.p90 > 89.0 && percentiles.p90 < 91.0);
125 assert!(percentiles.p99 > 98.0 && percentiles.p99 < 100.0);
126 }
127}