Skip to content

Commit fa10d65

Browse files
authored
Render individual compilation sections in --timings pipeline graph (#15923)
### What does this PR try to resolve? This PR expands the support for `--json-timings` (added in #15780), by rendering the individual compilation sections in the pipeline graph of the `--timings` page. Before, the linking section was only shown in the table at the bottom of the page, now it should also be clearly visible in the compilation graph, which should help more quickly understand how much time is spent in linking. <img width="1219" height="358" alt="image" src="https://github.com/user-attachments/assets/71d0200d-4175-43b7-8aab-997008e2af47" /> I also added a legend to the pipeline graph, to explain what do the colors mean. <img width="338" height="118" alt="image" src="https://github.com/user-attachments/assets/69f9bac6-c33f-44c5-8e45-afa97f314e4c" /> One wart is that the linking time actually ends a bit before the unit ends, so there is some "vacuum" at the end where rustc does cleanup, persists files to disk, deallocates things, etc. That's why I marked the blue section "Frontend/rest" in the legend. ### How to test and review this PR? Same as for #15780, e.g.: ```bash export RUSTC=`rustup +nightly which rustc` target/debug/cargo build -Zsection-timings --timings ``` on some crate, e.g. [ripgrep](https://github.com/BurntSushi/ripgrep).
2 parents 43bb5c9 + 80912b6 commit fa10d65

File tree

2 files changed

+187
-56
lines changed

2 files changed

+187
-56
lines changed

src/cargo/core/compiler/timings.js

Lines changed: 155 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ const BG_COLOR = getCssColor('--background');
6666
const CANVAS_BG = getCssColor('--canvas-background');
6767
const AXES_COLOR = getCssColor('--canvas-axes');
6868
const GRID_COLOR = getCssColor('--canvas-grid');
69-
const BLOCK_COLOR = getCssColor('--canvas-block');
69+
const CODEGEN_COLOR = getCssColor('--canvas-codegen');
70+
const LINK_COLOR = getCssColor('--canvas-link');
71+
// Final leftover section after link
72+
const OTHER_COLOR = getCssColor('--canvas-other');
7073
const CUSTOM_BUILD_COLOR = getCssColor('--canvas-custom-build');
7174
const NOT_CUSTOM_BUILD_COLOR = getCssColor('--canvas-not-custom-build');
7275
const DEP_LINE_COLOR = getCssColor('--canvas-dep-line');
@@ -134,21 +137,40 @@ function render_pipeline_graph() {
134137
let unit = units[i];
135138
let y = i * Y_TICK_DIST + 1;
136139
let x = px_per_sec * unit.start;
137-
let rmeta_x = null;
138-
if (unit.rmeta_time != null) {
139-
rmeta_x = x + px_per_sec * unit.rmeta_time;
140+
141+
const sections = [];
142+
if (unit.sections !== null) {
143+
// We have access to compilation sections
144+
for (const section of unit.sections) {
145+
const [name, {start, end}] = section;
146+
sections.push({
147+
name,
148+
start: x + px_per_sec * start,
149+
width: (end - start) * px_per_sec
150+
});
151+
}
152+
}
153+
else if (unit.rmeta_time != null) {
154+
// We only know the rmeta time
155+
sections.push({
156+
name: "codegen",
157+
start: x + px_per_sec * unit.rmeta_time,
158+
width: (unit.duration - unit.rmeta_time) * px_per_sec
159+
});
140160
}
141161
let width = Math.max(px_per_sec * unit.duration, 1.0);
142-
UNIT_COORDS[unit.i] = {x, y, width, rmeta_x};
162+
UNIT_COORDS[unit.i] = {x, y, width, sections};
143163

144164
const count = unitCount.get(unit.name) || 0;
145165
unitCount.set(unit.name, count + 1);
146166
}
147167

168+
const presentSections = new Set();
169+
148170
// Draw the blocks.
149171
for (i=0; i<units.length; i++) {
150172
let unit = units[i];
151-
let {x, y, width, rmeta_x} = UNIT_COORDS[unit.i];
173+
let {x, y, width, sections} = UNIT_COORDS[unit.i];
152174

153175
HIT_BOXES.push({x: X_LINE+x, y:MARGIN+y, x2: X_LINE+x+width, y2: MARGIN+y+BOX_HEIGHT, i: unit.i});
154176

@@ -157,12 +179,12 @@ function render_pipeline_graph() {
157179
roundedRect(ctx, x, y, width, BOX_HEIGHT, RADIUS);
158180
ctx.fill();
159181

160-
if (unit.rmeta_time != null) {
161-
ctx.beginPath();
162-
ctx.fillStyle = BLOCK_COLOR;
163-
let ctime = unit.duration - unit.rmeta_time;
164-
roundedRect(ctx, rmeta_x, y, px_per_sec * ctime, BOX_HEIGHT, RADIUS);
165-
ctx.fill();
182+
for (const section of sections) {
183+
ctx.beginPath();
184+
ctx.fillStyle = get_section_color(section.name);
185+
roundedRect(ctx, section.start, y, section.width, BOX_HEIGHT, RADIUS);
186+
ctx.fill();
187+
presentSections.add(section.name);
166188
}
167189
ctx.fillStyle = TEXT_COLOR;
168190
ctx.textAlign = 'start';
@@ -178,6 +200,110 @@ function render_pipeline_graph() {
178200
draw_dep_lines(ctx, unit.i, false);
179201
}
180202
ctx.restore();
203+
204+
// Draw a legend.
205+
ctx.save();
206+
ctx.translate(canvas_width - 200, MARGIN);
207+
208+
const legend_entries = [{
209+
name: "Frontend/rest",
210+
color: NOT_CUSTOM_BUILD_COLOR,
211+
line: false
212+
}];
213+
if (presentSections.has("codegen")) {
214+
legend_entries.push({
215+
name: "Codegen",
216+
color: CODEGEN_COLOR,
217+
line: false
218+
});
219+
}
220+
if (presentSections.has("link")) {
221+
legend_entries.push({
222+
name: "Linking",
223+
color: LINK_COLOR,
224+
line: false
225+
});
226+
}
227+
if (presentSections.has("other")) {
228+
legend_entries.push({
229+
name: "Other",
230+
color: OTHER_COLOR,
231+
line: false
232+
});
233+
}
234+
draw_legend(ctx, 160, legend_entries);
235+
ctx.restore();
236+
}
237+
238+
// Draw a legend at the current position of the ctx.
239+
// entries should be an array of objects with the following scheme:
240+
// {
241+
// "name": <name of the legend entry> [string],
242+
// "color": <color of the legend entry> [string],
243+
// "line": <should the entry be a thin line or a rectangle> [bool]
244+
// }
245+
function draw_legend(ctx, width, entries) {
246+
const entry_height = 20;
247+
248+
// Add a bit of margin to the bottom and top
249+
const height = entries.length * entry_height + 4;
250+
251+
// Draw background
252+
ctx.fillStyle = BG_COLOR;
253+
ctx.strokeStyle = TEXT_COLOR;
254+
ctx.lineWidth = 1;
255+
ctx.textBaseline = 'middle';
256+
ctx.textAlign = 'start';
257+
ctx.beginPath();
258+
ctx.rect(0, 0, width, height);
259+
ctx.stroke();
260+
ctx.fill();
261+
262+
ctx.lineWidth = 2;
263+
264+
// Dimension of a block
265+
const block_height = 15;
266+
const block_width = 30;
267+
268+
// Margin from the left edge
269+
const x_start = 5;
270+
// Width of the "mark" section (line/block)
271+
const mark_width = 45;
272+
273+
// Draw legend entries
274+
let y = 12;
275+
for (const entry of entries) {
276+
ctx.beginPath();
277+
278+
if (entry.line) {
279+
ctx.strokeStyle = entry.color;
280+
ctx.moveTo(x_start, y);
281+
ctx.lineTo(x_start + mark_width, y);
282+
ctx.stroke();
283+
} else {
284+
ctx.fillStyle = entry.color;
285+
ctx.fillRect(x_start + (mark_width - block_width) / 2, y - (block_height / 2), block_width, block_height);
286+
}
287+
288+
ctx.fillStyle = TEXT_COLOR;
289+
ctx.fillText(entry.name, x_start + mark_width + 4, y + 1);
290+
291+
y += entry_height;
292+
}
293+
}
294+
295+
// Determine the color of a section block based on the section name.
296+
function get_section_color(name) {
297+
if (name === "codegen") {
298+
return CODEGEN_COLOR;
299+
} else if (name === "link") {
300+
return LINK_COLOR;
301+
} else if (name === "other") {
302+
return OTHER_COLOR;
303+
} else {
304+
// We do not know what section this is, so just use the default color
305+
return NOT_CUSTOM_BUILD_COLOR;
306+
}
181307
}
182308

183309
// Draws lines from the given unit to the units it unlocks.
@@ -296,47 +422,23 @@ function render_timing_graph() {
296422
ctx.restore();
297423
ctx.save();
298424
ctx.translate(canvas_width-200, MARGIN);
299-
// background
300-
ctx.fillStyle = BG_COLOR;
301-
ctx.strokeStyle = TEXT_COLOR;
302-
ctx.lineWidth = 1;
303-
ctx.textBaseline = 'middle'
304-
ctx.textAlign = 'start';
305-
ctx.beginPath();
306-
ctx.rect(0, 0, 150, 82);
307-
ctx.stroke();
308-
ctx.fill();
309-
310-
ctx.fillStyle = TEXT_COLOR;
311-
ctx.beginPath();
312-
ctx.lineWidth = 2;
313-
ctx.strokeStyle = 'red';
314-
ctx.moveTo(5, 10);
315-
ctx.lineTo(50, 10);
316-
ctx.stroke();
317-
ctx.fillText('Waiting', 54, 11);
318-
319-
ctx.beginPath();
320-
ctx.strokeStyle = 'blue';
321-
ctx.moveTo(5, 30);
322-
ctx.lineTo(50, 30);
323-
ctx.stroke();
324-
ctx.fillText('Inactive', 54, 31);
325-
326-
ctx.beginPath();
327-
ctx.strokeStyle = 'green';
328-
ctx.moveTo(5, 50);
329-
ctx.lineTo(50, 50);
330-
ctx.stroke();
331-
ctx.fillText('Active', 54, 51);
332-
333-
ctx.beginPath();
334-
ctx.fillStyle = cpuFillStyle
335-
ctx.fillRect(15, 60, 30, 15);
336-
ctx.fill();
337-
ctx.fillStyle = TEXT_COLOR;
338-
ctx.fillText('CPU Usage', 54, 71);
339-
425+
draw_legend(ctx, 150, [{
426+
name: "Waiting",
427+
color: "red",
428+
line: true
429+
}, {
430+
name: "Inactive",
431+
color: "blue",
432+
line: true
433+
}, {
434+
name: "Active",
435+
color: "green",
436+
line: true
437+
}, {
438+
name: "CPU Usage",
439+
color: cpuFillStyle,
440+
line: false
441+
}]);
340442
ctx.restore();
341443
}
342444

src/cargo/core/compiler/timings.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ impl SectionData {
191191
}
192192

193193
/// Contains post-processed data of individual compilation sections.
194-
#[derive(serde::Serialize)]
195194
enum AggregatedSections {
196195
/// We know the names and durations of individual compilation sections
197196
Sections(Vec<(String, SectionData)>),
@@ -587,6 +586,7 @@ impl<'gctx> Timings<'gctx> {
587586
rmeta_time: Option<f64>,
588587
unlocked_units: Vec<usize>,
589588
unlocked_rmeta_units: Vec<usize>,
589+
sections: Option<Vec<(String, SectionData)>>,
590590
}
591591
let round = |x: f64| (x * 100.0).round() / 100.0;
592592
let unit_data: Vec<UnitData> = self
@@ -600,7 +600,6 @@ impl<'gctx> Timings<'gctx> {
600600
"todo"
601601
}
602602
.to_string();
603-
604603
// These filter on the unlocked units because not all unlocked
605604
// units are actually "built". For example, Doctest mode units
606605
// don't actually generate artifacts.
@@ -614,6 +613,33 @@ impl<'gctx> Timings<'gctx> {
614613
.iter()
615614
.filter_map(|unit| unit_map.get(unit).copied())
616615
.collect();
616+
let aggregated = ut.aggregate_sections();
617+
let sections = match aggregated {
618+
AggregatedSections::Sections(mut sections) => {
619+
// We draw the sections in the pipeline graph in a way where the frontend
620+
// section has the "default" build color, and then additional sections
621+
// (codegen, link) are overlayed on top with a different color.
622+
// However, there might be some time after the final (usually link) section,
623+
// which definitely shouldn't be classified as "Frontend". We thus try to
624+
// detect this situation and add a final "Other" section.
625+
if let Some((_, section)) = sections.last()
626+
&& section.end < ut.duration
627+
{
628+
sections.push((
629+
"other".to_string(),
630+
SectionData {
631+
start: section.end,
632+
end: ut.duration,
633+
},
634+
));
635+
}
636+
637+
Some(sections)
638+
}
639+
AggregatedSections::OnlyMetadataTime { .. }
640+
| AggregatedSections::OnlyTotalDuration => None,
641+
};
642+
617643
UnitData {
618644
i,
619645
name: ut.unit.pkg.name().to_string(),
@@ -625,6 +651,7 @@ impl<'gctx> Timings<'gctx> {
625651
rmeta_time: ut.rmeta_time.map(round),
626652
unlocked_units,
627653
unlocked_rmeta_units,
654+
sections,
628655
}
629656
})
630657
.collect();
@@ -871,7 +898,9 @@ static HTML_TMPL: &str = r#"
871898
--canvas-background: #f7f7f7;
872899
--canvas-axes: #303030;
873900
--canvas-grid: #e6e6e6;
874-
--canvas-block: #aa95e8;
901+
--canvas-codegen: #aa95e8;
902+
--canvas-link: #95e8aa;
903+
--canvas-other: #e895aa;
875904
--canvas-custom-build: #f0b165;
876905
--canvas-not-custom-build: #95cce8;
877906
--canvas-dep-line: #ddd;

0 commit comments

Comments
 (0)