@@ -6,6 +6,7 @@ let normalData = null;
66let invertedData = null ;
77let currentThreadFilter = 'all' ;
88let 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, "&" ) . replace ( / < / g, "<" ) . replace ( / > / g, ">" ) ;
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+
81101function selectFlamegraphData ( ) {
82102 const baseData = isShowingElided ? elidedFlamegraphData : normalData ;
83103
@@ -228,6 +248,7 @@ function setupLogos() {
228248function 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 , "&" ) . replace ( / < / g , "<" ) . replace ( / > / g , ">" ) ;
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
0 commit comments