use crate::{BenchmarkReport, BenchmarkRunner, BenchmarkRuntimeOptions, ComparisonPolicy};
use std::{env, ffi::OsString, path::PathBuf, time::Duration};
pub const OUTPUT_PATH_ENVIRONMENT: &str = "MICROMEASURE_OUTPUT";
#[derive(Debug, Eq, PartialEq)]
enum ReportDestination {
Explicit(PathBuf),
Default,
Disabled,
}
fn report_destination(explicit_path: Option<OsString>, save_results: bool) -> ReportDestination {
match explicit_path {
Some(path) => ReportDestination::Explicit(PathBuf::from(path)),
None if save_results => ReportDestination::Default,
None => ReportDestination::Disabled,
}
}
#[derive(Clone, Debug)]
pub struct BenchmarkMainOptions {
pub suite: Option<String>,
pub filter_help: Option<String>,
pub comparison_policy: ComparisonPolicy,
pub save_results: bool,
pub runtime: BenchmarkRuntimeOptions,
}
impl Default for BenchmarkMainOptions {
fn default() -> Self {
Self {
suite: None,
filter_help: None,
comparison_policy: ComparisonPolicy::LatestCompatible,
save_results: true,
runtime: BenchmarkRuntimeOptions {
warm_up_duration: Duration::from_secs(1),
benchmark_duration: Duration::from_secs(5),
min_samples: 20,
max_samples: 100,
},
}
}
}
#[doc(hidden)]
pub fn benchmark_options_with_default_suite(
mut options: BenchmarkMainOptions,
default_suite: &str,
) -> BenchmarkMainOptions {
if options.suite.is_none() {
options.suite = Some(default_suite.to_string());
}
options
}
pub fn benchmark_filter_from_args(args: &[String]) -> Option<String> {
let separator_pos = args.iter().position(|arg| arg == "--");
if let Some(separator_pos) = separator_pos {
return args.get(separator_pos + 1).cloned();
}
args.iter()
.skip(1)
.find(|arg| !arg.starts_with("--") && !args[0].contains(arg.as_str()))
.cloned()
}
pub fn benchmark_filter_from_env() -> Option<String> {
let args: Vec<String> = env::args().collect();
benchmark_filter_from_args(&args)
}
pub fn run_benchmark_main(
options: BenchmarkMainOptions,
register: impl FnOnce(&mut BenchmarkRunner),
) -> BenchmarkReport {
let filter = benchmark_filter_from_env();
if let Some(filter) = filter.as_deref() {
eprintln!("Running benchmarks matching filter: '{filter}'");
if let Some(help) = options.filter_help.as_deref() {
eprintln!("Available filters: {help}");
}
eprintln!();
}
let mut runner = BenchmarkRunner::new().with_filter(filter.as_deref());
if let Some(suite) = options.suite {
runner = runner.with_suite(suite);
}
runner = runner.with_runtime(options.runtime.clone());
register(&mut runner);
if filter.is_some() {
eprintln!("\nBenchmark filtering complete.");
}
let report = runner.report();
report.print_summary_with(options.comparison_policy);
match report_destination(env::var_os(OUTPUT_PATH_ENVIRONMENT), options.save_results) {
ReportDestination::Explicit(path) => {
report.save_to_path(&path).unwrap_or_else(|error| {
panic!(
"failed to save benchmark results to {}: {error}",
path.display()
)
});
println!("\n💾 Results saved to: {}", path.display());
}
ReportDestination::Default => match report.save_to_default_location() {
Ok(path) => println!("\n💾 Results saved to: {}", path.display()),
Err(error) => println!("\n⚠️ Failed to save results: {error}"),
},
ReportDestination::Disabled => {}
}
report
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_output_takes_precedence_over_save_results() {
let path = OsString::from("artifacts/report.json");
assert_eq!(
report_destination(Some(path), false),
ReportDestination::Explicit(PathBuf::from("artifacts/report.json"))
);
}
#[test]
fn output_follows_save_results_without_an_explicit_path() {
assert_eq!(report_destination(None, true), ReportDestination::Default);
assert_eq!(report_destination(None, false), ReportDestination::Disabled);
}
#[test]
fn call_site_suite_is_stable_and_preserves_explicit_override() {
let options = benchmark_options_with_default_suite(BenchmarkMainOptions::default(), "gpu");
assert_eq!(options.suite.as_deref(), Some("gpu"));
let explicit = BenchmarkMainOptions {
suite: Some("nightly".to_string()),
..BenchmarkMainOptions::default()
};
let options = benchmark_options_with_default_suite(explicit, "gpu");
assert_eq!(options.suite.as_deref(), Some("nightly"));
}
}