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)] 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 pub cmdline: String,
19 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#[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 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
161pub 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 step.r#type.replace('<', "<").replace('>', ">")
184 );
185 writeln!(output, "{label:.<65}{:>8.2}s", step.duration.as_secs_f64()).unwrap();
186 }
187 output
188}