1use clap::{builder::PossibleValue, value_parser, Arg, ArgAction, ColorChoice, Command, ValueEnum};
2
3use crate::{
4 config::{ParsedSeconds, SortingAttr},
5 counter::MaxCountUInt,
6 time::TimerKind,
7 util,
8};
9
10pub(crate) fn command() -> Command {
11 fn option(name: &'static str) -> Arg {
12 Arg::new(name).long(name)
13 }
14
15 fn flag(name: &'static str) -> Arg {
16 option(name).action(ArgAction::SetTrue)
17 }
18
19 fn ignored_flag(name: &'static str) -> Arg {
20 flag(name).hide(true)
21 }
22
23 // Custom arguments not supported by libtest:
24 // - bytes-format
25 // - sample-count
26 // - sample-size
27 // - timer
28 // - sort
29 // - sortr
30
31 // TODO: `--format <pretty|terse>`
32
33 let mut cmd = Command::new("divan");
34
35 // Support `cargo-nextest` running us with `--list --format terse`.
36 //
37 // TODO: Add CI test to ensure this doesn't break.
38 if util::is_cargo_nextest() {
39 cmd = cmd.arg(option("format").value_parser(["terse"]).requires("list"));
40 }
41
42 cmd
43 .arg(
44 Arg::new("filter")
45 .value_name("FILTER")
46 .help("Only run benchmarks whose names match this pattern")
47 .action(ArgAction::Append),
48 )
49 .arg(
50 flag("test")
51 .help("Run benchmarks once to ensure they run successfully")
52 .conflicts_with("list"),
53 )
54 .arg(flag("list").help("Lists benchmarks").conflicts_with("test"))
55 .arg(
56 option("color")
57 .value_name("WHEN")
58 .help("Controls when to use colors")
59 .value_parser(value_parser!(ColorChoice))
60 )
61 .arg(
62 option("skip")
63 .value_name("FILTER")
64 .help("Skip benchmarks whose names match this pattern")
65 .action(ArgAction::Append),
66 )
67 .arg(flag("exact").help("Filter benchmarks by exact name rather than by pattern"))
68 .arg(flag("ignored").help("Run only ignored benchmarks").conflicts_with("include-ignored"))
69 .arg(
70 flag("include-ignored")
71 .help("Run ignored and not-ignored benchmarks")
72 .conflicts_with("ignored"),
73 )
74 .arg(
75 option("sort")
76 .env("DIVAN_SORT")
77 .value_name("ATTRIBUTE")
78 .help("Sort benchmarks in ascending order")
79 .value_parser(value_parser!(SortingAttr))
80 )
81 .arg(
82 option("sortr")
83 .env("DIVAN_SORTR")
84 .value_name("ATTRIBUTE")
85 .help("Sort benchmarks in descending order")
86 .value_parser(value_parser!(SortingAttr))
87 .overrides_with("sort"),
88 )
89 .arg(
90 option("timer")
91 .env("DIVAN_TIMER")
92 .value_name("os|tsc")
93 .help("Set the timer used for measuring samples")
94 .value_parser(value_parser!(TimerKind)),
95 )
96 .arg(
97 option("sample-count")
98 .env("DIVAN_SAMPLE_COUNT")
99 .value_name("N")
100 .help("Set the number of sampling iterations")
101 .value_parser(value_parser!(u32)),
102 )
103 .arg(
104 option("sample-size")
105 .env("DIVAN_SAMPLE_SIZE")
106 .value_name("N")
107 .help("Set the number of iterations inside a single sample")
108 .value_parser(value_parser!(u32)),
109 )
110 .arg(
111 option("threads")
112 .env("DIVAN_THREADS")
113 .value_name("N")
114 .value_delimiter(',')
115 .action(ArgAction::Append)
116 .help("Run across multiple threads to measure contention on atomics and locks")
117 .value_parser(value_parser!(usize)),
118 )
119 .arg(
120 option("min-time")
121 .env("DIVAN_MIN_TIME")
122 .value_name("SECS")
123 .help("Set the minimum seconds spent benchmarking a single function")
124 .value_parser(value_parser!(ParsedSeconds)),
125 )
126 .arg(
127 option("max-time")
128 .env("DIVAN_MAX_TIME")
129 .value_name("SECS")
130 .help("Set the maximum seconds spent benchmarking a single function, with priority over '--min-time'")
131 .value_parser(value_parser!(ParsedSeconds)),
132 )
133 .arg(
134 option("skip-ext-time")
135 .env("DIVAN_SKIP_EXT_TIME")
136 .value_name("true|false")
137 .help("When '--min-time' or '--max-time' is set, skip time external to benchmarked functions")
138 .value_parser(value_parser!(bool))
139 .num_args(0..=1),
140 )
141 .arg(
142 option("items-count")
143 .env("DIVAN_ITEMS_COUNT")
144 .value_name("N")
145 .help("Set every benchmark to have a throughput of N items")
146 .value_parser(value_parser!(MaxCountUInt)),
147 )
148 .arg(
149 option("bytes-count")
150 .env("DIVAN_BYTES_COUNT")
151 .value_name("N")
152 .help("Set every benchmark to have a throughput of N bytes")
153 .value_parser(value_parser!(MaxCountUInt)),
154 )
155 .arg(
156 option("bytes-format")
157 .env("DIVAN_BYTES_FORMAT")
158 .help("Set the numerical base for bytes in output")
159 .value_name("decimal|binary")
160 .value_parser(value_parser!(crate::counter::PrivBytesFormat))
161 )
162 .arg(
163 option("chars-count")
164 .env("DIVAN_CHARS_COUNT")
165 .value_name("N")
166 .help("Set every benchmark to have a throughput of N string scalars")
167 .value_parser(value_parser!(MaxCountUInt)),
168 )
169 .arg(
170 option("cycles-count")
171 .env("DIVAN_CYCLES_COUNT")
172 .value_name("N")
173 .help("Set every benchmark to have a throughput of N cycles, displayed as Hertz")
174 .value_parser(value_parser!(MaxCountUInt)),
175 )
176 // ignored:
177 .args([ignored_flag("bench"), ignored_flag("nocapture"), ignored_flag("show-output")])
178}
179
180impl ValueEnum for TimerKind {
181 fn value_variants<'a>() -> &'a [Self] {
182 &[Self::Os, Self::Tsc]
183 }
184
185 fn to_possible_value(&self) -> Option<PossibleValue> {
186 let name: &'static str = match self {
187 Self::Os => "os",
188 Self::Tsc => "tsc",
189 };
190 Some(PossibleValue::new(name))
191 }
192}
193
194impl ValueEnum for SortingAttr {
195 fn value_variants<'a>() -> &'a [Self] {
196 &[Self::Kind, Self::Name, Self::Location]
197 }
198
199 fn to_possible_value(&self) -> Option<PossibleValue> {
200 let name: &'static str = match self {
201 Self::Kind => "kind",
202 Self::Name => "name",
203 Self::Location => "location",
204 };
205 Some(PossibleValue::new(name))
206 }
207}
208