Skip to content

Commit 8e3c7a5

Browse files
committed
improve flamegraph speed
1 parent d824c00 commit 8e3c7a5

1 file changed

Lines changed: 56 additions & 72 deletions

File tree

profiler.js

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
1-
2-
3-
let queue = []
4-
5-
function queuehandler(ms) {
6-
7-
// Calculate the time difference in milliseconds
8-
queue.forEach(element => {
9-
const date1 = new Date();
10-
element();
11-
while (new date() - date1 < ms)
12-
console.log("slept a tick")
13-
});
14-
15-
16-
}
17-
18-
191
// GLOBAL STATE
202
let calls = {};
213
let deadBeat = {};
224
let idMap = {};
235
let aggregates = {};
246
let processedBatches = 0;
257
const renderEvery = 5;
8+
let currentRoot = null;
269

2710

2811

@@ -33,16 +16,19 @@ function finalizeProcessing() {
3316
renderForest(forest);
3417
renderTimeline(Object.values(calls));
3518
renderSummary(aggregates);
19+
addToFlamegraph(forest);
3620
renderFlamegraph(flamegraphRoot);
3721
if (forest) showFunctionDetails(forest[0]);
3822
}
3923

4024
function maybeRender() {
4125
if (processedBatches % renderEvery === 0) {
4226
requestSummaryRender();
43-
requestTreeRender();
27+
renderForestSnapshot = buildForest(calls);
28+
requestTreeRender(renderForestSnapshot);
29+
30+
if (flamegraphActive){ addToFlamegraph(renderForestSnapshot); requestFlamegraphRender();}
4431
// renderTimeline(Object.values(calls));
45-
// if (flamegraphActive) requestFlamegraphRender();
4632
}
4733
}
4834

@@ -66,8 +52,6 @@ function processBatch(batch) {
6652

6753
cCall = calls[id];
6854

69-
addToFlamegraph(cCall);
70-
7155

7256

7357
// update aggregates
@@ -184,7 +168,7 @@ function renderSummary(aggregates) {
184168

185169
// Flame graph handler ===============================================================================================================================================================================================================
186170
let flamegraphLastRender = 0;
187-
const flamegraphRenderInterval = 100;
171+
const flamegraphRenderInterval = 1000;
188172

189173
let resizeObserver;
190174

@@ -199,7 +183,6 @@ const color = d3.scaleOrdinal(d3.schemeTableau10);
199183
function getNodePath(d) {
200184
return d.ancestors().map(n => n.data.name).reverse().join("/");
201185
}
202-
203186
function renderFlamegraph(data) {
204187
const container = document.getElementById("flamegraphView");
205188
const width = container.clientWidth;
@@ -210,21 +193,24 @@ function renderFlamegraph(data) {
210193
const root = d3.hierarchy(data).sum(d => d.value);
211194
partition(root);
212195

213-
// Initialize SVG & zoom once
196+
// Save for semantic zoom
197+
currentRoot = data;
198+
199+
const MIN_WIDTH = 0;
200+
const nodes = root.descendants().filter(d => (d.x1 - d.x0) > MIN_WIDTH);
201+
214202
if (!flamegraphSVG) {
215203
flamegraphSVG = d3.select("#flamegraphView").append("svg")
216-
.attr("width", width)
217-
.attr("height", height);
204+
.attr("width", width).attr("height", height);
218205

219206
flamegraphG = flamegraphSVG.append("g");
220207

221208
const zoom = d3.zoom().scaleExtent([0.5, 10])
222209
.on("zoom", (event) => flamegraphG.attr("transform", event.transform));
223210
flamegraphSVG.call(zoom);
224211

225-
// Add reset zoom button
226212
const button = document.createElement("button");
227-
button.innerText = "Reset Zoom";
213+
button.innerText = "Zoom Out";
228214
button.style.position = "absolute";
229215
button.style.top = "10px";
230216
button.style.right = "20px";
@@ -237,24 +223,26 @@ function renderFlamegraph(data) {
237223
button.style.cursor = "pointer";
238224
document.body.appendChild(button);
239225
button.onclick = () => {
240-
flamegraphSVG.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
226+
renderFlamegraph(flamegraphRoot);
241227
};
242228
} else {
243229
flamegraphSVG.attr("width", width).attr("height", height);
244230
}
245231

246-
const rects = flamegraphG.selectAll("g.node")
247-
.data(root.descendants(), getNodePath);
248-
232+
const rects = flamegraphG.selectAll("g.node").data(nodes, getNodePath);
249233
rects.exit().remove();
250234

251235
const enter = rects.enter().append("g").attr("class", "node");
236+
enter.append("rect");
237+
enter.append("title");
252238

253-
enter.append("rect")
239+
const merged = enter.merge(rects);
240+
241+
merged.select("rect")
254242
.attr("x", d => d.x0).attr("y", d => d.y0)
255243
.attr("width", d => d.x1 - d.x0).attr("height", d => d.y1 - d.y0)
256244
.attr("fill", d => color(d.data.name))
257-
.on("click", (event, d) => zoomIntoNode(d, partition, root))
245+
.on("click", (event, d) => zoomIntoNode(d))
258246
.on("mouseover", function (event, d) {
259247
d3.select(this).attr("stroke", "#fff").attr("stroke-width", 2);
260248
showTooltip(event, d);
@@ -264,17 +252,7 @@ function renderFlamegraph(data) {
264252
hideTooltip();
265253
});
266254

267-
enter.append("title")
268-
.text(d => `${d.data.name}: ${Math.round(d.value)} ms`);
269-
270-
const merged = enter.merge(rects);
271-
merged.select("rect").transition().duration(200)
272-
.attr("x", d => d.x0).attr("y", d => d.y0)
273-
.attr("width", d => d.x1 - d.x0).attr("height", d => d.y1 - d.y0)
274-
.attr("fill", d => color(d.data.name));
275-
276-
merged.select("title")
277-
.text(d => `${d.data.name}: ${Math.round(d.value)} ms`);
255+
merged.select("title").text(d => `${d.data.name}: ${Math.round(d.value)} ms`);
278256
}
279257

280258
function zoomIntoNode(target, partition, root) {
@@ -313,37 +291,44 @@ function hideTooltip() {
313291
}
314292

315293
function addToFlamegraph(call) {
316-
let current = call;
317-
const path = [];
318-
while (current) {
319-
path.unshift(current.function);
320-
current = current.parent_call_id ? calls[current.parent_call_id] : null;
321-
}
322-
323-
let node = flamegraphRoot;
324-
for (const func of path) {
325-
let child = node.children.find(c => c.name === func);
326-
if (!child) {
327-
child = { name: func, value: 0, children: [] };
328-
node.children.push(child);
329-
}
330-
node = child;
331-
}
332-
node.value += call.end - call.start;
294+
flamegraphRoot = { name: "__ROOT__", value: 0, children: [] };
295+
call.forEach(element => {
296+
flamegraphRoot.children.push(get_path_from_root(element));
297+
});
333298
}
334299

335300

301+
function get_path_from_root(call) {
302+
303+
const node = {
304+
name: call.function,
305+
value: call.duration,
306+
children: []
307+
};
308+
309+
// Recurse over children, if any
310+
if (call.children && call.children.size > 0) {
311+
for (const childCall of call.children) {
312+
node.children.push(get_path_from_root(calls[childCall]));
313+
}
314+
}
315+
return node;
316+
336317

337-
function requestFlamegraphRender() {
338-
if (flamegraphRenderTimeout) {
339-
clearTimeout(flamegraphRenderTimeout);
340-
}
341-
flamegraphRenderTimeout = setTimeout(() => {
342-
renderFlamegraph(flamegraphRoot);
343-
}, 100); // slight delay to batch fast incoming updates
344318
}
345319

320+
function requestFlamegraphRender() {
346321

322+
const now = performance.now();
323+
if (now - flamegraphLastRender >= flamegraphRenderInterval) {
324+
summaryLastRender = now;
325+
currentRoot = flamegraphRoot;
326+
renderFlamegraph(flamegraphRoot);
327+
}
328+
}
329+
function zoomIntoNode(d) {
330+
renderFlamegraph(d.data); // drill into this node
331+
}
347332
// tree Handler ===============================================================================================================================================================================================================
348333
let treeLastRender = 0;
349334
const treeRenderInterval = 100;
@@ -352,11 +337,10 @@ let rowPool = [];
352337

353338

354339

355-
function requestTreeRender() {
340+
function requestTreeRender(renderForestSnapshot) {
356341
const now = performance.now();
357342
if (now - treeLastRender >= treeRenderInterval) {
358343
treeLastRender = now;
359-
renderForestSnapshot = buildForest(calls);
360344
renderVirtualTree(renderForestSnapshot);
361345
}
362346
}

0 commit comments

Comments
 (0)