1#![cfg_attr(
5 not(any(feature = "precise-timing", feature = "alloc-profiling")),
6 forbid(unsafe_code)
7)]
8#![cfg_attr(
9 any(feature = "precise-timing", feature = "alloc-profiling"),
10 deny(unsafe_code)
11)]
12#![doc = include_str!("../README.md")]
13
14#[cfg(feature = "alloc-profiling")]
15mod alloc;
16pub mod baseline;
17mod bench;
18pub mod calibration;
19#[cfg(feature = "charts")]
20pub mod charts;
21mod checks;
22mod ci;
23#[cfg(feature = "criterion-compat")]
24pub mod criterion_compat;
25pub mod daemon;
26mod engine;
27mod format;
28mod gate;
29mod html;
30pub mod mcp;
31pub mod platform;
32mod report;
33mod results;
34mod stats;
35#[cfg(feature = "precise-timing")]
36mod timing;
37
38pub use bench::{BenchGroup, Bencher, GroupConfig, Suite, Throughput};
39
40#[doc(hidden)]
45pub fn postprocess_result(result: &SuiteResult) {
46 let args: Vec<String> = std::env::args().collect();
47 let format = args
48 .iter()
49 .find_map(|a| a.strip_prefix("--format=").map(String::from))
50 .or_else(|| std::env::var("ZENBENCH_FORMAT").ok());
51 let save_baseline: Option<String> = args
52 .iter()
53 .find_map(|a| a.strip_prefix("--save-baseline=").map(String::from));
54 let baseline_name: Option<String> = args
55 .iter()
56 .find_map(|a| a.strip_prefix("--baseline=").map(String::from));
57 let max_regression: f64 = args
58 .iter()
59 .find_map(|a| {
60 a.strip_prefix("--max-regression=")
61 .and_then(|v| v.parse().ok())
62 })
63 .unwrap_or(5.0);
64 let update_on_pass = args.iter().any(|a| a == "--update-on-pass");
65
66 match format.as_deref() {
68 Some("llm") => print!("{}", result.to_llm()),
69 Some("csv") => print!("{}", result.to_csv()),
70 Some("markdown" | "md") => print!("{}", result.to_markdown()),
71 Some("html") => print!("{}", result.to_html()),
72 Some("json") => {
73 if let Ok(json) = serde_json::to_string_pretty(result) {
74 println!("{json}");
75 }
76 }
77 _ => {} }
79
80 if let Some(ref name) = save_baseline {
82 match baseline::save_baseline(result, name) {
83 Ok(path) => eprintln!("[zenbench] baseline '{name}' saved to {}", path.display()),
84 Err(e) => {
85 eprintln!("[zenbench] error saving baseline '{name}': {e}");
86 std::process::exit(2);
87 }
88 }
89 }
90
91 if let Some(ref name) = baseline_name {
93 match baseline::load_baseline(name) {
94 Ok(saved) => {
95 let comparison = baseline::compare_against_baseline(&saved, result, max_regression);
96 baseline::print_comparison_report(&comparison);
97
98 if comparison.regressions > 0 {
99 eprintln!(
100 "\n[zenbench] FAIL: {} regression(s) exceed {max_regression}% threshold",
101 comparison.regressions,
102 );
103 std::process::exit(1);
104 } else {
105 eprintln!(
106 "\n[zenbench] PASS: no regressions exceed {max_regression}% threshold"
107 );
108 if update_on_pass {
110 match baseline::save_baseline(result, name) {
111 Ok(path) => eprintln!(
112 "[zenbench] baseline '{name}' updated (--update-on-pass) → {}",
113 path.display()
114 ),
115 Err(e) => {
116 eprintln!("[zenbench] warning: failed to update baseline: {e}");
117 }
118 }
119 }
120 }
121 }
122 Err(e) => {
123 eprintln!("[zenbench] {e}");
124 std::process::exit(2);
125 }
126 }
127 }
128
129 if let Some(path) = daemon::result_path_from_env()
131 && let Err(e) = result.save(&path)
132 {
133 eprintln!("[zenbench] error saving results: {e}");
134 }
135}
136#[cfg(feature = "alloc-profiling")]
137pub use alloc::{AllocProfiler, AllocStats};
138
139#[doc(hidden)]
141pub fn engine_new(suite: Suite) -> engine::Engine {
142 engine::Engine::new(suite)
143}
144pub use format::format_ns;
145pub use gate::GateConfig;
146pub use platform::Testbed;
147pub use results::{BenchmarkResult, ComparisonResult, RunId, SuiteResult};
148pub use stats::{MeanCi, PairedAnalysis, Summary};
149
150#[inline(always)]
155pub fn black_box<T>(x: T) -> T {
156 std::hint::black_box(x)
157}
158
159pub mod prelude {
165 pub use crate::bench::{BenchGroup, Bencher, GroupConfig, Suite, Throughput};
166 pub use crate::black_box;
167 pub use crate::gate::GateConfig;
168 pub use crate::results::SuiteResult;
169 pub use crate::stats::{MeanCi, PairedAnalysis, Summary};
170}
171
172pub fn run<F: FnOnce(&mut Suite)>(f: F) -> SuiteResult {
188 let mut suite = Suite::new();
189 f(&mut suite);
190 let engine = engine::Engine::new(suite);
191 engine.run()
192}
193
194pub fn run_gated<F: FnOnce(&mut Suite)>(gate: GateConfig, f: F) -> SuiteResult {
196 let mut suite = Suite::new();
197 f(&mut suite);
198 let engine = engine::Engine::with_gate(suite, gate);
199 engine.run()
200}
201
202pub fn run_and_save<F: FnOnce(&mut Suite)>(f: F) -> SuiteResult {
208 let result = run(f);
209
210 let path = daemon::result_path_from_env().unwrap_or_else(|| {
211 let name = format!("zenbench-{}.json", result.run_id);
212 std::path::PathBuf::from(name)
213 });
214
215 if let Err(e) = result.save(&path) {
216 eprintln!("[zenbench] error saving results to {}: {e}", path.display());
217 } else {
218 eprintln!("[zenbench] results saved to {}", path.display());
219 }
220
221 result
222}
223
224#[macro_export]
298macro_rules! main {
299 ($($func:path),+ $(,)?) => {
301 fn main() {
302 let group_filter: Option<String> = std::env::args()
303 .find_map(|a| a.strip_prefix("--group=").map(String::from));
304
305 let result = $crate::run(|suite: &mut $crate::Suite| {
306 if let Some(ref filter) = group_filter {
307 suite.set_group_filter(filter.clone());
308 }
309 $( $func(suite); )+
310 });
311
312 $crate::postprocess_result(&result);
313 }
314 };
315 (|$suite:ident| $body:block) => {
317 fn main() {
318 let group_filter: Option<String> = std::env::args()
319 .find_map(|a| a.strip_prefix("--group=").map(String::from));
320
321 let result = $crate::run(|$suite: &mut $crate::Suite| {
322 if let Some(ref filter) = group_filter {
323 $suite.set_group_filter(filter.clone());
324 }
325 $body
326 });
327
328 $crate::postprocess_result(&result);
329 }
330 };
331}