Skip to content

Commit 2a33346

Browse files
committed
Add a build script to the site package that pre-loads compile benchmarks metadata
Use this preloaded metadata to find stable benchmarks.
1 parent 1ffe4ac commit 2a33346

File tree

8 files changed

+248
-50
lines changed

8 files changed

+248
-50
lines changed

Cargo.lock

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

collector/src/compile/benchmark/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn default_runs() -> usize {
2222
3
2323
}
2424

25-
#[derive(Debug, Default, Clone, serde::Deserialize)]
25+
#[derive(Debug, Default, PartialEq, Copy, Clone, serde::Deserialize)]
2626
#[serde(rename_all = "lowercase")]
2727
pub enum ArtifactType {
2828
Binary,
@@ -68,6 +68,10 @@ impl BenchmarkConfig {
6868
pub fn category(&self) -> Category {
6969
self.category
7070
}
71+
72+
pub fn artifact(&self) -> ArtifactType {
73+
self.artifact
74+
}
7175
}
7276

7377
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Hash)]

site/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ mime = "0.3"
4646
prometheus = "0.13"
4747
uuid = { version = "1.3.0", features = ["v4"] }
4848
tera = "1.18"
49-
rust-embed = { version = "6.6.0", features = ["include-exclude"] }
49+
rust-embed = { version = "6.6.0", features = ["include-exclude", "interpolate-folder-path"] }
5050

5151
[target.'cfg(unix)'.dependencies]
5252
jemallocator = "0.5"
@@ -58,3 +58,8 @@ path = "../collector"
5858
[dev-dependencies]
5959
lazy_static = "1"
6060
pretty_assertions = "1.3"
61+
62+
[build-dependencies]
63+
serde = { version = "1", features = ["derive"] }
64+
serde_json = "1"
65+
toml = "0.7"

site/build.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//! This build script goes through compile-benchmarks located in the `collector` crate
2+
//! and gathers some metadata from them (contents of perf-config.json, useful data out of
3+
//! Cargo.toml).
4+
//!
5+
//! It then serializes this data into a JSON, which is stored into the OUT_DIR.
6+
7+
use std::collections::HashMap;
8+
use std::error::Error;
9+
use std::path::PathBuf;
10+
11+
use crate::benchmark_metadata::{
12+
CompileBenchmarkMetadata, CompileBenchmarkSuite, ProfileMetadata, SERIALIZED_SUITE_NAME,
13+
};
14+
15+
#[path = "src/benchmark_metadata/metadata.rs"]
16+
mod benchmark_metadata;
17+
18+
fn main() -> Result<(), Box<dyn Error>> {
19+
let site_dir = PathBuf::from(&std::env::var("CARGO_MANIFEST_DIR")?);
20+
let root_dir = site_dir.parent().unwrap();
21+
let compile_benchmarks_dir = root_dir.join("collector/compile-benchmarks");
22+
assert!(
23+
compile_benchmarks_dir.is_dir(),
24+
"Compile benchmarks directory not found"
25+
);
26+
27+
println!(
28+
"cargo:rerun-if-changed={}",
29+
compile_benchmarks_dir.display()
30+
);
31+
32+
let mut suite = HashMap::new();
33+
34+
for compile_benchmark in std::fs::read_dir(compile_benchmarks_dir)? {
35+
let compile_benchmark = compile_benchmark?;
36+
if !compile_benchmark.file_type()?.is_dir() {
37+
continue;
38+
}
39+
40+
let benchmark_name = compile_benchmark
41+
.path()
42+
.file_name()
43+
.and_then(|n| n.to_str())
44+
.map(|s| s.to_string())
45+
.unwrap();
46+
47+
// Load perf-config.json
48+
let config_path = compile_benchmark.path().join("perf-config.json");
49+
let config_contents = std::fs::read_to_string(config_path)?;
50+
let config = serde_json::from_str::<serde_json::Value>(&config_contents)?;
51+
52+
// Load Cargo manifest to find profile information
53+
let manifest_path = compile_benchmark.path().join("Cargo.toml");
54+
let manifest_contents = std::fs::read_to_string(manifest_path)?;
55+
let manifest: toml::Value = toml::from_str(&manifest_contents)?;
56+
57+
let table = manifest.as_table().unwrap();
58+
let profile = table.get("profile");
59+
let release = profile
60+
.and_then(|p| p.as_table())
61+
.and_then(|t| t.get("release"))
62+
.and_then(|t| t.as_table());
63+
let debug = release.and_then(|t| t.get("debug"));
64+
let lto = release.and_then(|t| t.get("lto"));
65+
let codegen_units = release.and_then(|t| t.get("codegen-units"));
66+
67+
let metadata = CompileBenchmarkMetadata {
68+
perf_config: config,
69+
release_metadata: ProfileMetadata {
70+
debug: debug.map(|v| v.to_string()),
71+
lto: lto.map(|v| v.to_string()),
72+
codegen_units: codegen_units.and_then(|v| v.as_integer().map(|v| v as u32)),
73+
},
74+
};
75+
suite.insert(benchmark_name, metadata);
76+
}
77+
78+
// Write the serialized benchmarks metadata to OUT_DIR.
79+
let serialized = serde_json::to_string(&CompileBenchmarkSuite { benchmarks: suite })?;
80+
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
81+
std::fs::write(out_dir.join(SERIALIZED_SUITE_NAME), serialized)?;
82+
83+
Ok(())
84+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! This file is also included from the crate's build script.
2+
//! It contains "bare-bones" structs designed for easy (de)serialization,
3+
//! to minimize the dependencies of the build script.
4+
//!
5+
//! For example, the perf-config is stored simply as an opaque JSON, not as `BenchmarkConfig`,
6+
//! so that the build script doesn't have to depend on the `collector` crate.
7+
8+
use std::collections::HashMap;
9+
10+
/// File in `OUT_DIR` into which the JSON serialized metadata of compile benchmarks will be stored
11+
/// by the build script of this crate.
12+
pub const SERIALIZED_SUITE_NAME: &str = "compile-benchmarks.json";
13+
14+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
15+
pub struct ProfileMetadata {
16+
pub debug: Option<String>,
17+
pub lto: Option<String>,
18+
pub codegen_units: Option<u32>,
19+
}
20+
21+
#[derive(serde::Serialize, serde::Deserialize)]
22+
pub struct CompileBenchmarkMetadata {
23+
pub perf_config: serde_json::Value,
24+
pub release_metadata: ProfileMetadata,
25+
}
26+
27+
#[derive(serde::Serialize, serde::Deserialize)]
28+
pub struct CompileBenchmarkSuite {
29+
pub benchmarks: HashMap<String, CompileBenchmarkMetadata>,
30+
}

site/src/benchmark_metadata/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use hashbrown::HashMap;
2+
use rust_embed::RustEmbed;
3+
4+
use collector::compile::benchmark::category::Category;
5+
use collector::compile::benchmark::BenchmarkConfig;
6+
7+
use crate::benchmark_metadata::metadata::{ProfileMetadata, SERIALIZED_SUITE_NAME};
8+
9+
mod metadata;
10+
11+
#[derive(Debug, Clone)]
12+
pub struct CompileBenchmarkMetadata {
13+
pub perf_config: BenchmarkConfig,
14+
pub release_metadata: ProfileMetadata,
15+
}
16+
17+
/// The metadata of compile-time benchmarks is embedded directly within the binary using
18+
/// the `EmbeddedCompileBenchmarks` struct.
19+
#[derive(RustEmbed)]
20+
#[folder = "$OUT_DIR"]
21+
struct EmbeddedCompileBenchmarks;
22+
23+
pub fn get_compile_benchmarks_metadata() -> &'static HashMap<String, CompileBenchmarkMetadata> {
24+
lazy_static::lazy_static! {
25+
static ref METADATA: HashMap<String, CompileBenchmarkMetadata> = load_compile_benchmark_metadata();
26+
}
27+
&METADATA
28+
}
29+
30+
pub fn get_stable_benchmark_names() -> Vec<String> {
31+
get_compile_benchmarks_metadata()
32+
.iter()
33+
.filter(|(_, metadata)| metadata.perf_config.category() == Category::Stable)
34+
.map(|(key, _)| key.clone())
35+
.collect()
36+
}
37+
38+
fn load_compile_benchmark_metadata() -> HashMap<String, CompileBenchmarkMetadata> {
39+
let serialized = EmbeddedCompileBenchmarks::get(SERIALIZED_SUITE_NAME)
40+
.unwrap()
41+
.data
42+
.to_vec();
43+
let metadata: metadata::CompileBenchmarkSuite = serde_json::from_slice(&serialized)
44+
.expect("Cannot deserialize compile benchmarks metadata");
45+
metadata
46+
.benchmarks
47+
.into_iter()
48+
.map(|(name, metadata)| {
49+
let metadata::CompileBenchmarkMetadata {
50+
perf_config,
51+
release_metadata,
52+
} = metadata;
53+
let perf_config: BenchmarkConfig =
54+
serde_json::from_value(perf_config).expect("Cannot deserialize perf-config.json");
55+
56+
let metadata = CompileBenchmarkMetadata {
57+
perf_config,
58+
release_metadata,
59+
};
60+
(name, metadata)
61+
})
62+
.collect()
63+
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
use crate::benchmark_metadata::get_compile_benchmarks_metadata;
68+
69+
#[test]
70+
fn check_embedded_compile_benchmarks_metadata() {
71+
assert!(!get_compile_benchmarks_metadata().is_empty());
72+
}
73+
}

site/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod load;
1616
pub mod server;
1717

1818
mod average;
19+
mod benchmark_metadata;
1920
mod comparison;
2021
mod interpolate;
2122
mod request_handlers;

site/src/request_handlers/dashboard.rs

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
use collector::compile::benchmark::category::Category;
2-
use collector::compile::benchmark::BenchmarkConfig;
3-
use lazy_static::lazy_static;
4-
use rust_embed::RustEmbed;
5-
use std::path::Path;
61
use std::sync::Arc;
72

3+
use lazy_static::lazy_static;
4+
85
use crate::api::{dashboard, ServerResult};
6+
use crate::benchmark_metadata::get_stable_benchmark_names;
97
use crate::comparison::Metric;
108
use crate::db::{self, ArtifactId, Profile, Scenario};
119
use crate::load::SiteCtxt;
@@ -81,7 +79,7 @@ pub async fn handle_dashboard(ctxt: Arc<SiteCtxt>) -> ServerResult<dashboard::Re
8179
);
8280

8381
lazy_static! {
84-
static ref STABLE_BENCHMARKS: Vec<String> = get_stable_benchmarks();
82+
static ref STABLE_BENCHMARKS: Vec<String> = get_stable_benchmark_names();
8583
}
8684

8785
let query = selector::CompileBenchmarkQuery::default()
@@ -147,44 +145,6 @@ pub async fn handle_dashboard(ctxt: Arc<SiteCtxt>) -> ServerResult<dashboard::Re
147145
})
148146
}
149147

150-
#[derive(RustEmbed)]
151-
#[folder = "../collector/compile-benchmarks"]
152-
#[include = "*/perf-config.json"]
153-
struct EmbeddedBenchmarks;
154-
155-
/// The configurations of compile-time benchmarks are embedded directly within the binary using
156-
/// the `Benchmarks` struct.
157-
///
158-
/// Here we parse the benchmarks configurations and return only stable benchmarks.
159-
fn get_stable_benchmarks() -> Vec<String> {
160-
EmbeddedBenchmarks::iter()
161-
.filter_map(|path| EmbeddedBenchmarks::get(&path).map(|file| (file, path)))
162-
.filter_map(|(file, path)| {
163-
let config: BenchmarkConfig = match serde_json::from_slice(&file.data) {
164-
Ok(config) => config,
165-
Err(error) => {
166-
log::error!(
167-
"Cannot deserialized stored perf-config.json from {path}: {error:?}"
168-
);
169-
return None;
170-
}
171-
};
172-
if config.category() == Category::Stable {
173-
Some(
174-
Path::new(path.as_ref())
175-
.parent()
176-
.unwrap()
177-
.to_str()
178-
.unwrap()
179-
.to_string(),
180-
)
181-
} else {
182-
None
183-
}
184-
})
185-
.collect()
186-
}
187-
188148
pub struct ByProfile<T> {
189149
pub check: T,
190150
pub debug: T,

0 commit comments

Comments
 (0)