Skip to content

Commit 78e8d81

Browse files
committed
Add command to benchlib to profile a single benchmark
1 parent 65eb109 commit 78e8d81

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed

collector/benchlib/src/benchmark.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use crate::cli::{parse_cli, Args, BenchmarkArgs};
1+
use crate::cli::{parse_cli, Args, BenchmarkArgs, ProfileArgs};
22
use crate::comm::messages::{BenchmarkMessage, BenchmarkResult, BenchmarkStats};
33
use crate::comm::output_message;
44
use crate::measure::benchmark_function;
55
use crate::process::raise_process_priority;
6+
use crate::profile::profile_function;
67
use std::collections::HashMap;
8+
use std::rc::Rc;
79

810
/// Create and run a new benchmark group. Use the closure argument to register
911
/// the individual benchmarks.
@@ -18,12 +20,21 @@ where
1820
group.run().expect("Benchmark group execution has failed");
1921
}
2022

21-
/// Type-erased function that executes a single benchmark.
23+
/// Type-erased function that executes a single benchmark and measures counter and wall-time
24+
/// metrics.
2225
type BenchmarkFn<'a> = Box<dyn Fn() -> anyhow::Result<BenchmarkStats> + 'a>;
2326

27+
/// Type-erased function that executes a single benchmark once.
28+
type ProfileFn<'a> = Box<dyn Fn() + 'a>;
29+
30+
struct BenchmarkFunctions<'a> {
31+
benchmark_fn: BenchmarkFn<'a>,
32+
profile_fn: ProfileFn<'a>,
33+
}
34+
2435
#[derive(Default)]
2536
pub struct BenchmarkGroup<'a> {
26-
benchmarks: HashMap<&'static str, BenchmarkFn<'a>>,
37+
benchmarks: HashMap<&'static str, BenchmarkFunctions<'a>>,
2738
}
2839

2940
impl<'a> BenchmarkGroup<'a> {
@@ -40,8 +51,13 @@ impl<'a> BenchmarkGroup<'a> {
4051
Bench: FnOnce() -> R,
4152
{
4253
// We want to type-erase the target `func` by wrapping it in a Box.
43-
let benchmark_fn = Box::new(move || benchmark_function(&constructor));
44-
if self.benchmarks.insert(name, benchmark_fn).is_some() {
54+
let constructor = Rc::new(constructor);
55+
let constructor2 = constructor.clone();
56+
let benchmark_fns = BenchmarkFunctions {
57+
benchmark_fn: Box::new(move || benchmark_function(constructor.as_ref())),
58+
profile_fn: Box::new(move || profile_function(constructor2.as_ref())),
59+
};
60+
if self.benchmarks.insert(name, benchmark_fns).is_some() {
4561
panic!("Benchmark '{}' was registered twice", name);
4662
}
4763
}
@@ -56,14 +72,15 @@ impl<'a> BenchmarkGroup<'a> {
5672
Args::Run(args) => {
5773
self.run_benchmarks(args)?;
5874
}
75+
Args::Profile(args) => self.profile_benchmark(args)?,
5976
Args::List => self.list_benchmarks()?,
6077
}
6178

6279
Ok(())
6380
}
6481

6582
fn run_benchmarks(self, args: BenchmarkArgs) -> anyhow::Result<()> {
66-
let mut items: Vec<(&'static str, BenchmarkFn)> = self
83+
let mut items: Vec<(&'static str, BenchmarkFunctions)> = self
6784
.benchmarks
6885
.into_iter()
6986
.filter(|(name, _)| {
@@ -74,17 +91,17 @@ impl<'a> BenchmarkGroup<'a> {
7491

7592
let mut stdout = std::io::stdout().lock();
7693

77-
for (name, benchmark_fn) in items {
94+
for (name, benchmark_fns) in items {
7895
let mut stats: Vec<BenchmarkStats> = Vec::with_capacity(args.iterations as usize);
7996
// Warm-up
8097
for _ in 0..3 {
81-
let benchmark_stats = benchmark_fn()?;
98+
let benchmark_stats = (benchmark_fns.benchmark_fn)()?;
8299
black_box(benchmark_stats);
83100
}
84101

85102
// Actual measurement
86103
for i in 0..args.iterations {
87-
let benchmark_stats = benchmark_fn()?;
104+
let benchmark_stats = (benchmark_fns.benchmark_fn)()?;
88105
log::info!("Benchmark (run {i}) `{name}` completed: {benchmark_stats:?}");
89106
stats.push(benchmark_stats);
90107
}
@@ -100,6 +117,16 @@ impl<'a> BenchmarkGroup<'a> {
100117
Ok(())
101118
}
102119

120+
fn profile_benchmark(self, args: ProfileArgs) -> anyhow::Result<()> {
121+
let Some(benchmark) = self.benchmarks.get(args.benchmark.as_str()) else {
122+
return Err(anyhow::anyhow!("Benchmark `{}` not found. Available benchmarks: {}", args.benchmark,
123+
self.benchmarks.keys().map(|s| s.to_string()).collect::<Vec<_>>().join(", ")));
124+
};
125+
(benchmark.profile_fn)();
126+
127+
Ok(())
128+
}
129+
103130
fn list_benchmarks(self) -> anyhow::Result<()> {
104131
let benchmark_list: Vec<&str> = self.benchmarks.into_keys().collect();
105132
serde_json::to_writer(std::io::stdout(), &benchmark_list)?;

collector/benchlib/src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use clap::{CommandFactory, FromArgMatches};
44
pub enum Args {
55
/// Benchmark all benchmarks in this benchmark group and print the results as JSON.
66
Run(BenchmarkArgs),
7+
/// Profile a single benchmark execution.
8+
Profile(ProfileArgs),
79
/// List benchmarks that are defined in the current group as a JSON array.
810
List,
911
}
@@ -23,6 +25,12 @@ pub struct BenchmarkArgs {
2325
pub include: Option<String>,
2426
}
2527

28+
#[derive(clap::Parser, Debug)]
29+
pub struct ProfileArgs {
30+
/// Name of the benchmark that should be profiled.
31+
pub benchmark: String,
32+
}
33+
2634
#[test]
2735
fn verify_cli() {
2836
// By default, clap lazily checks subcommands. This provides eager testing

collector/benchlib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod cli;
1818
pub mod comm;
1919
pub mod measure;
2020
pub mod process;
21+
mod profile;
2122
mod utils;
2223

2324
#[cfg(feature = "compression")]

collector/benchlib/src/profile.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub fn profile_function<F: Fn() -> Bench, R, Bench: FnOnce() -> R>(benchmark_constructor: &F) {
2+
let func = benchmark_constructor();
3+
func();
4+
}

0 commit comments

Comments
 (0)