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 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)]
28pub 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.
41pub(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
49impl 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
55impl 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.
392impl 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.
723impl 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