mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 23:06:32 +00:00
223 lines
6.8 KiB
JavaScript
223 lines
6.8 KiB
JavaScript
const codeCoverageDiv = document.querySelector("#code-coverage");
|
|
const flameGraphDiv = document.querySelector("#flame-graph");
|
|
|
|
function drawText(text, codeCoverageData) {
|
|
const tooltip = document.createElement("div");
|
|
tooltip.style.position = "fixed";
|
|
document.body.appendChild(tooltip);
|
|
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
|
|
const charEntries = {};
|
|
const elements = [];
|
|
let line = 1;
|
|
let col = 1;
|
|
for (let index = 0; index < text.length; ++index) {
|
|
if (text[index] == "\n") {
|
|
col = 1;
|
|
line += 1;
|
|
elements.push("\n");
|
|
continue;
|
|
}
|
|
const entry = entries.find((entry) => index >= entry.index);
|
|
charEntries[`${line}-${col}`] = entry;
|
|
|
|
const color = (ratio) =>
|
|
`rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
|
|
|
|
const span = document.createElement("span");
|
|
span.style.backgroundColor = color(Math.min(entry.covers / 25, 1));
|
|
span.innerText = text[index];
|
|
span.addEventListener("mouseover", (event) => {
|
|
tooltip.style.display = "block";
|
|
tooltip.style.left = `${event.clientX + 20}px`;
|
|
tooltip.style.top = `${event.clientY + 20}px`;
|
|
tooltip.innerText = `Ran ${entry.covers} time${
|
|
entry.covers !== 1 ? "s" : ""
|
|
}`;
|
|
});
|
|
elements.push(span);
|
|
col += 1;
|
|
}
|
|
|
|
testytestytesty.append(...elements);
|
|
}
|
|
|
|
function loadCodeCoverage(text, codeCoverageData) {
|
|
codeCoverageDiv.innerHTML = `
|
|
<canvas id="code-coverage-canvas"></canvas>
|
|
<pre><code>${text}</code></pre>
|
|
<span id="covers-tooltip" hidden></span>
|
|
`;
|
|
|
|
/** @type { HTMLCanvasElement } */
|
|
const canvas = document.querySelector("#code-coverage-canvas");
|
|
canvas.width = 1000;
|
|
canvas.height = 500;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
ctx.font = "20px monospace";
|
|
|
|
const { width: chWidth } = ctx.measureText("-");
|
|
const chHeight = 26;
|
|
|
|
const color = (ratio) =>
|
|
`rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
|
|
|
|
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
|
|
|
|
const charEntries = {};
|
|
|
|
let line = 1;
|
|
let col = 1;
|
|
for (let index = 0; index < text.length; ++index) {
|
|
if (text[index] == "\n") {
|
|
col = 1;
|
|
line += 1;
|
|
continue;
|
|
}
|
|
const entry = entries.find((entry) => index >= entry.index);
|
|
charEntries[`${line}-${col}`] = entry;
|
|
ctx.fillStyle = color(Math.min(entry.covers / 25, 1));
|
|
ctx.fillRect(
|
|
(col - 1) * chWidth,
|
|
(line - 1) * chHeight,
|
|
chWidth,
|
|
chHeight,
|
|
);
|
|
col += 1;
|
|
}
|
|
|
|
const tooltip = document.getElementById("covers-tooltip");
|
|
|
|
canvas.addEventListener("mousemove", (e) => {
|
|
const col = Math.floor(e.offsetX / chWidth + 1);
|
|
const line = Math.floor(e.offsetY / chHeight + 1);
|
|
const key = `${line}-${col}`;
|
|
if (!(key in charEntries)) {
|
|
tooltip.hidden = true;
|
|
return;
|
|
}
|
|
const entry = charEntries[key];
|
|
tooltip.innerText = `Ran ${entry.covers} time${
|
|
entry.covers !== 1 ? "s" : ""
|
|
}`;
|
|
tooltip.style.left = `${e.clientX + 20}px`;
|
|
tooltip.style.top = `${e.clientY + 20}px`;
|
|
tooltip.hidden = false;
|
|
});
|
|
canvas.addEventListener("mouseleave", () => {
|
|
tooltip.hidden = true;
|
|
});
|
|
}
|
|
|
|
function loadFlameGraph(flameGraphData, fnNames) {
|
|
flameGraphDiv.innerHTML = `
|
|
<canvas id="flame-graph-canvas"></canvas>
|
|
<span id="flame-graph-tooltip" hidden></span>
|
|
`;
|
|
|
|
/** @type { HTMLCanvasElement } */
|
|
const canvas = document.querySelector("#flame-graph-canvas");
|
|
canvas.width = 1000;
|
|
canvas.height = 500;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.font = "16px monospace";
|
|
|
|
const nodes = [];
|
|
|
|
function calculateNodeRects(node, depth, totalAcc, offsetAcc) {
|
|
const x = (offsetAcc / totalAcc) * canvas.width;
|
|
const y = canvas.height - 30 * depth - 30;
|
|
const w = ((node.acc + 1) / totalAcc) * canvas.width;
|
|
const h = 30;
|
|
|
|
const title = fnNames[node.fn];
|
|
const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
|
|
nodes.push({ x, y, w, h, title, percent });
|
|
|
|
const totalChildrenAcc = node.children.reduce(
|
|
(acc, child) => acc + child.acc,
|
|
0,
|
|
);
|
|
let newOffsetAcc = offsetAcc + (node.acc - totalChildrenAcc) / 2;
|
|
for (const child of node.children) {
|
|
calculateNodeRects(child, depth + 1, totalAcc, newOffsetAcc);
|
|
newOffsetAcc += child.acc;
|
|
}
|
|
}
|
|
calculateNodeRects(flameGraphData, 0, flameGraphData.acc, 0);
|
|
|
|
for (const node of nodes) {
|
|
const { x, y, w, h, title } = node;
|
|
ctx.fillStyle = "rgb(255, 125, 0)";
|
|
ctx.fillRect(
|
|
x + 1,
|
|
y + 1,
|
|
w - 2,
|
|
h - 2,
|
|
);
|
|
ctx.fillStyle = "black";
|
|
ctx.fillText(
|
|
title,
|
|
(x + (w - 10) / 2 - ctx.measureText(title).width / 2) + 5,
|
|
y + 20,
|
|
);
|
|
}
|
|
|
|
const tooltip = document.getElementById("flame-graph-tooltip");
|
|
|
|
canvas.addEventListener("mousemove", (e) => {
|
|
const x = e.offsetX;
|
|
const y = e.offsetY;
|
|
const node = nodes.find((node) =>
|
|
x >= node.x && x < node.x + node.w && y >= node.y &&
|
|
y < node.y + node.h
|
|
);
|
|
|
|
if (!node) {
|
|
tooltip.hidden = true;
|
|
return;
|
|
}
|
|
tooltip.innerText = `${node.title} ${node.percent}`;
|
|
tooltip.style.left = `${e.clientX + 20}px`;
|
|
tooltip.style.top = `${e.clientY + 20}px`;
|
|
tooltip.hidden = false;
|
|
});
|
|
canvas.addEventListener("mouseleave", () => {
|
|
tooltip.hidden = true;
|
|
});
|
|
}
|
|
|
|
const codeData = `\
|
|
fn add(a, b) {
|
|
+ a b
|
|
}
|
|
|
|
let result = 0;
|
|
let i = 0;
|
|
loop {
|
|
if >= i 10 {
|
|
break;
|
|
}
|
|
result = add(result, 5);
|
|
i = + i 1;
|
|
}
|
|
`;
|
|
|
|
const codeCoverageData = JSON.parse(
|
|
`[{"index":0,"line":1,"col":1,"covers":2},{"index":28,"line":5,"col":1,"covers":1},{"index":44,"line":6,"col":1,"covers":1},{"index":55,"line":7,"col":1,"covers":1},{"index":66,"line":8,"col":5,"covers":11},{"index":104,"line":11,"col":5,"covers":10},{"index":19,"line":2,"col":5,"covers":10},{"index":133,"line":12,"col":5,"covers":10},{"index":87,"line":9,"col":9,"covers":1}]`,
|
|
);
|
|
|
|
const flameGraphData = JSON.parse(
|
|
`{"fn":0,"acc":257,"parent":0,"children":[{"fn":18,"acc":251,"parent":0,"children":[{"fn":12,"acc":30,"parent":1,"children":[]}]}]}`,
|
|
);
|
|
|
|
drawText(codeData, codeCoverageData);
|
|
loadCodeCoverage(codeData, codeCoverageData);
|
|
loadFlameGraph(flameGraphData, {
|
|
0: "<entry>",
|
|
12: "add",
|
|
18: "main",
|
|
});
|