Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up reflow when resizing flamegraphs with a monospace font #262

Merged
merged 14 commits into from
Oct 24, 2022
Merged
94 changes: 87 additions & 7 deletions src/flamegraph/flamegraph.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"use strict";
var details, searchbtn, unzoombtn, matchedtxt, svg, searching, frames;
var details, searchbtn, unzoombtn, matchedtxt, svg, searching, frames, known_font_width;
function init(evt) {
details = document.getElementById("details").firstChild;
searchbtn = document.getElementById("search");
unzoombtn = document.getElementById("unzoom");
matchedtxt = document.getElementById("matched");
svg = document.getElementsByTagName("svg")[0];
frames = document.getElementById("frames");
known_font_width = get_monospace_width(frames);
total_samples = parseInt(frames.attributes.total_samples.value);
searching = 0;

Expand Down Expand Up @@ -37,9 +38,7 @@ function init(evt) {

// Text truncation needs to be adjusted for the current width.
var el = frames.children;
for(var i = 0; i < el.length; i++) {
update_text(el[i]);
}
update_text_for_elements(frames.children);
itamarst marked this conversation as resolved.
Show resolved Hide resolved

// Keep search elements at a fixed distance from right edge.
var svgWidth = svg.width.baseVal.value;
Expand Down Expand Up @@ -162,12 +161,91 @@ function g_to_func(e) {
// name before it's searched, do it here before returning.
return (func);
}
function get_monospace_width(frames) {
// Given the id="frames" element, return the width of text characters if
// this is a monospace font, otherwise return 0.
text = find_child(frames.children[0], "text");
originalContent = text.textContent;
text.textContent = "!";
bangWidth = text.getComputedTextLength();
text.textContent = "W";
wWidth = text.getComputedTextLength();
text.textContent = originalContent;
if (bangWidth === wWidth) {
console.log("IS MONOSPACE");
return bangWidth;
} else {
return 0;
}
}
function update_text_for_elements(elements) {
// In order to render quickly in the browser, you want to do one pass of
// reading attributes, and one pass of mutating attributes. See
// https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/ for details.

// Fall back to inefficient calculation, if we're variable-width font.
// TODO This should be optimized somehow too.
if (known_font_width === 0) {
for (var i = 0; i < elements.length; i++) {
update_text(elements[i]);
}
return;
}

var textElemNewAttributes = [];
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var r = find_child(e, "rect");
var t = find_child(e, "text");
var w = parseFloat(r.attributes.width.value) * frames.attributes.width.value / 100 - 3;
var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
var newX = format_percent((parseFloat(r.attributes.x.value) + (100 * 3 / frames.attributes.width.value)));

// Smaller than this size won't fit anything
if (w < 2 * known_font_width) {
textElemNewAttributes.push([newX, ""]);
continue;
}

// Fit in full text width
if (/^ *\$/.test(txt) || txt.length * known_font_width < w) {
itamarst marked this conversation as resolved.
Show resolved Hide resolved
textElemNewAttributes.push([newX, txt]);
continue;
}

var substringLength = Math.floor(w / known_font_width) - 2;
if (truncate_text_right) {
// Truncate the right side of the text.
textElemNewAttributes.push([newX, txt.substring(0, substringLength) + ".."]);
continue;
} else {
// Truncate the left side of the text.
textElemNewAttributes.push([newX, ".." + txt.substring(txt.length - substringLength, txt.length)]);
continue;
}
}
if (textElemNewAttributes.length !== elements.length) {
console.log("UH OH", textElemNewAttributes.length, elements.length);
itamarst marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// Now that we know new textContent, set it all in one go so we don't refresh a bazillion times.
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var values = textElemNewAttributes[i];
var t = find_child(e, "text");
t.attributes.x.value = values[0];
t.textContent = values[1];
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
}
}

function update_text(e) {
var r = find_child(e, "rect");
var t = find_child(e, "text");
var w = parseFloat(r.attributes.width.value) * frames.attributes.width.value / 100 - 3;
var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
t.attributes.x.value = format_percent((parseFloat(r.attributes.x.value) + (100 * 3 / frames.attributes.width.value)));

// Smaller than this size won't fit anything
if (w < 2 * fontsize * fontwidth) {
t.textContent = "";
Expand Down Expand Up @@ -242,6 +320,7 @@ function zoom(node) {
var ymin = parseFloat(attr.y.value);
unzoombtn.classList.remove("hide");
var el = frames.children;
var to_update_text = [];
for (var i = 0; i < el.length; i++) {
var e = el[i];
var a = find_child(e, "rect").attributes;
Expand All @@ -258,7 +337,7 @@ function zoom(node) {
if (ex <= xmin && (ex+ew) >= xmax) {
e.classList.add("parent");
zoom_parent(e);
update_text(e);
to_update_text.push(e);
}
// not in current path
else
Expand All @@ -272,10 +351,11 @@ function zoom(node) {
}
else {
zoom_child(e, xmin, width);
update_text(e);
to_update_text.push(e);
}
}
}
update_text_for_elements(to_update_text);
}
function unzoom() {
unzoombtn.classList.add("hide");
Expand All @@ -284,8 +364,8 @@ function unzoom() {
el[i].classList.remove("parent");
el[i].classList.remove("hide");
zoom_reset(el[i]);
update_text(el[i]);
}
update_text_for_elements(el);
}
// search
function reset_search() {
Expand Down