Skip to content

Commit cb64224

Browse files
ci: run benchmark command
1 parent befb523 commit cb64224

File tree

7 files changed

+132
-3
lines changed

7 files changed

+132
-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

1416
[dev-dependencies]
1517
apollo_infra_utils.workspace = true

crates/bench_tools/src/gcs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub fn download_inputs(benchmark_name: &str, local_input_dir: &Path) {
5757
let output = Command::new("gcloud")
5858
.args(["storage", "cp", "-r", &source, &dest])
5959
.output()
60-
.expect("Failed to cp inputs from GCS");
60+
.unwrap_or_else(|e| panic!("Failed to cp inputs from GCS: {}", e));
6161

6262
if !output.status.success() {
6363
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
@@ -3,6 +3,7 @@ pub(crate) mod benches;
33
pub mod gcs;
44
#[cfg(test)]
55
pub mod gcs_test;
6+
pub mod runner;
67
#[cfg(test)]
78
pub mod test_utils;
89
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 {
@@ -46,8 +50,14 @@ enum Commands {
4650
fn main() {
4751
let cli = Cli::parse();
4852
match cli.command {
49-
Commands::Run { package: _, out: _ } => {
50-
unimplemented!()
53+
Commands::Run { package, out, input_dir } => {
54+
let benchmarks = find_benchmarks_by_package(&package);
55+
56+
if benchmarks.is_empty() {
57+
panic!("No benchmarks found for package: {}", package);
58+
}
59+
60+
bench_tools::runner::run_benchmarks(&benchmarks, input_dir.as_deref(), &out);
5161
}
5262
Commands::List { package } => match package {
5363
Some(package_name) => {

crates/bench_tools/src/runner.rs

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

0 commit comments

Comments
 (0)