Skip to content

Commit be00bcf

Browse files
committed
fix(criterion-compat): build walltime URIs like instrumented URIs
Walltime mode rebuilt the benchmark URI in the analysis phase from `current_file`/`macro_group`, which are only set by `criterion_group!`/`criterion_main!`. Bypassing the macros with a custom main produced URIs like `::::my_group::my_bench`. Build the URI once in `BenchmarkGroup` (skipping empty segments and deriving the file from the caller location when the macros are bypassed) and thread it through `BenchmarkId::codspeed_uri`, so walltime reports the same URI as the instrumented modes. Also derive the caller file per `benchmark_group` call instead of filling it once on `Criterion`, so benchmark functions living in different files don't inherit a stale file from a previous call. Fixes COD-2324
1 parent 8f30879 commit be00bcf

12 files changed

Lines changed: 140 additions & 38 deletions

File tree

crates/criterion_compat/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,5 @@ name = "test_benches"
7070
harness = false
7171

7272
[[bench]]
73-
name = "repro_custom_main"
73+
name = "custom_main"
7474
harness = false

crates/criterion_compat/benches/repro_custom_main.rs renamed to crates/criterion_compat/benches/custom_main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/// Reproducer for COD-2324: URI formatting issues when users bypass criterion_group!/criterion_main!
2-
/// and use a custom main function (like the rtmalloc project does).
1+
/// Test bench for custom main patterns (COD-2324).
2+
/// Verifies URI formatting when users bypass criterion_group!/criterion_main!.
33
use codspeed_criterion_compat::{criterion_group, BenchmarkId, Criterion};
44

55
fn bench_with_group(c: &mut Criterion) {

crates/criterion_compat/criterion_fork/src/analysis/mod.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,9 @@ pub(crate) fn common<M: Measurement, T: ?Sized>(
265265
mod codspeed {
266266
use crate::{measurement::Measurement, report::BenchmarkId, Criterion};
267267

268-
/// WARNING: Keep URI generation in sync with `codspeed-criterion-compat::compat::group::run_bench`
269-
pub fn create_uri_and_name<M: Measurement>(
270-
id: &BenchmarkId,
271-
c: &Criterion<M>,
272-
) -> (String, String) {
268+
/// The URI is built once in `BenchmarkGroup::build_codspeed_uri` and threaded through
269+
/// `BenchmarkId::codspeed_uri`, so that all runner modes report the same URI.
270+
pub fn create_uri_and_name(id: &BenchmarkId) -> (String, String) {
273271
let mut bench_name = id.group_id.clone();
274272
if let Some(ref function_name) = id.function_id {
275273
bench_name.push_str(&format!("::{}", function_name));
@@ -278,15 +276,7 @@ mod codspeed {
278276
bench_name.push_str(&format!("[{}]", parameter));
279277
}
280278

281-
let git_relative_file_path = codspeed::utils::get_git_relative_path(&c.current_file);
282-
let uri = format!(
283-
"{}::{}::{}",
284-
git_relative_file_path.to_string_lossy(),
285-
&c.macro_group,
286-
bench_name
287-
);
288-
289-
(uri, bench_name)
279+
(id.codspeed_uri.clone(), bench_name)
290280
}
291281

292282
pub(crate) fn collect_walltime_results<M: Measurement>(
@@ -295,7 +285,7 @@ mod codspeed {
295285
iters: &[f64],
296286
times: &[f64],
297287
) {
298-
let (uri, bench_name) = create_uri_and_name(id, c);
288+
let (uri, bench_name) = create_uri_and_name(id);
299289

300290
if let Err(error) =
301291
::codspeed::instrument_hooks::InstrumentHooks::instance().set_executed_benchmark(&uri)
@@ -320,6 +310,42 @@ mod codspeed {
320310
max_time_ns,
321311
);
322312
}
313+
314+
#[cfg(test)]
315+
mod tests {
316+
use super::*;
317+
318+
/// COD-2324: when `criterion_group!`/`criterion_main!` are bypassed, the URI threaded
319+
/// through `BenchmarkId::codspeed_uri` must be used as-is, without empty
320+
/// file/macro_group segments producing `::::my_group::my_bench`.
321+
#[test]
322+
fn create_uri_uses_threaded_codspeed_uri() {
323+
let id = BenchmarkId::new(
324+
"my_group".into(),
325+
Some("my_bench".into()),
326+
None,
327+
None,
328+
"my_group::my_bench".into(),
329+
);
330+
let (uri, bench_name) = create_uri_and_name(&id);
331+
assert_eq!(uri, "my_group::my_bench");
332+
assert_eq!(bench_name, "my_group::my_bench");
333+
}
334+
335+
#[test]
336+
fn create_uri_with_file_and_parameter() {
337+
let id = BenchmarkId::new(
338+
"my_group".into(),
339+
Some("parameterized".into()),
340+
Some("42".into()),
341+
None,
342+
"benches/custom_main.rs::my_group::parameterized[42]".into(),
343+
);
344+
let (uri, bench_name) = create_uri_and_name(&id);
345+
assert_eq!(uri, "benches/custom_main.rs::my_group::parameterized[42]");
346+
assert_eq!(bench_name, "my_group::parameterized[42]");
347+
}
348+
}
323349
}
324350

325351
fn base_dir_exists(id: &BenchmarkId, baseline: &str, output_directory: &Path) -> bool {

crates/criterion_compat/criterion_fork/src/benchmark_group.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::report::Report;
77
use crate::report::ReportContext;
88
use crate::routine::{Function, Routine};
99
use crate::{Bencher, Criterion, Mode, PlotConfiguration, SamplingMode, Throughput};
10+
use codspeed::utils::get_git_relative_path;
1011
use std::time::Duration;
1112

1213
/// Structure used to group together a set of related benchmarks, along with custom configuration
@@ -82,6 +83,8 @@ pub struct BenchmarkGroup<'a, M: Measurement> {
8283
any_matched: bool,
8384
partial_config: PartialBenchmarkConfig,
8485
throughput: Option<Throughput>,
86+
/// The file the benchmark group was created in, for CodSpeed URI generation.
87+
codspeed_current_file: String,
8588
}
8689
impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
8790
/// Changes the size of the sample for this benchmark
@@ -235,14 +238,19 @@ impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
235238
self
236239
}
237240

238-
pub(crate) fn new(criterion: &mut Criterion<M>, group_name: String) -> BenchmarkGroup<'_, M> {
241+
pub(crate) fn new(
242+
criterion: &mut Criterion<M>,
243+
group_name: String,
244+
codspeed_current_file: String,
245+
) -> BenchmarkGroup<'_, M> {
239246
BenchmarkGroup {
240247
criterion,
241248
group_name,
242249
all_ids: vec![],
243250
any_matched: false,
244251
partial_config: PartialBenchmarkConfig::default(),
245252
throughput: None,
253+
codspeed_current_file,
246254
}
247255
}
248256

@@ -270,6 +278,36 @@ impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
270278
self
271279
}
272280

281+
fn build_codspeed_uri(&self, id: &BenchmarkId) -> String {
282+
let mut uri = String::new();
283+
284+
if !self.codspeed_current_file.is_empty() {
285+
let git_relative_file_path = get_git_relative_path(&self.codspeed_current_file);
286+
let file_str = git_relative_file_path.to_string_lossy();
287+
if !file_str.is_empty() {
288+
uri.push_str(&file_str);
289+
}
290+
}
291+
if !self.criterion.macro_group.is_empty() {
292+
if !uri.is_empty() {
293+
uri.push_str("::");
294+
}
295+
uri.push_str(&self.criterion.macro_group);
296+
}
297+
if !uri.is_empty() {
298+
uri.push_str("::");
299+
}
300+
uri.push_str(&self.group_name);
301+
if let Some(function_name) = &id.function_name {
302+
uri = format!("{uri}::{function_name}");
303+
}
304+
if let Some(parameter) = &id.parameter {
305+
uri = format!("{uri}[{parameter}]");
306+
}
307+
308+
uri
309+
}
310+
273311
fn run_bench<F, I>(&mut self, id: BenchmarkId, input: &I, f: F)
274312
where
275313
F: FnMut(&mut Bencher<'_, M>, &I),
@@ -281,11 +319,13 @@ impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
281319
plot_config: self.partial_config.plot_config.clone(),
282320
};
283321

322+
let codspeed_uri = self.build_codspeed_uri(&id);
284323
let mut id = InternalBenchmarkId::new(
285324
self.group_name.clone(),
286325
id.function_name,
287326
id.parameter,
288327
self.throughput.clone(),
328+
codspeed_uri,
289329
);
290330

291331
assert!(

crates/criterion_compat/criterion_fork/src/connection.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ pub struct RawBenchmarkId {
293293
function_id: Option<String>,
294294
value_str: Option<String>,
295295
throughput: Vec<Throughput>,
296+
codspeed_uri: String,
296297
}
297298
impl From<&InternalBenchmarkId> for RawBenchmarkId {
298299
fn from(other: &InternalBenchmarkId) -> RawBenchmarkId {
@@ -301,6 +302,7 @@ impl From<&InternalBenchmarkId> for RawBenchmarkId {
301302
function_id: other.function_id.clone(),
302303
value_str: other.value_str.clone(),
303304
throughput: other.throughput.iter().cloned().collect(),
305+
codspeed_uri: other.codspeed_uri.clone(),
304306
}
305307
}
306308
}

crates/criterion_compat/criterion_fork/src/html/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ impl Report for Html {
460460

461461
if samples_with_function.len() > 1 {
462462
let subgroup_id =
463-
BenchmarkId::new(group_id.clone(), Some(function_id.clone()), None, None);
463+
BenchmarkId::new(group_id.clone(), Some(function_id.clone()), None, None, String::new());
464464

465465
self.generate_summary(
466466
&subgroup_id,
@@ -481,7 +481,7 @@ impl Report for Html {
481481

482482
if samples_with_value.len() > 1 {
483483
let subgroup_id =
484-
BenchmarkId::new(group_id.clone(), None, Some(value_str.clone()), None);
484+
BenchmarkId::new(group_id.clone(), None, Some(value_str.clone()), None, String::new());
485485

486486
self.generate_summary(&subgroup_id, &samples_with_value, context, formatter, false);
487487
}
@@ -509,7 +509,7 @@ impl Report for Html {
509509
all_data.sort_by_key(|(id, _)| id.function_id.as_ref());
510510

511511
self.generate_summary(
512-
&BenchmarkId::new(group_id, None, None, None),
512+
&BenchmarkId::new(group_id, None, None, None, String::new()),
513513
&all_data,
514514
context,
515515
formatter,

crates/criterion_compat/criterion_fork/src/lib.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,26 @@ mod codspeed {
398398
pub fn set_macro_group(&mut self, macro_group: impl Into<String>) {
399399
self.macro_group = macro_group.into();
400400
}
401+
402+
/// Returns the file the benchmark lives in, for CodSpeed URI generation.
403+
///
404+
/// Uses `current_file` when set explicitly (via `set_current_file`), otherwise derives
405+
/// it from the caller location. Derived per call so that benchmark functions living in
406+
/// different files don't inherit a stale file from a previous `benchmark_group` call.
407+
#[track_caller]
408+
pub(crate) fn codspeed_current_file(&self) -> String {
409+
if !self.current_file.is_empty() {
410+
return self.current_file.clone();
411+
}
412+
let caller = std::panic::Location::caller();
413+
match std::env::var("CODSPEED_CARGO_WORKSPACE_ROOT") {
414+
Ok(workspace_root) => std::path::PathBuf::from(workspace_root)
415+
.join(caller.file())
416+
.to_string_lossy()
417+
.into_owned(),
418+
Err(_) => caller.file().to_string(),
419+
}
420+
}
401421
}
402422
}
403423

@@ -1201,7 +1221,9 @@ https://bheisler.github.io/criterion.rs/book/faq.html
12011221
/// ```
12021222
/// # Panics:
12031223
/// Panics if the group name is empty
1224+
#[track_caller]
12041225
pub fn benchmark_group<S: Into<String>>(&mut self, group_name: S) -> BenchmarkGroup<'_, M> {
1226+
let codspeed_current_file = self.codspeed_current_file();
12051227
let group_name = group_name.into();
12061228
assert!(!group_name.is_empty(), "Group name must not be empty.");
12071229

@@ -1210,7 +1232,7 @@ https://bheisler.github.io/criterion.rs/book/faq.html
12101232
.unwrap();
12111233
}
12121234

1213-
BenchmarkGroup::new(self, group_name)
1235+
BenchmarkGroup::new(self, group_name, codspeed_current_file)
12141236
}
12151237
}
12161238
impl<M> Criterion<M>
@@ -1238,6 +1260,7 @@ where
12381260
/// criterion_group!(benches, bench);
12391261
/// criterion_main!(benches);
12401262
/// ```
1263+
#[track_caller]
12411264
pub fn bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion<M>
12421265
where
12431266
F: FnMut(&mut Bencher<'_, M>),
@@ -1270,6 +1293,7 @@ where
12701293
/// criterion_group!(benches, bench);
12711294
/// criterion_main!(benches);
12721295
/// ```
1296+
#[track_caller]
12731297
pub fn bench_with_input<F, I>(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion<M>
12741298
where
12751299
F: FnMut(&mut Bencher<'_, M>, &I),

crates/criterion_compat/criterion_fork/src/macros.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ macro_rules! criterion_group {
6969
.configure_from_args();
7070
$(
7171
if std::env::var("CODSPEED_ENV").is_ok() {
72-
criterion.set_current_file($crate::abs_file!());
7372
criterion.set_macro_group(format!("{}::{}", stringify!($name), stringify!($target)));
7473
}
7574
$target(&mut criterion);

crates/criterion_compat/criterion_fork/src/report.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ pub struct BenchmarkId {
6767
pub function_id: Option<String>,
6868
pub value_str: Option<String>,
6969
pub throughput: Option<Throughput>,
70+
#[serde(default)]
71+
pub codspeed_uri: String,
7072
full_id: String,
7173
directory_name: String,
7274
title: String,
@@ -114,6 +116,7 @@ impl BenchmarkId {
114116
function_id: Option<String>,
115117
value_str: Option<String>,
116118
throughput: Option<Throughput>,
119+
codspeed_uri: String,
117120
) -> BenchmarkId {
118121
let full_id = match (&function_id, &value_str) {
119122
(Some(func), Some(val)) => format!("{}/{}/{}", group_id, func, val),
@@ -153,6 +156,7 @@ impl BenchmarkId {
153156
function_id,
154157
value_str,
155158
throughput,
159+
codspeed_uri,
156160
full_id,
157161
directory_name,
158162
title,
@@ -823,6 +827,7 @@ mod test {
823827
Some("function".to_owned()),
824828
Some("value".to_owned()),
825829
None,
830+
String::new(),
826831
);
827832
let mut directories = HashSet::new();
828833
directories.insert(existing_id.as_directory_name().to_owned());
@@ -840,7 +845,7 @@ mod test {
840845
#[test]
841846
fn test_benchmark_id_make_long_directory_name_unique() {
842847
let long_name = (0..MAX_DIRECTORY_NAME_LEN).map(|_| 'a').collect::<String>();
843-
let existing_id = BenchmarkId::new(long_name, None, None, None);
848+
let existing_id = BenchmarkId::new(long_name, None, None, None, String::new());
844849
let mut directories = HashSet::new();
845850
directories.insert(existing_id.as_directory_name().to_owned());
846851

crates/criterion_compat/src/compat/criterion.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,27 @@ impl<M: Measurement> Criterion<M> {
122122

123123
#[track_caller]
124124
pub fn benchmark_group<S: Into<String>>(&mut self, group_name: S) -> BenchmarkGroup<M> {
125-
self.fill_missing_file_from_caller();
126-
BenchmarkGroup::<M>::new(self, group_name.into())
125+
let current_file = self.codspeed_current_file();
126+
BenchmarkGroup::<M>::new(self, group_name.into(), current_file)
127127
}
128128

129-
/// When `current_file` wasn't set by `criterion_group!`, derive it from the caller location.
129+
/// Returns the file the benchmark lives in, for CodSpeed URI generation.
130+
///
131+
/// Uses `current_file` when set explicitly (via `set_current_file`), otherwise derives
132+
/// it from the caller location. Derived per call so that benchmark functions living in
133+
/// different files don't inherit a stale file from a previous `benchmark_group` call.
130134
#[track_caller]
131-
fn fill_missing_file_from_caller(&mut self) {
135+
fn codspeed_current_file(&self) -> String {
132136
if !self.current_file.is_empty() {
133-
return;
137+
return self.current_file.clone();
134138
}
135139
let caller = std::panic::Location::caller();
136-
if let Ok(workspace_root) = std::env::var("CODSPEED_CARGO_WORKSPACE_ROOT") {
137-
self.current_file = std::path::PathBuf::from(workspace_root)
140+
match std::env::var("CODSPEED_CARGO_WORKSPACE_ROOT") {
141+
Ok(workspace_root) => std::path::PathBuf::from(workspace_root)
138142
.join(caller.file())
139143
.to_string_lossy()
140-
.into_owned();
141-
} else {
142-
self.current_file = caller.file().to_string();
144+
.into_owned(),
145+
Err(_) => caller.file().to_string(),
143146
}
144147
}
145148
}

0 commit comments

Comments
 (0)