build_helper/
metrics.rs

1use std::time::Duration;
2
3use serde_derive::{Deserialize, Serialize};
4
5#[derive(Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub struct JsonRoot {
8    #[serde(default)] // For version 0 the field was not present.
9    pub format_version: usize,
10    pub system_stats: JsonInvocationSystemStats,
11    pub invocations: Vec<JsonInvocation>,
12}
13
14#[derive(Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub struct JsonInvocation {
17    // Remembers the command-line invocation with which bootstrap was invoked.
18    pub cmdline: String,
19    // Unix timestamp in seconds
20    //
21    // This is necessary to easily correlate this invocation with logs or other data.
22    pub start_time: u64,
23    #[serde(deserialize_with = "null_as_f64_nan")]
24    pub duration_including_children_sec: f64,
25    pub children: Vec<JsonNode>,
26}
27
28#[derive(Serialize, Deserialize)]
29#[serde(tag = "kind", rename_all = "snake_case")]
30pub enum JsonNode {
31    RustbuildStep {
32        #[serde(rename = "type")]
33        type_: String,
34        debug_repr: String,
35
36        #[serde(deserialize_with = "null_as_f64_nan")]
37        duration_excluding_children_sec: f64,
38        system_stats: JsonStepSystemStats,
39
40        children: Vec<JsonNode>,
41    },
42    TestSuite(TestSuite),
43}
44
45#[derive(Serialize, Deserialize)]
46pub struct TestSuite {
47    pub metadata: TestSuiteMetadata,
48    pub tests: Vec<Test>,
49}
50
51#[derive(Serialize, Deserialize)]
52#[serde(tag = "kind", rename_all = "snake_case")]
53pub enum TestSuiteMetadata {
54    CargoPackage {
55        crates: Vec<String>,
56        target: String,
57        host: String,
58        stage: u32,
59    },
60    Compiletest {
61        suite: String,
62        mode: String,
63        compare_mode: Option<String>,
64        target: String,
65        host: String,
66        stage: u32,
67    },
68}
69
70#[derive(Serialize, Deserialize)]
71pub struct Test {
72    pub name: String,
73    #[serde(flatten)]
74    pub outcome: TestOutcome,
75}
76
77#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
78#[serde(tag = "outcome", rename_all = "snake_case")]
79pub enum TestOutcome {
80    Passed,
81    Failed,
82    Ignored { ignore_reason: Option<String> },
83}
84
85#[derive(Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub struct JsonInvocationSystemStats {
88    pub cpu_threads_count: usize,
89    pub cpu_model: String,
90
91    pub memory_total_bytes: u64,
92}
93
94#[derive(Serialize, Deserialize)]
95#[serde(rename_all = "snake_case")]
96pub struct JsonStepSystemStats {
97    #[serde(deserialize_with = "null_as_f64_nan")]
98    pub cpu_utilization_percent: f64,
99}
100
101fn null_as_f64_nan<'de, D: serde::Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
102    use serde::Deserialize as _;
103    Option::<f64>::deserialize(d).map(|f| f.unwrap_or(f64::NAN))
104}
105
106/// Represents a single bootstrap step, with the accumulated duration of all its children.
107#[derive(Clone, Debug)]
108pub struct BuildStep {
109    pub r#type: String,
110    pub children: Vec<BuildStep>,
111    pub duration: Duration,
112}
113
114impl BuildStep {
115    /// Create a `BuildStep` representing a single invocation of bootstrap.
116    /// The most important thing is that the build step aggregates the
117    /// durations of all children, so that it can be easily accessed.
118    pub fn from_invocation(invocation: &JsonInvocation) -> Self {
119        fn parse(node: &JsonNode) -> Option<BuildStep> {
120            match node {
121                JsonNode::RustbuildStep {
122                    type_: kind,
123                    children,
124                    duration_excluding_children_sec,
125                    ..
126                } => {
127                    let children: Vec<_> = children.into_iter().filter_map(parse).collect();
128                    let children_duration = children.iter().map(|c| c.duration).sum::<Duration>();
129                    Some(BuildStep {
130                        r#type: kind.to_string(),
131                        children,
132                        duration: children_duration
133                            + Duration::from_secs_f64(*duration_excluding_children_sec),
134                    })
135                }
136                JsonNode::TestSuite(_) => None,
137            }
138        }
139
140        let duration = Duration::from_secs_f64(invocation.duration_including_children_sec);
141        let children: Vec<_> = invocation.children.iter().filter_map(parse).collect();
142        Self { r#type: "total".to_string(), children, duration }
143    }
144
145    pub fn find_all_by_type(&self, r#type: &str) -> Vec<&Self> {
146        let mut result = Vec::new();
147        self.find_by_type(r#type, &mut result);
148        result
149    }
150
151    fn find_by_type<'a>(&'a self, r#type: &str, result: &mut Vec<&'a Self>) {
152        if self.r#type == r#type {
153            result.push(self);
154        }
155        for child in &self.children {
156            child.find_by_type(r#type, result);
157        }
158    }
159}
160
161/// Writes build steps into a nice indented table.
162pub fn format_build_steps(root: &BuildStep) -> String {
163    use std::fmt::Write;
164
165    let mut substeps: Vec<(u32, &BuildStep)> = Vec::new();
166
167    fn visit<'a>(step: &'a BuildStep, level: u32, substeps: &mut Vec<(u32, &'a BuildStep)>) {
168        substeps.push((level, step));
169        for child in &step.children {
170            visit(child, level + 1, substeps);
171        }
172    }
173
174    visit(root, 0, &mut substeps);
175
176    let mut output = String::new();
177    for (level, step) in substeps {
178        let label = format!(
179            "{}{}",
180            ".".repeat(level as usize),
181            // Bootstrap steps can be generic and thus contain angle brackets (<...>).
182            // However, Markdown interprets these as HTML, so we need to escap ethem.
183            step.r#type.replace('<', "&lt;").replace('>', "&gt;")
184        );
185        writeln!(output, "{label:.<65}{:>8.2}s", step.duration.as_secs_f64()).unwrap();
186    }
187    output
188}