Skip to content

Commit 4526f2b

Browse files
committed
Auto merge of rust-lang#137077 - Kobzol:citool-test-metrics, r=<try>
[WIP] Postprocess bootstrap metrics into GitHub summary Based on rust-lang#136864. r? `@ghost`
2 parents ed49386 + 18beca5 commit 4526f2b

File tree

22 files changed

+1347
-412
lines changed

22 files changed

+1347
-412
lines changed

.github/workflows/ci.yml

+21-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# This file defines our primary CI workflow that runs on pull requests
22
# and also on pushes to special branches (auto, try).
33
#
4-
# The actual definition of the executed jobs is calculated by a Python
5-
# script located at src/ci/github-actions/ci.py, which
4+
# The actual definition of the executed jobs is calculated by the
5+
# `src/ci/citool` crate, which
66
# uses job definition data from src/ci/github-actions/jobs.yml.
77
# You should primarily modify the `jobs.yml` file if you want to modify
88
# what jobs are executed in CI.
@@ -56,7 +56,10 @@ jobs:
5656
- name: Calculate the CI job matrix
5757
env:
5858
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
59-
run: python3 src/ci/github-actions/ci.py calculate-job-matrix >> $GITHUB_OUTPUT
59+
run: |
60+
cd src/ci/citool
61+
cargo test
62+
cargo run calculate-job-matrix >> $GITHUB_OUTPUT
6063
id: jobs
6164
job:
6265
name: ${{ matrix.full_name }}
@@ -173,6 +176,13 @@ jobs:
173176
- name: ensure the stable version number is correct
174177
run: src/ci/scripts/verify-stable-version-number.sh
175178

179+
# Pre-build citool before the following step uninstalls rustup
180+
# Build is into the build directory, to avoid modifying sources
181+
- name: build citool
182+
run: |
183+
cd src/ci/citool
184+
CARGO_TARGET_DIR=../../../build/citool cargo build
185+
176186
- name: run the build
177187
# Redirect stderr to stdout to avoid reordering the two streams in the GHA logs.
178188
run: src/ci/scripts/run-build-from-ci.sh 2>&1
@@ -209,16 +219,15 @@ jobs:
209219
# erroring about invalid credentials instead.
210220
if: github.event_name == 'push' || env.DEPLOY == '1' || env.DEPLOY_ALT == '1'
211221

212-
- name: upload job metrics to DataDog
213-
if: needs.calculate_matrix.outputs.run_type != 'pr'
214-
env:
215-
DATADOG_SITE: datadoghq.com
216-
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
217-
DD_GITHUB_JOB_NAME: ${{ matrix.full_name }}
222+
- name: postprocess metrics into the summary
218223
run: |
219-
cd src/ci
220-
npm ci
221-
python3 scripts/upload-build-metrics.py ../../build/cpu-usage.csv
224+
if [ -f build/metrics.json ]; then
225+
./build/citool/debug/citool postprocess-metrics build/metrics.json ${GITHUB_STEP_SUMMARY}
226+
elif [ -f obj/build/metrics.json ]; then
227+
./build/citool/debug/citool postprocess-metrics obj/build/metrics.json ${GITHUB_STEP_SUMMARY}
228+
else
229+
echo "No metrics.json found"
230+
fi
222231
223232
# This job isused to tell bors the final status of the build, as there is no practical way to detect
224233
# when a workflow is successful listening to webhooks only in our current bors implementation (homu).

src/bootstrap/src/utils/metrics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ impl BuildMetrics {
200200
}
201201
};
202202
invocations.push(JsonInvocation {
203+
cmdline: std::env::args_os()
204+
.skip(1)
205+
.map(|arg| arg.to_string_lossy().to_string())
206+
.collect::<Vec<_>>()
207+
.join(" "),
203208
start_time: state
204209
.invocation_start
205210
.duration_since(SystemTime::UNIX_EPOCH)

src/build_helper/src/metrics.rs

+78
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Duration;
2+
13
use serde_derive::{Deserialize, Serialize};
24

35
#[derive(Serialize, Deserialize)]
@@ -12,6 +14,8 @@ pub struct JsonRoot {
1214
#[derive(Serialize, Deserialize)]
1315
#[serde(rename_all = "snake_case")]
1416
pub struct JsonInvocation {
17+
// Remembers the command-line invocation with which was bootstrap invoked.
18+
pub cmdline: String,
1519
// Unix timestamp in seconds
1620
//
1721
// This is necessary to easily correlate this invocation with logs or other data.
@@ -98,3 +102,77 @@ fn null_as_f64_nan<'de, D: serde::Deserializer<'de>>(d: D) -> Result<f64, D::Err
98102
use serde::Deserialize as _;
99103
Option::<f64>::deserialize(d).map(|f| f.unwrap_or(f64::NAN))
100104
}
105+
106+
/// Represents a single bootstrap step, with the accumulated duration of all its children.
107+
#[derive(Clone, Debug)]
108+
pub struct BuildStep {
109+
pub r#type: String,
110+
pub children: Vec<BuildStep>,
111+
pub duration: Duration,
112+
}
113+
114+
impl BuildStep {
115+
pub fn from_invocation(invocation: &JsonInvocation) -> Self {
116+
fn parse(node: &JsonNode) -> Option<BuildStep> {
117+
match node {
118+
JsonNode::RustbuildStep {
119+
type_: kind,
120+
children,
121+
duration_excluding_children_sec,
122+
..
123+
} => {
124+
let children: Vec<_> = children.into_iter().filter_map(parse).collect();
125+
let children_duration = children.iter().map(|c| c.duration).sum::<Duration>();
126+
Some(BuildStep {
127+
r#type: kind.to_string(),
128+
children,
129+
duration: children_duration
130+
+ Duration::from_secs_f64(*duration_excluding_children_sec),
131+
})
132+
}
133+
JsonNode::TestSuite(_) => None,
134+
}
135+
}
136+
137+
let duration = Duration::from_secs_f64(invocation.duration_including_children_sec);
138+
let children: Vec<_> = invocation.children.iter().filter_map(parse).collect();
139+
Self { r#type: "total".to_string(), children, duration }
140+
}
141+
142+
pub fn find_all_by_type(&self, r#type: &str) -> Vec<&BuildStep> {
143+
let mut result = Vec::new();
144+
self.find_by_type(r#type, &mut result);
145+
result
146+
}
147+
fn find_by_type<'a>(&'a self, r#type: &str, result: &mut Vec<&'a BuildStep>) {
148+
if self.r#type == r#type {
149+
result.push(self);
150+
}
151+
for child in &self.children {
152+
child.find_by_type(r#type, result);
153+
}
154+
}
155+
}
156+
157+
/// Writes build steps into a nice indented table.
158+
pub fn format_build_steps(root: &BuildStep) -> String {
159+
use std::fmt::Write;
160+
161+
let mut substeps: Vec<(u32, &BuildStep)> = Vec::new();
162+
163+
fn visit<'a>(step: &'a BuildStep, level: u32, substeps: &mut Vec<(u32, &'a BuildStep)>) {
164+
substeps.push((level, step));
165+
for child in &step.children {
166+
visit(child, level + 1, substeps);
167+
}
168+
}
169+
170+
visit(root, 0, &mut substeps);
171+
172+
let mut output = String::new();
173+
for (level, step) in substeps {
174+
let label = format!("{}{}", ".".repeat(level as usize), step.r#type);
175+
writeln!(output, "{label:.<65}{:>8.2}s", step.duration.as_secs_f64()).unwrap();
176+
}
177+
output
178+
}

0 commit comments

Comments
 (0)