Skip to content

Commit 9dfeacc

Browse files
feat(divan): add benchmark filtering in instrumentation mode
1 parent 7159cf8 commit 9dfeacc

6 files changed

Lines changed: 119 additions & 16 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cargo-codspeed/tests/simple-divan.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use assert_cmd::assert::OutputAssertExt;
2-
use predicates::str::contains;
2+
use predicates::{prelude::PredicateBooleanExt, str::contains};
33

44
mod helpers;
55
use helpers::*;
@@ -80,20 +80,42 @@ fn test_divan_build_and_run_filtered_by_name() {
8080
.arg("fib_20")
8181
.assert()
8282
.success()
83+
.stdout(contains("fib_20"))
84+
.stdout(contains("bubble sort").not())
85+
.stderr(contains("Finished running 2 benchmark suite(s)"));
86+
cargo_codspeed(&dir)
87+
.arg("run")
88+
.arg("bu.*le_sort")
89+
.assert()
90+
.success()
91+
.stdout(contains("fib_20").not())
92+
.stdout(contains("bubble sort"))
8393
.stderr(contains("Finished running 2 benchmark suite(s)"));
8494
teardown(dir);
8595
}
8696

8797
#[test]
88-
fn test_divan_build_and_run_filtered_by_partial_name() {
98+
fn test_divan_build_and_run_filtered_by_name_single() {
8999
let dir = setup(DIR, Project::Simple);
90100
cargo_codspeed(&dir).arg("build").assert().success();
91101
cargo_codspeed(&dir)
92102
.arg("run")
93-
.arg("bubble_sort")
103+
.arg("bu.*le_sort")
104+
.args(["--bench", "divan_example"])
94105
.assert()
95106
.success()
96-
.stderr(contains("Finished running 2 benchmark suite(s)"));
107+
.stdout(contains("fib_20").not())
108+
.stdout(contains("bubble sort").not()) // We are filtering with a name that is not in the selected benchmark
109+
.stderr(contains("Finished running 1 benchmark suite(s)"));
110+
cargo_codspeed(&dir)
111+
.arg("run")
112+
.arg("fib")
113+
.args(["--bench", "divan_example"])
114+
.assert()
115+
.success()
116+
.stdout(contains("fib_20"))
117+
.stdout(contains("bubble_sort_bench").not())
118+
.stderr(contains("Finished running 1 benchmark suite(s)"));
97119
teardown(dir);
98120
}
99121

crates/divan_compat/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ keywords = ["codspeed", "benchmark", "divan"]
2121
codspeed = { path = "../codspeed", version = "=4.0.0" }
2222
divan = { package = "codspeed-divan-compat-walltime", path = "./divan_fork", version = "=4.0.0" }
2323
codspeed-divan-compat-macros = { version = "=4.0.0", path = './macros' }
24+
regex = "1.11.3"
25+
clap = { version = "4", default-features = false, features = ["std", "env"] }
2426

2527
[[bench]]
2628
name = "basic_example"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use clap::{Arg, ArgAction, Command};
2+
3+
pub(crate) fn command() -> Command {
4+
fn option(name: &'static str) -> Arg {
5+
Arg::new(name).long(name)
6+
}
7+
8+
fn flag(name: &'static str) -> Arg {
9+
option(name).action(ArgAction::SetTrue)
10+
}
11+
12+
Command::new("divan")
13+
.arg(
14+
Arg::new("filter")
15+
.value_name("FILTER")
16+
.help("Only run benchmarks whose names match this pattern")
17+
.action(ArgAction::Append),
18+
)
19+
.arg(flag("exact").help("Filter benchmarks by exact name rather than by pattern"))
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use regex::Regex;
2+
3+
/// Filters which benchmark to run based on name.
4+
pub(crate) enum Filter {
5+
Regex(Regex),
6+
Exact(String),
7+
}
8+
9+
impl Filter {
10+
/// Returns `true` if a string matches this filter.
11+
pub fn is_match(&self, s: &str) -> bool {
12+
match self {
13+
Self::Regex(r) => r.is_match(s),
14+
Self::Exact(e) => e == s,
15+
}
16+
}
17+
}

crates/divan_compat/src/compat/mod.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ pub mod __private {
1313
}
1414

1515
mod bench;
16+
mod cli;
17+
mod config;
1618
mod entry;
1719
mod uri;
1820
mod util;
1921

20-
use std::{cell::RefCell, rc::Rc};
21-
2222
pub use bench::*;
2323
use codspeed::codspeed::CodSpeed;
24+
use config::Filter;
2425
use entry::AnyBenchEntry;
26+
use regex::Regex;
27+
use std::{cell::RefCell, rc::Rc};
2528

2629
pub fn main() {
2730
// Outlined steps of original divan::main and their equivalent in codspeed instrumented mode
@@ -46,8 +49,37 @@ pub fn main() {
4649
// codspeed URI from entry metadata directly.
4750

4851
// 3. Filtering
49-
// We do not support finer filtering that bench targets for now, do nothing here, bench
50-
// filtering is managed by the `cargo-codspeed` wrappers before we reach this point.
52+
let should_run_benchmark_from_filters = {
53+
let mut command = cli::command();
54+
let matches = command.get_matches_mut();
55+
let is_exact = matches.get_flag("exact");
56+
57+
let parse_filter = |filter: &String| {
58+
if is_exact {
59+
Filter::Exact(filter.to_owned())
60+
} else {
61+
match Regex::new(filter) {
62+
Ok(r) => Filter::Regex(r),
63+
Err(error) => {
64+
let kind = clap::error::ErrorKind::ValueValidation;
65+
command.error(kind, error).exit();
66+
}
67+
}
68+
}
69+
};
70+
71+
let filters: Option<Vec<Filter>> = matches
72+
.get_many::<String>("filter")
73+
.map(|arg_filters| arg_filters.map(parse_filter).collect());
74+
75+
move |uri: &str| {
76+
if let Some(filters) = filters.as_ref() {
77+
filters.iter().any(|filter| filter.is_match(uri))
78+
} else {
79+
true
80+
}
81+
}
82+
};
5183

5284
// 4. Scan the tree and execute benchmarks
5385
let codspeed = Rc::new(RefCell::new(CodSpeed::new()));
@@ -66,6 +98,10 @@ pub fn main() {
6698
entry::BenchEntryRunner::Plain(bench_fn) => {
6799
let uri = uri::generate(&entry, entry.display_name());
68100

101+
if !should_run_benchmark_from_filters(&uri) {
102+
continue;
103+
}
104+
69105
bench_fn(bench::Bencher::new(&codspeed, uri));
70106
}
71107
entry::BenchEntryRunner::Args(bench_runner) => {
@@ -74,6 +110,10 @@ pub fn main() {
74110
for (arg_index, arg_name) in bench_runner.arg_names().iter().enumerate() {
75111
let uri = uri::generate(&entry, arg_name);
76112

113+
if !should_run_benchmark_from_filters(&uri) {
114+
continue;
115+
}
116+
77117
let bencher = bench::Bencher::new(&codspeed, uri);
78118

79119
bench_runner.bench(bencher, arg_index);

0 commit comments

Comments
 (0)