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