1use serde::{Deserialize, Serialize};
6use std::time::{Duration, Instant};
7use thiserror::Error;
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct BenchSpec {
11 pub name: String,
12 pub iterations: u32,
13 pub warmup: u32,
14}
15
16impl BenchSpec {
17 pub fn new(name: impl Into<String>, iterations: u32, warmup: u32) -> Result<Self, BenchError> {
18 if iterations == 0 {
19 return Err(BenchError::NoIterations);
20 }
21
22 Ok(Self {
23 name: name.into(),
24 iterations,
25 warmup,
26 })
27 }
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
31pub struct BenchSample {
32 pub duration_ns: u64,
33}
34
35impl BenchSample {
36 fn from_duration(duration: Duration) -> Self {
37 Self {
38 duration_ns: duration.as_nanos() as u64,
39 }
40 }
41}
42
43#[derive(Clone, Debug, Serialize, Deserialize)]
44pub struct BenchReport {
45 pub spec: BenchSpec,
46 pub samples: Vec<BenchSample>,
47}
48
49#[derive(Debug, Error)]
50pub enum BenchError {
51 #[error("iterations must be greater than zero")]
52 NoIterations,
53 #[error("benchmark function failed: {0}")]
54 Execution(String),
55}
56
57pub fn run_closure<F>(spec: BenchSpec, mut f: F) -> Result<BenchReport, BenchError>
58where
59 F: FnMut() -> Result<(), BenchError>,
60{
61 if spec.iterations == 0 {
62 return Err(BenchError::NoIterations);
63 }
64
65 for _ in 0..spec.warmup {
66 f()?;
67 }
68
69 let mut samples = Vec::with_capacity(spec.iterations as usize);
70 for _ in 0..spec.iterations {
71 let start = Instant::now();
72 f()?;
73 samples.push(BenchSample::from_duration(start.elapsed()));
74 }
75
76 Ok(BenchReport { spec, samples })
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn runs_benchmark() {
85 let spec = BenchSpec::new("noop", 3, 1).unwrap();
86 let report = run_closure(spec, || Ok(())).unwrap();
87
88 assert_eq!(report.samples.len(), 3);
89 let non_zero = report.samples.iter().filter(|s| s.duration_ns > 0).count();
90 assert!(non_zero >= 1);
91 }
92}