|
1 |
| -function initFlamegraphUI() { |
2 |
| - main(); |
| 1 | +const EMBEDDED_DATA = {{FLAMEGRAPH_DATA}}; |
3 | 2 |
|
4 |
| - const infoBtn = document.getElementById("show-info-btn"); |
5 |
| - const infoPanel = document.getElementById("info-panel"); |
6 |
| - const closeBtn = document.getElementById("close-info-btn"); |
7 |
| - const searchInput = document.getElementById("search-input"); |
8 |
| - |
9 |
| - if (infoBtn && infoPanel) { |
10 |
| - infoBtn.addEventListener("click", function () { |
11 |
| - const isOpen = infoPanel.style.display === "block"; |
12 |
| - infoPanel.style.display = isOpen ? "none" : "block"; |
13 |
| - }); |
14 |
| - } |
15 |
| - if (closeBtn && infoPanel) { |
16 |
| - closeBtn.addEventListener("click", function () { |
17 |
| - infoPanel.style.display = "none"; |
18 |
| - }); |
19 |
| - } |
20 |
| - |
21 |
| - // Add search functionality - wait for chart to be ready |
22 |
| - if (searchInput) { |
23 |
| - let searchTimeout; |
24 |
| - |
25 |
| - function performSearch() { |
26 |
| - const searchTerm = searchInput.value.trim(); |
27 |
| - |
28 |
| - // Clear previous search highlighting |
29 |
| - d3.selectAll("#chart rect") |
30 |
| - .style("stroke", null) |
31 |
| - .style("stroke-width", null) |
32 |
| - .style("opacity", null); |
33 |
| - |
34 |
| - if (searchTerm && searchTerm.length > 0) { |
35 |
| - // First, dim all rectangles |
36 |
| - d3.selectAll("#chart rect") |
37 |
| - .style("opacity", 0.3); |
38 |
| - |
39 |
| - // Then highlight and restore opacity for matching nodes |
40 |
| - let matchCount = 0; |
41 |
| - d3.selectAll("#chart rect") |
42 |
| - .each(function(d) { |
43 |
| - if (d && d.data) { |
44 |
| - const name = d.data.name || ""; |
45 |
| - const funcname = d.data.funcname || ""; |
46 |
| - const filename = d.data.filename || ""; |
47 |
| - |
48 |
| - const matches = name.toLowerCase().includes(searchTerm.toLowerCase()) || |
49 |
| - funcname.toLowerCase().includes(searchTerm.toLowerCase()) || |
50 |
| - filename.toLowerCase().includes(searchTerm.toLowerCase()); |
51 |
| - |
52 |
| - if (matches) { |
53 |
| - matchCount++; |
54 |
| - d3.select(this) |
55 |
| - .style("opacity", 1) |
56 |
| - .style("stroke", "#ff6b35") |
57 |
| - .style("stroke-width", "2px") |
58 |
| - .style("stroke-dasharray", "3,3"); |
59 |
| - } |
60 |
| - } |
61 |
| - }); |
62 |
| - |
63 |
| - // Update search input style based on results |
64 |
| - if (matchCount > 0) { |
65 |
| - searchInput.style.borderColor = "rgba(40, 167, 69, 0.8)"; |
66 |
| - searchInput.style.boxShadow = "0 6px 20px rgba(40, 167, 69, 0.2)"; |
67 |
| - } else { |
68 |
| - searchInput.style.borderColor = "rgba(220, 53, 69, 0.8)"; |
69 |
| - searchInput.style.boxShadow = "0 6px 20px rgba(220, 53, 69, 0.2)"; |
70 |
| - } |
71 |
| - } else { |
72 |
| - // Reset search input style |
73 |
| - searchInput.style.borderColor = "rgba(255, 255, 255, 0.2)"; |
74 |
| - searchInput.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.1)"; |
75 |
| - } |
76 |
| - } |
77 |
| - |
78 |
| - searchInput.addEventListener("input", function() { |
79 |
| - clearTimeout(searchTimeout); |
80 |
| - searchTimeout = setTimeout(performSearch, 150); |
81 |
| - }); |
82 |
| - |
83 |
| - // Make search function globally accessible |
84 |
| - window.performSearch = performSearch; |
85 |
| - } |
86 |
| -} |
87 |
| -function main() { |
88 |
| - const data = {{FLAMEGRAPH_DATA}} |
| 3 | +// Python color palette - cold to hot |
| 4 | +const pythonColors = [ |
| 5 | + "#fff4bf", // Coldest - light yellow (<1%) |
| 6 | + "#ffec9e", // Cold - yellow (1-3%) |
| 7 | + "#ffe47d", // Cool - golden yellow (3-6%) |
| 8 | + "#ffdc5c", // Medium - golden (6-12%) |
| 9 | + "#ffd43b", // Warm - Python gold (12-18%) |
| 10 | + "#5592cc", // Hot - light blue (18-35%) |
| 11 | + "#4584bb", // Very hot - medium blue (35-60%) |
| 12 | + "#3776ab", // Hottest - Python blue (≥60%) |
| 13 | +]; |
89 | 14 |
|
| 15 | +function ensureLibraryLoaded() { |
90 | 16 | if (typeof flamegraph === "undefined") {
|
91 | 17 | console.error("d3-flame-graph library not loaded");
|
92 | 18 | document.getElementById("chart").innerHTML =
|
93 | 19 | '<h2 style="text-align: center; color: #d32f2f;">Error: d3-flame-graph library failed to load</h2>';
|
94 | 20 | throw new Error("d3-flame-graph library failed to load");
|
95 | 21 | }
|
| 22 | +} |
96 | 23 |
|
| 24 | +function createPythonTooltip(data) { |
97 | 25 | const pythonTooltip = flamegraph.tooltip.defaultFlamegraphTooltip();
|
98 | 26 | pythonTooltip.show = function (d, element) {
|
99 | 27 | if (!this._tooltip) {
|
@@ -266,76 +194,141 @@ function main() {
|
266 | 194 | this._tooltip.transition().duration(200).style("opacity", 0);
|
267 | 195 | }
|
268 | 196 | };
|
| 197 | + return pythonTooltip; |
| 198 | +} |
269 | 199 |
|
270 |
| - // Store root value globally for color mapping |
271 |
| - let globalRootValue = data.value; |
272 |
| - // Store data globally for resize handling |
273 |
| - window.flamegraphData = data; |
274 |
| - |
275 |
| - // Create the flamegraph with proper color mapping |
| 200 | +function createFlamegraph(tooltip, rootValue) { |
276 | 201 | let chart = flamegraph()
|
277 | 202 | .width(window.innerWidth - 80)
|
278 | 203 | .cellHeight(20)
|
279 | 204 | .transitionDuration(300)
|
280 | 205 | .minFrameSize(1)
|
281 |
| - .tooltip(pythonTooltip) |
| 206 | + .tooltip(tooltip) |
282 | 207 | .inverted(true)
|
283 | 208 | .setColorMapper(function (d) {
|
284 |
| - // Use the stored global root value |
285 |
| - const percentage = d.data.value / globalRootValue; |
286 |
| - |
287 |
| - // Realistic thresholds for profiling data based on debug output |
| 209 | + const percentage = d.data.value / rootValue; |
288 | 210 | let colorIndex;
|
289 |
| - if (percentage >= 0.6) |
290 |
| - colorIndex = 7; // Hottest - ≥60% (like your 100%, 80%) |
291 |
| - else if (percentage >= 0.35) |
292 |
| - colorIndex = 6; // Very hot - 35-60% (like your 50%) |
293 |
| - else if (percentage >= 0.18) |
294 |
| - colorIndex = 5; // Hot - 18-35% (like your 30%, 25%, 20%) |
295 |
| - else if (percentage >= 0.12) |
296 |
| - colorIndex = 4; // Warm - 12-18% (like your 15%) |
297 |
| - else if (percentage >= 0.06) |
298 |
| - colorIndex = 3; // Medium - 6-12% |
299 |
| - else if (percentage >= 0.03) |
300 |
| - colorIndex = 2; // Cool - 3-6% (like your 5%) |
301 |
| - else if (percentage >= 0.01) |
302 |
| - colorIndex = 1; // Cold - 1-3% |
303 |
| - else colorIndex = 0; // Coldest - <1% |
304 |
| - |
305 |
| - const color = pythonColors[colorIndex]; |
306 |
| - |
307 |
| - return color; |
| 211 | + if (percentage >= 0.6) colorIndex = 7; |
| 212 | + else if (percentage >= 0.35) colorIndex = 6; |
| 213 | + else if (percentage >= 0.18) colorIndex = 5; |
| 214 | + else if (percentage >= 0.12) colorIndex = 4; |
| 215 | + else if (percentage >= 0.06) colorIndex = 3; |
| 216 | + else if (percentage >= 0.03) colorIndex = 2; |
| 217 | + else if (percentage >= 0.01) colorIndex = 1; |
| 218 | + else colorIndex = 0; // <1% |
| 219 | + return pythonColors[colorIndex]; |
308 | 220 | });
|
| 221 | + return chart; |
| 222 | +} |
309 | 223 |
|
310 |
| - // Render the flamegraph |
| 224 | +function renderFlamegraph(chart, data) { |
311 | 225 | d3.select("#chart").datum(data).call(chart);
|
| 226 | + window.flamegraphChart = chart; // for controls |
| 227 | + window.flamegraphData = data; // for resize/search |
| 228 | + populateStats(data); |
| 229 | +} |
312 | 230 |
|
313 |
| - // Make chart globally accessible for controls |
314 |
| - window.flamegraphChart = chart; |
| 231 | +function attachPanelControls() { |
| 232 | + const infoBtn = document.getElementById("show-info-btn"); |
| 233 | + const infoPanel = document.getElementById("info-panel"); |
| 234 | + const closeBtn = document.getElementById("close-info-btn"); |
| 235 | + if (infoBtn && infoPanel) { |
| 236 | + infoBtn.addEventListener("click", function () { |
| 237 | + const isOpen = infoPanel.style.display === "block"; |
| 238 | + infoPanel.style.display = isOpen ? "none" : "block"; |
| 239 | + }); |
| 240 | + } |
| 241 | + if (closeBtn && infoPanel) { |
| 242 | + closeBtn.addEventListener("click", function () { |
| 243 | + infoPanel.style.display = "none"; |
| 244 | + }); |
| 245 | + } |
| 246 | +} |
315 | 247 |
|
316 |
| - // Populate stats cards |
317 |
| - populateStats(data); |
| 248 | +function updateSearchHighlight(searchTerm, searchInput) { |
| 249 | + d3.selectAll("#chart rect") |
| 250 | + .style("stroke", null) |
| 251 | + .style("stroke-width", null) |
| 252 | + .style("opacity", null); |
| 253 | + if (searchTerm && searchTerm.length > 0) { |
| 254 | + d3.selectAll("#chart rect").style("opacity", 0.3); |
| 255 | + let matchCount = 0; |
| 256 | + d3.selectAll("#chart rect").each(function (d) { |
| 257 | + if (d && d.data) { |
| 258 | + const name = d.data.name || ""; |
| 259 | + const funcname = d.data.funcname || ""; |
| 260 | + const filename = d.data.filename || ""; |
| 261 | + const term = searchTerm.toLowerCase(); |
| 262 | + const matches = |
| 263 | + name.toLowerCase().includes(term) || |
| 264 | + funcname.toLowerCase().includes(term) || |
| 265 | + filename.toLowerCase().includes(term); |
| 266 | + if (matches) { |
| 267 | + matchCount++; |
| 268 | + d3.select(this) |
| 269 | + .style("opacity", 1) |
| 270 | + .style("stroke", "#ff6b35") |
| 271 | + .style("stroke-width", "2px") |
| 272 | + .style("stroke-dasharray", "3,3"); |
| 273 | + } |
| 274 | + } |
| 275 | + }); |
| 276 | + if (searchInput) { |
| 277 | + if (matchCount > 0) { |
| 278 | + searchInput.style.borderColor = "rgba(40, 167, 69, 0.8)"; |
| 279 | + searchInput.style.boxShadow = "0 6px 20px rgba(40, 167, 69, 0.2)"; |
| 280 | + } else { |
| 281 | + searchInput.style.borderColor = "rgba(220, 53, 69, 0.8)"; |
| 282 | + searchInput.style.boxShadow = "0 6px 20px rgba(220, 53, 69, 0.2)"; |
| 283 | + } |
| 284 | + } |
| 285 | + } else if (searchInput) { |
| 286 | + searchInput.style.borderColor = "rgba(255, 255, 255, 0.2)"; |
| 287 | + searchInput.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.1)"; |
| 288 | + } |
318 | 289 | }
|
319 | 290 |
|
| 291 | +function initSearchHandlers() { |
| 292 | + const searchInput = document.getElementById("search-input"); |
| 293 | + if (!searchInput) return; |
| 294 | + let searchTimeout; |
| 295 | + function performSearch() { |
| 296 | + const term = searchInput.value.trim(); |
| 297 | + updateSearchHighlight(term, searchInput); |
| 298 | + } |
| 299 | + searchInput.addEventListener("input", function () { |
| 300 | + clearTimeout(searchTimeout); |
| 301 | + searchTimeout = setTimeout(performSearch, 150); |
| 302 | + }); |
| 303 | + window.performSearch = performSearch; |
| 304 | +} |
| 305 | + |
| 306 | +function handleResize(chart, data) { |
| 307 | + window.addEventListener("resize", function () { |
| 308 | + if (chart && data) { |
| 309 | + const newWidth = window.innerWidth - 80; |
| 310 | + chart.width(newWidth); |
| 311 | + d3.select("#chart").datum(data).call(chart); |
| 312 | + } |
| 313 | + }); |
| 314 | +} |
| 315 | + |
| 316 | +function initFlamegraph() { |
| 317 | + ensureLibraryLoaded(); |
| 318 | + const tooltip = createPythonTooltip(EMBEDDED_DATA); |
| 319 | + const chart = createFlamegraph(tooltip, EMBEDDED_DATA.value); |
| 320 | + renderFlamegraph(chart, EMBEDDED_DATA); |
| 321 | + attachPanelControls(); |
| 322 | + initSearchHandlers(); |
| 323 | + handleResize(chart, EMBEDDED_DATA); |
| 324 | +} |
320 | 325 |
|
321 | 326 | if (document.readyState === "loading") {
|
322 |
| - document.addEventListener("DOMContentLoaded", initFlamegraphUI); |
| 327 | + document.addEventListener("DOMContentLoaded", initFlamegraph); |
323 | 328 | } else {
|
324 |
| - initFlamegraphUI(); |
| 329 | + initFlamegraph(); |
325 | 330 | }
|
326 | 331 |
|
327 |
| -// Python color palette - cold to hot |
328 |
| -const pythonColors = [ |
329 |
| - "#fff4bf", // Coldest - light yellow (<1%) |
330 |
| - "#ffec9e", // Cold - yellow (1-3%) |
331 |
| - "#ffe47d", // Cool - golden yellow (3-6%) |
332 |
| - "#ffdc5c", // Medium - golden (6-12%) |
333 |
| - "#ffd43b", // Warm - Python gold (12-18%) |
334 |
| - "#5592cc", // Hot - light blue (18-35%) |
335 |
| - "#4584bb", // Very hot - medium blue (35-60%) |
336 |
| - "#3776ab", // Hottest - Python blue (≥60%) |
337 |
| -]; |
338 |
| - |
339 | 332 | function populateStats(data) {
|
340 | 333 | const totalSamples = data.value || 0;
|
341 | 334 |
|
@@ -450,13 +443,3 @@ function clearSearch() {
|
450 | 443 | }
|
451 | 444 | }
|
452 | 445 |
|
453 |
| -// Handle window resize |
454 |
| -window.addEventListener("resize", function () { |
455 |
| - if (window.flamegraphChart && window.flamegraphData) { |
456 |
| - const newWidth = window.innerWidth - 80; |
457 |
| - window.flamegraphChart.width(newWidth); |
458 |
| - d3.select("#chart") |
459 |
| - .datum(window.flamegraphData) |
460 |
| - .call(window.flamegraphChart); |
461 |
| - } |
462 |
| -}); |
0 commit comments