use anyhow::Result;
use chrono::Utc;
use super::{BenchResult, BenchRun};
pub trait Bencher: Sync {
fn id(&self) -> &'static str;
fn run(&self) -> Result<BenchResult>;
}
#[macro_export]
macro_rules! register_bench {
($expr:expr) => {
$crate::bench::api::inventory_submit! {
$crate::bench::api::BencherRegistration {
order: 0,
make: || {
let b: ::std::boxed::Box<dyn $crate::bench::api::Bencher> =
::std::boxed::Box::new($expr);
::std::boxed::Box::leak(b)
},
}
}
};
}
#[macro_export]
macro_rules! register_bench_ordered {
($order:expr, $expr:expr) => {
$crate::bench::api::inventory_submit! {
$crate::bench::api::BencherRegistration {
order: $order,
make: || {
let b: ::std::boxed::Box<dyn $crate::bench::api::Bencher> =
::std::boxed::Box::new($expr);
::std::boxed::Box::leak(b)
},
}
}
};
}
pub use inventory::submit as inventory_submit;
pub struct BencherRegistration {
pub order: i32,
pub make: fn() -> &'static dyn Bencher,
}
inventory::collect!(BencherRegistration);
pub fn run_main_json() -> Result<()> {
let mut regs: Vec<&'static BencherRegistration> =
inventory::iter::<BencherRegistration>().collect();
let mut resolved: Vec<(i32, &'static dyn Bencher)> =
regs.drain(..).map(|r| (r.order, (r.make)())).collect();
resolved.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.id().cmp(b.1.id())));
let stop_on_error = std::env::var("NORNIR_BENCH_STOP_ON_ERROR")
.map(|v| v != "0" && !v.is_empty())
.unwrap_or(false);
let mut results: Vec<BenchResult> = Vec::new();
let mut tests: Vec<super::TestOutcome> = Vec::new();
for (_order, b) in resolved {
let id = b.id().to_string();
let start = std::time::Instant::now();
match b.run() {
Ok(r) => {
results.push(r);
tests.push(super::TestOutcome {
name: id,
passed: true,
duration_ms: Some(start.elapsed().as_secs_f64() * 1000.0),
message: None,
});
}
Err(e) => {
tests.push(super::TestOutcome {
name: id,
passed: false,
duration_ms: Some(start.elapsed().as_secs_f64() * 1000.0),
message: Some(format!("{e:#}")),
});
if stop_on_error {
break;
}
}
}
}
let now = Utc::now();
let run = BenchRun {
date: now.format("%Y-%m-%d").to_string(),
timestamp: Some(now.to_rfc3339()),
version: std::env::var("NORNIR_BENCH_VERSION")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string()),
machine: std::env::var("NORNIR_MACHINE").unwrap_or_default(),
cores: num_cpus_best_effort() as u32,
results,
tests,
};
println!("{}", serde_json::to_string(&run)?);
Ok(())
}
fn num_cpus_best_effort() -> usize {
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
struct Demo;
impl Bencher for Demo {
fn id(&self) -> &'static str { "demo.always_42" }
fn run(&self) -> Result<BenchResult> {
let mut metrics = serde_json::Map::new();
metrics.insert("answer".into(), serde_json::json!(42));
Ok(BenchResult { name: "demo".into(), metrics })
}
}
crate::register_bench!(Demo);
#[test]
fn registry_includes_demo() {
let ids: Vec<&'static str> =
inventory::iter::<BencherRegistration>().map(|r| (r.make)().id()).collect();
assert!(ids.contains(&"demo.always_42"), "registry missing demo: {ids:?}");
}
}