@@ -14,6 +14,25 @@ use std::io::BufWriter;
1414use std:: time:: { Duration , Instant , SystemTime } ;
1515use 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+
1736pub ( 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" ) ]
211246struct 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" ) ]
249314pub ( crate ) enum TestOutcome {
@@ -266,3 +331,9 @@ struct JsonInvocationSystemStats {
266331struct 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+ }
0 commit comments