hft_benchmarks/
stats.rs

1//! Statistical analysis for benchmark results
2
3extern crate alloc;
4use alloc::vec::Vec;
5use alloc::string::String;
6
7pub struct BenchmarkResults {
8    measurements: Vec<u64>,
9    name: String,
10}
11
12impl BenchmarkResults {
13    pub fn new(name: String) -> Self {
14        Self {
15            measurements: Vec::with_capacity(10000),
16            name,
17        }
18    }
19    
20    pub fn record(&mut self, nanoseconds: u64) {
21        self.measurements.push(nanoseconds);
22    }
23    
24    pub fn len(&self) -> usize {
25        self.measurements.len()
26    }
27    
28    pub fn is_empty(&self) -> bool {
29        self.measurements.is_empty()
30    }
31    
32    pub fn analyze(&self) -> BenchmarkAnalysis {
33        if self.measurements.is_empty() {
34            return BenchmarkAnalysis::empty(self.name.clone());
35        }
36        
37        let mut sorted = self.measurements.clone();
38        sorted.sort_unstable();
39        
40        let len = sorted.len();
41        let sum: u64 = sorted.iter().sum();
42        let mean = sum / len as u64;
43        
44        let variance = sorted.iter()
45            .map(|&x| {
46                let diff = (x as f64) - (mean as f64);
47                diff * diff
48            })
49            .sum::<f64>() / len as f64;
50        
51        BenchmarkAnalysis {
52            name: self.name.clone(),
53            count: len,
54            min: sorted[0],
55            max: sorted[len - 1],
56            mean,
57            p50: percentile(&sorted, 50.0),
58            p95: percentile(&sorted, 95.0),
59            p99: percentile(&sorted, 99.0),
60            p999: percentile(&sorted, 99.9),
61            std_dev: variance.sqrt(),
62        }
63    }
64    
65    pub fn clear(&mut self) {
66        self.measurements.clear();
67    }
68}
69
70#[derive(Debug, Clone)]
71pub struct BenchmarkAnalysis {
72    pub name: String,
73    pub count: usize,
74    pub min: u64,
75    pub max: u64,
76    pub mean: u64,
77    pub p50: u64,
78    pub p95: u64,
79    pub p99: u64,
80    pub p999: u64,
81    pub std_dev: f64,
82}
83
84fn percentile(sorted_data: &[u64], p: f64) -> u64 {
85    let len = sorted_data.len();
86    if len == 0 { return 0; }
87    if len == 1 { return sorted_data[0]; }
88    
89    let index = (p / 100.0 * (len - 1) as f64).round() as usize;
90    sorted_data[index.min(len - 1)]
91}
92
93impl BenchmarkAnalysis {
94    pub fn empty(name: String) -> Self {
95        Self {
96            name,
97            count: 0,
98            min: 0,
99            max: 0,
100            mean: 0,
101            p50: 0,
102            p95: 0,
103            p99: 0,
104            p999: 0,
105            std_dev: 0.0,
106        }
107    }
108    pub fn summary(&self) -> String {
109        format!(
110            "{}: {} samples, mean={:>6}ns, p50={:>6}ns, p95={:>6}ns, p99={:>6}ns, p99.9={:>6}ns, std_dev={:>6.1}ns",
111            self.name, self.count, self.mean, self.p50, self.p95, self.p99, self.p999, self.std_dev
112        )
113    }
114    
115    pub fn meets_target(&self, target_p99_ns: u64) -> bool {
116        self.p99 <= target_p99_ns
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    
124    #[test]
125    fn test_benchmark_results() {
126        let mut results = BenchmarkResults::new("test".to_string());
127        
128        for i in 1..=100 {
129            results.record(i * 10);
130        }
131        
132        let analysis = results.analyze();
133        assert_eq!(analysis.count, 100);
134        assert_eq!(analysis.min, 10);
135        assert_eq!(analysis.max, 1000);
136        assert_eq!(analysis.mean, 505);
137        assert_eq!(analysis.p50, 510);
138    }
139    
140    #[test]
141    fn test_empty_results() {
142        let results = BenchmarkResults::new("empty".to_string());
143        let analysis = results.analyze();
144        
145        assert_eq!(analysis.count, 0);
146        assert_eq!(analysis.mean, 0);
147    }
148    
149    #[test]
150    fn test_target_checking() {
151        let mut results = BenchmarkResults::new("target_test".to_string());
152        
153        for _ in 0..1000 {
154            results.record(100);
155        }
156        
157        let analysis = results.analyze();
158        assert!(analysis.meets_target(150));
159        assert!(!analysis.meets_target(50));
160    }
161    
162    #[test]
163    fn test_percentile_calculation() {
164        let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
165        
166        assert_eq!(percentile(&data, 50.0), 6);
167        assert_eq!(percentile(&data, 90.0), 9);
168        assert_eq!(percentile(&data, 99.0), 10);
169        
170        assert_eq!(percentile(&[], 50.0), 0);
171        assert_eq!(percentile(&[42], 50.0), 42);
172    }
173    
174    #[test]
175    fn test_clear_measurements() {
176        let mut results = BenchmarkResults::new("clear_test".to_string());
177        results.record(100);
178        results.record(200);
179        
180        assert_eq!(results.len(), 2);
181        assert!(!results.is_empty());
182        
183        results.clear();
184        assert_eq!(results.len(), 0);
185        assert!(results.is_empty());
186    }
187    
188    #[test]
189    fn test_analysis_summary_format() {
190        let mut results = BenchmarkResults::new("format_test".to_string());
191        results.record(100);
192        results.record(200);
193        
194        let analysis = results.analyze();
195        let summary = analysis.summary();
196        
197        assert!(summary.contains("format_test"));
198        assert!(summary.contains("2 samples"));
199        assert!(summary.contains("mean"));
200    }
201}