Skip to content

Commit 453f020

Browse files
ci: deserialize cargo bench result
1 parent 7999e17 commit 453f020

File tree

10 files changed

+264
-0
lines changed

10 files changed

+264
-0
lines changed

Cargo.lock

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

bench_tools/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,14 @@ workspace = true
88

99
[dependencies]
1010
clap = { workspace = true, features = ["derive"] }
11+
criterion.workspace = true
12+
serde = { workspace = true, features = ["derive"] }
13+
14+
[dev-dependencies]
15+
glob.workspace = true
16+
serde_json.workspace = true
17+
18+
[[bench]]
19+
harness = false
20+
name = "dummy_bench"
21+
path = "src/benches/dummy_bench.rs"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"mean": {
3+
"confidence_interval": {
4+
"confidence_level": 0.95,
5+
"lower_bound": 0.9280511380687992,
6+
"upper_bound": 0.9770326008358003
7+
},
8+
"point_estimate": 0.950567887590271,
9+
"standard_error": 0.012543975607897394
10+
},
11+
"median": {
12+
"confidence_interval": {
13+
"confidence_level": 0.95,
14+
"lower_bound": 0.8938102469877969,
15+
"upper_bound": 0.9104509947667936
16+
},
17+
"point_estimate": 0.9035953146446528,
18+
"standard_error": 0.004791521022015121
19+
},
20+
"median_abs_dev": {
21+
"confidence_interval": {
22+
"confidence_level": 0.95,
23+
"lower_bound": 0.020115181011407187,
24+
"upper_bound": 0.046529428364116145
25+
},
26+
"point_estimate": 0.035187421707573024,
27+
"standard_error": 0.007212517355406062
28+
},
29+
"slope": {
30+
"confidence_interval": {
31+
"confidence_level": 0.95,
32+
"lower_bound": 0.9570826102260792,
33+
"upper_bound": 1.054776136887248
34+
},
35+
"point_estimate": 1.003565137518582,
36+
"standard_error": 0.02512230790903042
37+
},
38+
"std_dev": {
39+
"confidence_interval": {
40+
"confidence_level": 0.95,
41+
"lower_bound": 0.07004563729649199,
42+
"upper_bound": 0.16991881743641407
43+
},
44+
"point_estimate": 0.12590223948952453,
45+
"standard_error": 0.024908952526773723
46+
}
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"mean": {
3+
"confidence_interval": {
4+
"confidence_level": 0.95,
5+
"lower_bound": 0.9142287240580292,
6+
"upper_bound": 0.9462306685839782
7+
},
8+
"point_estimate": 0.9285105039182452,
9+
"standard_error": 0.008165206850548515
10+
},
11+
"median": {
12+
"confidence_interval": {
13+
"confidence_level": 0.95,
14+
"lower_bound": 0.893664924087692,
15+
"upper_bound": 0.9134582623157296
16+
},
17+
"point_estimate": 0.9007806648132172,
18+
"standard_error": 0.005255178148246822
19+
},
20+
"median_abs_dev": {
21+
"confidence_interval": {
22+
"confidence_level": 0.95,
23+
"lower_bound": 0.014930366068731485,
24+
"upper_bound": 0.03712113624827432
25+
},
26+
"point_estimate": 0.02469204064276661,
27+
"standard_error": 0.006102570427001133
28+
},
29+
"slope": {
30+
"confidence_interval": {
31+
"confidence_level": 0.95,
32+
"lower_bound": 0.9148087731810076,
33+
"upper_bound": 0.9545184104032152
34+
},
35+
"point_estimate": 0.9316599097782416,
36+
"standard_error": 0.010133884941484783
37+
},
38+
"std_dev": {
39+
"confidence_interval": {
40+
"confidence_level": 0.95,
41+
"lower_bound": 0.04309489165021185,
42+
"upper_bound": 0.1199166738579459
43+
},
44+
"point_estimate": 0.08199702437151206,
45+
"standard_error": 0.020739973590370026
46+
}
47+
}

bench_tools/src/benches.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[cfg(test)]
2+
pub(crate) mod dummy_bench;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
3+
#[allow(dead_code)]
4+
fn dummy_function(n: u64) -> u64 {
5+
// Simple function that does some work
6+
(0..n).sum()
7+
}
8+
9+
#[allow(dead_code)]
10+
fn dummy_benchmark(c: &mut Criterion) {
11+
c.bench_function("dummy_sum_100", |b| b.iter(|| dummy_function(black_box(100))));
12+
13+
c.bench_function("dummy_sum_1000", |b| b.iter(|| dummy_function(black_box(1000))));
14+
}
15+
16+
criterion_group!(benches, dummy_benchmark);
17+
criterion_main!(benches);

bench_tools/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#[cfg(test)]
2+
pub(crate) mod benches;
3+
pub mod types;

bench_tools/src/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod estimates;
2+
#[cfg(test)]
3+
mod estimates_test;

bench_tools/src/types/estimates.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use serde::Deserialize;
2+
3+
/// Criterion benchmark estimates.
4+
#[derive(Debug, Deserialize)]
5+
#[allow(dead_code)]
6+
pub struct Estimates {
7+
pub mean: Stat,
8+
pub median: Stat,
9+
pub std_dev: Stat,
10+
pub median_abs_dev: Stat,
11+
pub slope: Option<Stat>,
12+
}
13+
14+
/// Statistical estimate with confidence interval.
15+
#[derive(Debug, Deserialize)]
16+
#[allow(dead_code)]
17+
pub struct Stat {
18+
pub point_estimate: f64,
19+
pub standard_error: f64,
20+
pub confidence_interval: ConfidenceInterval,
21+
}
22+
23+
/// Confidence interval bounds.
24+
#[derive(Debug, Deserialize)]
25+
#[allow(dead_code)]
26+
pub struct ConfidenceInterval {
27+
pub confidence_level: f64,
28+
pub lower_bound: f64,
29+
pub upper_bound: f64,
30+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::fs;
2+
use std::path::PathBuf;
3+
use std::process::Command;
4+
5+
use glob::glob;
6+
7+
use crate::types::estimates::Estimates;
8+
9+
#[test]
10+
#[ignore]
11+
/// Run dummy benchmark and deserialize the results.
12+
fn run_dummy_bench_and_deserialize_all_estimates() {
13+
let workspace_root = std::env::var("CARGO_MANIFEST_DIR")
14+
.map(PathBuf::from)
15+
.unwrap_or_else(|_| std::env::current_dir().unwrap())
16+
.parent()
17+
.unwrap()
18+
.to_path_buf();
19+
20+
let data_dir = workspace_root.join("bench_tools/data/dummy_benches_result");
21+
22+
// 1) Run dummy benchmark.
23+
let status = Command::new("cargo")
24+
.args(["bench", "-p", "bench_tools", "--bench", "dummy_bench"])
25+
.status()
26+
.expect("Failed to spawn `cargo bench`");
27+
assert!(status.success(), "`cargo bench` did not exit successfully");
28+
29+
// 2) Collect ALL estimates.json files under target/criterion/**/new/
30+
let patterns =
31+
vec!["target/criterion/**/new/estimates.json", "../target/criterion/**/new/estimates.json"];
32+
33+
let mut files: Vec<PathBuf> = Vec::new();
34+
for pattern in &patterns {
35+
for path in
36+
glob(pattern).unwrap_or_else(|_| panic!("Invalid glob pattern: {}", pattern)).flatten()
37+
{
38+
files.push(path);
39+
}
40+
}
41+
assert!(!files.is_empty(), "No Criterion results found; did any benches run?");
42+
43+
// 3) Save results to bench_tools/data.
44+
fs::create_dir_all(&data_dir).expect("Failed to create data directory");
45+
for path in &files {
46+
if let Some(filename) = path.file_name() {
47+
let bench_name = path
48+
.parent()
49+
.and_then(|p| p.parent())
50+
.and_then(|p| p.file_name())
51+
.and_then(|n| n.to_str())
52+
.unwrap_or("unknown");
53+
let dest = data_dir.join(format!("{}_{}", bench_name, filename.to_str().unwrap()));
54+
55+
// Read, parse, and write the result to the data directory.
56+
let data = fs::read_to_string(path).expect("Failed to read benchmark result");
57+
let json: serde_json::Value =
58+
serde_json::from_str(&data).expect("Failed to parse JSON");
59+
let pretty_json =
60+
serde_json::to_string_pretty(&json).expect("Failed to serialize JSON");
61+
fs::write(&dest, pretty_json).expect("Failed to write benchmark result");
62+
}
63+
}
64+
65+
// 4) Deserialize and validate the saved results
66+
deserialize_estimates();
67+
}
68+
69+
#[test]
70+
/// Test that Estimates can be deserialized from the saved results.
71+
fn deserialize_estimates() {
72+
let workspace_root = std::env::var("CARGO_MANIFEST_DIR")
73+
.map(PathBuf::from)
74+
.unwrap_or_else(|_| std::env::current_dir().unwrap())
75+
.parent()
76+
.unwrap()
77+
.to_path_buf();
78+
79+
let data_dir = workspace_root.join("bench_tools/data/dummy_benches_result");
80+
81+
// Collect all JSON files in the data directory
82+
let pattern = data_dir.join("*.json");
83+
let mut files: Vec<PathBuf> = Vec::new();
84+
85+
for path in glob(pattern.to_str().unwrap()).expect("Invalid glob pattern").flatten() {
86+
files.push(path);
87+
}
88+
89+
assert!(!files.is_empty(), "No JSON files found in {}", data_dir.display());
90+
91+
// Deserialize each file in the data directory.
92+
for path in &files {
93+
let data = fs::read_to_string(path)
94+
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path.display(), e));
95+
96+
let _est: Estimates = serde_json::from_str(&data).unwrap_or_else(|e| {
97+
panic!("Failed to deserialize {}: {}\nContent: {}", path.display(), e, data)
98+
});
99+
}
100+
}

0 commit comments

Comments
 (0)