| 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 | |