mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
Switch rendering to canvas.
Also add some more features.
This commit is contained in:
parent
095f154f37
commit
77a47b3e32
437
src/cargo/core/compiler/timings.js
Normal file
437
src/cargo/core/compiler/timings.js
Normal file
@ -0,0 +1,437 @@
|
||||
// Position of the vertical axis.
|
||||
const X_LINE = 50;
|
||||
// General-use margin size.
|
||||
const MARGIN = 5;
|
||||
// Position of the horizontal axis, relative to the bottom.
|
||||
const Y_LINE = 35;
|
||||
// Minimum distance between time tick labels.
|
||||
const MIN_TICK_DIST = 50;
|
||||
// Radius for rounded rectangle corners.
|
||||
const RADIUS = 3;
|
||||
// Height of unit boxes.
|
||||
const BOX_HEIGHT = 25;
|
||||
// Distance between Y tick marks on the unit graph.
|
||||
const Y_TICK_DIST = BOX_HEIGHT + 2;
|
||||
// Rects used for mouseover detection.
|
||||
// Objects of {x, y, x2, y2, i} where `i` is the index into UNIT_DATA.
|
||||
let HIT_BOXES = [];
|
||||
// Index into UNIT_DATA of the last unit hovered over by mouse.
|
||||
let LAST_HOVER = null;
|
||||
// Key is unit index, value is {x, y, width, rmeta_x} of the box.
|
||||
let UNIT_COORDS = {};
|
||||
// Map of unit index to the index it was unlocked by.
|
||||
let REVERSE_UNIT_DEPS = {};
|
||||
let REVERSE_UNIT_RMETA_DEPS = {};
|
||||
for (let n=0; n<UNIT_DATA.length; n++) {
|
||||
let unit = UNIT_DATA[n];
|
||||
for (let unlocked of unit.unlocked_units) {
|
||||
REVERSE_UNIT_DEPS[unlocked] = n;
|
||||
}
|
||||
for (let unlocked of unit.unlocked_rmeta_units) {
|
||||
REVERSE_UNIT_RMETA_DEPS[unlocked] = n;
|
||||
}
|
||||
}
|
||||
|
||||
function render_pipeline_graph() {
|
||||
if (UNIT_DATA.length == 0) {
|
||||
return;
|
||||
}
|
||||
let g = document.getElementById('pipeline-graph');
|
||||
HIT_BOXES.length = 0;
|
||||
g.onmousemove = pipeline_mousemove;
|
||||
const min_time = document.getElementById('min-unit-time').valueAsNumber;
|
||||
|
||||
const units = UNIT_DATA.filter(unit => unit.duration >= min_time);
|
||||
|
||||
const graph_height = Y_TICK_DIST * units.length;
|
||||
const {ctx, width, height} = draw_graph_axes('pipeline-graph', graph_height);
|
||||
const container = document.getElementById('pipeline-container');
|
||||
container.style.width = width;
|
||||
container.style.height = height;
|
||||
const PX_PER_SEC = document.getElementById('scale').valueAsNumber;
|
||||
|
||||
// Canvas for hover highlights. This is a separate layer to improve performance.
|
||||
const linectx = setup_canvas('pipeline-graph-lines', width, height);
|
||||
linectx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw Y tick marks.
|
||||
for (let n=1; n<units.length; n++) {
|
||||
const y = graph_height - (n * Y_TICK_DIST);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(X_LINE, y);
|
||||
ctx.lineTo(X_LINE-5, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw Y labels.
|
||||
ctx.textAlign = 'end';
|
||||
for (let n=0; n<units.length; n++) {
|
||||
let y = MARGIN + (Y_TICK_DIST * (n + 1)) - 13;
|
||||
ctx.fillText(n+1, X_LINE-4, y);
|
||||
}
|
||||
|
||||
// Draw the graph.
|
||||
ctx.save();
|
||||
ctx.translate(X_LINE, MARGIN);
|
||||
|
||||
// Compute x,y coordinate of each block.
|
||||
UNIT_COORDS = {};
|
||||
for (i=0; i<units.length; i++) {
|
||||
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;
|
||||
}
|
||||
let width = Math.max(PX_PER_SEC * unit.duration, 1.0);
|
||||
UNIT_COORDS[unit.i] = {x, y, width, rmeta_x};
|
||||
}
|
||||
|
||||
// Draw the blocks.
|
||||
for (i=0; i<units.length; i++) {
|
||||
let unit = units[i];
|
||||
let {x, y, width, rmeta_x} = 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});
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = unit.mode == 'run-custom-build' ? '#f0b165' : '#95cce8';
|
||||
roundedRect(ctx, x, y, width, BOX_HEIGHT, RADIUS);
|
||||
ctx.fill();
|
||||
|
||||
if (unit.rmeta_time != null) {
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = '#aa95e8';
|
||||
let ctime = unit.duration - unit.rmeta_time;
|
||||
roundedRect(ctx, rmeta_x, y, PX_PER_SEC * ctime, BOX_HEIGHT, RADIUS);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.textAlign = 'start';
|
||||
ctx.textBaseline = 'hanging';
|
||||
ctx.font = '14px sans-serif';
|
||||
ctx.fillText(`${unit.name}${unit.target} ${unit.duration}s`, x + 5.0, y + BOX_HEIGHT / 2 - 6);
|
||||
draw_dep_lines(ctx, unit.i, false);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Draws lines from the given unit to the units it unlocks.
|
||||
function draw_dep_lines(ctx, unit_idx, highlighted) {
|
||||
const unit = UNIT_DATA[unit_idx];
|
||||
const {x, y, rmeta_x} = UNIT_COORDS[unit_idx];
|
||||
ctx.save();
|
||||
for (const unlocked of unit.unlocked_units) {
|
||||
draw_one_dep_line(ctx, x, y, unlocked, highlighted);
|
||||
}
|
||||
for (const unlocked of unit.unlocked_rmeta_units) {
|
||||
draw_one_dep_line(ctx, rmeta_x, y, unlocked, highlighted);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function draw_one_dep_line(ctx, from_x, from_y, to_unit, highlighted) {
|
||||
if (to_unit in UNIT_COORDS) {
|
||||
let {x: u_x, y: u_y} = UNIT_COORDS[to_unit];
|
||||
ctx.strokeStyle = highlighted ? '#000' : '#ddd';
|
||||
ctx.setLineDash([2]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(from_x, from_y+BOX_HEIGHT/2);
|
||||
ctx.lineTo(from_x-5, from_y+BOX_HEIGHT/2);
|
||||
ctx.lineTo(from_x-5, u_y+BOX_HEIGHT/2);
|
||||
ctx.lineTo(u_x, u_y+BOX_HEIGHT/2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function render_timing_graph() {
|
||||
if (CONCURRENCY_DATA.length == 0) {
|
||||
return;
|
||||
}
|
||||
const HEIGHT = 400;
|
||||
const AXIS_HEIGHT = HEIGHT - MARGIN - Y_LINE;
|
||||
const TOP_MARGIN = 10;
|
||||
const GRAPH_HEIGHT = AXIS_HEIGHT - TOP_MARGIN;
|
||||
|
||||
const {graph_width, ctx} = draw_graph_axes('timing-graph', AXIS_HEIGHT);
|
||||
|
||||
// Draw Y tick marks and labels.
|
||||
let max_v = 0;
|
||||
for (c of CONCURRENCY_DATA) {
|
||||
max_v = Math.max(max_v, c.active, c.waiting, c.inactive);
|
||||
}
|
||||
let [step, top] = split_ticks(max_v, GRAPH_HEIGHT / MIN_TICK_DIST);
|
||||
let num_ticks = top / step;
|
||||
let tick_dist = GRAPH_HEIGHT / num_ticks;
|
||||
ctx.textAlign = 'end';
|
||||
for (n=0; n<num_ticks; n++) {
|
||||
let y = HEIGHT - Y_LINE - ((n + 1) * tick_dist);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(X_LINE, y);
|
||||
ctx.lineTo(X_LINE-5, y);
|
||||
ctx.stroke();
|
||||
ctx.fillText((n+1) * step, X_LINE-10, y+5);
|
||||
}
|
||||
|
||||
// Label the Y axis.
|
||||
let label_y = (HEIGHT - Y_LINE) / 2;
|
||||
ctx.save();
|
||||
ctx.translate(15, label_y);
|
||||
ctx.rotate(3*Math.PI/2);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('# Units', 0, 0);
|
||||
ctx.restore();
|
||||
|
||||
// Draw the graph.
|
||||
ctx.save();
|
||||
ctx.translate(X_LINE, MARGIN);
|
||||
|
||||
function coord(t, v) {
|
||||
return {
|
||||
x: graph_width * (t/DURATION),
|
||||
y: TOP_MARGIN + GRAPH_HEIGHT * (1.0 - (v / max_v))
|
||||
};
|
||||
}
|
||||
function draw_line(style, key) {
|
||||
let first = CONCURRENCY_DATA[0];
|
||||
let last = coord(first.t, key(first));
|
||||
ctx.strokeStyle = style;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(last.x, last.y);
|
||||
for (let i=1; i<CONCURRENCY_DATA.length; i++) {
|
||||
let c = CONCURRENCY_DATA[i];
|
||||
let {x, y} = coord(c.t, key(c));
|
||||
ctx.lineTo(x, last.y);
|
||||
ctx.lineTo(x, y);
|
||||
last = {x, y};
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
draw_line('blue', function(c) {return c.inactive;});
|
||||
draw_line('red', function(c) {return c.waiting;});
|
||||
draw_line('green', function(c) {return c.active;});
|
||||
|
||||
// Draw a legend.
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(graph_width-120, MARGIN);
|
||||
// background
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.textAlign = 'start';
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, 120, 62);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#000'
|
||||
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.restore();
|
||||
}
|
||||
|
||||
function setup_canvas(id, width, height) {
|
||||
let g = document.getElementById(id);
|
||||
let dpr = window.devicePixelRatio || 1;
|
||||
g.width = width * dpr;
|
||||
g.height = height * dpr;
|
||||
g.style.width = width;
|
||||
g.style.height = height;
|
||||
let ctx = g.getContext('2d');
|
||||
ctx.scale(dpr, dpr);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function draw_graph_axes(id, graph_height) {
|
||||
const PX_PER_SEC = document.getElementById('scale').valueAsNumber;
|
||||
const graph_width = PX_PER_SEC * DURATION;
|
||||
const width = graph_width + X_LINE + 30;
|
||||
const height = graph_height + MARGIN + Y_LINE;
|
||||
let ctx = setup_canvas(id, width, height);
|
||||
ctx.fillStyle = '#f7f7f7';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.lineWidth = 2;
|
||||
ctx.font = '16px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
// Draw main axes.
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(X_LINE, MARGIN);
|
||||
ctx.lineTo(X_LINE, graph_height + MARGIN);
|
||||
ctx.lineTo(X_LINE+graph_width+20, graph_height + MARGIN);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw X tick marks.
|
||||
const tick_width = graph_width - 10;
|
||||
const [step, top] = split_ticks(DURATION, tick_width / MIN_TICK_DIST);
|
||||
const num_ticks = top / step;
|
||||
const tick_dist = tick_width / num_ticks;
|
||||
ctx.fillStyle = '#303030';
|
||||
for (let n=0; n<num_ticks; n++) {
|
||||
const x = X_LINE + ((n + 1) * tick_dist);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, height-Y_LINE);
|
||||
ctx.lineTo(x, height-Y_LINE+5);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillText(`${(n+1) * step}s`, x, height - Y_LINE + 20);
|
||||
}
|
||||
|
||||
// Draw vertical lines.
|
||||
ctx.strokeStyle = '#e6e6e6';
|
||||
ctx.setLineDash([2, 4]);
|
||||
for (n=0; n<num_ticks; n++) {
|
||||
const x = X_LINE + ((n + 1) * tick_dist);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, MARGIN);
|
||||
ctx.lineTo(x, MARGIN+graph_height);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.setLineDash([]);
|
||||
return {width, height, graph_width, graph_height, ctx};
|
||||
}
|
||||
|
||||
function round_up(n, step) {
|
||||
if (n % step == 0) {
|
||||
return n;
|
||||
} else {
|
||||
return (step - n % step) + n;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the `(step, max_value)` of the number of ticks along an axis.
|
||||
function split_ticks(n, max_ticks) {
|
||||
if (n <= max_ticks) {
|
||||
return [1, n];
|
||||
} else if (n <= max_ticks * 2) {
|
||||
return [2, round_up(n, 2)];
|
||||
} else if (n <= max_ticks * 4) {
|
||||
return [4, round_up(n, 4)];
|
||||
} else if (n <= max_ticks * 5) {
|
||||
return [5, round_up(n, 5)];
|
||||
} else {
|
||||
let step = 10;
|
||||
while (true) {
|
||||
let top = round_up(n, step);
|
||||
if (top <= max_ticks * step) {
|
||||
return [step, top];
|
||||
}
|
||||
step += 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function codegen_time(unit) {
|
||||
if (unit.rmeta_time == null) {
|
||||
return null;
|
||||
}
|
||||
let ctime = unit.duration - unit.rmeta_time;
|
||||
return [unit.rmeta_time, ctime];
|
||||
}
|
||||
|
||||
function roundedRect(ctx, x, y, width, height, r) {
|
||||
r = Math.min(r, width, height);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x+r, y);
|
||||
ctx.lineTo(x+width-r, y);
|
||||
ctx.arc(x+width-r, y+r, r, 3*Math.PI/2, 0);
|
||||
ctx.lineTo(x+width, y+height-r);
|
||||
ctx.arc(x+width-r, y+height-r, r, 0, Math.PI/2);
|
||||
ctx.lineTo(x+r, y+height);
|
||||
ctx.arc(x+r, y+height-r, r, Math.PI/2, Math.PI);
|
||||
ctx.lineTo(x, y-r);
|
||||
ctx.arc(x+r, y+r, r, Math.PI, 3*Math.PI/2);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
function pipeline_mouse_hit(event) {
|
||||
// This brute-force method can be optimized if needed.
|
||||
for (let box of HIT_BOXES) {
|
||||
if (event.offsetX >= box.x && event.offsetX <= box.x2 &&
|
||||
event.offsetY >= box.y && event.offsetY <= box.y2) {
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pipeline_mousemove(event) {
|
||||
// Highlight dependency lines on mouse hover.
|
||||
let box = pipeline_mouse_hit(event);
|
||||
if (box) {
|
||||
if (box.i != LAST_HOVER) {
|
||||
LAST_HOVER = box.i;
|
||||
let g = document.getElementById('pipeline-graph-lines');
|
||||
let ctx = g.getContext('2d');
|
||||
ctx.clearRect(0, 0, g.width, g.height);
|
||||
ctx.save();
|
||||
ctx.translate(X_LINE, MARGIN);
|
||||
ctx.lineWidth = 2;
|
||||
draw_dep_lines(ctx, box.i, true);
|
||||
|
||||
if (box.i in REVERSE_UNIT_DEPS) {
|
||||
const dep_unit = REVERSE_UNIT_DEPS[box.i];
|
||||
if (dep_unit in UNIT_COORDS) {
|
||||
const {x, y, rmeta_x} = UNIT_COORDS[dep_unit];
|
||||
draw_one_dep_line(ctx, x, y, box.i, true);
|
||||
}
|
||||
}
|
||||
if (box.i in REVERSE_UNIT_RMETA_DEPS) {
|
||||
const dep_unit = REVERSE_UNIT_RMETA_DEPS[box.i];
|
||||
if (dep_unit in UNIT_COORDS) {
|
||||
const {x, y, rmeta_x} = UNIT_COORDS[dep_unit];
|
||||
draw_one_dep_line(ctx, rmeta_x, y, box.i, true);
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_pipeline_graph();
|
||||
render_timing_graph();
|
||||
|
||||
// Set up and handle controls.
|
||||
{
|
||||
const range = document.getElementById('min-unit-time');
|
||||
const time_output = document.getElementById('min-unit-time-output');
|
||||
time_output.innerHTML = `${range.value}s`;
|
||||
range.oninput = event => {
|
||||
time_output.innerHTML = `${range.value}s`;
|
||||
render_pipeline_graph();
|
||||
};
|
||||
|
||||
const scale = document.getElementById('scale');
|
||||
const scale_output = document.getElementById('scale-output');
|
||||
scale_output.innerHTML = `${scale.value}`;
|
||||
scale.oninput = event => {
|
||||
scale_output.innerHTML = `${scale.value}`;
|
||||
render_pipeline_graph();
|
||||
render_timing_graph();
|
||||
};
|
||||
}
|
@ -7,10 +7,9 @@ use crate::core::compiler::BuildContext;
|
||||
use crate::core::PackageId;
|
||||
use crate::util::machine_message::{self, Message};
|
||||
use crate::util::{paths, CargoResult, Config};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
pub struct Timings<'a, 'cfg> {
|
||||
@ -66,6 +65,7 @@ struct UnitTime<'a> {
|
||||
}
|
||||
|
||||
/// Periodic concurrency tracking information.
|
||||
#[derive(serde::Serialize)]
|
||||
struct Concurrency {
|
||||
/// Time as an offset in seconds from `Timings::start`.
|
||||
t: f64,
|
||||
@ -245,18 +245,32 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
let duration = self.start.elapsed().as_secs() as u32 + 1;
|
||||
let timestamp = self.start_str.replace(&['-', ':'][..], "");
|
||||
let filename = format!("cargo-timing-{}.html", timestamp);
|
||||
let mut f = File::create(&filename)?;
|
||||
let mut f = BufWriter::new(File::create(&filename)?);
|
||||
let roots: Vec<&str> = self
|
||||
.root_targets
|
||||
.iter()
|
||||
.map(|(name, _targets)| name.as_str())
|
||||
.collect();
|
||||
f.write_all(HTML_TMPL.replace("{ROOTS}", &roots.join(", ")).as_bytes())?;
|
||||
self.fmt_summary_table(&mut f, duration)?;
|
||||
let graph_width = self.fmt_pipeline_graph(&mut f, duration)?;
|
||||
self.fmt_timing_graph(&mut f, graph_width, duration)?;
|
||||
self.fmt_unit_table(&mut f)?;
|
||||
f.write_all(HTML_TMPL_FOOT.as_bytes())?;
|
||||
self.write_summary_table(&mut f, duration)?;
|
||||
f.write_all(HTML_CANVAS.as_bytes())?;
|
||||
self.write_unit_table(&mut f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"<script>\n\
|
||||
DURATION = {};",
|
||||
duration
|
||||
)?;
|
||||
self.write_js_data(&mut f)?;
|
||||
write!(
|
||||
f,
|
||||
"{}\n\
|
||||
</script>\n\
|
||||
</body>\n\
|
||||
</html>\n\
|
||||
",
|
||||
include_str!("timings.js")
|
||||
)?;
|
||||
drop(f);
|
||||
let msg = format!(
|
||||
"report saved to {}",
|
||||
@ -273,7 +287,7 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
}
|
||||
|
||||
/// Render the summary table.
|
||||
fn fmt_summary_table(&self, f: &mut File, duration: u32) -> CargoResult<()> {
|
||||
fn write_summary_table(&self, f: &mut impl Write, duration: u32) -> CargoResult<()> {
|
||||
let targets: Vec<String> = self
|
||||
.root_targets
|
||||
.iter()
|
||||
@ -334,222 +348,78 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the box graph of the units over time.
|
||||
fn fmt_pipeline_graph(&self, f: &mut File, duration: u32) -> CargoResult<u32> {
|
||||
if self.unit_times.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
const BOX_HEIGHT: u32 = 25;
|
||||
const Y_TICK_DIST: u32 = BOX_HEIGHT + 2;
|
||||
|
||||
let graph_height = Y_TICK_DIST * self.unit_times.len() as u32;
|
||||
|
||||
let graph_width = draw_graph_axes(f, graph_height, duration)?;
|
||||
|
||||
// Draw Y tick marks.
|
||||
write!(f, "<path class=\"graph-axes\" d=\"")?;
|
||||
for n in 1..self.unit_times.len() as u32 {
|
||||
let y = graph_height - (n * Y_TICK_DIST);
|
||||
write!(f, "M {} {} l -5 0 ", X_LINE, y)?;
|
||||
}
|
||||
writeln!(f, "\" />")?;
|
||||
|
||||
// Draw Y labels.
|
||||
for n in 0..self.unit_times.len() as u32 {
|
||||
let y = MARGIN + (Y_TICK_DIST * (n + 1)) - 13;
|
||||
writeln!(
|
||||
f,
|
||||
r#"<text x="{}" y="{}" class="graph-label-v">{}</text>"#,
|
||||
X_LINE - 4,
|
||||
y,
|
||||
n + 1
|
||||
)?;
|
||||
}
|
||||
|
||||
// Draw the graph.
|
||||
writeln!(
|
||||
f,
|
||||
r#"<svg x="{}" y="{}" width="{}" height="{}">"#,
|
||||
X_LINE, MARGIN, graph_width, graph_height
|
||||
)?;
|
||||
|
||||
// Create a map that will be used for drawing dependency lines.
|
||||
let unit_map: HashMap<&Unit<'_>, (f64, u32)> = self
|
||||
fn write_js_data(&self, f: &mut impl Write) -> CargoResult<()> {
|
||||
// Create a map to link indices of unlocked units.
|
||||
let unit_map: HashMap<Unit<'_>, usize> = self
|
||||
.unit_times
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, unit)| {
|
||||
let y = i as u32 * Y_TICK_DIST + 1;
|
||||
let x = PX_PER_SEC * unit.start;
|
||||
(&unit.unit, (x, y))
|
||||
.map(|(i, ut)| (ut.unit, i))
|
||||
.collect();
|
||||
#[derive(serde::Serialize)]
|
||||
struct UnitData {
|
||||
i: usize,
|
||||
name: String,
|
||||
version: String,
|
||||
mode: String,
|
||||
target: String,
|
||||
start: f64,
|
||||
duration: f64,
|
||||
rmeta_time: Option<f64>,
|
||||
unlocked_units: Vec<usize>,
|
||||
unlocked_rmeta_units: Vec<usize>,
|
||||
}
|
||||
let round = |x: f64| (x * 100.0).round() / 100.0;
|
||||
let unit_data: Vec<UnitData> = self
|
||||
.unit_times
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ut)| {
|
||||
let mode = if ut.unit.mode.is_run_custom_build() {
|
||||
"run-custom-build"
|
||||
} else {
|
||||
"todo"
|
||||
}
|
||||
.to_string();
|
||||
let unlocked_units: Vec<usize> = ut
|
||||
.unlocked_units
|
||||
.iter()
|
||||
.map(|unit| unit_map[unit])
|
||||
.collect();
|
||||
let unlocked_rmeta_units: Vec<usize> = ut
|
||||
.unlocked_rmeta_units
|
||||
.iter()
|
||||
.map(|unit| unit_map[unit])
|
||||
.collect();
|
||||
UnitData {
|
||||
i,
|
||||
name: ut.unit.pkg.name().to_string(),
|
||||
version: ut.unit.pkg.version().to_string(),
|
||||
mode,
|
||||
target: ut.target.clone(),
|
||||
start: round(ut.start),
|
||||
duration: round(ut.duration),
|
||||
rmeta_time: ut.rmeta_time.map(|t| round(t)),
|
||||
unlocked_units,
|
||||
unlocked_rmeta_units,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (i, unit) in self.unit_times.iter().enumerate() {
|
||||
let (x, y) = unit_map[&unit.unit];
|
||||
let width = (PX_PER_SEC * unit.duration).max(1.0);
|
||||
|
||||
let dep_class = format!("dep-{}", i);
|
||||
let class = if unit.unit.mode.is_run_custom_build() {
|
||||
"unit-block-custom"
|
||||
} else {
|
||||
"unit-block"
|
||||
};
|
||||
writeln!(
|
||||
f,
|
||||
" <rect x=\"{:.1}\" y=\"{}\" width=\"{:.1}\" height=\"{}\" \
|
||||
rx=\"3\" class=\"{}\" data-dep-class=\"{}\" />",
|
||||
x, y, width, BOX_HEIGHT, class, dep_class,
|
||||
)?;
|
||||
let draw_dep_lines = |f: &mut File, x, units| -> CargoResult<()> {
|
||||
for unlocked in units {
|
||||
let (u_x, u_y) = unit_map[&unlocked];
|
||||
writeln!(
|
||||
f,
|
||||
" <path class=\"{} dep-line\" d=\"M {:.1} {} l -5 0 l 0 {} l {:.1} 0\" />",
|
||||
dep_class,
|
||||
x,
|
||||
y + BOX_HEIGHT / 2,
|
||||
u_y - y,
|
||||
u_x - x + 5.0,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
if let Some((rmeta_time, ctime, _cent)) = unit.codegen_time() {
|
||||
let rmeta_x = x + PX_PER_SEC * rmeta_time;
|
||||
writeln!(
|
||||
f,
|
||||
" <rect x=\"{:.1}\" y=\"{}\" width=\"{:.1}\" \
|
||||
height=\"{}\" rx=\"3\" class=\"unit-block-codegen\"/>",
|
||||
rmeta_x,
|
||||
y,
|
||||
PX_PER_SEC * ctime,
|
||||
BOX_HEIGHT,
|
||||
)?;
|
||||
draw_dep_lines(f, rmeta_x, &unit.unlocked_rmeta_units)?;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
" <text x=\"{:.1}\" y=\"{}\" class=\"unit-label\">{}{} {:.1}s</text>",
|
||||
x + 5.0,
|
||||
y + BOX_HEIGHT / 2 - 5,
|
||||
unit.unit.pkg.name(),
|
||||
unit.target,
|
||||
unit.duration
|
||||
)?;
|
||||
draw_dep_lines(f, x + width, &unit.unlocked_units)?;
|
||||
}
|
||||
writeln!(f, r#"</svg>"#)?;
|
||||
writeln!(f, r#"</svg>"#)?;
|
||||
Ok(graph_width)
|
||||
}
|
||||
|
||||
/// Render the line chart of concurrency information.
|
||||
fn fmt_timing_graph(&self, f: &mut File, graph_width: u32, duration: u32) -> CargoResult<()> {
|
||||
if graph_width == 0 || self.concurrency.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
const HEIGHT: u32 = 400;
|
||||
const AXIS_HEIGHT: u32 = HEIGHT - MARGIN - Y_LINE;
|
||||
const TOP_MARGIN: u32 = 10;
|
||||
const GRAPH_HEIGHT: u32 = AXIS_HEIGHT - TOP_MARGIN;
|
||||
|
||||
draw_graph_axes(f, AXIS_HEIGHT, duration)?;
|
||||
|
||||
// Draw Y tick marks and labels.
|
||||
write!(f, "<path class=\"graph-axes\" d=\"")?;
|
||||
let max_v = self
|
||||
.concurrency
|
||||
.iter()
|
||||
.map(|c| max(max(c.active, c.waiting), c.inactive))
|
||||
.max()
|
||||
.unwrap();
|
||||
let (step, top) = split_ticks(max_v as u32, GRAPH_HEIGHT / MIN_TICK_DIST);
|
||||
let num_ticks = top / step;
|
||||
let tick_dist = GRAPH_HEIGHT / num_ticks;
|
||||
let mut labels = String::new();
|
||||
|
||||
for n in 0..num_ticks {
|
||||
let y = HEIGHT - Y_LINE - ((n + 1) * tick_dist);
|
||||
write!(f, "M {} {} l -5 0 ", X_LINE, y)?;
|
||||
labels.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"graph-label-v\">{}</text>\n",
|
||||
X_LINE - 10,
|
||||
y + 5,
|
||||
(n + 1) * step
|
||||
));
|
||||
}
|
||||
writeln!(f, "\"/>")?;
|
||||
f.write_all(labels.as_bytes())?;
|
||||
|
||||
// Label the Y axis.
|
||||
let label_y = (HEIGHT - Y_LINE) / 2;
|
||||
writeln!(
|
||||
f,
|
||||
"<text x=\"15\", y=\"{}\" \
|
||||
class=\"graph-label-v\" transform=\"rotate(-90, 15, {})\"># Units</text>",
|
||||
label_y, label_y
|
||||
"const UNIT_DATA = {};",
|
||||
serde_json::to_string_pretty(&unit_data)?
|
||||
)?;
|
||||
|
||||
// Draw the graph.
|
||||
writeln!(
|
||||
f,
|
||||
r#"<svg x="{}" y="{}" width="{}" height="{}">"#,
|
||||
X_LINE,
|
||||
MARGIN,
|
||||
graph_width,
|
||||
GRAPH_HEIGHT + TOP_MARGIN
|
||||
"const CONCURRENCY_DATA = {};",
|
||||
serde_json::to_string_pretty(&self.concurrency)?
|
||||
)?;
|
||||
|
||||
let coord = |t, v| {
|
||||
(
|
||||
f64::from(graph_width) * (t / f64::from(duration)),
|
||||
f64::from(TOP_MARGIN) + f64::from(GRAPH_HEIGHT) * (1.0 - (v as f64 / max_v as f64)),
|
||||
)
|
||||
};
|
||||
let mut draw_line = |class, key: fn(&Concurrency) -> usize| {
|
||||
write!(f, "<polyline points=\"")?;
|
||||
let first = &self.concurrency[0];
|
||||
let mut last = coord(first.t, key(first));
|
||||
for c in &self.concurrency {
|
||||
let (x, y) = coord(c.t, key(c));
|
||||
write!(f, "{:.1},{:.1} {:.1},{:.1} ", x, last.1, x, y)?;
|
||||
last = (x, y);
|
||||
}
|
||||
writeln!(f, "\" class=\"{}\" />", class)
|
||||
};
|
||||
|
||||
draw_line("line-inactive", |c| c.inactive)?;
|
||||
draw_line("line-waiting", |c| c.waiting)?;
|
||||
draw_line("line-active", |c| c.active)?;
|
||||
|
||||
// Draw a legend.
|
||||
write!(
|
||||
f,
|
||||
r#"
|
||||
<svg x="{}" y="20" width="100" height="62">
|
||||
<rect width="100%" height="100%" fill="white" stroke="black" stroke-width="1" />
|
||||
<line x1="5" y1="10" x2="50" y2="10" stroke="red" stroke-width="2"/>
|
||||
<text x="54" y="11" dominant-baseline="middle" font-size="12px">Waiting</text>
|
||||
|
||||
<line x1="5" y1="50" x2="50" y2="50" stroke="green" stroke-width="2"/>
|
||||
<text x="54" y="51" dominant-baseline="middle" font-size="12px">Active</text>
|
||||
|
||||
<line x1="5" y1="30" x2="50" y2="30" stroke="blue" stroke-width="2"/>
|
||||
<text x="54" y="31" dominant-baseline="middle" font-size="12px">Inactive</text>
|
||||
</svg>
|
||||
"#,
|
||||
graph_width - 120
|
||||
)?;
|
||||
|
||||
writeln!(f, "</svg>")?;
|
||||
writeln!(f, "</svg>")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the table of all units.
|
||||
fn fmt_unit_table(&self, f: &mut File) -> CargoResult<()> {
|
||||
fn write_unit_table(&self, f: &mut impl Write) -> CargoResult<()> {
|
||||
write!(
|
||||
f,
|
||||
r#"
|
||||
@ -560,6 +430,7 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
<th>Unit</th>
|
||||
<th>Total</th>
|
||||
<th>Codegen</th>
|
||||
<th>Features</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -572,6 +443,7 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
None => "".to_string(),
|
||||
Some((_rt, ctime, cent)) => format!("{:.1}s ({:.0}%)", ctime, cent),
|
||||
};
|
||||
let features = unit.unit.features.join(", ");
|
||||
write!(
|
||||
f,
|
||||
r#"
|
||||
@ -580,13 +452,15 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
|
||||
<td>{}{}</td>
|
||||
<td>{:.1}s</td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
</tr>
|
||||
"#,
|
||||
i + 1,
|
||||
unit.name_ver(),
|
||||
unit.target,
|
||||
unit.duration,
|
||||
codegen
|
||||
codegen,
|
||||
features,
|
||||
)?;
|
||||
}
|
||||
write!(f, "</tbody>\n</table>\n")?;
|
||||
@ -614,96 +488,6 @@ fn d_as_f64(d: Duration) -> f64 {
|
||||
(d.as_secs() as f64) + f64::from(d.subsec_nanos()) / 1_000_000_000.0
|
||||
}
|
||||
|
||||
fn round_up(n: u32, step: u32) -> u32 {
|
||||
if n % step == 0 {
|
||||
n
|
||||
} else {
|
||||
(step - n % step) + n
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the `(step, max_value)` of the number of ticks along an axis.
|
||||
fn split_ticks(n: u32, max_ticks: u32) -> (u32, u32) {
|
||||
if n <= max_ticks {
|
||||
(1, n)
|
||||
} else if n <= max_ticks * 2 {
|
||||
(2, round_up(n, 2))
|
||||
} else if n <= max_ticks * 4 {
|
||||
(4, round_up(n, 4))
|
||||
} else if n <= max_ticks * 5 {
|
||||
(5, round_up(n, 5))
|
||||
} else {
|
||||
let mut step = 10;
|
||||
loop {
|
||||
let top = round_up(n, step);
|
||||
if top <= max_ticks * step {
|
||||
break (step, top);
|
||||
}
|
||||
step += 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const X_LINE: u32 = 50;
|
||||
const MARGIN: u32 = 5;
|
||||
const Y_LINE: u32 = 35; // relative to bottom
|
||||
const PX_PER_SEC: f64 = 20.0;
|
||||
const MIN_TICK_DIST: u32 = 50;
|
||||
|
||||
fn draw_graph_axes(f: &mut File, graph_height: u32, duration: u32) -> CargoResult<u32> {
|
||||
let graph_width = PX_PER_SEC as u32 * duration;
|
||||
let width = graph_width + X_LINE + 30;
|
||||
let height = graph_height + MARGIN + Y_LINE;
|
||||
writeln!(
|
||||
f,
|
||||
r#"<svg width="{}" height="{}" class="graph">"#,
|
||||
width, height
|
||||
)?;
|
||||
|
||||
// Draw axes.
|
||||
write!(
|
||||
f,
|
||||
"<path class=\"graph-axes\" d=\"\
|
||||
M {} {} \
|
||||
l 0 {} \
|
||||
l {} 0 ",
|
||||
X_LINE,
|
||||
MARGIN,
|
||||
graph_height,
|
||||
graph_width + 20
|
||||
)?;
|
||||
|
||||
// Draw X tick marks.
|
||||
let tick_width = graph_width - 10;
|
||||
let (step, top) = split_ticks(duration, tick_width / MIN_TICK_DIST);
|
||||
let num_ticks = top / step;
|
||||
let tick_dist = tick_width / num_ticks;
|
||||
let mut labels = String::new();
|
||||
for n in 0..num_ticks {
|
||||
let x = X_LINE + ((n + 1) * tick_dist);
|
||||
write!(f, "M {} {} l 0 5 ", x, height - Y_LINE)?;
|
||||
labels.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"graph-label-h\">{}s</text>\n",
|
||||
x,
|
||||
height - Y_LINE + 20,
|
||||
(n + 1) * step
|
||||
));
|
||||
}
|
||||
|
||||
writeln!(f, "\" />")?;
|
||||
f.write_all(labels.as_bytes())?;
|
||||
|
||||
// Draw vertical lines.
|
||||
write!(f, "<path class=\"vert-line\" d=\"")?;
|
||||
for n in 0..num_ticks {
|
||||
let x = X_LINE + ((n + 1) * tick_dist);
|
||||
write!(f, "M {} {} l 0 {} ", x, MARGIN, graph_height)?;
|
||||
}
|
||||
writeln!(f, "\" />")?;
|
||||
|
||||
Ok(graph_width)
|
||||
}
|
||||
|
||||
fn render_rustc_info(bcx: &BuildContext<'_, '_>) -> String {
|
||||
let version = bcx
|
||||
.rustc
|
||||
@ -731,58 +515,21 @@ static HTML_TMPL: &str = r#"
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
svg {
|
||||
|
||||
.canvas-container {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
h1 {
|
||||
border-bottom: 1px solid #c0c0c0;
|
||||
}
|
||||
.unit-label {
|
||||
font-size: 12px;
|
||||
dominant-baseline: hanging;
|
||||
}
|
||||
|
||||
.unit-block {
|
||||
fill: #95cce8;
|
||||
}
|
||||
|
||||
.unit-block-custom {
|
||||
fill: #f0b165;
|
||||
}
|
||||
|
||||
.unit-block-codegen {
|
||||
fill: #aa95e8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.graph {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.graph-label-v {
|
||||
text-anchor: end;
|
||||
fill: #303030;
|
||||
}
|
||||
|
||||
.graph-label-h {
|
||||
text-anchor: middle;
|
||||
fill: #303030;
|
||||
}
|
||||
|
||||
.graph-axes {
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
.vert-line {
|
||||
fill: none;
|
||||
stroke: #c0c0c0;
|
||||
stroke-dasharray: 2,4
|
||||
}
|
||||
|
||||
.my-table {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
@ -857,34 +604,8 @@ h1 {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.line-waiting {
|
||||
fill: none;
|
||||
stroke: red;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.line-active {
|
||||
fill: none;
|
||||
stroke: green;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.line-inactive {
|
||||
fill: none;
|
||||
stroke: blue;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.dep-line {
|
||||
fill: none;
|
||||
stroke: #ddd;
|
||||
stroke-dasharray: 2;
|
||||
}
|
||||
|
||||
.dep-line-highlight {
|
||||
stroke: #3e3e3e;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 4;
|
||||
.input-table td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -894,21 +615,27 @@ h1 {
|
||||
<h1>Cargo Build Timings</h1>
|
||||
"#;
|
||||
|
||||
static HTML_TMPL_FOOT: &str = r#"
|
||||
<script>
|
||||
function show_deps(event) {
|
||||
for (const el of document.getElementsByClassName('dep-line')) {
|
||||
el.classList.remove('dep-line-highlight');
|
||||
}
|
||||
for (const el of document.getElementsByClassName(event.currentTarget.dataset.depClass)) {
|
||||
el.classList.add('dep-line-highlight');
|
||||
}
|
||||
}
|
||||
static HTML_CANVAS: &str = r#"
|
||||
<table class="input-table">
|
||||
<tr>
|
||||
<td><label for="min-unit-time">Min unit time:</label></td>
|
||||
<td><label for="scale">Scale:</label></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="range" min="0" max="30" value="0" id="min-unit-time"></td>
|
||||
<td><input type="range" min="1" max="50" value="20" id="scale"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><output for="min-unit-time" id="min-unit-time-output"></output></td>
|
||||
<td><output for="scale" id="scale-output"></output></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
for (const el of document.getElementsByClassName('unit-block')) {
|
||||
el.onmouseover = show_deps;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<div id="pipeline-container" class="canvas-container">
|
||||
<canvas id="pipeline-graph" class="graph" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
|
||||
<canvas id="pipeline-graph-lines" style="position: absolute; left: 0; top: 0; z-index: 1; pointer-events:none;"></canvas>
|
||||
</div>
|
||||
<div class="canvas-container">
|
||||
<canvas id="timing-graph" class="graph"></canvas>
|
||||
</div>
|
||||
"#;
|
||||
|
Loading…
x
Reference in New Issue
Block a user