1use crate::stats;
2
3use std::{
4 fs::{File, OpenOptions},
5 hint::black_box,
6 io::{self, Write},
7 path::PathBuf,
8 process,
9 sync::{
10 atomic::{self, AtomicBool},
11 Arc,
12 },
13 time::Instant,
14};
15
16use ctrlc;
17use rand::{Rng, SeedableRng};
18use rand_chacha::ChaChaRng;
19
20#[derive(Copy, Clone)]
22pub struct BenchName(pub &'static str);
23
24impl BenchName {
25 fn padded(&self, column_count: usize) -> String {
26 let mut name = self.0.to_string();
27 let pad_len = column_count.saturating_sub(name.len());
28 let pad = " ".repeat(pad_len);
29 name.push_str(&pad);
30
31 name
32 }
33}
34
35pub type BenchRng = ChaChaRng;
38
39pub type BenchFn = fn(&mut CtRunner, &mut BenchRng);
41
42#[derive(Clone)]
44enum BenchEvent {
45 ContStart,
46 Begin(Vec<BenchName>),
47 Wait(BenchName),
48 Result(MonitorMsg),
49 Seed(u64, BenchName),
50}
51
52type MonitorMsg = (BenchName, stats::CtSummary);
53
54struct CtBencher {
57 samples: (Vec<u64>, Vec<u64>),
58 ctx: Option<stats::CtCtx>,
59 file_out: Option<File>,
60 rng: BenchRng,
61}
62
63impl CtBencher {
64 pub fn new() -> CtBencher {
66 CtBencher {
67 samples: (Vec::new(), Vec::new()),
68 ctx: None,
69 file_out: None,
70 rng: BenchRng::seed_from_u64(0u64),
71 }
72 }
73
74 fn go(&mut self, f: BenchFn) -> stats::CtSummary {
76 let mut runner = CtRunner::default();
78 f(&mut runner, &mut self.rng);
79 self.samples = runner.runtimes;
80
81 let old_self = ::std::mem::replace(self, CtBencher::new());
83 let (summ, new_ctx) = stats::update_ct_stats(old_self.ctx, &old_self.samples);
84
85 self.samples = old_self.samples;
87 self.file_out = old_self.file_out;
88 self.ctx = Some(new_ctx);
89 self.rng = old_self.rng;
90
91 summ
92 }
93
94 fn rand_seed() -> u64 {
96 rand::rng().next_u64()
97 }
98
99 pub fn seed_with(&mut self, seed: u64) {
101 self.rng = BenchRng::seed_from_u64(seed);
102 }
103
104 fn clear_data(&mut self) {
106 self.samples = (Vec::new(), Vec::new());
107 self.ctx = None;
108 }
109}
110
111pub struct BenchMetadata {
113 pub name: BenchName,
114 pub seed: Option<u64>,
115 pub benchfn: BenchFn,
116}
117
118#[derive(Default)]
128pub struct BenchOpts {
129 pub continuous: bool,
130 pub filter: Option<String>,
131 pub file_out: Option<PathBuf>,
132}
133
134struct ConsoleBenchState {
135 max_name_len: usize,
137}
138
139impl ConsoleBenchState {
140 fn write_plain(&mut self, s: &str) -> io::Result<()> {
141 let mut stdout = io::stdout();
142 stdout.write_all(s.as_bytes())?;
143 stdout.flush()
144 }
145
146 fn write_bench_start(&mut self, name: &BenchName) -> io::Result<()> {
147 let name = name.padded(self.max_name_len);
148 self.write_plain(&format!("bench {} ... ", name))
149 }
150
151 fn write_seed(&mut self, seed: u64, name: &BenchName) -> io::Result<()> {
152 let name = name.padded(self.max_name_len);
153 self.write_plain(&format!("bench {} seeded with 0x{:016x}\n", name, seed))
154 }
155
156 fn write_run_start(&mut self, len: usize) -> io::Result<()> {
157 let noun = if len != 1 { "benches" } else { "bench" };
158 self.write_plain(&format!("\nrunning {} {}\n", len, noun))
159 }
160
161 fn write_continuous_start(&mut self) -> io::Result<()> {
162 self.write_plain("running 1 benchmark continuously\n")
163 }
164
165 fn write_result(&mut self, summ: &stats::CtSummary) -> io::Result<()> {
166 self.write_plain(&format!(": {}\n", summ.fmt()))
167 }
168
169 fn write_run_finish(&mut self) -> io::Result<()> {
170 self.write_plain("\ndudect benches complete\n\n")
171 }
172}
173
174pub fn run_benches_console(opts: BenchOpts, benches: Vec<BenchMetadata>) -> io::Result<()> {
176 fn callback(event: &BenchEvent, st: &mut ConsoleBenchState) -> io::Result<()> {
179 match (*event).clone() {
180 BenchEvent::ContStart => st.write_continuous_start(),
181 BenchEvent::Begin(ref filtered_benches) => st.write_run_start(filtered_benches.len()),
182 BenchEvent::Wait(ref b) => st.write_bench_start(b),
183 BenchEvent::Result(msg) => {
184 let (_, summ) = msg;
185 st.write_result(&summ)
186 }
187 BenchEvent::Seed(seed, ref name) => st.write_seed(seed, name),
188 }
189 }
190
191 let mut st = ConsoleBenchState {
192 max_name_len: benches.iter().map(|t| t.name.0.len()).max().unwrap_or(0),
193 };
194
195 run_benches(&opts, benches, |x| callback(&x, &mut st))?;
196 st.write_run_finish()
197}
198
199fn setup_kill_bit() -> Arc<AtomicBool> {
201 let x = Arc::new(AtomicBool::new(false));
202 let y = x.clone();
203
204 ctrlc::set_handler(move || y.store(true, atomic::Ordering::SeqCst))
205 .expect("Error setting Ctrl-C handler");
206
207 x
208}
209
210fn run_benches<F>(opts: &BenchOpts, benches: Vec<BenchMetadata>, mut callback: F) -> io::Result<()>
211where
212 F: FnMut(BenchEvent) -> io::Result<()>,
213{
214 let filter = &opts.filter;
215 let filtered_benches = filter_benches(filter, benches);
216 let filtered_names = filtered_benches.iter().map(|b| b.name).collect();
217
218 let mut file_out = opts.file_out.as_ref().map(|filename| {
220 OpenOptions::new()
221 .write(true)
222 .truncate(true)
223 .create(true)
224 .open(filename)
225 .unwrap_or_else(|e| panic!("Could not open file '{:?}' for writing: {e}", filename))
226 });
227 file_out.as_mut().map(|f| {
228 f.write(b"benchname,class,runtime")
229 .expect("Error writing CSV header to file")
230 });
231
232 let mut cb: CtBencher = {
234 let mut d = CtBencher::new();
235 d.file_out = file_out;
236 d
237 };
238
239 if opts.continuous {
240 callback(BenchEvent::ContStart)?;
241
242 if filtered_benches.is_empty() {
243 match *filter {
244 Some(ref f) => panic!("No benchmark matching '{}' was found", f),
245 None => return Ok(()),
246 }
247 }
248
249 let kill_bit = setup_kill_bit();
251
252 let mut filtered_benches = filtered_benches;
254 let bench = filtered_benches.remove(0);
255
256 let seed = bench.seed.unwrap_or_else(CtBencher::rand_seed);
258 cb.seed_with(seed);
259 callback(BenchEvent::Seed(seed, bench.name))?;
260
261 loop {
262 callback(BenchEvent::Wait(bench.name))?;
263 let msg = run_bench_with_bencher(&bench.name, bench.benchfn, &mut cb);
264 callback(BenchEvent::Result(msg))?;
265
266 if kill_bit.load(atomic::Ordering::SeqCst) {
268 process::exit(0);
269 }
270 }
271 } else {
272 callback(BenchEvent::Begin(filtered_names))?;
273
274 for bench in filtered_benches {
276 cb.clear_data();
278
279 let seed = bench.seed.unwrap_or_else(CtBencher::rand_seed);
281 cb.seed_with(seed);
282 callback(BenchEvent::Seed(seed, bench.name))?;
283
284 callback(BenchEvent::Wait(bench.name))?;
285 let msg = run_bench_with_bencher(&bench.name, bench.benchfn, &mut cb);
286 callback(BenchEvent::Result(msg))?;
287 }
288 Ok(())
289 }
290}
291
292fn run_bench_with_bencher(name: &BenchName, benchfn: BenchFn, cb: &mut CtBencher) -> MonitorMsg {
293 let summ = cb.go(benchfn);
294
295 let samples_iter = cb.samples.0.iter().zip(cb.samples.1.iter());
297 if let Some(f) = cb.file_out.as_mut() {
298 for (x, y) in samples_iter {
299 write!(f, "\n{},0,{}", name.0, x).expect("Error writing data to file");
300 write!(f, "\n{},0,{}", name.0, y).expect("Error writing data to file");
301 }
302 };
303
304 (*name, summ)
305}
306
307fn filter_benches(filter: &Option<String>, bs: Vec<BenchMetadata>) -> Vec<BenchMetadata> {
308 let mut filtered = bs;
309
310 filtered = match *filter {
312 None => filtered,
313 Some(ref filter) => filtered
314 .into_iter()
315 .filter(|b| b.name.0.contains(&filter[..]))
316 .collect(),
317 };
318
319 filtered.sort_by(|b1, b2| b1.name.0.cmp(b2.name.0));
321
322 filtered
323}
324
325#[derive(Copy, Clone)]
327pub enum Class {
328 Left,
329 Right,
330}
331
332#[derive(Default)]
334pub struct CtRunner {
335 runtimes: (Vec<u64>, Vec<u64>),
337}
338
339impl CtRunner {
340 pub fn run_one<T, F>(&mut self, class: Class, f: F)
342 where
343 F: Fn() -> T,
344 {
345 let start = Instant::now();
346 black_box(f());
347 let end = Instant::now();
348
349 let runtime = {
350 let dur = end.duration_since(start);
351 dur.as_secs() * 1_000_000_000 + u64::from(dur.subsec_nanos())
352 };
353
354 match class {
355 Class::Left => self.runtimes.0.push(runtime),
356 Class::Right => self.runtimes.1.push(runtime),
357 }
358 }
359}