1use crate::progress::{emit_progress, ProgressMessage, ProgressPhase};
2use crate::{calculate_percentiles, config::BenchmarkConfig, BenchResult, CpuMonitor, CpuSnapshot};
3use std::time::{Duration, Instant};
4
5fn warmup_benchmark<F>(bench_fn: &F, warmup_duration: Duration, iterations: usize) -> (u128, u64)
8where
9 F: Fn(),
10{
11 let start = Instant::now();
12 let mut total_iterations = 0u64;
13 let mut batch_size = 1u64;
14
15 while start.elapsed() < warmup_duration {
16 for _ in 0..batch_size {
18 for _ in 0..iterations {
19 bench_fn();
20 }
21 }
22
23 total_iterations += batch_size * (iterations as u64);
24 batch_size *= 2; }
26
27 (start.elapsed().as_millis(), total_iterations)
28}
29
30fn get_pinned_core() -> usize {
32 std::env::var("SIMPLEBENCH_PIN_CORE")
34 .ok()
35 .and_then(|s| s.parse().ok())
36 .unwrap_or(0)
37}
38
39fn warmup_closure<F>(
41 func: &mut F,
42 duration: Duration,
43 iterations: usize,
44 bench_name: &str,
45) -> (u128, u64)
46where
47 F: FnMut(),
48{
49 let start = Instant::now();
50 let mut total_iterations = 0u64;
51 let mut batch_size = 1u64;
52 let mut last_report = Instant::now();
53 let target_ms = duration.as_millis() as u64;
54
55 while start.elapsed() < duration {
56 for _ in 0..batch_size {
57 for _ in 0..iterations {
58 func();
59 }
60 }
61 total_iterations += batch_size * (iterations as u64);
62 batch_size *= 2;
63
64 if last_report.elapsed() >= Duration::from_millis(100) {
66 emit_progress(&ProgressMessage {
67 bench: bench_name,
68 phase: ProgressPhase::Warmup {
69 elapsed_ms: start.elapsed().as_millis() as u64,
70 target_ms,
71 },
72 });
73 last_report = Instant::now();
74 }
75 }
76
77 (start.elapsed().as_millis(), total_iterations)
78}
79
80fn measure_closure<F>(
82 func: &mut F,
83 iterations: usize,
84 samples: usize,
85 bench_name: &str,
86) -> (Vec<Duration>, Vec<CpuSnapshot>)
87where
88 F: FnMut(),
89{
90 let mut all_timings = Vec::with_capacity(samples);
91 let mut cpu_samples = Vec::with_capacity(samples);
92
93 let cpu_core = get_pinned_core();
95 let monitor = CpuMonitor::new(cpu_core);
96
97 let report_interval = (samples / 100).max(1);
99
100 for sample_idx in 0..samples {
101 if sample_idx % report_interval == 0 {
103 emit_progress(&ProgressMessage {
104 bench: bench_name,
105 phase: ProgressPhase::Samples {
106 current: sample_idx as u32,
107 total: samples as u32,
108 },
109 });
110 }
111
112 let freq_before = monitor.read_frequency();
114
115 let start = Instant::now();
116 for _ in 0..iterations {
117 func();
118 }
119 let elapsed = start.elapsed();
120 all_timings.push(elapsed);
121
122 let freq_after = monitor.read_frequency();
124 let frequency_khz = match (freq_before, freq_after) {
125 (Some(before), Some(after)) => Some(before.max(after)),
126 (Some(f), None) | (None, Some(f)) => Some(f),
127 (None, None) => None,
128 };
129
130 let snapshot = CpuSnapshot {
131 timestamp: Instant::now(),
132 frequency_khz,
133 temperature_millic: monitor.read_temperature(),
134 };
135 cpu_samples.push(snapshot);
136 }
137
138 emit_progress(&ProgressMessage {
140 bench: bench_name,
141 phase: ProgressPhase::Complete,
142 });
143
144 (all_timings, cpu_samples)
145}
146
147pub fn measure_simple<F>(
152 config: &BenchmarkConfig,
153 name: &str,
154 module: &str,
155 mut func: F,
156) -> BenchResult
157where
158 F: FnMut(),
159{
160 let (warmup_ms, warmup_iters) = warmup_closure(
162 &mut func,
163 Duration::from_secs(config.measurement.warmup_duration_secs),
164 config.measurement.iterations,
165 name,
166 );
167
168 let (all_timings, cpu_samples) = measure_closure(
170 &mut func,
171 config.measurement.iterations,
172 config.measurement.samples,
173 name,
174 );
175
176 let percentiles = calculate_percentiles(&all_timings);
177
178 BenchResult {
179 name: name.to_string(),
180 module: module.to_string(),
181 iterations: config.measurement.iterations,
182 samples: config.measurement.samples,
183 percentiles,
184 all_timings,
185 cpu_samples,
186 warmup_ms: Some(warmup_ms),
187 warmup_iterations: Some(warmup_iters),
188 }
189}
190
191pub fn measure_with_setup<T, S, B>(
197 config: &BenchmarkConfig,
198 name: &str,
199 module: &str,
200 setup: S,
201 mut bench: B,
202) -> BenchResult
203where
204 S: FnOnce() -> T,
205 B: FnMut(&T),
206{
207 let data = setup();
209
210 let mut func = || bench(&data);
212
213 let (warmup_ms, warmup_iters) = warmup_closure(
215 &mut func,
216 Duration::from_secs(config.measurement.warmup_duration_secs),
217 config.measurement.iterations,
218 name,
219 );
220
221 let (all_timings, cpu_samples) = measure_closure(
223 &mut func,
224 config.measurement.iterations,
225 config.measurement.samples,
226 name,
227 );
228
229 let percentiles = calculate_percentiles(&all_timings);
230
231 BenchResult {
232 name: name.to_string(),
233 module: module.to_string(),
234 iterations: config.measurement.iterations,
235 samples: config.measurement.samples,
236 percentiles,
237 all_timings,
238 cpu_samples,
239 warmup_ms: Some(warmup_ms),
240 warmup_iterations: Some(warmup_iters),
241 }
242}
243
244pub fn measure_with_warmup<F>(
245 name: String,
246 module: String,
247 func: F,
248 iterations: usize,
249 samples: usize,
250 warmup_duration_secs: u64,
251) -> BenchResult
252where
253 F: Fn(),
254{
255 let (warmup_ms, warmup_iters) =
257 warmup_benchmark(&func, Duration::from_secs(warmup_duration_secs), iterations);
258
259 let mut result = measure_function_impl(name, module, func, iterations, samples);
260
261 result.warmup_ms = Some(warmup_ms);
263 result.warmup_iterations = Some(warmup_iters);
264
265 result
266}
267
268pub fn measure_function_impl<F>(
269 name: String,
270 module: String,
271 func: F,
272 iterations: usize,
273 samples: usize,
274) -> BenchResult
275where
276 F: Fn(),
277{
278 let mut all_timings = Vec::with_capacity(samples);
279 let mut cpu_samples = Vec::with_capacity(samples);
280
281 let cpu_core = get_pinned_core();
283 let monitor = CpuMonitor::new(cpu_core);
284
285 for _ in 0..samples {
286 let freq_before = monitor.read_frequency();
288
289 let start = Instant::now();
290 for _ in 0..iterations {
291 func();
292 }
293 let elapsed = start.elapsed();
294 all_timings.push(elapsed);
295
296 let freq_after = monitor.read_frequency();
298 let frequency_khz = match (freq_before, freq_after) {
299 (Some(before), Some(after)) => Some(before.max(after)),
300 (Some(f), None) | (None, Some(f)) => Some(f),
301 (None, None) => None,
302 };
303
304 let snapshot = CpuSnapshot {
305 timestamp: Instant::now(),
306 frequency_khz,
307 temperature_millic: monitor.read_temperature(),
308 };
309 cpu_samples.push(snapshot);
310 }
311
312 let percentiles = calculate_percentiles(&all_timings);
313
314 BenchResult {
315 name,
316 module,
317 iterations,
318 samples,
319 percentiles,
320 all_timings,
321 cpu_samples,
322 warmup_ms: None,
323 warmup_iterations: None,
324 }
325}
326
327pub fn measure_single_iteration<F>(func: F) -> Duration
328where
329 F: FnOnce(),
330{
331 let start = Instant::now();
332 func();
333 start.elapsed()
334}
335
336pub fn validate_measurement_params(iterations: usize, samples: usize) -> Result<(), String> {
337 if iterations == 0 {
338 return Err("Iterations must be greater than 0".to_string());
339 }
340 if samples == 0 {
341 return Err("Samples must be greater than 0".to_string());
342 }
343 if samples > 1_000_000 {
344 return Err(
345 "Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
346 );
347 }
348 Ok(())
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use std::thread;
355
356 #[test]
357 fn test_measure_single_iteration() {
358 let duration = measure_single_iteration(|| {
359 thread::sleep(Duration::from_millis(1));
360 });
361
362 assert!(duration >= Duration::from_millis(1));
363 assert!(duration < Duration::from_millis(10)); }
365
366 #[test]
367 fn test_validate_measurement_params() {
368 assert!(validate_measurement_params(100, 100).is_ok());
369 assert!(validate_measurement_params(0, 100).is_err());
370 assert!(validate_measurement_params(100, 0).is_err());
371 assert!(validate_measurement_params(100, 1_000_001).is_err());
372 assert!(validate_measurement_params(5, 100_000).is_ok());
373 }
374
375 #[test]
376 fn test_measure_function_basic() {
377 let result = measure_function_impl(
378 "test_bench".to_string(),
379 "test_module".to_string(),
380 || {
381 let _ = (0..100).sum::<i32>();
383 },
384 100,
385 10,
386 );
387
388 assert_eq!(result.name, "test_bench");
389 assert_eq!(result.module, "test_module");
390 assert_eq!(result.iterations, 100);
391 assert_eq!(result.samples, 10);
392 assert_eq!(result.all_timings.len(), 10);
393
394 for timing in &result.all_timings {
396 assert!(*timing > Duration::from_nanos(0));
397 assert!(*timing < Duration::from_secs(1));
398 }
399 }
400}