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 batch_size = 1u64;
22 let mut last_report = Instant::now();
23 let target_ms = duration.as_millis() as u64;
24
25 while start.elapsed() < duration {
26 for _ in 0..batch_size {
27 func();
28 }
29 total_iterations += batch_size;
30 batch_size *= 2;
31
32 if last_report.elapsed() >= Duration::from_millis(100) {
34 emit_progress(&ProgressMessage {
35 bench: bench_name,
36 phase: ProgressPhase::Warmup {
37 elapsed_ms: start.elapsed().as_millis() as u64,
38 target_ms,
39 },
40 });
41 last_report = Instant::now();
42 }
43 }
44
45 (start.elapsed().as_millis(), total_iterations)
46}
47
48fn measure_closure<F>(
50 func: &mut F,
51 samples: usize,
52 bench_name: &str,
53) -> (Vec<Duration>, Vec<CpuSnapshot>)
54where
55 F: FnMut(),
56{
57 let mut all_timings = Vec::with_capacity(samples);
58 let mut cpu_samples = Vec::with_capacity(samples);
59
60 let cpu_core = get_pinned_core();
62 let monitor = CpuMonitor::new(cpu_core);
63
64 let report_interval = (samples / 100).max(1);
66
67 for sample_idx in 0..samples {
68 if sample_idx % report_interval == 0 {
70 emit_progress(&ProgressMessage {
71 bench: bench_name,
72 phase: ProgressPhase::Samples {
73 current: sample_idx as u32,
74 total: samples as u32,
75 },
76 });
77 }
78
79 let freq_before = monitor.read_frequency();
81
82 let start = Instant::now();
83 func();
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 emit_progress(&ProgressMessage {
105 bench: bench_name,
106 phase: ProgressPhase::Complete,
107 });
108
109 (all_timings, cpu_samples)
110}
111
112pub fn measure_simple<F>(
117 config: &BenchmarkConfig,
118 name: &str,
119 module: &str,
120 mut func: F,
121) -> BenchResult
122where
123 F: FnMut(),
124{
125 let (warmup_ms, warmup_iters) = warmup_closure(
127 &mut func,
128 Duration::from_secs(config.measurement.warmup_duration_secs),
129 name,
130 );
131
132 let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
134
135 let percentiles = calculate_percentiles(&all_timings);
136
137 BenchResult {
138 name: name.to_string(),
139 module: module.to_string(),
140 samples: config.measurement.samples,
141 percentiles,
142 all_timings,
143 cpu_samples,
144 warmup_ms: Some(warmup_ms),
145 warmup_iterations: Some(warmup_iters),
146 }
147}
148
149pub fn measure_with_setup<T, S, B>(
155 config: &BenchmarkConfig,
156 name: &str,
157 module: &str,
158 setup: S,
159 mut bench: B,
160) -> BenchResult
161where
162 S: FnOnce() -> T,
163 B: FnMut(&T),
164{
165 let data = setup();
167
168 let mut func = || bench(&data);
170
171 let (warmup_ms, warmup_iters) = warmup_closure(
173 &mut func,
174 Duration::from_secs(config.measurement.warmup_duration_secs),
175 name,
176 );
177
178 let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
180
181 let percentiles = calculate_percentiles(&all_timings);
182
183 BenchResult {
184 name: name.to_string(),
185 module: module.to_string(),
186 samples: config.measurement.samples,
187 percentiles,
188 all_timings,
189 cpu_samples,
190 warmup_ms: Some(warmup_ms),
191 warmup_iterations: Some(warmup_iters),
192 }
193}
194
195fn warmup_with_setup<T, S, B>(
197 setup: &mut S,
198 bench: &mut B,
199 duration: Duration,
200 bench_name: &str,
201) -> (u128, u64)
202where
203 S: FnMut() -> T,
204 B: FnMut(T),
205{
206 let start = Instant::now();
207 let mut total_iterations = 0u64;
208 let mut batch_size = 1u64;
209 let mut last_report = Instant::now();
210 let target_ms = duration.as_millis() as u64;
211
212 while start.elapsed() < duration {
213 for _ in 0..batch_size {
214 let data = setup();
215 bench(data);
216 }
217 total_iterations += batch_size;
218 batch_size *= 2;
219
220 if last_report.elapsed() >= Duration::from_millis(100) {
222 emit_progress(&ProgressMessage {
223 bench: bench_name,
224 phase: ProgressPhase::Warmup {
225 elapsed_ms: start.elapsed().as_millis() as u64,
226 target_ms,
227 },
228 });
229 last_report = Instant::now();
230 }
231 }
232
233 (start.elapsed().as_millis(), total_iterations)
234}
235
236fn warmup_with_setup_ref<T, S, B>(
238 setup: &mut S,
239 bench: &mut B,
240 duration: Duration,
241 bench_name: &str,
242) -> (u128, u64)
243where
244 S: FnMut() -> T,
245 B: FnMut(&T),
246{
247 let start = Instant::now();
248 let mut total_iterations = 0u64;
249 let mut batch_size = 1u64;
250 let mut last_report = Instant::now();
251 let target_ms = duration.as_millis() as u64;
252
253 while start.elapsed() < duration {
254 for _ in 0..batch_size {
255 let data = setup();
256 bench(&data);
257 }
258 total_iterations += batch_size;
259 batch_size *= 2;
260
261 if last_report.elapsed() >= Duration::from_millis(100) {
263 emit_progress(&ProgressMessage {
264 bench: bench_name,
265 phase: ProgressPhase::Warmup {
266 elapsed_ms: start.elapsed().as_millis() as u64,
267 target_ms,
268 },
269 });
270 last_report = Instant::now();
271 }
272 }
273
274 (start.elapsed().as_millis(), total_iterations)
275}
276
277pub fn measure_with_setup_each<T, S, B>(
282 config: &BenchmarkConfig,
283 name: &str,
284 module: &str,
285 mut setup: S,
286 mut bench: B,
287) -> BenchResult
288where
289 S: FnMut() -> T,
290 B: FnMut(T),
291{
292 let (warmup_ms, warmup_iters) = warmup_with_setup(
294 &mut setup,
295 &mut bench,
296 Duration::from_secs(config.measurement.warmup_duration_secs),
297 name,
298 );
299
300 let samples = config.measurement.samples;
302 let mut all_timings = Vec::with_capacity(samples);
303 let mut cpu_samples = Vec::with_capacity(samples);
304
305 let cpu_core = get_pinned_core();
307 let monitor = CpuMonitor::new(cpu_core);
308
309 let report_interval = (samples / 100).max(1);
311
312 for sample_idx in 0..samples {
313 if sample_idx % report_interval == 0 {
315 emit_progress(&ProgressMessage {
316 bench: name,
317 phase: ProgressPhase::Samples {
318 current: sample_idx as u32,
319 total: samples as u32,
320 },
321 });
322 }
323
324 let data = setup();
326
327 let freq_before = monitor.read_frequency();
329
330 let start = Instant::now();
331 bench(data); let elapsed = start.elapsed();
333 all_timings.push(elapsed);
334
335 let freq_after = monitor.read_frequency();
337 let frequency_khz = match (freq_before, freq_after) {
338 (Some(before), Some(after)) => Some(before.max(after)),
339 (Some(f), None) | (None, Some(f)) => Some(f),
340 (None, None) => None,
341 };
342
343 let snapshot = CpuSnapshot {
344 timestamp: Instant::now(),
345 frequency_khz,
346 temperature_millic: monitor.read_temperature(),
347 };
348 cpu_samples.push(snapshot);
349 }
350
351 emit_progress(&ProgressMessage {
353 bench: name,
354 phase: ProgressPhase::Complete,
355 });
356
357 let percentiles = calculate_percentiles(&all_timings);
358
359 BenchResult {
360 name: name.to_string(),
361 module: module.to_string(),
362 samples,
363 percentiles,
364 all_timings,
365 cpu_samples,
366 warmup_ms: Some(warmup_ms),
367 warmup_iterations: Some(warmup_iters),
368 }
369}
370
371pub fn measure_with_setup_each_ref<T, S, B>(
376 config: &BenchmarkConfig,
377 name: &str,
378 module: &str,
379 mut setup: S,
380 mut bench: B,
381) -> BenchResult
382where
383 S: FnMut() -> T,
384 B: FnMut(&T),
385{
386 let (warmup_ms, warmup_iters) = warmup_with_setup_ref(
388 &mut setup,
389 &mut bench,
390 Duration::from_secs(config.measurement.warmup_duration_secs),
391 name,
392 );
393
394 let samples = config.measurement.samples;
396 let mut all_timings = Vec::with_capacity(samples);
397 let mut cpu_samples = Vec::with_capacity(samples);
398
399 let cpu_core = get_pinned_core();
401 let monitor = CpuMonitor::new(cpu_core);
402
403 let report_interval = (samples / 100).max(1);
405
406 for sample_idx in 0..samples {
407 if sample_idx % report_interval == 0 {
409 emit_progress(&ProgressMessage {
410 bench: name,
411 phase: ProgressPhase::Samples {
412 current: sample_idx as u32,
413 total: samples as u32,
414 },
415 });
416 }
417
418 let data = setup();
420
421 let freq_before = monitor.read_frequency();
423
424 let start = Instant::now();
425 bench(&data); let elapsed = start.elapsed();
427 all_timings.push(elapsed);
428
429 let freq_after = monitor.read_frequency();
431 let frequency_khz = match (freq_before, freq_after) {
432 (Some(before), Some(after)) => Some(before.max(after)),
433 (Some(f), None) | (None, Some(f)) => Some(f),
434 (None, None) => None,
435 };
436
437 let snapshot = CpuSnapshot {
438 timestamp: Instant::now(),
439 frequency_khz,
440 temperature_millic: monitor.read_temperature(),
441 };
442 cpu_samples.push(snapshot);
443
444 drop(data); }
446
447 emit_progress(&ProgressMessage {
449 bench: name,
450 phase: ProgressPhase::Complete,
451 });
452
453 let percentiles = calculate_percentiles(&all_timings);
454
455 BenchResult {
456 name: name.to_string(),
457 module: module.to_string(),
458 samples,
459 percentiles,
460 all_timings,
461 cpu_samples,
462 warmup_ms: Some(warmup_ms),
463 warmup_iterations: Some(warmup_iters),
464 }
465}
466
467pub fn measure_single_iteration<F>(func: F) -> Duration
468where
469 F: FnOnce(),
470{
471 let start = Instant::now();
472 func();
473 start.elapsed()
474}
475
476pub fn validate_measurement_params(samples: usize) -> Result<(), String> {
477 if samples == 0 {
478 return Err("Samples must be greater than 0".to_string());
479 }
480 if samples > 1_000_000 {
481 return Err(
482 "Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
483 );
484 }
485 Ok(())
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use std::thread;
492
493 #[test]
494 fn test_measure_single_iteration() {
495 let duration = measure_single_iteration(|| {
496 thread::sleep(Duration::from_millis(1));
497 });
498
499 assert!(duration >= Duration::from_millis(1));
500 assert!(duration < Duration::from_millis(10)); }
502
503 #[test]
504 fn test_validate_measurement_params() {
505 assert!(validate_measurement_params(100).is_ok());
506 assert!(validate_measurement_params(0).is_err());
507 assert!(validate_measurement_params(1_000_001).is_err());
508 assert!(validate_measurement_params(100_000).is_ok());
509 }
510
511 #[test]
512 fn test_measure_simple_basic() {
513 let config = BenchmarkConfig {
514 measurement: crate::config::MeasurementConfig {
515 samples: 10,
516 warmup_duration_secs: 0, },
518 ..Default::default()
519 };
520
521 let result = measure_simple(&config, "test_bench", "test_module", || {
522 let _ = (0..100).sum::<i32>();
524 });
525
526 assert_eq!(result.name, "test_bench");
527 assert_eq!(result.module, "test_module");
528 assert_eq!(result.samples, 10);
529 assert_eq!(result.all_timings.len(), 10);
530
531 for timing in &result.all_timings {
533 assert!(*timing > Duration::from_nanos(0));
534 assert!(*timing < Duration::from_secs(1));
535 }
536 }
537}