codspeed_divan_compat_walltime/
divan.rs

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