|
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