Skip to content

Commit 17c4e78

Browse files
feat(divan): add benchmark filtering in instrumentation mode
1 parent 9ace864 commit 17c4e78

File tree

6 files changed

+121
-16
lines changed

6 files changed

+121
-16
lines changed

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: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
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::*;
66

77
const DIR: &str = "tests/simple-divan.in";
8+
const FIB_BENCH_NAME: &str = "fib_20";
9+
const BUBBLE_SORT_BENCH_NAME: &str = "bubble_sort_bench";
810

911
#[test]
1012
fn test_divan_run_without_build() {
@@ -80,20 +82,42 @@ fn test_divan_build_and_run_filtered_by_name() {
8082
.arg("fib_20")
8183
.assert()
8284
.success()
85+
.stdout(contains(FIB_BENCH_NAME))
86+
.stdout(contains(BUBBLE_SORT_BENCH_NAME).not())
87+
.stderr(contains("Finished running 2 benchmark suite(s)"));
88+
cargo_codspeed(&dir)
89+
.arg("run")
90+
.arg("bu.*le_sort")
91+
.assert()
92+
.success()
93+
.stdout(contains(FIB_BENCH_NAME).not())
94+
.stdout(contains(BUBBLE_SORT_BENCH_NAME))
8395
.stderr(contains("Finished running 2 benchmark suite(s)"));
8496
teardown(dir);
8597
}
8698

8799
#[test]
88-
fn test_divan_build_and_run_filtered_by_partial_name() {
100+
fn test_divan_build_and_run_filtered_by_name_single() {
89101
let dir = setup(DIR, Project::Simple);
90102
cargo_codspeed(&dir).arg("build").assert().success();
91103
cargo_codspeed(&dir)
92104
.arg("run")
93-
.arg("bubble_sort")
105+
.arg("bu.*le_sort")
106+
.args(["--bench", "divan_example"])
94107
.assert()
95108
.success()
96-
.stderr(contains("Finished running 2 benchmark suite(s)"));
109+
.stdout(contains(FIB_BENCH_NAME).not())
110+
.stdout(contains(BUBBLE_SORT_BENCH_NAME).not()) // We are filtering with a name that is not in the selected benchmark
111+
.stderr(contains("Finished running 1 benchmark suite(s)"));
112+
cargo_codspeed(&dir)
113+
.arg("run")
114+
.arg("fib")
115+
.args(["--bench", "divan_example"])
116+
.assert()
117+
.success()
118+
.stdout(contains(FIB_BENCH_NAME))
119+
.stdout(contains(BUBBLE_SORT_BENCH_NAME).not())
120+
.stderr(contains("Finished running 1 benchmark suite(s)"));
97121
teardown(dir);
98122
}
99123

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)