1use crate::progress::{emit_progress, ProgressMessage, ProgressPhase};
2use crate::{calculate_percentiles, config::BenchmarkConfig, BenchResult, CpuMonitor, CpuSnapshot};
3use std::time::{Duration, Instant};
4
5fn get_pinned_core() -> usize {
7 std::env::var("SIMPLEBENCH_PIN_CORE")
9 .ok()
10 .and_then(|s| s.parse().ok())
11 .unwrap_or(0)
12}
13
14fn warmup_closure<F>(func: &mut F, duration: Duration, bench_name: &str) -> (u128, u64)
16where
17 F: FnMut(),
18{
19 let start = Instant::now();
20 let mut total_iterations = 0u64;
21 let mut last_report = Instant::now();
22 let target_ms = duration.as_millis() as u64;
23
24 while start.elapsed() < duration {
25 func();
26 total_iterations += 1;
27
28 if last_report.elapsed() >= Duration::from_millis(100) {
30 emit_progress(&ProgressMessage {
31 bench: bench_name,
32 phase: ProgressPhase::Warmup {
33 elapsed_ms: start.elapsed().as_millis() as u64,
34 target_ms,
35 },
36 });
37 last_report = Instant::now();
38 }
39 }
40
41 (start.elapsed().as_millis(), total_iterations)
42}
43
44fn measure_closure<F>(
46 func: &mut F,
47 samples: usize,
48 bench_name: &str,
49) -> (Vec<Duration>, Vec<CpuSnapshot>)
50where
51 F: FnMut(),
52{
53 let mut all_timings = Vec::with_capacity(samples);
54 let mut cpu_samples = Vec::with_capacity(samples);
55
56 let cpu_core = get_pinned_core();
58 let monitor = CpuMonitor::new(cpu_core);
59
60 let report_interval = (samples / 100).max(1);
62
63 for sample_idx in 0..samples {
64 if sample_idx % report_interval == 0 {
66 emit_progress(&ProgressMessage {
67 bench: bench_name,
68 phase: ProgressPhase::Samples {
69 current: sample_idx as u32,
70 total: samples as u32,
71 },
72 });
73 }
74
75 let freq_before = monitor.read_frequency();
77
78 let start = Instant::now();
79 func();
80 let elapsed = start.elapsed();
81 all_timings.push(elapsed);
82
83 let freq_after = monitor.read_frequency();
85 let frequency_khz = match (freq_before, freq_after) {
86 (Some(before), Some(after)) => Some(before.max(after)),
87 (Some(f), None) | (None, Some(f)) => Some(f),
88 (None, None) => None,
89 };
90
91 let snapshot = CpuSnapshot {
92 timestamp: Instant::now(),
93 frequency_khz,
94 temperature_millic: monitor.read_temperature(),
95 };
96 cpu_samples.push(snapshot);
97 }
98
99 emit_progress(&ProgressMessage {
101 bench: bench_name,
102 phase: ProgressPhase::Complete,
103 });
104
105 (all_timings, cpu_samples)
106}
107
108pub fn measure_simple<F>(
113 config: &BenchmarkConfig,
114 name: &str,
115 module: &str,
116 mut func: F,
117) -> BenchResult
118where
119 F: FnMut(),
120{
121 let (warmup_ms, warmup_iters) = warmup_closure(
123 &mut func,
124 Duration::from_secs(config.measurement.warmup_duration_secs),
125 name,
126 );
127
128 let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
130
131 let percentiles = calculate_percentiles(&all_timings);
132
133 BenchResult {
134 name: name.to_string(),
135 module: module.to_string(),
136 samples: config.measurement.samples,
137 percentiles,
138 all_timings,
139 cpu_samples,
140 warmup_ms: Some(warmup_ms),
141 warmup_iterations: Some(warmup_iters),
142 }
143}
144
145pub fn measure_with_setup<T, S, B>(
151 config: &BenchmarkConfig,
152 name: &str,
153 module: &str,
154 setup: S,
155 mut bench: B,
156) -> BenchResult
157where
158 S: FnOnce() -> T,
159 B: FnMut(&T),
160{
161 let data = setup();
163
164 let mut func = || bench(&data);
166
167 let (warmup_ms, warmup_iters) = warmup_closure(
169 &mut func,
170 Duration::from_secs(config.measurement.warmup_duration_secs),
171 name,
172 );
173
174 let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
176
177 let percentiles = calculate_percentiles(&all_timings);
178
179 BenchResult {
180 name: name.to_string(),
181 module: module.to_string(),
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
191fn warmup_with_setup<T, S, B>(
193 setup: &mut S,
194 bench: &mut B,
195 duration: Duration,
196 bench_name: &str,
197) -> (u128, u64)
198where
199 S: FnMut() -> T,
200 B: FnMut(T),
201{
202 let start = Instant::now();
203 let mut total_iterations = 0u64;
204 let mut last_report = Instant::now();
205 let target_ms = duration.as_millis() as u64;
206
207 while start.elapsed() < duration {
208 let data = setup();
209 bench(data);
210 total_iterations += 1;
211
212 if last_report.elapsed() >= Duration::from_millis(100) {
214 emit_progress(&ProgressMessage {
215 bench: bench_name,
216 phase: ProgressPhase::Warmup {
217 elapsed_ms: start.elapsed().as_millis() as u64,
218 target_ms,
219 },
220 });
221 last_report = Instant::now();
222 }
223 }
224
225 (start.elapsed().as_millis(), total_iterations)
226}
227
228fn warmup_with_setup_ref<T, S, B>(
230 setup: &mut S,
231 bench: &mut B,
232 duration: Duration,
233 bench_name: &str,
234) -> (u128, u64)
235where
236 S: FnMut() -> T,
237 B: FnMut(&T),
238{
239 let start = Instant::now();
240 let mut total_iterations = 0u64;
241 let mut last_report = Instant::now();
242 let target_ms = duration.as_millis() as u64;
243
244 while start.elapsed() < duration {
245 let data = setup();
246 bench(&data);
247 total_iterations += 1;
248
249 if last_report.elapsed() >= Duration::from_millis(100) {
251 emit_progress(&ProgressMessage {
252 bench: bench_name,
253 phase: ProgressPhase::Warmup {
254 elapsed_ms: start.elapsed().as_millis() as u64,
255 target_ms,
256 },
257 });
258 last_report = Instant::now();
259 }
260 }
261
262 (start.elapsed().as_millis(), total_iterations)
263}
264
265pub fn measure_with_setup_each<T, S, B>(
270 config: &BenchmarkConfig,
271 name: &str,
272 module: &str,
273 mut setup: S,
274 mut bench: B,
275) -> BenchResult
276where
277 S: FnMut() -> T,
278 B: FnMut(T),
279{
280 let (warmup_ms, warmup_iters) = warmup_with_setup(
282 &mut setup,
283 &mut bench,
284 Duration::from_secs(config.measurement.warmup_duration_secs),
285 name,
286 );
287
288 let samples = config.measurement.samples;
290 let mut all_timings = Vec::with_capacity(samples);
291 let mut cpu_samples = Vec::with_capacity(samples);
292
293 let cpu_core = get_pinned_core();
295 let monitor = CpuMonitor::new(cpu_core);
296
297 let report_interval = (samples / 100).max(1);
299
300 for sample_idx in 0..samples {
301 if sample_idx % report_interval == 0 {
303 emit_progress(&ProgressMessage {
304 bench: name,
305 phase: ProgressPhase::Samples {
306 current: sample_idx as u32,
307 total: samples as u32,
308 },
309 });
310 }
311
312 let data = setup();
314
315 let freq_before = monitor.read_frequency();
317
318 let start = Instant::now();
319 bench(data); let elapsed = start.elapsed();
321 all_timings.push(elapsed);
322
323 let freq_after = monitor.read_frequency();
325 let frequency_khz = match (freq_before, freq_after) {
326 (Some(before), Some(after)) => Some(before.max(after)),
327 (Some(f), None) | (None, Some(f)) => Some(f),
328 (None, None) => None,
329 };
330
331 let snapshot = CpuSnapshot {
332 timestamp: Instant::now(),
333 frequency_khz,
334 temperature_millic: monitor.read_temperature(),
335 };
336 cpu_samples.push(snapshot);
337 }
338
339 emit_progress(&ProgressMessage {
341 bench: name,
342 phase: ProgressPhase::Complete,
343 });
344
345 let percentiles = calculate_percentiles(&all_timings);
346
347 BenchResult {
348 name: name.to_string(),
349 module: module.to_string(),
350 samples,
351 percentiles,
352 all_timings,
353 cpu_samples,
354 warmup_ms: Some(warmup_ms),
355 warmup_iterations: Some(warmup_iters),
356 }
357}
358
359pub fn measure_with_setup_each_ref<T, S, B>(
364 config: &BenchmarkConfig,
365 name: &str,
366 module: &str,
367 mut setup: S,
368 mut bench: B,
369) -> BenchResult
370where
371 S: FnMut() -> T,
372 B: FnMut(&T),
373{
374 let (warmup_ms, warmup_iters) = warmup_with_setup_ref(
376 &mut setup,
377 &mut bench,
378 Duration::from_secs(config.measurement.warmup_duration_secs),
379 name,
380 );
381
382 let samples = config.measurement.samples;
384 let mut all_timings = Vec::with_capacity(samples);
385 let mut cpu_samples = Vec::with_capacity(samples);
386
387 let cpu_core = get_pinned_core();
389 let monitor = CpuMonitor::new(cpu_core);
390
391 let report_interval = (samples / 100).max(1);
393
394 for sample_idx in 0..samples {
395 if sample_idx % report_interval == 0 {
397 emit_progress(&ProgressMessage {
398 bench: name,
399 phase: ProgressPhase::Samples {
400 current: sample_idx as u32,
401 total: samples as u32,
402 },
403 });
404 }
405
406 let data = setup();
408
409 let freq_before = monitor.read_frequency();
411
412 let start = Instant::now();
413 bench(&data); let elapsed = start.elapsed();
415 all_timings.push(elapsed);
416
417 let freq_after = monitor.read_frequency();
419 let frequency_khz = match (freq_before, freq_after) {
420 (Some(before), Some(after)) => Some(before.max(after)),
421 (Some(f), None) | (None, Some(f)) => Some(f),
422 (None, None) => None,
423 };
424
425 let snapshot = CpuSnapshot {
426 timestamp: Instant::now(),
427 frequency_khz,
428 temperature_millic: monitor.read_temperature(),
429 };
430 cpu_samples.push(snapshot);
431
432 drop(data); }
434
435 emit_progress(&ProgressMessage {
437 bench: name,
438 phase: ProgressPhase::Complete,
439 });
440
441 let percentiles = calculate_percentiles(&all_timings);
442
443 BenchResult {
444 name: name.to_string(),
445 module: module.to_string(),
446 samples,
447 percentiles,
448 all_timings,
449 cpu_samples,
450 warmup_ms: Some(warmup_ms),
451 warmup_iterations: Some(warmup_iters),
452 }
453}
454
455pub fn measure_single_iteration<F>(func: F) -> Duration
456where
457 F: FnOnce(),
458{
459 let start = Instant::now();
460 func();
461 start.elapsed()
462}
463
464pub fn validate_measurement_params(samples: usize) -> Result<(), String> {
465 if samples == 0 {
466 return Err("Samples must be greater than 0".to_string());
467 }
468 if samples > 1_000_000 {
469 return Err(
470 "Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
471 );
472 }
473 Ok(())
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use std::thread;
480
481 #[test]
482 fn test_measure_single_iteration() {
483 let duration = measure_single_iteration(|| {
484 thread::sleep(Duration::from_millis(1));
485 });
486
487 assert!(duration >= Duration::from_millis(1));
488 assert!(duration < Duration::from_millis(10)); }
490
491 #[test]
492 fn test_validate_measurement_params() {
493 assert!(validate_measurement_params(100).is_ok());
494 assert!(validate_measurement_params(0).is_err());
495 assert!(validate_measurement_params(1_000_001).is_err());
496 assert!(validate_measurement_params(100_000).is_ok());
497 }
498
499 #[test]
500 fn test_measure_simple_basic() {
501 let config = BenchmarkConfig {
502 measurement: crate::config::MeasurementConfig {
503 samples: 10,
504 warmup_duration_secs: 0, },
506 ..Default::default()
507 };
508
509 let result = measure_simple(&config, "test_bench", "test_module", || {
510 let _ = (0..100).sum::<i32>();
512 });
513
514 assert_eq!(result.name, "test_bench");
515 assert_eq!(result.module, "test_module");
516 assert_eq!(result.samples, 10);
517 assert_eq!(result.all_timings.len(), 10);
518
519 for timing in &result.all_timings {
521 assert!(*timing > Duration::from_nanos(0));
522 assert!(*timing < Duration::from_secs(1));
523 }
524 }
525}