summaryrefslogtreecommitdiff
path: root/internal/flamegraph
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-27 17:11:05 +0200
committerPaul Buetow <paul@buetow.org>2026-02-27 17:11:05 +0200
commit5f3d3c71650c23b66b347e748263c9759d3be2b5 (patch)
tree47ba4dc699c18df129b16e93718fe96e60bda380 /internal/flamegraph
parent28fdc31b710dc13b99b38e9d7f8726c5eab70866 (diff)
flamegraph: port frame color and layout logic to live JS
Diffstat (limited to 'internal/flamegraph')
-rw-r--r--internal/flamegraph/livehtml.go87
1 files changed, 82 insertions, 5 deletions
diff --git a/internal/flamegraph/livehtml.go b/internal/flamegraph/livehtml.go
index d2324d4..15ad7d6 100644
--- a/internal/flamegraph/livehtml.go
+++ b/internal/flamegraph/livehtml.go
@@ -116,15 +116,92 @@ const liveHTML = `<!doctype html>
<script>
(function () {
- var paused = false;
+ var fg = {
+ paused: false,
+ cfg: {
+ width: 1200,
+ frameHeight: 16,
+ fontSize: 12,
+ minWidthPx: 1.0
+ }
+ };
+
+ function fgFrameColor(name) {
+ var bytes = new TextEncoder().encode(name || "");
+ var h = 2166136261 >>> 0;
+ for (var i = 0; i < bytes.length; i++) {
+ h ^= bytes[i];
+ h = Math.imul(h, 16777619) >>> 0;
+ }
+ var r = 200 + (h % 35);
+ var g = 80 + ((h >>> 8) % 120);
+ var b = 40 + ((h >>> 16) % 90);
+ return "rgb(" + r + "," + g + "," + b + ")";
+ }
+
+ function fgMaxDepth(node, depth) {
+ if (!node || !Array.isArray(node.c) || node.c.length === 0) {
+ return depth;
+ }
+ var maxDepth = depth;
+ for (var i = 0; i < node.c.length; i++) {
+ var childDepth = fgMaxDepth(node.c[i], depth + 1);
+ if (childDepth > maxDepth) {
+ maxDepth = childDepth;
+ }
+ }
+ return maxDepth;
+ }
+
+ function fgBuildFrames(node, rootTotal, x, depth, canvasHeight, isRoot, out) {
+ if (!node || rootTotal <= 0) {
+ return;
+ }
+ if (!isRoot) {
+ var w = fg.cfg.width * (Number(node.t || 0) / Number(rootTotal));
+ if (w < fg.cfg.minWidthPx) {
+ return;
+ }
+ var y = canvasHeight - ((depth + 1) * fg.cfg.frameHeight);
+ var total = Number(node.t || 0);
+ var pct = 100 * total / Number(rootTotal);
+ out.push({
+ name: node.n || "",
+ x: x,
+ y: y,
+ w: w,
+ h: fg.cfg.frameHeight - 1,
+ depth: depth,
+ total: total,
+ pct: pct,
+ fill: fgFrameColor(node.n || "")
+ });
+ }
+
+ var cursor = x;
+ var children = Array.isArray(node.c) ? node.c : [];
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var childTotal = Number(child.t || 0);
+ var childWidth = fg.cfg.width * (childTotal / Number(rootTotal));
+ fgBuildFrames(child, rootTotal, cursor, depth + 1, canvasHeight, false, out);
+ cursor += childWidth;
+ }
+ }
+
var pauseBtn = document.getElementById("btn-pause");
var status = document.getElementById("status");
pauseBtn.addEventListener("click", function () {
- paused = !paused;
- document.body.classList.toggle("paused", paused);
- pauseBtn.textContent = paused ? "Resume" : "Pause";
- status.textContent = paused ? "PAUSED" : "LIVE";
+ fg.paused = !fg.paused;
+ document.body.classList.toggle("paused", fg.paused);
+ pauseBtn.textContent = fg.paused ? "Resume" : "Pause";
+ status.textContent = fg.paused ? "PAUSED" : "LIVE";
});
+
+ window.fgFrameColor = fgFrameColor;
+ window.fgBuildFrames = fgBuildFrames;
+ window.fgMaxDepth = fgMaxDepth;
+ window.liveFlamegraphState = fg;
})();
</script>
</body>