]> git.proxmox.com Git - rustc.git/blame - src/bootstrap/metrics.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / src / bootstrap / metrics.rs
CommitLineData
923072b8
FG
1//! This module is responsible for collecting metrics profiling information for the current build
2//! and dumping it to disk as JSON, to aid investigations on build and CI performance.
3//!
4//! As this module requires additional dependencies not present during local builds, it's cfg'd
5//! away whenever the `build.metrics` config option is not set to `true`.
6
49aad941 7use crate::builder::{Builder, Step};
923072b8
FG
8use crate::util::t;
9use crate::Build;
add651ee
FG
10use build_helper::metrics::{
11 JsonInvocation, JsonInvocationSystemStats, JsonNode, JsonRoot, JsonStepSystemStats, Test,
12 TestOutcome, TestSuite, TestSuiteMetadata,
13};
923072b8
FG
14use std::cell::RefCell;
15use std::fs::File;
16use std::io::BufWriter;
353b0b11 17use std::time::{Duration, Instant, SystemTime};
923072b8
FG
18use sysinfo::{CpuExt, System, SystemExt};
19
49aad941
FG
20// Update this number whenever a breaking change is made to the build metrics.
21//
22// The output format is versioned for two reasons:
23//
24// - The metadata is intended to be consumed by external tooling, and exposing a format version
25// helps the tools determine whether they're compatible with a metrics file.
26//
27// - If a developer enables build metrics in their local checkout, making a breaking change to the
28// metrics format would result in a hard-to-diagnose error message when an existing metrics file
29// is not compatible with the new changes. With a format version number, bootstrap can discard
30// incompatible metrics files instead of appending metrics to them.
31//
32// Version changelog:
33//
34// - v0: initial version
35// - v1: replaced JsonNode::Test with JsonNode::TestSuite
36//
37const CURRENT_FORMAT_VERSION: usize = 1;
38
923072b8
FG
39pub(crate) struct BuildMetrics {
40 state: RefCell<MetricsState>,
41}
42
add651ee
FG
43/// NOTE: this isn't really cloning anything, but `x suggest` doesn't need metrics so this is probably ok.
44impl Clone for BuildMetrics {
45 fn clone(&self) -> Self {
46 Self::init()
47 }
48}
49
923072b8
FG
50impl BuildMetrics {
51 pub(crate) fn init() -> Self {
52 let state = RefCell::new(MetricsState {
53 finished_steps: Vec::new(),
54 running_steps: Vec::new(),
55
56 system_info: System::new(),
57 timer_start: None,
58 invocation_timer_start: Instant::now(),
353b0b11 59 invocation_start: SystemTime::now(),
923072b8
FG
60 });
61
62 BuildMetrics { state }
63 }
64
49aad941
FG
65 pub(crate) fn enter_step<S: Step>(&self, step: &S, builder: &Builder<'_>) {
66 // Do not record dry runs, as they'd be duplicates of the actual steps.
67 if builder.config.dry_run() {
68 return;
69 }
70
923072b8
FG
71 let mut state = self.state.borrow_mut();
72
73 // Consider all the stats gathered so far as the parent's.
74 if !state.running_steps.is_empty() {
75 self.collect_stats(&mut *state);
76 }
77
78 state.system_info.refresh_cpu();
79 state.timer_start = Some(Instant::now());
80
81 state.running_steps.push(StepMetrics {
82 type_: std::any::type_name::<S>().into(),
83 debug_repr: format!("{step:?}"),
84
85 cpu_usage_time_sec: 0.0,
86 duration_excluding_children_sec: Duration::ZERO,
87
88 children: Vec::new(),
49aad941 89 test_suites: Vec::new(),
923072b8
FG
90 });
91 }
92
49aad941
FG
93 pub(crate) fn exit_step(&self, builder: &Builder<'_>) {
94 // Do not record dry runs, as they'd be duplicates of the actual steps.
95 if builder.config.dry_run() {
96 return;
97 }
98
923072b8
FG
99 let mut state = self.state.borrow_mut();
100
101 self.collect_stats(&mut *state);
102
103 let step = state.running_steps.pop().unwrap();
104 if state.running_steps.is_empty() {
105 state.finished_steps.push(step);
106 state.timer_start = None;
107 } else {
108 state.running_steps.last_mut().unwrap().children.push(step);
109
110 // Start collecting again for the parent step.
111 state.system_info.refresh_cpu();
112 state.timer_start = Some(Instant::now());
113 }
114 }
115
49aad941
FG
116 pub(crate) fn begin_test_suite(&self, metadata: TestSuiteMetadata, builder: &Builder<'_>) {
117 // Do not record dry runs, as they'd be duplicates of the actual steps.
118 if builder.config.dry_run() {
119 return;
120 }
121
122 let mut state = self.state.borrow_mut();
123 let step = state.running_steps.last_mut().unwrap();
124 step.test_suites.push(TestSuite { metadata, tests: Vec::new() });
125 }
126
127 pub(crate) fn record_test(&self, name: &str, outcome: TestOutcome, builder: &Builder<'_>) {
128 // Do not record dry runs, as they'd be duplicates of the actual steps.
129 if builder.config.dry_run() {
130 return;
131 }
132
353b0b11 133 let mut state = self.state.borrow_mut();
49aad941
FG
134 let step = state.running_steps.last_mut().unwrap();
135
136 if let Some(test_suite) = step.test_suites.last_mut() {
137 test_suite.tests.push(Test { name: name.to_string(), outcome });
138 } else {
139 panic!("metrics.record_test() called without calling metrics.begin_test_suite() first");
140 }
353b0b11
FG
141 }
142
923072b8
FG
143 fn collect_stats(&self, state: &mut MetricsState) {
144 let step = state.running_steps.last_mut().unwrap();
145
146 let elapsed = state.timer_start.unwrap().elapsed();
147 step.duration_excluding_children_sec += elapsed;
148
149 state.system_info.refresh_cpu();
150 let cpu = state.system_info.cpus().iter().map(|p| p.cpu_usage()).sum::<f32>();
151 step.cpu_usage_time_sec += cpu as f64 / 100.0 * elapsed.as_secs_f64();
152 }
153
154 pub(crate) fn persist(&self, build: &Build) {
155 let mut state = self.state.borrow_mut();
156 assert!(state.running_steps.is_empty(), "steps are still executing");
157
158 let dest = build.out.join("metrics.json");
159
160 let mut system = System::new();
161 system.refresh_cpu();
162 system.refresh_memory();
163
164 let system_stats = JsonInvocationSystemStats {
165 cpu_threads_count: system.cpus().len(),
166 cpu_model: system.cpus()[0].brand().into(),
167
487cf647 168 memory_total_bytes: system.total_memory(),
923072b8
FG
169 };
170 let steps = std::mem::take(&mut state.finished_steps);
171
172 // Some of our CI builds consist of multiple independent CI invocations. Ensure all the
173 // previous invocations are still present in the resulting file.
174 let mut invocations = match std::fs::read(&dest) {
49aad941
FG
175 Ok(contents) => {
176 // We first parse just the format_version field to have the check succeed even if
177 // the rest of the contents are not valid anymore.
178 let version: OnlyFormatVersion = t!(serde_json::from_slice(&contents));
179 if version.format_version == CURRENT_FORMAT_VERSION {
180 t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations
181 } else {
182 println!(
183 "warning: overriding existing build/metrics.json, as it's not \
184 compatible with build metrics format version {CURRENT_FORMAT_VERSION}."
185 );
186 Vec::new()
187 }
188 }
923072b8
FG
189 Err(err) => {
190 if err.kind() != std::io::ErrorKind::NotFound {
191 panic!("failed to open existing metrics file at {}: {err}", dest.display());
192 }
193 Vec::new()
194 }
195 };
196 invocations.push(JsonInvocation {
353b0b11
FG
197 start_time: state
198 .invocation_start
199 .duration_since(SystemTime::UNIX_EPOCH)
200 .unwrap()
201 .as_secs(),
923072b8
FG
202 duration_including_children_sec: state.invocation_timer_start.elapsed().as_secs_f64(),
203 children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(),
204 });
205
49aad941 206 let json = JsonRoot { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations };
923072b8
FG
207
208 t!(std::fs::create_dir_all(dest.parent().unwrap()));
209 let mut file = BufWriter::new(t!(File::create(&dest)));
210 t!(serde_json::to_writer(&mut file, &json));
211 }
212
213 fn prepare_json_step(&self, step: StepMetrics) -> JsonNode {
353b0b11
FG
214 let mut children = Vec::new();
215 children.extend(step.children.into_iter().map(|child| self.prepare_json_step(child)));
49aad941 216 children.extend(step.test_suites.into_iter().map(JsonNode::TestSuite));
353b0b11 217
923072b8
FG
218 JsonNode::RustbuildStep {
219 type_: step.type_,
220 debug_repr: step.debug_repr,
221
222 duration_excluding_children_sec: step.duration_excluding_children_sec.as_secs_f64(),
223 system_stats: JsonStepSystemStats {
224 cpu_utilization_percent: step.cpu_usage_time_sec * 100.0
225 / step.duration_excluding_children_sec.as_secs_f64(),
226 },
227
353b0b11 228 children,
923072b8
FG
229 }
230 }
231}
232
233struct MetricsState {
234 finished_steps: Vec<StepMetrics>,
235 running_steps: Vec<StepMetrics>,
236
237 system_info: System,
238 timer_start: Option<Instant>,
239 invocation_timer_start: Instant,
353b0b11 240 invocation_start: SystemTime,
923072b8
FG
241}
242
243struct StepMetrics {
244 type_: String,
245 debug_repr: String,
246
247 cpu_usage_time_sec: f64,
248 duration_excluding_children_sec: Duration,
249
250 children: Vec<StepMetrics>,
49aad941 251 test_suites: Vec<TestSuite>,
923072b8
FG
252}
253
add651ee 254#[derive(serde_derive::Deserialize)]
49aad941
FG
255struct OnlyFormatVersion {
256 #[serde(default)] // For version 0 the field was not present.
257 format_version: usize,
258}