]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/metrics.rs
Rollup merge of #107740 - oli-obk:lock_tcx, r=petrochenkov
[rust.git] / src / bootstrap / metrics.rs
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
7 use crate::builder::Step;
8 use crate::util::t;
9 use crate::Build;
10 use serde::{Deserialize, Serialize};
11 use std::cell::RefCell;
12 use std::fs::File;
13 use std::io::BufWriter;
14 use std::time::{Duration, Instant};
15 use sysinfo::{CpuExt, System, SystemExt};
16
17 pub(crate) struct BuildMetrics {
18     state: RefCell<MetricsState>,
19 }
20
21 impl BuildMetrics {
22     pub(crate) fn init() -> Self {
23         let state = RefCell::new(MetricsState {
24             finished_steps: Vec::new(),
25             running_steps: Vec::new(),
26
27             system_info: System::new(),
28             timer_start: None,
29             invocation_timer_start: Instant::now(),
30         });
31
32         BuildMetrics { state }
33     }
34
35     pub(crate) fn enter_step<S: Step>(&self, step: &S) {
36         let mut state = self.state.borrow_mut();
37
38         // Consider all the stats gathered so far as the parent's.
39         if !state.running_steps.is_empty() {
40             self.collect_stats(&mut *state);
41         }
42
43         state.system_info.refresh_cpu();
44         state.timer_start = Some(Instant::now());
45
46         state.running_steps.push(StepMetrics {
47             type_: std::any::type_name::<S>().into(),
48             debug_repr: format!("{step:?}"),
49
50             cpu_usage_time_sec: 0.0,
51             duration_excluding_children_sec: Duration::ZERO,
52
53             children: Vec::new(),
54         });
55     }
56
57     pub(crate) fn exit_step(&self) {
58         let mut state = self.state.borrow_mut();
59
60         self.collect_stats(&mut *state);
61
62         let step = state.running_steps.pop().unwrap();
63         if state.running_steps.is_empty() {
64             state.finished_steps.push(step);
65             state.timer_start = None;
66         } else {
67             state.running_steps.last_mut().unwrap().children.push(step);
68
69             // Start collecting again for the parent step.
70             state.system_info.refresh_cpu();
71             state.timer_start = Some(Instant::now());
72         }
73     }
74
75     fn collect_stats(&self, state: &mut MetricsState) {
76         let step = state.running_steps.last_mut().unwrap();
77
78         let elapsed = state.timer_start.unwrap().elapsed();
79         step.duration_excluding_children_sec += elapsed;
80
81         state.system_info.refresh_cpu();
82         let cpu = state.system_info.cpus().iter().map(|p| p.cpu_usage()).sum::<f32>();
83         step.cpu_usage_time_sec += cpu as f64 / 100.0 * elapsed.as_secs_f64();
84     }
85
86     pub(crate) fn persist(&self, build: &Build) {
87         let mut state = self.state.borrow_mut();
88         assert!(state.running_steps.is_empty(), "steps are still executing");
89
90         let dest = build.out.join("metrics.json");
91
92         let mut system = System::new();
93         system.refresh_cpu();
94         system.refresh_memory();
95
96         let system_stats = JsonInvocationSystemStats {
97             cpu_threads_count: system.cpus().len(),
98             cpu_model: system.cpus()[0].brand().into(),
99
100             memory_total_bytes: system.total_memory(),
101         };
102         let steps = std::mem::take(&mut state.finished_steps);
103
104         // Some of our CI builds consist of multiple independent CI invocations. Ensure all the
105         // previous invocations are still present in the resulting file.
106         let mut invocations = match std::fs::read(&dest) {
107             Ok(contents) => t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations,
108             Err(err) => {
109                 if err.kind() != std::io::ErrorKind::NotFound {
110                     panic!("failed to open existing metrics file at {}: {err}", dest.display());
111                 }
112                 Vec::new()
113             }
114         };
115         invocations.push(JsonInvocation {
116             duration_including_children_sec: state.invocation_timer_start.elapsed().as_secs_f64(),
117             children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(),
118         });
119
120         let json = JsonRoot { system_stats, invocations };
121
122         t!(std::fs::create_dir_all(dest.parent().unwrap()));
123         let mut file = BufWriter::new(t!(File::create(&dest)));
124         t!(serde_json::to_writer(&mut file, &json));
125     }
126
127     fn prepare_json_step(&self, step: StepMetrics) -> JsonNode {
128         JsonNode::RustbuildStep {
129             type_: step.type_,
130             debug_repr: step.debug_repr,
131
132             duration_excluding_children_sec: step.duration_excluding_children_sec.as_secs_f64(),
133             system_stats: JsonStepSystemStats {
134                 cpu_utilization_percent: step.cpu_usage_time_sec * 100.0
135                     / step.duration_excluding_children_sec.as_secs_f64(),
136             },
137
138             children: step
139                 .children
140                 .into_iter()
141                 .map(|child| self.prepare_json_step(child))
142                 .collect(),
143         }
144     }
145 }
146
147 struct MetricsState {
148     finished_steps: Vec<StepMetrics>,
149     running_steps: Vec<StepMetrics>,
150
151     system_info: System,
152     timer_start: Option<Instant>,
153     invocation_timer_start: Instant,
154 }
155
156 struct StepMetrics {
157     type_: String,
158     debug_repr: String,
159
160     cpu_usage_time_sec: f64,
161     duration_excluding_children_sec: Duration,
162
163     children: Vec<StepMetrics>,
164 }
165
166 #[derive(Serialize, Deserialize)]
167 #[serde(rename_all = "snake_case")]
168 struct JsonRoot {
169     system_stats: JsonInvocationSystemStats,
170     invocations: Vec<JsonInvocation>,
171 }
172
173 #[derive(Serialize, Deserialize)]
174 #[serde(rename_all = "snake_case")]
175 struct JsonInvocation {
176     duration_including_children_sec: f64,
177     children: Vec<JsonNode>,
178 }
179
180 #[derive(Serialize, Deserialize)]
181 #[serde(tag = "kind", rename_all = "snake_case")]
182 enum JsonNode {
183     RustbuildStep {
184         #[serde(rename = "type")]
185         type_: String,
186         debug_repr: String,
187
188         duration_excluding_children_sec: f64,
189         system_stats: JsonStepSystemStats,
190
191         children: Vec<JsonNode>,
192     },
193 }
194
195 #[derive(Serialize, Deserialize)]
196 #[serde(rename_all = "snake_case")]
197 struct JsonInvocationSystemStats {
198     cpu_threads_count: usize,
199     cpu_model: String,
200
201     memory_total_bytes: u64,
202 }
203
204 #[derive(Serialize, Deserialize)]
205 #[serde(rename_all = "snake_case")]
206 struct JsonStepSystemStats {
207     cpu_utilization_percent: f64,
208 }