Skip to content

Commit d4eee16

Browse files
gh-142927: Show module names instead of file paths in flamegraph (#146040)
1 parent d71e3bc commit d4eee16

7 files changed

Lines changed: 215 additions & 141 deletions

File tree

Lib/profiling/sampling/_flamegraph_assets/flamegraph.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ body.resizing-sidebar {
315315
}
316316

317317
/* View Mode Section */
318+
.view-mode-section {
319+
display: flex;
320+
flex-direction: column;
321+
gap: 8px;
322+
}
323+
318324
.view-mode-section .section-content {
319325
display: flex;
320326
flex-direction: column;
@@ -1067,7 +1073,8 @@ body.resizing-sidebar {
10671073
-------------------------------------------------------------------------- */
10681074

10691075
#toggle-invert .toggle-track.on,
1070-
#toggle-elided .toggle-track.on {
1076+
#toggle-elided .toggle-track.on,
1077+
#toggle-path-display .toggle-track.on {
10711078
background: #8e44ad;
10721079
border-color: #8e44ad;
10731080
box-shadow: 0 0 8px rgba(142, 68, 173, 0.3);

Lib/profiling/sampling/_flamegraph_assets/flamegraph.js

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ let normalData = null;
66
let invertedData = null;
77
let currentThreadFilter = 'all';
88
let isInverted = false;
9+
let useModuleNames = true;
910

1011
// Heat colors are now defined in CSS variables (--heat-1 through --heat-8)
1112
// and automatically switch with theme changes - no JS color arrays needed!
@@ -64,6 +65,12 @@ function resolveStringIndices(node, table) {
6465
if (typeof resolved.funcname === 'number') {
6566
resolved.funcname = resolveString(resolved.funcname, table);
6667
}
68+
if (typeof resolved.module === 'number') {
69+
resolved.module = resolveString(resolved.module, table);
70+
}
71+
if (typeof resolved.label === 'number') {
72+
resolved.label = resolveString(resolved.label, table);
73+
}
6774

6875
if (Array.isArray(resolved.source)) {
6976
resolved.source = resolved.source.map(index =>
@@ -78,6 +85,19 @@ function resolveStringIndices(node, table) {
7885
return resolved;
7986
}
8087

88+
// Escape HTML special characters
89+
function escapeHtml(str) {
90+
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
91+
}
92+
93+
// Get display path based on user preference (module or full path)
94+
function getDisplayName(moduleName, filename) {
95+
if (useModuleNames) {
96+
return moduleName || filename;
97+
}
98+
return filename;
99+
}
100+
81101
function selectFlamegraphData() {
82102
const baseData = isShowingElided ? elidedFlamegraphData : normalData;
83103

@@ -228,6 +248,7 @@ function setupLogos() {
228248
function updateStatusBar(nodeData, rootValue) {
229249
const funcname = resolveString(nodeData.funcname) || resolveString(nodeData.name) || "--";
230250
const filename = resolveString(nodeData.filename) || "";
251+
const moduleName = resolveString(nodeData.module) || "";
231252
const lineno = nodeData.lineno;
232253
const timeMs = (nodeData.value / 1000).toFixed(2);
233254
const percent = rootValue > 0 ? ((nodeData.value / rootValue) * 100).toFixed(1) : "0.0";
@@ -249,8 +270,8 @@ function updateStatusBar(nodeData, rootValue) {
249270

250271
const fileEl = document.getElementById('status-file');
251272
if (fileEl && filename && filename !== "~") {
252-
const basename = filename.split('/').pop();
253-
fileEl.textContent = lineno ? `${basename}:${lineno}` : basename;
273+
const displayName = getDisplayName(moduleName, filename);
274+
fileEl.textContent = lineno ? `${displayName}:${lineno}` : displayName;
254275
}
255276

256277
const funcEl = document.getElementById('status-func');
@@ -301,6 +322,8 @@ function createPythonTooltip(data) {
301322

302323
const funcname = resolveString(d.data.funcname) || resolveString(d.data.name);
303324
const filename = resolveString(d.data.filename) || "";
325+
const moduleName = resolveString(d.data.module) || "";
326+
const displayName = escapeHtml(useModuleNames ? (moduleName || filename) : filename);
304327
const isSpecialFrame = filename === "~";
305328

306329
// Build source section
@@ -309,7 +332,7 @@ function createPythonTooltip(data) {
309332
const sourceLines = source
310333
.map((line) => {
311334
const isCurrent = line.startsWith("→");
312-
const escaped = line.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
335+
const escaped = escapeHtml(line);
313336
return `<div class="tooltip-source-line${isCurrent ? ' current' : ''}">${escaped}</div>`;
314337
})
315338
.join("");
@@ -369,7 +392,7 @@ function createPythonTooltip(data) {
369392
}
370393

371394
const fileLocationHTML = isSpecialFrame ? "" : `
372-
<div class="tooltip-location">${filename}${d.data.lineno ? ":" + d.data.lineno : ""}</div>`;
395+
<div class="tooltip-location">${displayName}${d.data.lineno ? ":" + d.data.lineno : ""}</div>`;
373396

374397
// Differential stats section
375398
let diffSection = "";
@@ -586,6 +609,7 @@ function createFlamegraph(tooltip, rootValue, data) {
586609
.minFrameSize(1)
587610
.tooltip(tooltip)
588611
.inverted(true)
612+
.getName(d => resolveString(useModuleNames ? d.data.label : d.data.name) || resolveString(d.data.name) || '')
589613
.setColorMapper(function (d) {
590614
if (d.depth === 0) return 'transparent';
591615

@@ -628,25 +652,25 @@ function updateSearchHighlight(searchTerm, searchInput) {
628652
const name = resolveString(d.data.name) || "";
629653
const funcname = resolveString(d.data.funcname) || "";
630654
const filename = resolveString(d.data.filename) || "";
655+
const moduleName = resolveString(d.data.module) || "";
656+
const displayName = getDisplayName(moduleName, filename);
631657
const lineno = d.data.lineno;
632658
const term = searchTerm.toLowerCase();
633659

634-
// Check if search term looks like file:line pattern
660+
// Check if search term looks like path:line pattern
635661
const fileLineMatch = term.match(/^(.+):(\d+)$/);
636662
let matches = false;
637663

638664
if (fileLineMatch) {
639-
// Exact file:line matching
640665
const searchFile = fileLineMatch[1];
641666
const searchLine = parseInt(fileLineMatch[2], 10);
642-
const basename = filename.split('/').pop().toLowerCase();
643-
matches = basename.includes(searchFile) && lineno === searchLine;
667+
matches = displayName.toLowerCase().includes(searchFile) && lineno === searchLine;
644668
} else {
645669
// Regular substring search
646670
matches =
647671
name.toLowerCase().includes(term) ||
648672
funcname.toLowerCase().includes(term) ||
649-
filename.toLowerCase().includes(term);
673+
displayName.toLowerCase().includes(term);
650674
}
651675

652676
if (matches) {
@@ -1047,6 +1071,7 @@ function populateStats(data) {
10471071

10481072
let filename = resolveString(node.filename);
10491073
let funcname = resolveString(node.funcname);
1074+
let moduleName = resolveString(node.module);
10501075

10511076
if (!filename || !funcname) {
10521077
const nameStr = resolveString(node.name);
@@ -1061,6 +1086,7 @@ function populateStats(data) {
10611086

10621087
filename = filename || 'unknown';
10631088
funcname = funcname || 'unknown';
1089+
moduleName = moduleName || 'unknown';
10641090

10651091
if (filename !== 'unknown' && funcname !== 'unknown' && node.value > 0) {
10661092
const directSamples = node.self || 0;
@@ -1073,12 +1099,14 @@ function populateStats(data) {
10731099
existing.directPercent = (existing.directSamples / totalSamples) * 100;
10741100
if (directSamples > existing.maxSingleSamples) {
10751101
existing.filename = filename;
1102+
existing.module = moduleName;
10761103
existing.lineno = node.lineno || '?';
10771104
existing.maxSingleSamples = directSamples;
10781105
}
10791106
} else {
10801107
functionMap.set(funcKey, {
10811108
filename: filename,
1109+
module: moduleName,
10821110
lineno: node.lineno || '?',
10831111
funcname: funcname,
10841112
directSamples,
@@ -1113,6 +1141,7 @@ function populateStats(data) {
11131141
const h = hotSpots[i];
11141142
const filename = h.filename || 'unknown';
11151143
const lineno = h.lineno ?? '?';
1144+
const moduleName = h.module || 'unknown';
11161145
const isSpecialFrame = filename === '~' && (lineno === 0 || lineno === '?');
11171146

11181147
let funcDisplay = h.funcname || 'unknown';
@@ -1123,8 +1152,8 @@ function populateStats(data) {
11231152
if (isSpecialFrame) {
11241153
fileEl.textContent = '--';
11251154
} else {
1126-
const basename = filename !== 'unknown' ? filename.split('/').pop() : 'unknown';
1127-
fileEl.textContent = `${basename}:${lineno}`;
1155+
const displayName = getDisplayName(moduleName, filename);
1156+
fileEl.textContent = `${displayName}:${lineno}`;
11281157
}
11291158
}
11301159
if (percentEl) percentEl.textContent = `${h.directPercent.toFixed(1)}%`;
@@ -1140,8 +1169,11 @@ function populateStats(data) {
11401169
if (card) {
11411170
if (i < hotSpots.length && hotSpots[i]) {
11421171
const h = hotSpots[i];
1143-
const basename = h.filename !== 'unknown' ? h.filename.split('/').pop() : '';
1144-
const searchTerm = basename && h.lineno !== '?' ? `${basename}:${h.lineno}` : h.funcname;
1172+
const moduleName = h.module || 'unknown';
1173+
const filename = h.filename || 'unknown';
1174+
const displayName = getDisplayName(moduleName, filename);
1175+
const hasValidLocation = displayName !== 'unknown' && h.lineno !== '?';
1176+
const searchTerm = hasValidLocation ? `${displayName}:${h.lineno}` : h.funcname;
11451177
card.dataset.searchterm = searchTerm;
11461178
card.onclick = () => searchForHotspot(searchTerm);
11471179
card.style.cursor = 'pointer';
@@ -1273,10 +1305,12 @@ function accumulateInvertedNode(parent, stackFrame, leaf, isDifferential) {
12731305
if (!parent.children[key]) {
12741306
const newNode = {
12751307
name: stackFrame.name,
1308+
label: stackFrame.label,
12761309
value: 0,
12771310
self: 0,
12781311
children: {},
12791312
filename: stackFrame.filename,
1313+
module: stackFrame.module,
12801314
lineno: stackFrame.lineno,
12811315
funcname: stackFrame.funcname,
12821316
source: stackFrame.source,
@@ -1370,6 +1404,7 @@ function generateInvertedFlamegraph(data) {
13701404

13711405
const invertedRoot = {
13721406
name: data.name,
1407+
label: data.label,
13731408
value: data.value,
13741409
children: {},
13751410
stats: data.stats,
@@ -1394,6 +1429,12 @@ function toggleInvert() {
13941429
updateFlamegraphView();
13951430
}
13961431

1432+
function togglePathDisplay() {
1433+
useModuleNames = !useModuleNames;
1434+
updateToggleUI('toggle-path-display', useModuleNames);
1435+
updateFlamegraphView();
1436+
}
1437+
13971438
// ============================================================================
13981439
// Initialization
13991440
// ============================================================================
@@ -1441,6 +1482,11 @@ function initFlamegraph() {
14411482
if (toggleInvertBtn) {
14421483
toggleInvertBtn.addEventListener('click', toggleInvert);
14431484
}
1485+
1486+
const togglePathDisplayBtn = document.getElementById('toggle-path-display');
1487+
if (togglePathDisplayBtn) {
1488+
togglePathDisplayBtn.addEventListener('click', togglePathDisplay);
1489+
}
14441490
}
14451491

14461492
// Keyboard shortcut: Enter/Space activates toggle switches

Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ <h3 class="section-title">View Mode</h3>
117117
<span class="toggle-label" data-text="Elided" title="Code paths that existed in baseline but are missing from current profile">Elided</span>
118118
</div>
119119

120+
<div class="toggle-switch" id="toggle-path-display" title="Toggle between module names and full file paths" tabindex="0">
121+
<span class="toggle-label" data-text="File Paths">File Paths</span>
122+
<div class="toggle-track on"></div>
123+
<span class="toggle-label active" data-text="Module Names">Module Names</span>
124+
</div>
125+
120126
<div class="toggle-switch" id="toggle-invert" title="Toggle between standard and inverted flamegraph view" tabindex="0">
121127
<span class="toggle-label active" data-text="Flamegraph">Flamegraph</span>
122128
<div class="toggle-track"></div>

0 commit comments

Comments
 (0)