simplebench_runtime/
measurement.rs1use crate::{calculate_percentiles, BenchResult, CpuMonitor, CpuSnapshot};
2use std::time::{Duration, Instant};
3
4fn warmup_benchmark<F>(bench_fn: &F, warmup_duration: Duration, iterations: usize) -> (u128, u64)
7where
8 F: Fn(),
9{
10 let start = Instant::now();
11 let mut total_iterations = 0u64;
12 let mut batch_size = 1u64;
13
14 while start.elapsed() < warmup_duration {
15 for _ in 0..batch_size {
17 for _ in 0..iterations {
18 bench_fn();
19 }
20 }
21
22 total_iterations += batch_size * (iterations as u64);
23 batch_size *= 2; }
25
26 (start.elapsed().as_millis(), total_iterations)
27}
28
29fn get_pinned_core() -> usize {
31 std::env::var("SIMPLEBENCH_PIN_CORE")
33 .ok()
34 .and_then(|s| s.parse().ok())
35 .unwrap_or(0)
36}
37
38pub fn measure_with_warmup<F>(
39 name: String,
40 module: String,
41 func: F,
42 iterations: usize,
43 samples: usize,
44 warmup_duration_secs: u64,
45) -> BenchResult
46where
47 F: Fn(),
48{
49 let (warmup_ms, warmup_iters) =
51 warmup_benchmark(&func, Duration::from_secs(warmup_duration_secs), iterations);
52
53 let mut result = measure_function_impl(name, module, func, iterations, samples);
54
55 result.warmup_ms = Some(warmup_ms);
57 result.warmup_iterations = Some(warmup_iters);
58
59 result
60}
61
62pub fn measure_function_impl<F>(
63 name: String,
64 module: String,
65 func: F,
66 iterations: usize,
67 samples: usize,
68) -> BenchResult
69where
70 F: Fn(),
71{
72 let mut all_timings = Vec::with_capacity(samples);
73 let mut cpu_samples = Vec::with_capacity(samples);
74
75 let cpu_core = get_pinned_core();
77 let monitor = CpuMonitor::new(cpu_core);
78
79 for _ in 0..samples {
80 let freq_before = monitor.read_frequency();
82
83 let start = Instant::now();
84 for _ in 0..iterations {
85 func();
86 }
87 let elapsed = start.elapsed();
88 all_timings.push(elapsed);
89
90 let freq_after = monitor.read_frequency();
92 let frequency_khz = match (freq_before, freq_after) {
93 (Some(before), Some(after)) => Some(before.max(after)),
94 (Some(f), None) | (None, Some(f)) => Some(f),
95 (None, None) => None,
96 };
97
98 let snapshot = CpuSnapshot {
99 timestamp: Instant::now(),
100 frequency_khz,
101 temperature_millic: monitor.read_temperature(),
102 };
103 cpu_samples.push(snapshot);
104 }
105
106 let percentiles = calculate_percentiles(&all_timings);
107
108 BenchResult {
109 name,
110 module,
111 iterations,
112 samples,
113 percentiles,
114 all_timings,
115 cpu_samples,
116 warmup_ms: None,
117 warmup_iterations: None,
118 }
119}
120
121pub fn measure_single_iteration<F>(func: F) -> Duration
122where
123 F: FnOnce(),
124{
125 let start = Instant::now();
126 func();
127 start.elapsed()
128}
129
130pub fn validate_measurement_params(iterations: usize, samples: usize) -> Result<(), String> {
131 if iterations == 0 {
132 return Err("Iterations must be greater than 0".to_string());
133 }
134 if samples == 0 {
135 return Err("Samples must be greater than 0".to_string());
136 }
137 if samples > 1_000_000 {
138 return Err(
139 "Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
140 );
141 }
142 Ok(())
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use std::thread;
149
150 #[test]
151 fn test_measure_single_iteration() {
152 let duration = measure_single_iteration(|| {
153 thread::sleep(Duration::from_millis(1));
154 });
155
156 assert!(duration >= Duration::from_millis(1));
157 assert!(duration < Duration::from_millis(10)); }
159
160 #[test]
161 fn test_validate_measurement_params() {
162 assert!(validate_measurement_params(100, 100).is_ok());
163 assert!(validate_measurement_params(0, 100).is_err());
164 assert!(validate_measurement_params(100, 0).is_err());
165 assert!(validate_measurement_params(100, 1_000_001).is_err());
166 assert!(validate_measurement_params(5, 100_000).is_ok());
167 }
168
169 #[test]
170 fn test_measure_function_basic() {
171 let result = measure_function_impl(
172 "test_bench".to_string(),
173 "test_module".to_string(),
174 || {
175 let _ = (0..100).sum::<i32>();
177 },
178 100,
179 10,
180 );
181
182 assert_eq!(result.name, "test_bench");
183 assert_eq!(result.module, "test_module");
184 assert_eq!(result.iterations, 100);
185 assert_eq!(result.samples, 10);
186 assert_eq!(result.all_timings.len(), 10);
187
188 for timing in &result.all_timings {
190 assert!(*timing > Duration::from_nanos(0));
191 assert!(*timing < Duration::from_secs(1));
192 }
193 }
194}