Skip to content

Commit 859068c

Browse files
Rollup merge of #111936 - ferrocene:pa-test-suite-metadata, r=jyn514
Include test suite metadata in the build metrics This PR enhances the build metadata to include structured information about the test suites being executed, allowing external tools consuming the metadata to understand what was being tested. The included metadata is: * Target triple * Host triple * Stage number * For compiletest tests: * Suite name * Mode * Comparing mode * For crate tests: * List of crate names This is implemented by replacing the `test` JSON node with a new `test_suite` node, which contains the metadata and the list of tests. This change also improves the handling of multiple test suites executed in the same step (for example in compiletest tests with a compare mode), as the multiple test suite executions will now be tracked in separate `test_suite` nodes. This included a breaking change in the build metrics metadata format. To better handle this, in the second commit this PR introduces the `metadata_version` top-level field. The old version is considered to be `0`, while the new one `1`. Bootstrap will also gracefully handle existing metadata of a different version. r? `@jyn514`
2 parents a525c7d + c5139b9 commit 859068c

File tree

2 files changed

+144
-24
lines changed

2 files changed

+144
-24
lines changed

src/bootstrap/metrics.rs

+95-24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ use std::io::BufWriter;
1414
use std::time::{Duration, Instant, SystemTime};
1515
use sysinfo::{CpuExt, System, SystemExt};
1616

17+
// Update this number whenever a breaking change is made to the build metrics.
18+
//
19+
// The output format is versioned for two reasons:
20+
//
21+
// - The metadata is intended to be consumed by external tooling, and exposing a format version
22+
// helps the tools determine whether they're compatible with a metrics file.
23+
//
24+
// - If a developer enables build metrics in their local checkout, making a breaking change to the
25+
// metrics format would result in a hard-to-diagnose error message when an existing metrics file
26+
// is not compatible with the new changes. With a format version number, bootstrap can discard
27+
// incompatible metrics files instead of appending metrics to them.
28+
//
29+
// Version changelog:
30+
//
31+
// - v0: initial version
32+
// - v1: replaced JsonNode::Test with JsonNode::TestSuite
33+
//
34+
const CURRENT_FORMAT_VERSION: usize = 1;
35+
1736
pub(crate) struct BuildMetrics {
1837
state: RefCell<MetricsState>,
1938
}
@@ -57,7 +76,7 @@ impl BuildMetrics {
5776
duration_excluding_children_sec: Duration::ZERO,
5877

5978
children: Vec::new(),
60-
tests: Vec::new(),
79+
test_suites: Vec::new(),
6180
});
6281
}
6382

@@ -84,19 +103,31 @@ impl BuildMetrics {
84103
}
85104
}
86105

106+
pub(crate) fn begin_test_suite(&self, metadata: TestSuiteMetadata, builder: &Builder<'_>) {
107+
// Do not record dry runs, as they'd be duplicates of the actual steps.
108+
if builder.config.dry_run() {
109+
return;
110+
}
111+
112+
let mut state = self.state.borrow_mut();
113+
let step = state.running_steps.last_mut().unwrap();
114+
step.test_suites.push(TestSuite { metadata, tests: Vec::new() });
115+
}
116+
87117
pub(crate) fn record_test(&self, name: &str, outcome: TestOutcome, builder: &Builder<'_>) {
88118
// Do not record dry runs, as they'd be duplicates of the actual steps.
89119
if builder.config.dry_run() {
90120
return;
91121
}
92122

93123
let mut state = self.state.borrow_mut();
94-
state
95-
.running_steps
96-
.last_mut()
97-
.unwrap()
98-
.tests
99-
.push(Test { name: name.to_string(), outcome });
124+
let step = state.running_steps.last_mut().unwrap();
125+
126+
if let Some(test_suite) = step.test_suites.last_mut() {
127+
test_suite.tests.push(Test { name: name.to_string(), outcome });
128+
} else {
129+
panic!("metrics.record_test() called without calling metrics.begin_test_suite() first");
130+
}
100131
}
101132

102133
fn collect_stats(&self, state: &mut MetricsState) {
@@ -131,7 +162,20 @@ impl BuildMetrics {
131162
// Some of our CI builds consist of multiple independent CI invocations. Ensure all the
132163
// previous invocations are still present in the resulting file.
133164
let mut invocations = match std::fs::read(&dest) {
134-
Ok(contents) => t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations,
165+
Ok(contents) => {
166+
// We first parse just the format_version field to have the check succeed even if
167+
// the rest of the contents are not valid anymore.
168+
let version: OnlyFormatVersion = t!(serde_json::from_slice(&contents));
169+
if version.format_version == CURRENT_FORMAT_VERSION {
170+
t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations
171+
} else {
172+
println!(
173+
"warning: overriding existing build/metrics.json, as it's not \
174+
compatible with build metrics format version {CURRENT_FORMAT_VERSION}."
175+
);
176+
Vec::new()
177+
}
178+
}
135179
Err(err) => {
136180
if err.kind() != std::io::ErrorKind::NotFound {
137181
panic!("failed to open existing metrics file at {}: {err}", dest.display());
@@ -149,7 +193,7 @@ impl BuildMetrics {
149193
children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(),
150194
});
151195

152-
let json = JsonRoot { system_stats, invocations };
196+
let json = JsonRoot { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations };
153197

154198
t!(std::fs::create_dir_all(dest.parent().unwrap()));
155199
let mut file = BufWriter::new(t!(File::create(&dest)));
@@ -159,11 +203,7 @@ impl BuildMetrics {
159203
fn prepare_json_step(&self, step: StepMetrics) -> JsonNode {
160204
let mut children = Vec::new();
161205
children.extend(step.children.into_iter().map(|child| self.prepare_json_step(child)));
162-
children.extend(
163-
step.tests
164-
.into_iter()
165-
.map(|test| JsonNode::Test { name: test.name, outcome: test.outcome }),
166-
);
206+
children.extend(step.test_suites.into_iter().map(JsonNode::TestSuite));
167207

168208
JsonNode::RustbuildStep {
169209
type_: step.type_,
@@ -198,17 +238,14 @@ struct StepMetrics {
198238
duration_excluding_children_sec: Duration,
199239

200240
children: Vec<StepMetrics>,
201-
tests: Vec<Test>,
202-
}
203-
204-
struct Test {
205-
name: String,
206-
outcome: TestOutcome,
241+
test_suites: Vec<TestSuite>,
207242
}
208243

209244
#[derive(Serialize, Deserialize)]
210245
#[serde(rename_all = "snake_case")]
211246
struct JsonRoot {
247+
#[serde(default)] // For version 0 the field was not present.
248+
format_version: usize,
212249
system_stats: JsonInvocationSystemStats,
213250
invocations: Vec<JsonInvocation>,
214251
}
@@ -237,13 +274,41 @@ enum JsonNode {
237274

238275
children: Vec<JsonNode>,
239276
},
240-
Test {
241-
name: String,
242-
#[serde(flatten)]
243-
outcome: TestOutcome,
277+
TestSuite(TestSuite),
278+
}
279+
280+
#[derive(Serialize, Deserialize)]
281+
struct TestSuite {
282+
metadata: TestSuiteMetadata,
283+
tests: Vec<Test>,
284+
}
285+
286+
#[derive(Serialize, Deserialize)]
287+
#[serde(tag = "kind", rename_all = "snake_case")]
288+
pub(crate) enum TestSuiteMetadata {
289+
CargoPackage {
290+
crates: Vec<String>,
291+
target: String,
292+
host: String,
293+
stage: u32,
294+
},
295+
Compiletest {
296+
suite: String,
297+
mode: String,
298+
compare_mode: Option<String>,
299+
target: String,
300+
host: String,
301+
stage: u32,
244302
},
245303
}
246304

305+
#[derive(Serialize, Deserialize)]
306+
pub(crate) struct Test {
307+
name: String,
308+
#[serde(flatten)]
309+
outcome: TestOutcome,
310+
}
311+
247312
#[derive(Serialize, Deserialize)]
248313
#[serde(tag = "outcome", rename_all = "snake_case")]
249314
pub(crate) enum TestOutcome {
@@ -266,3 +331,9 @@ struct JsonInvocationSystemStats {
266331
struct JsonStepSystemStats {
267332
cpu_utilization_percent: f64,
268333
}
334+
335+
#[derive(Deserialize)]
336+
struct OnlyFormatVersion {
337+
#[serde(default)] // For version 0 the field was not present.
338+
format_version: usize,
339+
}

src/bootstrap/test.rs

+49
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,17 @@ impl Step for Cargo {
317317
cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1");
318318
cargo.env("PATH", &path_for_cargo(builder, compiler));
319319

320+
#[cfg(feature = "build-metrics")]
321+
builder.metrics.begin_test_suite(
322+
crate::metrics::TestSuiteMetadata::CargoPackage {
323+
crates: vec!["cargo".into()],
324+
target: self.host.triple.to_string(),
325+
host: self.host.triple.to_string(),
326+
stage: self.stage,
327+
},
328+
builder,
329+
);
330+
320331
let _time = util::timeit(&builder);
321332
add_flags_and_try_run_tests(builder, &mut cargo);
322333
}
@@ -1699,6 +1710,19 @@ note: if you're sure you want to do this, please open an issue as to why. In the
16991710

17001711
builder.ci_env.force_coloring_in_ci(&mut cmd);
17011712

1713+
#[cfg(feature = "build-metrics")]
1714+
builder.metrics.begin_test_suite(
1715+
crate::metrics::TestSuiteMetadata::Compiletest {
1716+
suite: suite.into(),
1717+
mode: mode.into(),
1718+
compare_mode: None,
1719+
target: self.target.triple.to_string(),
1720+
host: self.compiler.host.triple.to_string(),
1721+
stage: self.compiler.stage,
1722+
},
1723+
builder,
1724+
);
1725+
17021726
builder.info(&format!(
17031727
"Check compiletest suite={} mode={} ({} -> {})",
17041728
suite, mode, &compiler.host, target
@@ -1708,6 +1732,20 @@ note: if you're sure you want to do this, please open an issue as to why. In the
17081732

17091733
if let Some(compare_mode) = compare_mode {
17101734
cmd.arg("--compare-mode").arg(compare_mode);
1735+
1736+
#[cfg(feature = "build-metrics")]
1737+
builder.metrics.begin_test_suite(
1738+
crate::metrics::TestSuiteMetadata::Compiletest {
1739+
suite: suite.into(),
1740+
mode: mode.into(),
1741+
compare_mode: Some(compare_mode.into()),
1742+
target: self.target.triple.to_string(),
1743+
host: self.compiler.host.triple.to_string(),
1744+
stage: self.compiler.stage,
1745+
},
1746+
builder,
1747+
);
1748+
17111749
builder.info(&format!(
17121750
"Check compiletest suite={} mode={} compare_mode={} ({} -> {})",
17131751
suite, mode, compare_mode, &compiler.host, target
@@ -2034,6 +2072,17 @@ fn run_cargo_test(
20342072
let mut cargo =
20352073
prepare_cargo_test(cargo, libtest_args, crates, primary_crate, compiler, target, builder);
20362074
let _time = util::timeit(&builder);
2075+
2076+
#[cfg(feature = "build-metrics")]
2077+
builder.metrics.begin_test_suite(
2078+
crate::metrics::TestSuiteMetadata::CargoPackage {
2079+
crates: crates.iter().map(|c| c.to_string()).collect(),
2080+
target: target.triple.to_string(),
2081+
host: compiler.host.triple.to_string(),
2082+
stage: compiler.stage,
2083+
},
2084+
builder,
2085+
);
20372086
add_flags_and_try_run_tests(builder, &mut cargo)
20382087
}
20392088

0 commit comments

Comments
 (0)