1use crate::{calculate_percentiles, config::BenchmarkConfig, 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
38fn warmup_closure<F>(func: &mut F, duration: Duration, iterations: usize) -> (u128, u64)
40where
41 F: FnMut(),
42{
43 let start = Instant::now();
44 let mut total_iterations = 0u64;
45 let mut batch_size = 1u64;
46
47 while start.elapsed() < duration {
48 for _ in 0..batch_size {
49 for _ in 0..iterations {
50 func();
51 }
52 }
53 total_iterations += batch_size * (iterations as u64);
54 batch_size *= 2;
55 }
56
57 (start.elapsed().as_millis(), total_iterations)
58}
59
60fn measure_closure<F>(
62 func: &mut F,
63 iterations: usize,
64 samples: usize,
65) -> (Vec<Duration>, Vec<CpuSnapshot>)
66where
67 F: FnMut(),
68{
69 let mut all_timings = Vec::with_capacity(samples);
70 let mut cpu_samples = Vec::with_capacity(samples);
71
72 let cpu_core = get_pinned_core();
74 let monitor = CpuMonitor::new(cpu_core);
75
76 for _ in 0..samples {
77 let freq_before = monitor.read_frequency();
79
80 let start = Instant::now();
81 for _ in 0..iterations {
82 func();
83 }
84 let elapsed = start.elapsed();
85 all_timings.push(elapsed);
86
87 let freq_after = monitor.read_frequency();
89 let frequency_khz = match (freq_before, freq_after) {
90 (Some(before), Some(after)) => Some(before.max(after)),
91 (Some(f), None) | (None, Some(f)) => Some(f),
92 (None, None) => None,
93 };
94
95 let snapshot = CpuSnapshot {
96 timestamp: Instant::now(),
97 frequency_khz,
98 temperature_millic: monitor.read_temperature(),
99 };
100 cpu_samples.push(snapshot);
101 }
102
103 (all_timings, cpu_samples)
104}
105
106pub fn measure_simple<F>(
111 config: &BenchmarkConfig,
112 name: &str,
113 module: &str,
114 mut func: F,
115) -> BenchResult
116where
117 F: FnMut(),
118{
119 let (warmup_ms, warmup_iters) = warmup_closure(
121 &mut func,
122 Duration::from_secs(config.measurement.warmup_duration_secs),
123 config.measurement.iterations,
124 );
125
126 let (all_timings, cpu_samples) = measure_closure(
128 &mut func,
129 config.measurement.iterations,
130 config.measurement.samples,
131 );
132
133 let percentiles = calculate_percentiles(&all_timings);
134
135 BenchResult {
136 name: name.to_string(),
137 module: module.to_string(),
138 iterations: config.measurement.iterations,
139 samples: config.measurement.samples,
140 percentiles,
141 all_timings,
142 cpu_samples,
143 warmup_ms: Some(warmup_ms),
144 warmup_iterations: Some(warmup_iters),
145 }
146}
147
148pub fn measure_with_setup<T, S, B>(
154 config: &BenchmarkConfig,
155 name: &str,
156 module: &str,
157 setup: S,
158 mut bench: B,
159) -> BenchResult
160where
161 S: FnOnce() -> T,
162 B: FnMut(&T),
163{
164 let data = setup();
166
167 let mut func = || bench(&data);
169
170 let (warmup_ms, warmup_iters) = warmup_closure(
172 &mut func,
173 Duration::from_secs(config.measurement.warmup_duration_secs),
174 config.measurement.iterations,
175 );
176
177 let (all_timings, cpu_samples) = measure_closure(
179 &mut func,
180 config.measurement.iterations,
181 config.measurement.samples,
182 );
183
184 let percentiles = calculate_percentiles(&all_timings);
185
186 BenchResult {
187 name: name.to_string(),
188 module: module.to_string(),
189 iterations: config.measurement.iterations,
190 samples: config.measurement.samples,
191 percentiles,
192 all_timings,
193 cpu_samples,
194 warmup_ms: Some(warmup_ms),
195 warmup_iterations: Some(warmup_iters),
196 }
197}
198
199pub fn measure_with_warmup<F>(
200 name: String,
201 module: String,
202 func: F,
203 iterations: usize,
204 samples: usize,
205 warmup_duration_secs: u64,
206) -> BenchResult
207where
208 F: Fn(),
209{
210 let (warmup_ms, warmup_iters) =
212 warmup_benchmark(&func, Duration::from_secs(warmup_duration_secs), iterations);
213
214 let mut result = measure_function_impl(name, module, func, iterations, samples);
215
216 result.warmup_ms = Some(warmup_ms);
218 result.warmup_iterations = Some(warmup_iters);
219
220 result
221}
222
223pub fn measure_function_impl<F>(
224 name: String,
225 module: String,
226 func: F,
227 iterations: usize,
228 samples: usize,
229) -> BenchResult
230where
231 F: Fn(),
232{
233 let mut all_timings = Vec::with_capacity(samples);
234 let mut cpu_samples = Vec::with_capacity(samples);
235
236 let cpu_core = get_pinned_core();
238 let monitor = CpuMonitor::new(cpu_core);
239
240 for _ in 0..samples {
241 let freq_before = monitor.read_frequency();
243
244 let start = Instant::now();
245 for _ in 0..iterations {
246 func();
247 }
248 let elapsed = start.elapsed();
249 all_timings.push(elapsed);
250
251 let freq_after = monitor.read_frequency();
253 let frequency_khz = match (freq_before, freq_after) {
254 (Some(before), Some(after)) => Some(before.max(after)),
255 (Some(f), None) | (None, Some(f)) => Some(f),
256 (None, None) => None,
257 };
258
259 let snapshot = CpuSnapshot {
260 timestamp: Instant::now(),
261 frequency_khz,
262 temperature_millic: monitor.read_temperature(),
263 };
264 cpu_samples.push(snapshot);
265 }
266
267 let percentiles = calculate_percentiles(&all_timings);
268
269 BenchResult {
270 name,
271 module,
272 iterations,
273 samples,
274 percentiles,
275 all_timings,
276 cpu_samples,
277 warmup_ms: None,
278 warmup_iterations: None,
279 }
280}
281
282pub fn measure_single_iteration<F>(func: F) -> Duration
283where
284 F: FnOnce(),
285{
286 let start = Instant::now();
287 func();
288 start.elapsed()
289}
290
291pub fn validate_measurement_params(iterations: usize, samples: usize) -> Result<(), String> {
292 if iterations == 0 {
293 return Err("Iterations must be greater than 0".to_string());
294 }
295 if samples == 0 {
296 return Err("Samples must be greater than 0".to_string());
297 }
298 if samples > 1_000_000 {
299 return Err(
300 "Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
301 );
302 }
303 Ok(())
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use std::thread;
310
311 #[test]
312 fn test_measure_single_iteration() {
313 let duration = measure_single_iteration(|| {
314 thread::sleep(Duration::from_millis(1));
315 });
316
317 assert!(duration >= Duration::from_millis(1));
318 assert!(duration < Duration::from_millis(10)); }
320
321 #[test]
322 fn test_validate_measurement_params() {
323 assert!(validate_measurement_params(100, 100).is_ok());
324 assert!(validate_measurement_params(0, 100).is_err());
325 assert!(validate_measurement_params(100, 0).is_err());
326 assert!(validate_measurement_params(100, 1_000_001).is_err());
327 assert!(validate_measurement_params(5, 100_000).is_ok());
328 }
329
330 #[test]
331 fn test_measure_function_basic() {
332 let result = measure_function_impl(
333 "test_bench".to_string(),
334 "test_module".to_string(),
335 || {
336 let _ = (0..100).sum::<i32>();
338 },
339 100,
340 10,
341 );
342
343 assert_eq!(result.name, "test_bench");
344 assert_eq!(result.module, "test_module");
345 assert_eq!(result.iterations, 100);
346 assert_eq!(result.samples, 10);
347 assert_eq!(result.all_timings.len(), 10);
348
349 for timing in &result.all_timings {
351 assert!(*timing > Duration::from_nanos(0));
352 assert!(*timing < Duration::from_secs(1));
353 }
354 }
355}