Skip to content

Commit c45a008

Browse files
ci: run benchmark command
1 parent f388830 commit c45a008

File tree

7 files changed

+136
-3
lines changed

7 files changed

+136
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ ethnum = "1.5.0"
253253
expect-test = "1.5.1"
254254
flate2 = "1.0.24"
255255
fs2 = "0.4"
256+
fs_extra = "1.3.0"
256257
futures = "0.3.21"
257258
futures-channel = "0.3.21"
258259
futures-util = "0.3.21"

crates/bench_tools/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ workspace = true
99
[dependencies]
1010
clap = { workspace = true, features = ["derive"] }
1111
criterion.workspace = true
12+
fs_extra.workspace = true
1213
serde = { workspace = true, features = ["derive"] }
14+
serde_json.workspace = true
1315
tokio = { workspace = true, features = ["full"] }
1416

1517
[dev-dependencies]

crates/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);

crates/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;

crates/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) => {

crates/bench_tools/src/runner.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 copies the contents from the local directory to the expected input location.
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+
let benchmark_input_dir = PathBuf::from(bench.input_dir.unwrap());
19+
20+
// Create the input directory if it doesn't exist.
21+
fs::create_dir_all(&benchmark_input_dir).await.unwrap_or_else(|e| {
22+
panic!("Failed to create directory {}: {}", benchmark_input_dir.display(), e)
23+
});
24+
25+
if let Some(local_dir) = input_dir {
26+
let local_path = PathBuf::from(local_dir);
27+
if !local_path.exists() {
28+
panic!("Input directory does not exist: {}", local_dir);
29+
}
30+
31+
// Copy local directory contents to the benchmark input directory.
32+
let options = fs_extra::dir::CopyOptions::new().content_only(true);
33+
fs_extra::dir::copy(&local_path, &benchmark_input_dir, &options).unwrap_or_else(|e| {
34+
panic!(
35+
"Failed to copy inputs from {} to {}: {}",
36+
local_dir,
37+
benchmark_input_dir.display(),
38+
e
39+
)
40+
});
41+
42+
println!("Copied inputs from {} to {}", local_dir, benchmark_input_dir.display());
43+
} else {
44+
gcs::download_inputs(bench.name, &benchmark_input_dir).await;
45+
if !benchmark_input_dir.exists() {
46+
panic!("Failed to download inputs for {}: {}", bench.name, benchmark_input_dir.display());
47+
}
48+
}
49+
}
50+
51+
/// Runs a single benchmark and panic if it fails.
52+
async fn run_single_benchmark(bench: &BenchmarkConfig) {
53+
println!("Running: {}", bench.name);
54+
55+
let output = tokio::process::Command::new("cargo")
56+
.args(bench.cmd_args)
57+
.output()
58+
.await
59+
.unwrap_or_else(|e| panic!("Failed to execute {}: {}", bench.name, e));
60+
61+
if !output.status.success() {
62+
panic!("\nBenchmark {} failed:\n{}", bench.name, String::from_utf8_lossy(&output.stderr));
63+
}
64+
}
65+
66+
/// Collects benchmark results from criterion output and saves them to the output directory.
67+
async fn save_benchmark_results(_bench: &BenchmarkConfig, output_dir: &str) {
68+
let criterion_base = PathBuf::from("target/criterion");
69+
let Ok(mut entries) = fs::read_dir(&criterion_base).await else { return };
70+
71+
// Collect all estimates files.
72+
while let Ok(Some(entry)) = entries.next_entry().await {
73+
let path = entry.path();
74+
if !path.is_dir() {
75+
continue;
76+
}
77+
78+
// Save estimates file.
79+
let estimates_path = path.join("new/estimates.json");
80+
if let Ok(data) = fs::read_to_string(&estimates_path).await {
81+
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&data) {
82+
if let Ok(pretty) = serde_json::to_string_pretty(&json) {
83+
let bench_name = path.file_name().unwrap().to_string_lossy();
84+
let dest =
85+
PathBuf::from(output_dir).join(format!("{}_estimates.json", bench_name));
86+
if fs::write(&dest, pretty).await.is_ok() {
87+
println!("Saved results: {}", dest.display());
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
95+
/// Runs benchmarks for a given package, handling input downloads if needed.
96+
pub async fn run_benchmarks(
97+
benchmarks: &[&BenchmarkConfig],
98+
input_dir: Option<&str>,
99+
output_dir: &str,
100+
) {
101+
// Prepare inputs.
102+
for bench in benchmarks {
103+
prepare_inputs(bench, input_dir).await;
104+
}
105+
106+
// Create output directory.
107+
tokio::fs::create_dir_all(output_dir)
108+
.await
109+
.unwrap_or_else(|e| panic!("Failed to create output dir: {}", e));
110+
111+
// Run benchmarks.
112+
for bench in benchmarks {
113+
run_single_benchmark(bench).await;
114+
save_benchmark_results(bench, output_dir).await;
115+
}
116+
117+
println!("\n✓ All benchmarks completed! Results saved to: {}", output_dir);
118+
}

0 commit comments

Comments
 (0)