mobench_runner/
lib.rs

1//! Shared benchmarking harness that will be compiled into mobile targets.
2//! For now this runs on the host and provides the same API surface we will
3//! expose over FFI to Kotlin/Swift.
4
5use 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}