@@ -14,6 +14,25 @@ use std::io::BufWriter;
14
14
use std:: time:: { Duration , Instant , SystemTime } ;
15
15
use sysinfo:: { CpuExt , System , SystemExt } ;
16
16
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
+
17
36
pub ( crate ) struct BuildMetrics {
18
37
state : RefCell < MetricsState > ,
19
38
}
@@ -57,7 +76,7 @@ impl BuildMetrics {
57
76
duration_excluding_children_sec : Duration :: ZERO ,
58
77
59
78
children : Vec :: new ( ) ,
60
- tests : Vec :: new ( ) ,
79
+ test_suites : Vec :: new ( ) ,
61
80
} ) ;
62
81
}
63
82
@@ -84,19 +103,31 @@ impl BuildMetrics {
84
103
}
85
104
}
86
105
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
+
87
117
pub ( crate ) fn record_test ( & self , name : & str , outcome : TestOutcome , builder : & Builder < ' _ > ) {
88
118
// Do not record dry runs, as they'd be duplicates of the actual steps.
89
119
if builder. config . dry_run ( ) {
90
120
return ;
91
121
}
92
122
93
123
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
+ }
100
131
}
101
132
102
133
fn collect_stats ( & self , state : & mut MetricsState ) {
@@ -131,7 +162,20 @@ impl BuildMetrics {
131
162
// Some of our CI builds consist of multiple independent CI invocations. Ensure all the
132
163
// previous invocations are still present in the resulting file.
133
164
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
+ }
135
179
Err ( err) => {
136
180
if err. kind ( ) != std:: io:: ErrorKind :: NotFound {
137
181
panic ! ( "failed to open existing metrics file at {}: {err}" , dest. display( ) ) ;
@@ -149,7 +193,7 @@ impl BuildMetrics {
149
193
children : steps. into_iter ( ) . map ( |step| self . prepare_json_step ( step) ) . collect ( ) ,
150
194
} ) ;
151
195
152
- let json = JsonRoot { system_stats, invocations } ;
196
+ let json = JsonRoot { format_version : CURRENT_FORMAT_VERSION , system_stats, invocations } ;
153
197
154
198
t ! ( std:: fs:: create_dir_all( dest. parent( ) . unwrap( ) ) ) ;
155
199
let mut file = BufWriter :: new ( t ! ( File :: create( & dest) ) ) ;
@@ -159,11 +203,7 @@ impl BuildMetrics {
159
203
fn prepare_json_step ( & self , step : StepMetrics ) -> JsonNode {
160
204
let mut children = Vec :: new ( ) ;
161
205
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 ) ) ;
167
207
168
208
JsonNode :: RustbuildStep {
169
209
type_ : step. type_ ,
@@ -198,17 +238,14 @@ struct StepMetrics {
198
238
duration_excluding_children_sec : Duration ,
199
239
200
240
children : Vec < StepMetrics > ,
201
- tests : Vec < Test > ,
202
- }
203
-
204
- struct Test {
205
- name : String ,
206
- outcome : TestOutcome ,
241
+ test_suites : Vec < TestSuite > ,
207
242
}
208
243
209
244
#[ derive( Serialize , Deserialize ) ]
210
245
#[ serde( rename_all = "snake_case" ) ]
211
246
struct JsonRoot {
247
+ #[ serde( default ) ] // For version 0 the field was not present.
248
+ format_version : usize ,
212
249
system_stats : JsonInvocationSystemStats ,
213
250
invocations : Vec < JsonInvocation > ,
214
251
}
@@ -237,13 +274,41 @@ enum JsonNode {
237
274
238
275
children : Vec < JsonNode > ,
239
276
} ,
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 ,
244
302
} ,
245
303
}
246
304
305
+ #[ derive( Serialize , Deserialize ) ]
306
+ pub ( crate ) struct Test {
307
+ name : String ,
308
+ #[ serde( flatten) ]
309
+ outcome : TestOutcome ,
310
+ }
311
+
247
312
#[ derive( Serialize , Deserialize ) ]
248
313
#[ serde( tag = "outcome" , rename_all = "snake_case" ) ]
249
314
pub ( crate ) enum TestOutcome {
@@ -266,3 +331,9 @@ struct JsonInvocationSystemStats {
266
331
struct JsonStepSystemStats {
267
332
cpu_utilization_percent : f64 ,
268
333
}
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