Skip to content

Commit 105f5d2

Browse files
ci: run benchmark command
1 parent e8bee92 commit 105f5d2

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

bench_tools/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ workspace = true
1010
clap = { workspace = true, features = ["derive"] }
1111
criterion.workspace = true
1212
serde = { workspace = true, features = ["derive"] }
13+
serde_json.workspace = true
1314
tokio = { workspace = true, features = ["full"] }
1415

1516
[dev-dependencies]

bench_tools/src/gcs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub async fn download_inputs(benchmark_name: &str, output_dir: &Path) {
5858
.args(["storage", "cp", "-r", &source, &dest])
5959
.output()
6060
.await
61-
.expect("Failed to cp inputs from GCS");
61+
.unwrap_or_else(|e| panic!("Failed to cp inputs from GCS: {}", e));
6262

6363
if !output.status.success() {
6464
let stderr = String::from_utf8_lossy(&output.stderr);

bench_tools/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#[cfg(test)]
22
pub(crate) mod benches;
33
pub mod gcs;
4+
pub mod runner;
45
pub mod types;

bench_tools/src/main.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ enum Commands {
2525
/// Output directory for results.
2626
#[arg(short, long)]
2727
out: String,
28+
/// Optional: Local directory containing input files. If not provided, inputs will be
29+
/// downloaded from GCS for benchmarks that require them.
30+
#[arg(long)]
31+
input_dir: Option<String>,
2832
},
2933
/// List benchmarks for a package.
3034
List {
@@ -47,8 +51,14 @@ enum Commands {
4751
async fn main() {
4852
let cli = Cli::parse();
4953
match cli.command {
50-
Commands::Run { package: _, out: _ } => {
51-
unimplemented!()
54+
Commands::Run { package, out, input_dir } => {
55+
let benchmarks = find_benchmarks_by_package(&package);
56+
57+
if benchmarks.is_empty() {
58+
panic!("No benchmarks found for package: {}", package);
59+
}
60+
61+
bench_tools::runner::run_benchmarks(&benchmarks, input_dir.as_deref(), &out).await;
5262
}
5363
Commands::List { package } => match package {
5464
Some(package_name) => {

bench_tools/src/runner.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::path::PathBuf;
2+
3+
use tokio::fs;
4+
5+
use crate::gcs;
6+
use crate::types::benchmark_config::BenchmarkConfig;
7+
8+
/// Prepares inputs for a benchmark.
9+
/// If the benchmark needs inputs and a local input directory is provided,
10+
/// it verifies the directory exists.
11+
/// If the benchmark needs inputs and no local input directory is provided,
12+
/// it downloads the inputs from GCS.
13+
async fn prepare_inputs(bench: &BenchmarkConfig, input_dir: Option<&str>) {
14+
if !bench.needs_inputs() {
15+
return;
16+
}
17+
18+
if let Some(local_dir) = input_dir {
19+
let local_path = PathBuf::from(local_dir);
20+
if !local_path.exists() {
21+
panic!("Input directory does not exist: {}", local_dir);
22+
}
23+
} else {
24+
let target = PathBuf::from(bench.input_dir.unwrap());
25+
gcs::download_inputs(bench.name, &target).await;
26+
if !target.exists() {
27+
panic!("Failed to prepare inputs for {}", bench.name);
28+
}
29+
}
30+
}
31+
32+
/// Runs a single benchmark and panic if it fails.
33+
async fn run_single_benchmark(bench: &BenchmarkConfig) {
34+
println!("Running: {}", bench.name);
35+
36+
let output = tokio::process::Command::new("cargo")
37+
.args(bench.cmd_args)
38+
.output()
39+
.await
40+
.unwrap_or_else(|e| panic!("Failed to execute {}: {}", bench.name, e));
41+
42+
if !output.status.success() {
43+
panic!("\nBenchmark {} failed:\n{}", bench.name, String::from_utf8_lossy(&output.stderr));
44+
}
45+
}
46+
47+
/// Collects benchmark results from criterion output and saves them to the output directory.
48+
async fn save_benchmark_results(_bench: &BenchmarkConfig, output_dir: &str) {
49+
let criterion_base = PathBuf::from("target/criterion");
50+
let Ok(mut entries) = fs::read_dir(&criterion_base).await else { return };
51+
52+
// Collect all estimates files.
53+
while let Ok(Some(entry)) = entries.next_entry().await {
54+
let path = entry.path();
55+
if !path.is_dir() {
56+
continue;
57+
}
58+
59+
// Save estimates file.
60+
let estimates_path = path.join("new/estimates.json");
61+
if let Ok(data) = fs::read_to_string(&estimates_path).await {
62+
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&data) {
63+
if let Ok(pretty) = serde_json::to_string_pretty(&json) {
64+
let bench_name = path.file_name().unwrap().to_string_lossy();
65+
let dest =
66+
PathBuf::from(output_dir).join(format!("{}_estimates.json", bench_name));
67+
if fs::write(&dest, pretty).await.is_ok() {
68+
println!("Saved results: {}", dest.display());
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
/// Runs benchmarks for a given package, handling input downloads if needed.
77+
pub async fn run_benchmarks(
78+
benchmarks: &[&BenchmarkConfig],
79+
input_dir: Option<&str>,
80+
output_dir: &str,
81+
) {
82+
// Prepare inputs
83+
for bench in benchmarks {
84+
prepare_inputs(bench, input_dir).await;
85+
}
86+
87+
// Create output directory
88+
tokio::fs::create_dir_all(output_dir)
89+
.await
90+
.unwrap_or_else(|e| panic!("Failed to create output dir: {}", e));
91+
92+
// Run benchmarks
93+
for bench in benchmarks {
94+
run_single_benchmark(bench).await;
95+
save_benchmark_results(bench, output_dir).await;
96+
}
97+
98+
println!("\n✓ All benchmarks completed! Results saved to: {}", output_dir);
99+
}

0 commit comments

Comments
 (0)