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#[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
38pub(crate) struct SharedContext {
40 pub action: Action,
42
43 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 pub fn main(&self) {
58 self.run_action(self.action);
59 }
60
61 pub fn run_benches(&self) {
63 self.run_action(Action::Bench);
64 }
65
66 pub fn test_benches(&self) {
71 self.run_action(Action::Test);
72 }
73
74 pub fn list_benches(&self) {
76 self.run_action(Action::Test);
77 }
78
79 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 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 EntryTree::retain(&mut tree, |entry_path| self.filter(entry_path));
128
129 if tree.is_empty() {
131 return;
132 }
133
134 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 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 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 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 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 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 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
442pub 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
467impl Divan {
469 pub fn from_args() -> Self {
471 Self::default().config_with_args()
472 }
473
474 #[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 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 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 #[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 #[must_use]
609 pub fn run_ignored(mut self) -> Self {
610 self.run_ignored = RunIgnored::Yes;
611 self
612 }
613
614 #[must_use]
618 pub fn run_only_ignored(mut self) -> Self {
619 self.run_ignored = RunIgnored::Only;
620 self
621 }
622
623 #[must_use]
659 pub fn skip_regex(mut self, filter: impl SkipRegex) -> Self {
660 filter.skip_regex(&mut self);
661 self
662 }
663
664 #[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 #[inline]
701 pub fn sample_count(mut self, count: u32) -> Self {
702 self.bench_options.sample_count = Some(count);
703 self
704 }
705
706 #[inline]
710 pub fn sample_size(mut self, count: u32) -> Self {
711 self.bench_options.sample_size = Some(count);
712 self
713 }
714
715 #[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 #[inline]
741 pub fn min_time(mut self, time: Duration) -> Self {
742 self.bench_options.min_time = Some(time);
743 self
744 }
745
746 #[inline]
750 pub fn max_time(mut self, time: Duration) -> Self {
751 self.bench_options.max_time = Some(time);
752 self
753 }
754
755 #[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
766impl 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 #[inline]
777 pub fn counter<C: IntoCounter>(mut self, counter: C) -> Self {
778 self.counter_mut(counter);
779 self
780 }
781
782 #[inline]
787 pub fn items_count<C: Into<ItemsCount>>(self, count: C) -> Self {
788 self.counter(count.into())
789 }
790
791 #[inline]
796 pub fn bytes_count<C: Into<BytesCount>>(self, count: C) -> Self {
797 self.counter(count.into())
798 }
799
800 #[inline]
805 pub fn bytes_format(mut self, format: BytesFormat) -> Self {
806 self.bytes_format = format;
807 self
808 }
809
810 #[inline]
815 pub fn chars_count<C: Into<CharsCount>>(self, count: C) -> Self {
816 self.counter(count.into())
817 }
818
819 #[inline]
824 pub fn cycles_count<C: Into<CyclesCount>>(self, count: C) -> Self {
825 self.counter(count.into())
826 }
827}