1 | #![allow (clippy::too_many_arguments)] |
2 | |
3 | use std::{borrow::Cow, cell::RefCell, fmt, num::NonZeroUsize, time::Duration}; |
4 | |
5 | use clap::ColorChoice; |
6 | use regex::Regex; |
7 | |
8 | use crate::{ |
9 | benchmark::BenchOptions, |
10 | config::{ |
11 | filter::{Filter, FilterSet}, |
12 | Action, ParsedSeconds, RunIgnored, SortingAttr, |
13 | }, |
14 | counter::{ |
15 | BytesCount, BytesFormat, CharsCount, CyclesCount, IntoCounter, ItemsCount, MaxCountUInt, |
16 | PrivBytesFormat, |
17 | }, |
18 | entry::{AnyBenchEntry, BenchEntryRunner, EntryTree}, |
19 | thread_pool::BENCH_POOL, |
20 | time::{Timer, TimerKind}, |
21 | tree_painter::{TreeColumn, TreePainter}, |
22 | util::{self, defer, IntoRegex}, |
23 | Bencher, |
24 | }; |
25 | |
26 | /// The benchmark runner. |
27 | #[derive (Default)] |
28 | pub struct Divan { |
29 | action: Action, |
30 | timer: TimerKind, |
31 | reverse_sort: bool, |
32 | sorting_attr: SortingAttr, |
33 | color: ColorChoice, |
34 | bytes_format: BytesFormat, |
35 | filters: FilterSet, |
36 | run_ignored: RunIgnored, |
37 | bench_options: BenchOptions<'static>, |
38 | } |
39 | |
40 | /// Immutable context shared between entry runs. |
41 | pub(crate) struct SharedContext { |
42 | /// The specific action being performed. |
43 | pub action: Action, |
44 | |
45 | /// The timer used to measure samples. |
46 | pub timer: Timer, |
47 | } |
48 | |
49 | impl fmt::Debug for Divan { |
50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
51 | f.debug_struct(name:"Divan" ).finish_non_exhaustive() |
52 | } |
53 | } |
54 | |
55 | impl Divan { |
56 | /// Perform the configured action. |
57 | /// |
58 | /// By default, this will be [`Divan::run_benches`]. |
59 | pub fn main(&self) { |
60 | self.run_action(self.action); |
61 | } |
62 | |
63 | /// Benchmark registered functions. |
64 | pub fn run_benches(&self) { |
65 | self.run_action(Action::Bench); |
66 | } |
67 | |
68 | /// Test registered functions as if the `--test` flag was used. |
69 | /// |
70 | /// Unlike [`Divan::run_benches`], this runs each benchmarked function only |
71 | /// once. |
72 | pub fn test_benches(&self) { |
73 | self.run_action(Action::Test); |
74 | } |
75 | |
76 | /// Print registered functions as if the `--list` flag was used. |
77 | pub fn list_benches(&self) { |
78 | self.run_action(Action::Test); |
79 | } |
80 | |
81 | /// Returns `true` if an entry at the given path should be considered for |
82 | /// running. |
83 | /// |
84 | /// This does not take into account `entry.ignored` because that is handled |
85 | /// separately. |
86 | fn filter(&self, entry_path: &str) -> bool { |
87 | self.filters.is_match(entry_path) |
88 | } |
89 | |
90 | pub(crate) fn should_ignore(&self, ignored: bool) -> bool { |
91 | !self.run_ignored.should_run(ignored) |
92 | } |
93 | |
94 | pub(crate) fn run_action(&self, action: Action) { |
95 | let _drop_threads = defer(|| BENCH_POOL.drop_threads()); |
96 | |
97 | let mut tree: Vec<EntryTree> = if cfg!(miri) { |
98 | // Miri does not work with our linker tricks. |
99 | Vec::new() |
100 | } else { |
101 | let group_entries = &crate::entry::GROUP_ENTRIES; |
102 | |
103 | let generic_bench_entries = group_entries |
104 | .iter() |
105 | .flat_map(|group| group.generic_benches_iter().map(AnyBenchEntry::GenericBench)); |
106 | |
107 | let bench_entries = crate::entry::BENCH_ENTRIES |
108 | .iter() |
109 | .map(AnyBenchEntry::Bench) |
110 | .chain(generic_bench_entries); |
111 | |
112 | let mut tree = EntryTree::from_benches(bench_entries); |
113 | |
114 | for group in group_entries.iter() { |
115 | EntryTree::insert_group(&mut tree, group); |
116 | } |
117 | |
118 | tree |
119 | }; |
120 | |
121 | // Filter after inserting groups so that we can properly use groups' |
122 | // display names. |
123 | EntryTree::retain(&mut tree, |entry_path| self.filter(entry_path)); |
124 | |
125 | // Quick exit without doing unnecessary work. |
126 | if tree.is_empty() { |
127 | return; |
128 | } |
129 | |
130 | // When run under `cargo-nextest`, it provides `--list --format terse`. |
131 | // We don't currently accept this action under any other circumstances. |
132 | if action.is_list_terse() { |
133 | self.run_tree_list(&tree, "" ); |
134 | return; |
135 | } |
136 | |
137 | // Sorting is after filtering to compare fewer elements. |
138 | EntryTree::sort_by_attr(&mut tree, self.sorting_attr, self.reverse_sort); |
139 | |
140 | let timer = match self.timer { |
141 | TimerKind::Os => Timer::Os, |
142 | |
143 | TimerKind::Tsc => { |
144 | match Timer::get_tsc() { |
145 | Ok(tsc) => tsc, |
146 | Err(error) => { |
147 | eprintln!("warning: CPU timestamp counter is unavailable ( {error}), defaulting to OS" ); |
148 | Timer::Os |
149 | } |
150 | } |
151 | } |
152 | }; |
153 | |
154 | if action.is_bench() { |
155 | eprintln!("Timer precision: {}" , timer.precision()); |
156 | } |
157 | |
158 | let shared_context = SharedContext { action, timer }; |
159 | |
160 | let column_widths = if action.is_bench() { |
161 | TreeColumn::ALL.map(|column| { |
162 | if column.is_last() { |
163 | // The last column doesn't use padding. |
164 | 0 |
165 | } else { |
166 | EntryTree::common_column_width(&tree, column) |
167 | } |
168 | }) |
169 | } else { |
170 | [0; TreeColumn::COUNT] |
171 | }; |
172 | |
173 | let tree_painter = |
174 | RefCell::new(TreePainter::new(EntryTree::max_name_span(&tree, 0), column_widths)); |
175 | |
176 | self.run_tree(action, &tree, &shared_context, None, &tree_painter); |
177 | } |
178 | |
179 | /// Emits the entries in `tree` for the purpose of `--list --format terse`. |
180 | /// |
181 | /// This only happens when running under `cargo-nextest` (`NEXTEST=1`). |
182 | fn run_tree_list(&self, tree: &[EntryTree], parent_path: &str) { |
183 | let mut full_path = String::with_capacity(parent_path.len()); |
184 | |
185 | for child in tree { |
186 | let ignore = |
187 | child.bench_options().and_then(|options| options.ignore).unwrap_or_default(); |
188 | |
189 | if self.should_ignore(ignore) { |
190 | continue; |
191 | } |
192 | |
193 | full_path.clear(); |
194 | |
195 | if !parent_path.is_empty() { |
196 | full_path.push_str(parent_path); |
197 | full_path.push_str("::" ); |
198 | } |
199 | |
200 | full_path.push_str(child.display_name()); |
201 | |
202 | match child { |
203 | EntryTree::Leaf { args: None, .. } => println!(" {full_path}: benchmark" ), |
204 | EntryTree::Leaf { args: Some(args), .. } => { |
205 | for arg in args { |
206 | println!(" {full_path}:: {arg}: benchmark" ) |
207 | } |
208 | } |
209 | EntryTree::Parent { children, .. } => self.run_tree_list(children, &full_path), |
210 | } |
211 | } |
212 | } |
213 | |
214 | fn run_tree( |
215 | &self, |
216 | action: Action, |
217 | tree: &[EntryTree], |
218 | shared_context: &SharedContext, |
219 | parent_options: Option<&BenchOptions>, |
220 | tree_painter: &RefCell<TreePainter>, |
221 | ) { |
222 | for (i, child) in tree.iter().enumerate() { |
223 | let is_last = i == tree.len() - 1; |
224 | |
225 | let name = child.display_name(); |
226 | |
227 | let child_options = child.bench_options(); |
228 | |
229 | // Overwrite `parent_options` with `child_options` if applicable. |
230 | let options: BenchOptions; |
231 | let options: Option<&BenchOptions> = match (parent_options, child_options) { |
232 | (None, None) => None, |
233 | (Some(options), None) | (None, Some(options)) => Some(options), |
234 | (Some(parent_options), Some(child_options)) => { |
235 | options = child_options.overwrite(parent_options); |
236 | Some(&options) |
237 | } |
238 | }; |
239 | |
240 | match child { |
241 | EntryTree::Leaf { entry, args } => self.run_bench_entry( |
242 | action, |
243 | *entry, |
244 | args.as_deref(), |
245 | shared_context, |
246 | options, |
247 | tree_painter, |
248 | is_last, |
249 | ), |
250 | EntryTree::Parent { children, .. } => { |
251 | tree_painter.borrow_mut().start_parent(name, is_last); |
252 | |
253 | self.run_tree(action, children, shared_context, options, tree_painter); |
254 | |
255 | tree_painter.borrow_mut().finish_parent(); |
256 | } |
257 | } |
258 | } |
259 | } |
260 | |
261 | fn run_bench_entry( |
262 | &self, |
263 | action: Action, |
264 | bench_entry: AnyBenchEntry, |
265 | bench_arg_names: Option<&[&&str]>, |
266 | shared_context: &SharedContext, |
267 | entry_options: Option<&BenchOptions>, |
268 | tree_painter: &RefCell<TreePainter>, |
269 | is_last_entry: bool, |
270 | ) { |
271 | use crate::benchmark::BenchContext; |
272 | |
273 | let entry_display_name = bench_entry.display_name(); |
274 | |
275 | // User runtime options override all other options. |
276 | let options: BenchOptions; |
277 | let options: &BenchOptions = match entry_options { |
278 | None => &self.bench_options, |
279 | Some(entry_options) => { |
280 | options = self.bench_options.overwrite(entry_options); |
281 | &options |
282 | } |
283 | }; |
284 | |
285 | if self.should_ignore(options.ignore.unwrap_or_default()) { |
286 | tree_painter.borrow_mut().ignore_leaf(entry_display_name, is_last_entry); |
287 | return; |
288 | } |
289 | |
290 | // Paint empty leaf when simply listing. |
291 | if action.is_list() { |
292 | let mut tree_painter = tree_painter.borrow_mut(); |
293 | tree_painter.start_leaf(entry_display_name, is_last_entry); |
294 | tree_painter.finish_empty_leaf(); |
295 | return; |
296 | } |
297 | |
298 | let mut thread_counts: Vec<NonZeroUsize> = options |
299 | .threads |
300 | .as_deref() |
301 | .unwrap_or_default() |
302 | .iter() |
303 | .map(|&n| match NonZeroUsize::new(n) { |
304 | Some(n) => n, |
305 | None => crate::util::known_parallelism(), |
306 | }) |
307 | .collect(); |
308 | |
309 | thread_counts.sort_unstable(); |
310 | thread_counts.dedup(); |
311 | |
312 | let thread_counts: &[NonZeroUsize] = |
313 | if thread_counts.is_empty() { &[NonZeroUsize::MIN] } else { &thread_counts }; |
314 | |
315 | // Whether we should emit child branches for thread counts. |
316 | let has_thread_branches = thread_counts.len() > 1; |
317 | |
318 | let run_bench = |bench_display_name: &str, |
319 | is_last_bench: bool, |
320 | with_bencher: &dyn Fn(Bencher)| { |
321 | if has_thread_branches { |
322 | tree_painter.borrow_mut().start_parent(bench_display_name, is_last_bench); |
323 | } else { |
324 | tree_painter.borrow_mut().start_leaf(bench_display_name, is_last_bench); |
325 | } |
326 | |
327 | for (i, &thread_count) in thread_counts.iter().enumerate() { |
328 | let is_last_thread_count = |
329 | if has_thread_branches { i == thread_counts.len() - 1 } else { is_last_bench }; |
330 | |
331 | if has_thread_branches { |
332 | tree_painter |
333 | .borrow_mut() |
334 | .start_leaf(&format!("t= {thread_count}" ), is_last_thread_count); |
335 | } |
336 | |
337 | let mut bench_context = BenchContext::new(shared_context, options, thread_count); |
338 | with_bencher(Bencher::new(&mut bench_context)); |
339 | |
340 | if !bench_context.did_run { |
341 | eprintln!( |
342 | "warning: No benchmark function registered for ' {bench_display_name}'" |
343 | ); |
344 | } |
345 | |
346 | let should_compute_stats = |
347 | bench_context.did_run && shared_context.action.is_bench(); |
348 | |
349 | if should_compute_stats { |
350 | let stats = bench_context.compute_stats(); |
351 | tree_painter.borrow_mut().finish_leaf( |
352 | is_last_thread_count, |
353 | &stats, |
354 | self.bytes_format, |
355 | ); |
356 | } else { |
357 | tree_painter.borrow_mut().finish_empty_leaf(); |
358 | } |
359 | } |
360 | |
361 | if has_thread_branches { |
362 | tree_painter.borrow_mut().finish_parent(); |
363 | } |
364 | }; |
365 | |
366 | match bench_entry.bench_runner() { |
367 | BenchEntryRunner::Plain(bench) => run_bench(entry_display_name, is_last_entry, bench), |
368 | |
369 | BenchEntryRunner::Args(bench_runner) => { |
370 | tree_painter.borrow_mut().start_parent(entry_display_name, is_last_entry); |
371 | |
372 | let bench_runner = bench_runner(); |
373 | let orig_arg_names = bench_runner.arg_names(); |
374 | let bench_arg_names = bench_arg_names.unwrap_or_default(); |
375 | |
376 | for (i, &arg_name) in bench_arg_names.iter().enumerate() { |
377 | let is_last_arg = i == bench_arg_names.len() - 1; |
378 | let arg_index = util::slice_ptr_index(orig_arg_names, arg_name); |
379 | |
380 | run_bench(arg_name, is_last_arg, &|bencher| { |
381 | bench_runner.bench(bencher, arg_index); |
382 | }); |
383 | } |
384 | |
385 | tree_painter.borrow_mut().finish_parent(); |
386 | } |
387 | } |
388 | } |
389 | } |
390 | |
391 | /// Configuration options. |
392 | impl Divan { |
393 | /// Creates an instance with options set by parsing CLI arguments. |
394 | pub fn from_args() -> Self { |
395 | Self::default().config_with_args() |
396 | } |
397 | |
398 | /// Sets options by parsing CLI arguments. |
399 | /// |
400 | /// This may override any previously-set options. |
401 | #[must_use ] |
402 | pub fn config_with_args(mut self) -> Self { |
403 | let mut command = crate::cli::command(); |
404 | |
405 | let mut matches = command.get_matches_mut(); |
406 | let is_exact = matches.get_flag("exact" ); |
407 | |
408 | // Insert filters. |
409 | { |
410 | let mut parse_filter = |filter: String| -> Filter { |
411 | if is_exact { |
412 | Filter::Exact(filter) |
413 | } else { |
414 | Filter::Regex(Regex::new(&filter).unwrap_or_else(|error| { |
415 | let kind = clap::error::ErrorKind::ValueValidation; |
416 | command.error(kind, error).exit(); |
417 | })) |
418 | } |
419 | }; |
420 | |
421 | let inclusive_filters = matches.remove_many::<String>("filter" ); |
422 | let exclusive_filters = matches.remove_many::<String>("skip" ); |
423 | |
424 | // Reduce allocation size and reallocation count. |
425 | self.filters.reserve_exact({ |
426 | let inclusive_count = |
427 | inclusive_filters.as_ref().map(|f| f.len()).unwrap_or_default(); |
428 | |
429 | let exclusive_count = |
430 | exclusive_filters.as_ref().map(|f| f.len()).unwrap_or_default(); |
431 | |
432 | inclusive_count + exclusive_count |
433 | }); |
434 | |
435 | if let Some(inclusive_filters) = inclusive_filters { |
436 | for filter in inclusive_filters { |
437 | self.filters.include(parse_filter(filter)); |
438 | } |
439 | } |
440 | |
441 | if let Some(exclusive_filters) = exclusive_filters { |
442 | for filter in exclusive_filters { |
443 | self.filters.exclude(parse_filter(filter)); |
444 | } |
445 | } |
446 | } |
447 | |
448 | self.action = if matches.get_flag("list" ) { |
449 | // We support `--list --format terse` only under `cargo-nextest`. |
450 | let is_terse = matches |
451 | .try_get_one::<String>("format" ) |
452 | .ok() |
453 | .flatten() |
454 | .map(|format| format == "terse" ) |
455 | .unwrap_or_default(); |
456 | |
457 | if is_terse { |
458 | Action::ListTerse |
459 | } else { |
460 | Action::List |
461 | } |
462 | } else if matches.get_flag("test" ) || !matches.get_flag("bench" ) { |
463 | // Either of: |
464 | // `cargo bench -- --test` |
465 | // `cargo test --benches` |
466 | Action::Test |
467 | } else { |
468 | Action::Bench |
469 | }; |
470 | |
471 | if let Some(&color) = matches.get_one("color" ) { |
472 | self.color = color; |
473 | } |
474 | |
475 | if matches.get_flag("ignored" ) { |
476 | self.run_ignored = RunIgnored::Only; |
477 | } else if matches.get_flag("include-ignored" ) { |
478 | self.run_ignored = RunIgnored::Yes; |
479 | } |
480 | |
481 | if let Some(&timer) = matches.get_one("timer" ) { |
482 | self.timer = timer; |
483 | } |
484 | |
485 | if let Some(&sorting_attr) = matches.get_one("sortr" ) { |
486 | self.reverse_sort = true; |
487 | self.sorting_attr = sorting_attr; |
488 | } else if let Some(&sorting_attr) = matches.get_one("sort" ) { |
489 | self.reverse_sort = false; |
490 | self.sorting_attr = sorting_attr; |
491 | } |
492 | |
493 | if let Some(&sample_count) = matches.get_one("sample-count" ) { |
494 | self.bench_options.sample_count = Some(sample_count); |
495 | } |
496 | |
497 | if let Some(&sample_size) = matches.get_one("sample-size" ) { |
498 | self.bench_options.sample_size = Some(sample_size); |
499 | } |
500 | |
501 | if let Some(thread_counts) = matches.get_many::<usize>("threads" ) { |
502 | let mut threads: Vec<usize> = thread_counts.copied().collect(); |
503 | threads.sort_unstable(); |
504 | threads.dedup(); |
505 | self.bench_options.threads = Some(Cow::Owned(threads)); |
506 | } |
507 | |
508 | if let Some(&ParsedSeconds(min_time)) = matches.get_one("min-time" ) { |
509 | self.bench_options.min_time = Some(min_time); |
510 | } |
511 | |
512 | if let Some(&ParsedSeconds(max_time)) = matches.get_one("max-time" ) { |
513 | self.bench_options.max_time = Some(max_time); |
514 | } |
515 | |
516 | if let Some(mut skip_ext_time) = matches.get_many::<bool>("skip-ext-time" ) { |
517 | // If the option is present without a value, then it's `true`. |
518 | self.bench_options.skip_ext_time = |
519 | Some(matches!(skip_ext_time.next(), Some(true) | None)); |
520 | } |
521 | |
522 | if let Some(&count) = matches.get_one::<MaxCountUInt>("items-count" ) { |
523 | self.counter_mut(ItemsCount::new(count)); |
524 | } |
525 | |
526 | if let Some(&count) = matches.get_one::<MaxCountUInt>("bytes-count" ) { |
527 | self.counter_mut(BytesCount::new(count)); |
528 | } |
529 | |
530 | if let Some(&PrivBytesFormat(bytes_format)) = matches.get_one("bytes-format" ) { |
531 | self.bytes_format = bytes_format; |
532 | } |
533 | |
534 | if let Some(&count) = matches.get_one::<MaxCountUInt>("chars-count" ) { |
535 | self.counter_mut(CharsCount::new(count)); |
536 | } |
537 | |
538 | if let Some(&count) = matches.get_one::<MaxCountUInt>("cycles-count" ) { |
539 | self.counter_mut(CyclesCount::new(count)); |
540 | } |
541 | |
542 | self |
543 | } |
544 | |
545 | /// Sets whether output should be colored. |
546 | /// |
547 | /// This option is equivalent to the `--color` CLI argument, where [`None`] |
548 | /// here means "auto". |
549 | #[must_use ] |
550 | pub fn color(mut self, yes: impl Into<Option<bool>>) -> Self { |
551 | self.color = match yes.into() { |
552 | None => ColorChoice::Auto, |
553 | Some(true) => ColorChoice::Always, |
554 | Some(false) => ColorChoice::Never, |
555 | }; |
556 | self |
557 | } |
558 | |
559 | /// Also run benchmarks marked [`#[ignore]`](https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute). |
560 | /// |
561 | /// This option is equivalent to the `--include-ignored` CLI argument. |
562 | #[must_use ] |
563 | pub fn run_ignored(mut self) -> Self { |
564 | self.run_ignored = RunIgnored::Yes; |
565 | self |
566 | } |
567 | |
568 | /// Only run benchmarks marked [`#[ignore]`](https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute). |
569 | /// |
570 | /// This option is equivalent to the `--ignored` CLI argument. |
571 | #[must_use ] |
572 | pub fn run_only_ignored(mut self) -> Self { |
573 | self.run_ignored = RunIgnored::Only; |
574 | self |
575 | } |
576 | |
577 | /// Skips benchmarks that match `filter` as a regular expression pattern. |
578 | /// |
579 | /// This option is equivalent to the `--skip filter` CLI argument, without |
580 | /// `--exact`. |
581 | /// |
582 | /// # Examples |
583 | /// |
584 | /// This method is commonly used with a [`&str`](prim@str) or [`String`]: |
585 | /// |
586 | /// ``` |
587 | /// # use divan::Divan; |
588 | /// let filter = "(add|sub)" ; |
589 | /// let divan = Divan::default().skip_regex(filter); |
590 | /// ``` |
591 | /// |
592 | /// A pre-built [`Regex`] can also be provided: |
593 | /// |
594 | /// ``` |
595 | /// # use divan::Divan; |
596 | /// let filter = regex::Regex::new("(add|sub)" ).unwrap(); |
597 | /// let divan = Divan::default().skip_regex(filter); |
598 | /// ``` |
599 | /// |
600 | /// Calling this repeatedly will add multiple skip filters: |
601 | /// |
602 | /// ``` |
603 | /// # use divan::Divan; |
604 | /// let divan = Divan::default() |
605 | /// .skip_regex("(add|sub)" ) |
606 | /// .skip_regex("collections.*default" ); |
607 | /// ``` |
608 | /// |
609 | /// # Panics |
610 | /// |
611 | /// Panics if `filter` is a string and [`Regex::new`] fails. |
612 | #[must_use ] |
613 | #[track_caller ] |
614 | pub fn skip_regex(mut self, filter: impl IntoRegex) -> Self { |
615 | self.filters.exclude(Filter::Regex(filter.into_regex())); |
616 | self |
617 | } |
618 | |
619 | /// Skips benchmarks that exactly match `filter`. |
620 | /// |
621 | /// This option is equivalent to the `--skip filter --exact` CLI arguments. |
622 | /// |
623 | /// # Examples |
624 | /// |
625 | /// This method is commonly used with a [`&str`](prim@str) or [`String`]: |
626 | /// |
627 | /// ``` |
628 | /// # use divan::Divan; |
629 | /// let filter = "arithmetic::add" ; |
630 | /// let divan = Divan::default().skip_exact(filter); |
631 | /// ``` |
632 | /// |
633 | /// Calling this repeatedly will add multiple skip filters: |
634 | /// |
635 | /// ``` |
636 | /// # use divan::Divan; |
637 | /// let divan = Divan::default() |
638 | /// .skip_exact("arithmetic::add" ) |
639 | /// .skip_exact("collections::vec::default" ); |
640 | /// ``` |
641 | #[must_use ] |
642 | pub fn skip_exact(mut self, filter: impl Into<String>) -> Self { |
643 | self.filters.exclude(Filter::Exact(filter.into())); |
644 | self |
645 | } |
646 | |
647 | /// Sets the number of sampling iterations. |
648 | /// |
649 | /// This option is equivalent to the `--sample-count` CLI argument. |
650 | /// |
651 | /// If a benchmark enables [`threads`](macro@crate::bench#threads), sample |
652 | /// count becomes a multiple of the number of threads. This is because each |
653 | /// thread operates over the same sample size to ensure there are always N |
654 | /// competing threads doing the same amount of work. |
655 | #[inline ] |
656 | pub fn sample_count(mut self, count: u32) -> Self { |
657 | self.bench_options.sample_count = Some(count); |
658 | self |
659 | } |
660 | |
661 | /// Sets the number of iterations inside a single sample. |
662 | /// |
663 | /// This option is equivalent to the `--sample-size` CLI argument. |
664 | #[inline ] |
665 | pub fn sample_size(mut self, count: u32) -> Self { |
666 | self.bench_options.sample_size = Some(count); |
667 | self |
668 | } |
669 | |
670 | /// Run across multiple threads. |
671 | /// |
672 | /// This enables you to measure contention on [atomics and |
673 | /// locks](std::sync). A value of 0 indicates [available |
674 | /// parallelism](std::thread::available_parallelism). |
675 | /// |
676 | /// This option is equivalent to the `--threads` CLI argument or |
677 | /// `DIVAN_THREADS` environment variable. |
678 | #[inline ] |
679 | pub fn threads<T>(mut self, threads: T) -> Self |
680 | where |
681 | T: IntoIterator<Item = usize>, |
682 | { |
683 | self.bench_options.threads = { |
684 | let mut threads: Vec<usize> = threads.into_iter().collect(); |
685 | threads.sort_unstable(); |
686 | threads.dedup(); |
687 | Some(Cow::Owned(threads)) |
688 | }; |
689 | self |
690 | } |
691 | |
692 | /// Sets the time floor for benchmarking a function. |
693 | /// |
694 | /// This option is equivalent to the `--min-time` CLI argument. |
695 | #[inline ] |
696 | pub fn min_time(mut self, time: Duration) -> Self { |
697 | self.bench_options.min_time = Some(time); |
698 | self |
699 | } |
700 | |
701 | /// Sets the time ceiling for benchmarking a function. |
702 | /// |
703 | /// This option is equivalent to the `--max-time` CLI argument. |
704 | #[inline ] |
705 | pub fn max_time(mut self, time: Duration) -> Self { |
706 | self.bench_options.max_time = Some(time); |
707 | self |
708 | } |
709 | |
710 | /// When accounting for `min_time` or `max_time`, skip time external to |
711 | /// benchmarked functions. |
712 | /// |
713 | /// This option is equivalent to the `--skip-ext-time` CLI argument. |
714 | #[inline ] |
715 | pub fn skip_ext_time(mut self, skip: bool) -> Self { |
716 | self.bench_options.skip_ext_time = Some(skip); |
717 | self |
718 | } |
719 | } |
720 | |
721 | /// Use [`Counter`s](crate::counter::Counter) to get throughput across all |
722 | /// benchmarks. |
723 | impl Divan { |
724 | #[inline ] |
725 | fn counter_mut<C: IntoCounter>(&mut self, counter: C) -> &mut Self { |
726 | self.bench_options.counters.insert(counter); |
727 | self |
728 | } |
729 | |
730 | /// Counts the number of values processed. |
731 | #[inline ] |
732 | pub fn counter<C: IntoCounter>(mut self, counter: C) -> Self { |
733 | self.counter_mut(counter); |
734 | self |
735 | } |
736 | |
737 | /// Sets the number of items processed. |
738 | /// |
739 | /// This option is equivalent to the `--items-count` CLI argument or |
740 | /// `DIVAN_ITEMS_COUNT` environment variable. |
741 | #[inline ] |
742 | pub fn items_count<C: Into<ItemsCount>>(self, count: C) -> Self { |
743 | self.counter(count.into()) |
744 | } |
745 | |
746 | /// Sets the number of bytes processed. |
747 | /// |
748 | /// This option is equivalent to the `--bytes-count` CLI argument or |
749 | /// `DIVAN_BYTES_COUNT` environment variable. |
750 | #[inline ] |
751 | pub fn bytes_count<C: Into<BytesCount>>(self, count: C) -> Self { |
752 | self.counter(count.into()) |
753 | } |
754 | |
755 | /// Determines how [`BytesCount`] is scaled in benchmark outputs. |
756 | /// |
757 | /// This option is equivalent to the `--bytes-format` CLI argument or |
758 | /// `DIVAN_BYTES_FORMAT` environment variable. |
759 | #[inline ] |
760 | pub fn bytes_format(mut self, format: BytesFormat) -> Self { |
761 | self.bytes_format = format; |
762 | self |
763 | } |
764 | |
765 | /// Sets the number of bytes processed. |
766 | /// |
767 | /// This option is equivalent to the `--chars-count` CLI argument or |
768 | /// `DIVAN_CHARS_COUNT` environment variable. |
769 | #[inline ] |
770 | pub fn chars_count<C: Into<CharsCount>>(self, count: C) -> Self { |
771 | self.counter(count.into()) |
772 | } |
773 | |
774 | /// Sets the number of cycles processed, displayed as Hertz. |
775 | /// |
776 | /// This option is equivalent to the `--cycles-count` CLI argument or |
777 | /// `DIVAN_CYCLES_COUNT` environment variable. |
778 | #[inline ] |
779 | pub fn cycles_count<C: Into<CyclesCount>>(self, count: C) -> Self { |
780 | self.counter(count.into()) |
781 | } |
782 | } |
783 | |