Skip to content

Commit e21127b

Browse files
authored
Merge branch 'main' into main
2 parents 1bc04b4 + 3e06cfc commit e21127b

File tree

6 files changed

+325
-181
lines changed

6 files changed

+325
-181
lines changed

Lib/profiling/sampling/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
from .collector import Collector
88
from .pstats_collector import PstatsCollector
99
from .stack_collector import CollapsedStackCollector
10+
from .string_table import StringTable
1011

11-
__all__ = ("Collector", "PstatsCollector", "CollapsedStackCollector")
12+
__all__ = ("Collector", "PstatsCollector", "CollapsedStackCollector", "StringTable")

Lib/profiling/sampling/flamegraph.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,27 @@ body {
403403
margin: 0;
404404
padding: 12px 0;
405405
}
406+
407+
/* Tooltip overflow fixes */
408+
.python-tooltip {
409+
max-width: 500px !important;
410+
word-wrap: break-word !important;
411+
overflow-wrap: break-word !important;
412+
box-sizing: border-box !important;
413+
}
414+
415+
/* Responsive tooltip adjustments */
416+
@media (max-width: 768px) {
417+
.python-tooltip {
418+
max-width: calc(100vw - 40px) !important;
419+
max-height: calc(100vh - 80px) !important;
420+
overflow-y: auto !important;
421+
}
422+
}
423+
424+
@media (max-width: 480px) {
425+
.python-tooltip {
426+
max-width: calc(100vw - 20px) !important;
427+
font-size: 12px !important;
428+
}
429+
}

Lib/profiling/sampling/flamegraph.js

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
const EMBEDDED_DATA = {{FLAMEGRAPH_DATA}};
22

3+
// Global string table for resolving string indices
4+
let stringTable = [];
5+
6+
// Function to resolve string indices to actual strings
7+
function resolveString(index) {
8+
if (typeof index === 'number' && index >= 0 && index < stringTable.length) {
9+
return stringTable[index];
10+
}
11+
// Fallback for non-indexed strings or invalid indices
12+
return String(index);
13+
}
14+
15+
// Function to recursively resolve all string indices in flamegraph data
16+
function resolveStringIndices(node) {
17+
if (!node) return node;
18+
19+
// Create a copy to avoid mutating the original
20+
const resolved = { ...node };
21+
22+
// Resolve string fields
23+
if (typeof resolved.name === 'number') {
24+
resolved.name = resolveString(resolved.name);
25+
}
26+
if (typeof resolved.filename === 'number') {
27+
resolved.filename = resolveString(resolved.filename);
28+
}
29+
if (typeof resolved.funcname === 'number') {
30+
resolved.funcname = resolveString(resolved.funcname);
31+
}
32+
33+
// Resolve source lines if present
34+
if (Array.isArray(resolved.source)) {
35+
resolved.source = resolved.source.map(index =>
36+
typeof index === 'number' ? resolveString(index) : index
37+
);
38+
}
39+
40+
// Recursively resolve children
41+
if (Array.isArray(resolved.children)) {
42+
resolved.children = resolved.children.map(child => resolveStringIndices(child));
43+
}
44+
45+
return resolved;
46+
}
47+
348
// Python color palette - cold to hot
449
const pythonColors = [
550
"#fff4bf", // Coldest - light yellow (<1%)
@@ -42,6 +87,8 @@ function createPythonTooltip(data) {
4287
.style("font-weight", "400")
4388
.style("line-height", "1.5")
4489
.style("max-width", "500px")
90+
.style("word-wrap", "break-word")
91+
.style("overflow-wrap", "break-word")
4592
.style("font-family", "'Source Sans Pro', sans-serif")
4693
.style("opacity", 0);
4794
}
@@ -61,7 +108,7 @@ function createPythonTooltip(data) {
61108
`<div style="font-family: 'SF Mono', 'Monaco', 'Consolas', ` +
62109
`monospace; font-size: 12px; color: ${
63110
line.startsWith("→") ? "#3776ab" : "#5a6c7d"
64-
}; white-space: pre; line-height: 1.4; padding: 2px 0;">${line
111+
}; white-space: pre-wrap; word-break: break-all; overflow-wrap: break-word; line-height: 1.4; padding: 2px 0;">${line
65112
.replace(/&/g, "&amp;")
66113
.replace(/</g, "&lt;")
67114
.replace(/>/g, "&gt;")}</div>`,
@@ -77,7 +124,7 @@ function createPythonTooltip(data) {
77124
</div>
78125
<div style="background: #f8f9fa; border: 1px solid #e9ecef;
79126
border-radius: 6px; padding: 12px; max-height: 150px;
80-
overflow-y: auto;">
127+
overflow-y: auto; overflow-x: hidden;">
81128
${sourceLines}
82129
</div>
83130
</div>`;
@@ -92,22 +139,26 @@ function createPythonTooltip(data) {
92139
</div>
93140
<div style="background: #f8f9fa; border: 1px solid #e9ecef;
94141
border-radius: 6px; padding: 12px; max-height: 150px;
95-
overflow-y: auto; font-family: monospace; font-size: 11px;">
142+
overflow-y: auto; overflow-x: hidden; font-family: monospace; font-size: 11px; word-break: break-all; overflow-wrap: break-word;">
96143
${JSON.stringify(source, null, 2)}
97144
</div>
98145
</div>`;
99146
}
100147

148+
// Resolve strings for display
149+
const funcname = resolveString(d.data.funcname) || resolveString(d.data.name);
150+
const filename = resolveString(d.data.filename) || "";
151+
101152
const tooltipHTML = `
102153
<div>
103154
<div style="color: #3776ab; font-weight: 600; font-size: 16px;
104-
margin-bottom: 8px; line-height: 1.3;">
105-
${d.data.funcname || d.data.name}
155+
margin-bottom: 8px; line-height: 1.3; word-break: break-word; overflow-wrap: break-word;">
156+
${funcname}
106157
</div>
107158
<div style="color: #5a6c7d; font-size: 13px; margin-bottom: 12px;
108159
font-family: monospace; background: #f8f9fa;
109-
padding: 4px 8px; border-radius: 4px;">
110-
${d.data.filename || ""}${d.data.lineno ? ":" + d.data.lineno : ""}
160+
padding: 4px 8px; border-radius: 4px; word-break: break-all; overflow-wrap: break-word;">
161+
${filename}${d.data.lineno ? ":" + d.data.lineno : ""}
111162
</div>
112163
<div style="display: grid; grid-template-columns: auto 1fr;
113164
gap: 8px 16px; font-size: 14px;">
@@ -255,9 +306,9 @@ function updateSearchHighlight(searchTerm, searchInput) {
255306
let matchCount = 0;
256307
d3.selectAll("#chart rect").each(function (d) {
257308
if (d && d.data) {
258-
const name = d.data.name || "";
259-
const funcname = d.data.funcname || "";
260-
const filename = d.data.filename || "";
309+
const name = resolveString(d.data.name) || "";
310+
const funcname = resolveString(d.data.funcname) || "";
311+
const filename = resolveString(d.data.filename) || "";
261312
const term = searchTerm.toLowerCase();
262313
const matches =
263314
name.toLowerCase().includes(term) ||
@@ -315,12 +366,20 @@ function handleResize(chart, data) {
315366

316367
function initFlamegraph() {
317368
ensureLibraryLoaded();
318-
const tooltip = createPythonTooltip(EMBEDDED_DATA);
319-
const chart = createFlamegraph(tooltip, EMBEDDED_DATA.value);
320-
renderFlamegraph(chart, EMBEDDED_DATA);
369+
370+
// Extract string table if present and resolve string indices
371+
let processedData = EMBEDDED_DATA;
372+
if (EMBEDDED_DATA.strings) {
373+
stringTable = EMBEDDED_DATA.strings;
374+
processedData = resolveStringIndices(EMBEDDED_DATA);
375+
}
376+
377+
const tooltip = createPythonTooltip(processedData);
378+
const chart = createFlamegraph(tooltip, processedData.value);
379+
renderFlamegraph(chart, processedData);
321380
attachPanelControls();
322381
initSearchHandlers();
323-
handleResize(chart, EMBEDDED_DATA);
382+
handleResize(chart, processedData);
324383
}
325384

326385
if (document.readyState === "loading") {
@@ -336,7 +395,10 @@ function populateStats(data) {
336395
const functionMap = new Map();
337396

338397
function collectFunctions(node) {
339-
if (node.filename && node.funcname) {
398+
const filename = resolveString(node.filename);
399+
const funcname = resolveString(node.funcname);
400+
401+
if (filename && funcname) {
340402
// Calculate direct samples (this node's value minus children's values)
341403
let childrenValue = 0;
342404
if (node.children) {
@@ -345,23 +407,23 @@ function populateStats(data) {
345407
const directSamples = Math.max(0, node.value - childrenValue);
346408

347409
// Use file:line:funcname as key to ensure uniqueness
348-
const funcKey = `${node.filename}:${node.lineno || '?'}:${node.funcname}`;
410+
const funcKey = `${filename}:${node.lineno || '?'}:${funcname}`;
349411

350412
if (functionMap.has(funcKey)) {
351413
const existing = functionMap.get(funcKey);
352414
existing.directSamples += directSamples;
353415
existing.directPercent = (existing.directSamples / totalSamples) * 100;
354416
// Keep the most representative file/line (the one with more samples)
355417
if (directSamples > existing.maxSingleSamples) {
356-
existing.filename = node.filename;
418+
existing.filename = filename;
357419
existing.lineno = node.lineno || '?';
358420
existing.maxSingleSamples = directSamples;
359421
}
360422
} else {
361423
functionMap.set(funcKey, {
362-
filename: node.filename,
424+
filename: filename,
363425
lineno: node.lineno || '?',
364-
funcname: node.funcname,
426+
funcname: funcname,
365427
directSamples,
366428
directPercent: (directSamples / totalSamples) * 100,
367429
maxSingleSamples: directSamples

0 commit comments

Comments
 (0)