mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
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 https://github.com/rust-lang/cargo/pull/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 https://github.com/rust-lang/cargo/pull/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).
This commit is contained in:
commit
fa10d65e8e
@ -66,7 +66,10 @@ const BG_COLOR = getCssColor('--background');
|
||||
const CANVAS_BG = getCssColor('--canvas-background');
|
||||
const AXES_COLOR = getCssColor('--canvas-axes');
|
||||
const GRID_COLOR = getCssColor('--canvas-grid');
|
||||
const BLOCK_COLOR = getCssColor('--canvas-block');
|
||||
const CODEGEN_COLOR = getCssColor('--canvas-codegen');
|
||||
const LINK_COLOR = getCssColor('--canvas-link');
|
||||
// Final leftover section after link
|
||||
const OTHER_COLOR = getCssColor('--canvas-other');
|
||||
const CUSTOM_BUILD_COLOR = getCssColor('--canvas-custom-build');
|
||||
const NOT_CUSTOM_BUILD_COLOR = getCssColor('--canvas-not-custom-build');
|
||||
const DEP_LINE_COLOR = getCssColor('--canvas-dep-line');
|
||||
@ -134,21 +137,40 @@ function render_pipeline_graph() {
|
||||
let unit = units[i];
|
||||
let y = i * Y_TICK_DIST + 1;
|
||||
let x = px_per_sec * unit.start;
|
||||
let rmeta_x = null;
|
||||
if (unit.rmeta_time != null) {
|
||||
rmeta_x = x + px_per_sec * unit.rmeta_time;
|
||||
|
||||
const sections = [];
|
||||
if (unit.sections !== null) {
|
||||
// We have access to compilation sections
|
||||
for (const section of unit.sections) {
|
||||
const [name, {start, end}] = section;
|
||||
sections.push({
|
||||
name,
|
||||
start: x + px_per_sec * start,
|
||||
width: (end - start) * px_per_sec
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (unit.rmeta_time != null) {
|
||||
// We only know the rmeta time
|
||||
sections.push({
|
||||
name: "codegen",
|
||||
start: x + px_per_sec * unit.rmeta_time,
|
||||
width: (unit.duration - unit.rmeta_time) * px_per_sec
|
||||
});
|
||||
}
|
||||
let width = Math.max(px_per_sec * unit.duration, 1.0);
|
||||
UNIT_COORDS[unit.i] = {x, y, width, rmeta_x};
|
||||
UNIT_COORDS[unit.i] = {x, y, width, sections};
|
||||
|
||||
const count = unitCount.get(unit.name) || 0;
|
||||
unitCount.set(unit.name, count + 1);
|
||||
}
|
||||
|
||||
const presentSections = new Set();
|
||||
|
||||
// Draw the blocks.
|
||||
for (i=0; i<units.length; i++) {
|
||||
let unit = units[i];
|
||||
let {x, y, width, rmeta_x} = UNIT_COORDS[unit.i];
|
||||
let {x, y, width, sections} = UNIT_COORDS[unit.i];
|
||||
|
||||
HIT_BOXES.push({x: X_LINE+x, y:MARGIN+y, x2: X_LINE+x+width, y2: MARGIN+y+BOX_HEIGHT, i: unit.i});
|
||||
|
||||
@ -157,12 +179,12 @@ function render_pipeline_graph() {
|
||||
roundedRect(ctx, x, y, width, BOX_HEIGHT, RADIUS);
|
||||
ctx.fill();
|
||||
|
||||
if (unit.rmeta_time != null) {
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = BLOCK_COLOR;
|
||||
let ctime = unit.duration - unit.rmeta_time;
|
||||
roundedRect(ctx, rmeta_x, y, px_per_sec * ctime, BOX_HEIGHT, RADIUS);
|
||||
ctx.fill();
|
||||
for (const section of sections) {
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = get_section_color(section.name);
|
||||
roundedRect(ctx, section.start, y, section.width, BOX_HEIGHT, RADIUS);
|
||||
ctx.fill();
|
||||
presentSections.add(section.name);
|
||||
}
|
||||
ctx.fillStyle = TEXT_COLOR;
|
||||
ctx.textAlign = 'start';
|
||||
@ -178,6 +200,110 @@ function render_pipeline_graph() {
|
||||
draw_dep_lines(ctx, unit.i, false);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
// Draw a legend.
|
||||
ctx.save();
|
||||
ctx.translate(canvas_width - 200, MARGIN);
|
||||
|
||||
const legend_entries = [{
|
||||
name: "Frontend/rest",
|
||||
color: NOT_CUSTOM_BUILD_COLOR,
|
||||
line: false
|
||||
}];
|
||||
if (presentSections.has("codegen")) {
|
||||
legend_entries.push({
|
||||
name: "Codegen",
|
||||
color: CODEGEN_COLOR,
|
||||
line: false
|
||||
});
|
||||
}
|
||||
if (presentSections.has("link")) {
|
||||
legend_entries.push({
|
||||
name: "Linking",
|
||||
color: LINK_COLOR,
|
||||
line: false
|
||||
});
|
||||
}
|
||||
if (presentSections.has("other")) {
|
||||
legend_entries.push({
|
||||
name: "Other",
|
||||
color: OTHER_COLOR,
|
||||
line: false
|
||||
});
|
||||
}
|
||||
draw_legend(ctx, 160, legend_entries);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Draw a legend at the current position of the ctx.
|
||||
// entries should be an array of objects with the following scheme:
|
||||
// {
|
||||
// "name": <name of the legend entry> [string],
|
||||
// "color": <color of the legend entry> [string],
|
||||
// "line": <should the entry be a thin line or a rectangle> [bool]
|
||||
// }
|
||||
function draw_legend(ctx, width, entries) {
|
||||
const entry_height = 20;
|
||||
|
||||
// Add a bit of margin to the bottom and top
|
||||
const height = entries.length * entry_height + 4;
|
||||
|
||||
// Draw background
|
||||
ctx.fillStyle = BG_COLOR;
|
||||
ctx.strokeStyle = TEXT_COLOR;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'start';
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, width, height);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
// Dimension of a block
|
||||
const block_height = 15;
|
||||
const block_width = 30;
|
||||
|
||||
// Margin from the left edge
|
||||
const x_start = 5;
|
||||
// Width of the "mark" section (line/block)
|
||||
const mark_width = 45;
|
||||
|
||||
// Draw legend entries
|
||||
let y = 12;
|
||||
for (const entry of entries) {
|
||||
ctx.beginPath();
|
||||
|
||||
if (entry.line) {
|
||||
ctx.strokeStyle = entry.color;
|
||||
ctx.moveTo(x_start, y);
|
||||
ctx.lineTo(x_start + mark_width, y);
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.fillStyle = entry.color;
|
||||
ctx.fillRect(x_start + (mark_width - block_width) / 2, y - (block_height / 2), block_width, block_height);
|
||||
}
|
||||
|
||||
ctx.fillStyle = TEXT_COLOR;
|
||||
ctx.fillText(entry.name, x_start + mark_width + 4, y + 1);
|
||||
|
||||
y += entry_height;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the color of a section block based on the section name.
|
||||
function get_section_color(name) {
|
||||
if (name === "codegen") {
|
||||
return CODEGEN_COLOR;
|
||||
} else if (name === "link") {
|
||||
return LINK_COLOR;
|
||||
} else if (name === "other") {
|
||||
return OTHER_COLOR;
|
||||
} else {
|
||||
// We do not know what section this is, so just use the default color
|
||||
return NOT_CUSTOM_BUILD_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
// Draws lines from the given unit to the units it unlocks.
|
||||
@ -296,47 +422,23 @@ function render_timing_graph() {
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(canvas_width-200, MARGIN);
|
||||
// background
|
||||
ctx.fillStyle = BG_COLOR;
|
||||
ctx.strokeStyle = TEXT_COLOR;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.textAlign = 'start';
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, 150, 82);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = TEXT_COLOR;
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = 'red';
|
||||
ctx.moveTo(5, 10);
|
||||
ctx.lineTo(50, 10);
|
||||
ctx.stroke();
|
||||
ctx.fillText('Waiting', 54, 11);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = 'blue';
|
||||
ctx.moveTo(5, 30);
|
||||
ctx.lineTo(50, 30);
|
||||
ctx.stroke();
|
||||
ctx.fillText('Inactive', 54, 31);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = 'green';
|
||||
ctx.moveTo(5, 50);
|
||||
ctx.lineTo(50, 50);
|
||||
ctx.stroke();
|
||||
ctx.fillText('Active', 54, 51);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = cpuFillStyle
|
||||
ctx.fillRect(15, 60, 30, 15);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = TEXT_COLOR;
|
||||
ctx.fillText('CPU Usage', 54, 71);
|
||||
|
||||
draw_legend(ctx, 150, [{
|
||||
name: "Waiting",
|
||||
color: "red",
|
||||
line: true
|
||||
}, {
|
||||
name: "Inactive",
|
||||
color: "blue",
|
||||
line: true
|
||||
}, {
|
||||
name: "Active",
|
||||
color: "green",
|
||||
line: true
|
||||
}, {
|
||||
name: "CPU Usage",
|
||||
color: cpuFillStyle,
|
||||
line: false
|
||||
}]);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,6 @@ impl SectionData {
|
||||
}
|
||||
|
||||
/// Contains post-processed data of individual compilation sections.
|
||||
#[derive(serde::Serialize)]
|
||||
enum AggregatedSections {
|
||||
/// We know the names and durations of individual compilation sections
|
||||
Sections(Vec<(String, SectionData)>),
|
||||
@ -587,6 +586,7 @@ impl<'gctx> Timings<'gctx> {
|
||||
rmeta_time: Option<f64>,
|
||||
unlocked_units: Vec<usize>,
|
||||
unlocked_rmeta_units: Vec<usize>,
|
||||
sections: Option<Vec<(String, SectionData)>>,
|
||||
}
|
||||
let round = |x: f64| (x * 100.0).round() / 100.0;
|
||||
let unit_data: Vec<UnitData> = self
|
||||
@ -600,7 +600,6 @@ impl<'gctx> Timings<'gctx> {
|
||||
"todo"
|
||||
}
|
||||
.to_string();
|
||||
|
||||
// These filter on the unlocked units because not all unlocked
|
||||
// units are actually "built". For example, Doctest mode units
|
||||
// don't actually generate artifacts.
|
||||
@ -614,6 +613,33 @@ impl<'gctx> Timings<'gctx> {
|
||||
.iter()
|
||||
.filter_map(|unit| unit_map.get(unit).copied())
|
||||
.collect();
|
||||
let aggregated = ut.aggregate_sections();
|
||||
let sections = match aggregated {
|
||||
AggregatedSections::Sections(mut sections) => {
|
||||
// We draw the sections in the pipeline graph in a way where the frontend
|
||||
// section has the "default" build color, and then additional sections
|
||||
// (codegen, link) are overlayed on top with a different color.
|
||||
// However, there might be some time after the final (usually link) section,
|
||||
// which definitely shouldn't be classified as "Frontend". We thus try to
|
||||
// detect this situation and add a final "Other" section.
|
||||
if let Some((_, section)) = sections.last()
|
||||
&& section.end < ut.duration
|
||||
{
|
||||
sections.push((
|
||||
"other".to_string(),
|
||||
SectionData {
|
||||
start: section.end,
|
||||
end: ut.duration,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Some(sections)
|
||||
}
|
||||
AggregatedSections::OnlyMetadataTime { .. }
|
||||
| AggregatedSections::OnlyTotalDuration => None,
|
||||
};
|
||||
|
||||
UnitData {
|
||||
i,
|
||||
name: ut.unit.pkg.name().to_string(),
|
||||
@ -625,6 +651,7 @@ impl<'gctx> Timings<'gctx> {
|
||||
rmeta_time: ut.rmeta_time.map(round),
|
||||
unlocked_units,
|
||||
unlocked_rmeta_units,
|
||||
sections,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -871,7 +898,9 @@ static HTML_TMPL: &str = r#"
|
||||
--canvas-background: #f7f7f7;
|
||||
--canvas-axes: #303030;
|
||||
--canvas-grid: #e6e6e6;
|
||||
--canvas-block: #aa95e8;
|
||||
--canvas-codegen: #aa95e8;
|
||||
--canvas-link: #95e8aa;
|
||||
--canvas-other: #e895aa;
|
||||
--canvas-custom-build: #f0b165;
|
||||
--canvas-not-custom-build: #95cce8;
|
||||
--canvas-dep-line: #ddd;
|
||||
|
Loading…
x
Reference in New Issue
Block a user