use crate::progress::{emit_progress, ProgressMessage, ProgressPhase};
use crate::{calculate_percentiles, config::BenchmarkConfig, BenchResult, CpuMonitor, CpuSnapshot};
use std::time::{Duration, Instant};
fn get_pinned_core() -> usize {
std::env::var("SIMPLEBENCH_PIN_CORE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0)
}
fn warmup_closure<F>(func: &mut F, duration: Duration, bench_name: &str) -> (u128, u64)
where
F: FnMut(),
{
let start = Instant::now();
let mut total_iterations = 0u64;
let mut last_report = Instant::now();
let target_ms = duration.as_millis() as u64;
while start.elapsed() < duration {
func();
total_iterations += 1;
if last_report.elapsed() >= Duration::from_millis(100) {
emit_progress(&ProgressMessage {
bench: bench_name,
phase: ProgressPhase::Warmup {
elapsed_ms: start.elapsed().as_millis() as u64,
target_ms,
},
});
last_report = Instant::now();
}
}
(start.elapsed().as_millis(), total_iterations)
}
fn measure_closure<F>(
func: &mut F,
samples: usize,
bench_name: &str,
) -> (Vec<Duration>, Vec<CpuSnapshot>)
where
F: FnMut(),
{
let mut all_timings = Vec::with_capacity(samples);
let mut cpu_samples = Vec::with_capacity(samples);
let cpu_core = get_pinned_core();
let monitor = CpuMonitor::new(cpu_core);
let report_interval = (samples / 100).max(1);
for sample_idx in 0..samples {
if sample_idx % report_interval == 0 {
emit_progress(&ProgressMessage {
bench: bench_name,
phase: ProgressPhase::Samples {
current: sample_idx as u32,
total: samples as u32,
},
});
}
let freq_before = monitor.read_frequency();
let start = Instant::now();
func();
let elapsed = start.elapsed();
all_timings.push(elapsed);
let freq_after = monitor.read_frequency();
let frequency_khz = match (freq_before, freq_after) {
(Some(before), Some(after)) => Some(before.max(after)),
(Some(f), None) | (None, Some(f)) => Some(f),
(None, None) => None,
};
let snapshot = CpuSnapshot {
timestamp: Instant::now(),
frequency_khz,
temperature_millic: monitor.read_temperature(),
};
cpu_samples.push(snapshot);
}
emit_progress(&ProgressMessage {
bench: bench_name,
phase: ProgressPhase::Complete,
});
(all_timings, cpu_samples)
}
pub fn measure_simple<F>(
config: &BenchmarkConfig,
name: &str,
module: &str,
mut func: F,
) -> BenchResult
where
F: FnMut(),
{
let (warmup_ms, warmup_iters) = warmup_closure(
&mut func,
Duration::from_secs(config.measurement.warmup_duration_secs),
name,
);
let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
let percentiles = calculate_percentiles(&all_timings);
BenchResult {
name: name.to_string(),
module: module.to_string(),
samples: config.measurement.samples,
percentiles,
all_timings,
cpu_samples,
warmup_ms: Some(warmup_ms),
warmup_iterations: Some(warmup_iters),
}
}
pub fn measure_with_setup<T, S, B>(
config: &BenchmarkConfig,
name: &str,
module: &str,
setup: S,
mut bench: B,
) -> BenchResult
where
S: FnOnce() -> T,
B: FnMut(&T),
{
let data = setup();
let mut func = || bench(&data);
let (warmup_ms, warmup_iters) = warmup_closure(
&mut func,
Duration::from_secs(config.measurement.warmup_duration_secs),
name,
);
let (all_timings, cpu_samples) = measure_closure(&mut func, config.measurement.samples, name);
let percentiles = calculate_percentiles(&all_timings);
BenchResult {
name: name.to_string(),
module: module.to_string(),
samples: config.measurement.samples,
percentiles,
all_timings,
cpu_samples,
warmup_ms: Some(warmup_ms),
warmup_iterations: Some(warmup_iters),
}
}
fn warmup_with_setup<T, S, B>(
setup: &mut S,
bench: &mut B,
duration: Duration,
bench_name: &str,
) -> (u128, u64)
where
S: FnMut() -> T,
B: FnMut(T),
{
let start = Instant::now();
let mut total_iterations = 0u64;
let mut last_report = Instant::now();
let target_ms = duration.as_millis() as u64;
while start.elapsed() < duration {
let data = setup();
bench(data);
total_iterations += 1;
if last_report.elapsed() >= Duration::from_millis(100) {
emit_progress(&ProgressMessage {
bench: bench_name,
phase: ProgressPhase::Warmup {
elapsed_ms: start.elapsed().as_millis() as u64,
target_ms,
},
});
last_report = Instant::now();
}
}
(start.elapsed().as_millis(), total_iterations)
}
fn warmup_with_setup_ref<T, S, B>(
setup: &mut S,
bench: &mut B,
duration: Duration,
bench_name: &str,
) -> (u128, u64)
where
S: FnMut() -> T,
B: FnMut(&T),
{
let start = Instant::now();
let mut total_iterations = 0u64;
let mut last_report = Instant::now();
let target_ms = duration.as_millis() as u64;
while start.elapsed() < duration {
let data = setup();
bench(&data);
total_iterations += 1;
if last_report.elapsed() >= Duration::from_millis(100) {
emit_progress(&ProgressMessage {
bench: bench_name,
phase: ProgressPhase::Warmup {
elapsed_ms: start.elapsed().as_millis() as u64,
target_ms,
},
});
last_report = Instant::now();
}
}
(start.elapsed().as_millis(), total_iterations)
}
pub fn measure_with_setup_each<T, S, B>(
config: &BenchmarkConfig,
name: &str,
module: &str,
mut setup: S,
mut bench: B,
) -> BenchResult
where
S: FnMut() -> T,
B: FnMut(T),
{
let (warmup_ms, warmup_iters) = warmup_with_setup(
&mut setup,
&mut bench,
Duration::from_secs(config.measurement.warmup_duration_secs),
name,
);
let samples = config.measurement.samples;
let mut all_timings = Vec::with_capacity(samples);
let mut cpu_samples = Vec::with_capacity(samples);
let cpu_core = get_pinned_core();
let monitor = CpuMonitor::new(cpu_core);
let report_interval = (samples / 100).max(1);
for sample_idx in 0..samples {
if sample_idx % report_interval == 0 {
emit_progress(&ProgressMessage {
bench: name,
phase: ProgressPhase::Samples {
current: sample_idx as u32,
total: samples as u32,
},
});
}
let data = setup();
let freq_before = monitor.read_frequency();
let start = Instant::now();
bench(data); let elapsed = start.elapsed();
all_timings.push(elapsed);
let freq_after = monitor.read_frequency();
let frequency_khz = match (freq_before, freq_after) {
(Some(before), Some(after)) => Some(before.max(after)),
(Some(f), None) | (None, Some(f)) => Some(f),
(None, None) => None,
};
let snapshot = CpuSnapshot {
timestamp: Instant::now(),
frequency_khz,
temperature_millic: monitor.read_temperature(),
};
cpu_samples.push(snapshot);
}
emit_progress(&ProgressMessage {
bench: name,
phase: ProgressPhase::Complete,
});
let percentiles = calculate_percentiles(&all_timings);
BenchResult {
name: name.to_string(),
module: module.to_string(),
samples,
percentiles,
all_timings,
cpu_samples,
warmup_ms: Some(warmup_ms),
warmup_iterations: Some(warmup_iters),
}
}
pub fn measure_with_setup_each_ref<T, S, B>(
config: &BenchmarkConfig,
name: &str,
module: &str,
mut setup: S,
mut bench: B,
) -> BenchResult
where
S: FnMut() -> T,
B: FnMut(&T),
{
let (warmup_ms, warmup_iters) = warmup_with_setup_ref(
&mut setup,
&mut bench,
Duration::from_secs(config.measurement.warmup_duration_secs),
name,
);
let samples = config.measurement.samples;
let mut all_timings = Vec::with_capacity(samples);
let mut cpu_samples = Vec::with_capacity(samples);
let cpu_core = get_pinned_core();
let monitor = CpuMonitor::new(cpu_core);
let report_interval = (samples / 100).max(1);
for sample_idx in 0..samples {
if sample_idx % report_interval == 0 {
emit_progress(&ProgressMessage {
bench: name,
phase: ProgressPhase::Samples {
current: sample_idx as u32,
total: samples as u32,
},
});
}
let data = setup();
let freq_before = monitor.read_frequency();
let start = Instant::now();
bench(&data); let elapsed = start.elapsed();
all_timings.push(elapsed);
let freq_after = monitor.read_frequency();
let frequency_khz = match (freq_before, freq_after) {
(Some(before), Some(after)) => Some(before.max(after)),
(Some(f), None) | (None, Some(f)) => Some(f),
(None, None) => None,
};
let snapshot = CpuSnapshot {
timestamp: Instant::now(),
frequency_khz,
temperature_millic: monitor.read_temperature(),
};
cpu_samples.push(snapshot);
drop(data); }
emit_progress(&ProgressMessage {
bench: name,
phase: ProgressPhase::Complete,
});
let percentiles = calculate_percentiles(&all_timings);
BenchResult {
name: name.to_string(),
module: module.to_string(),
samples,
percentiles,
all_timings,
cpu_samples,
warmup_ms: Some(warmup_ms),
warmup_iterations: Some(warmup_iters),
}
}
pub fn measure_single_iteration<F>(func: F) -> Duration
where
F: FnOnce(),
{
let start = Instant::now();
func();
start.elapsed()
}
pub fn validate_measurement_params(samples: usize) -> Result<(), String> {
if samples == 0 {
return Err("Samples must be greater than 0".to_string());
}
if samples > 1_000_000 {
return Err(
"Samples should not exceed 1,000,000 for reasonable execution time".to_string(),
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_measure_single_iteration() {
let duration = measure_single_iteration(|| {
thread::sleep(Duration::from_millis(1));
});
assert!(duration >= Duration::from_millis(1));
assert!(duration < Duration::from_millis(10)); }
#[test]
fn test_validate_measurement_params() {
assert!(validate_measurement_params(100).is_ok());
assert!(validate_measurement_params(0).is_err());
assert!(validate_measurement_params(1_000_001).is_err());
assert!(validate_measurement_params(100_000).is_ok());
}
#[test]
fn test_measure_simple_basic() {
let config = BenchmarkConfig {
measurement: crate::config::MeasurementConfig {
samples: 10,
warmup_duration_secs: 0, },
..Default::default()
};
let result = measure_simple(&config, "test_bench", "test_module", || {
let _ = (0..100).sum::<i32>();
});
assert_eq!(result.name, "test_bench");
assert_eq!(result.module, "test_module");
assert_eq!(result.samples, 10);
assert_eq!(result.all_timings.len(), 10);
for timing in &result.all_timings {
assert!(*timing > Duration::from_nanos(0));
assert!(*timing < Duration::from_secs(1));
}
}
}