1mod charts;
4mod history;
5pub(crate) mod report;
6mod runner;
7
8mod types;
9
10use anyhow::Result;
11use clap::Args;
12use wowlab_common::output;
13use wowlab_fs::{directory, path::PathBuf};
14
15use crate::run::{FightDurationSeconds, IterationCount, RunParameters, RunParametersError};
16
17const DEFAULT_ITERATIONS: u32 = 500_000;
18const DEFAULT_DURATION: u32 = 300;
19
20#[derive(Args, Debug)]
21pub(crate) struct BenchArgs {
22 #[arg(value_name = "SPEC")]
24 pub specs: Vec<String>,
25
26 #[arg(long, short = 'n', default_value_t = DEFAULT_ITERATIONS)]
28 pub iterations: u32,
29
30 #[arg(long, short = 'd', default_value_t = DEFAULT_DURATION)]
32 pub duration: u32,
33
34 #[arg(long, default_value = "benchmarks/history.json")]
36 pub history: PathBuf,
37
38 #[arg(long, default_value = "benchmarks/report.html")]
40 pub output: PathBuf,
41
42 #[arg(long)]
44 pub no_save: bool,
45
46 #[arg(long)]
48 pub report_only: bool,
49
50 #[arg(long)]
52 pub no_scaling: bool,
53}
54
55impl TryFrom<&BenchArgs> for RunParameters {
56 type Error = RunParametersError;
57
58 fn try_from(args: &BenchArgs) -> Result<Self, Self::Error> {
59 let fight_duration = FightDurationSeconds::try_from(args.duration)?;
60 let iterations = IterationCount::try_from(args.iterations)?;
61
62 Ok(Self::new(iterations, fight_duration))
63 }
64}
65
66pub(crate) fn run(args: &BenchArgs) -> Result<()> {
67 let mut hist = history::load(&args.history);
68
69 if !args.report_only {
70 let parameters = RunParameters::try_from(args)?;
71 let specs = runner::discover_specs(&args.specs)?;
72
73 if specs.is_empty() {
74 anyhow::bail!("No matching specs found with rotation scripts");
75 }
76
77 let (results, rt) = runner::run_benchmarks(&specs, parameters)?;
78
79 let scaling = if args.no_scaling {
80 vec![]
81 } else {
82 runner::run_scaling(&rt, &specs, &results, parameters)
83 };
84
85 let bench_run = runner::build_bench_run(results, scaling);
86
87 if args.no_save {
88 hist.runs.push(bench_run);
89 } else {
90 hist.runs.push(bench_run);
91 history::save(&args.history, &hist)?;
92 }
93 }
94
95 if hist.runs.is_empty() {
96 output::detail("No benchmark data to report.");
97
98 return Ok(());
99 }
100
101 report::generate(&args.output, &hist)?;
102 let abs_path = directory::canonicalize(&args.output).unwrap_or_else(|_| args.output.clone());
103 let file_url = format!("file://{}", abs_path.display());
104
105 output::success(&format!("Report written to {file_url}"));
106
107 Ok(())
108}