11const 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
449const pythonColors = [
550 "#fff4bf" , // Coldest - light yellow (<1%)
@@ -98,16 +143,20 @@ function createPythonTooltip(data) {
98143 </div>` ;
99144 }
100145
146+ // Resolve strings for display
147+ const funcname = resolveString ( d . data . funcname ) || resolveString ( d . data . name ) ;
148+ const filename = resolveString ( d . data . filename ) || "" ;
149+
101150 const tooltipHTML = `
102151 <div>
103152 <div style="color: #3776ab; font-weight: 600; font-size: 16px;
104153 margin-bottom: 8px; line-height: 1.3;">
105- ${ d . data . funcname || d . data . name }
154+ ${ funcname }
106155 </div>
107156 <div style="color: #5a6c7d; font-size: 13px; margin-bottom: 12px;
108157 font-family: monospace; background: #f8f9fa;
109158 padding: 4px 8px; border-radius: 4px;">
110- ${ d . data . filename || "" } ${ d . data . lineno ? ":" + d . data . lineno : "" }
159+ ${ filename } ${ d . data . lineno ? ":" + d . data . lineno : "" }
111160 </div>
112161 <div style="display: grid; grid-template-columns: auto 1fr;
113162 gap: 8px 16px; font-size: 14px;">
@@ -255,9 +304,9 @@ function updateSearchHighlight(searchTerm, searchInput) {
255304 let matchCount = 0 ;
256305 d3 . selectAll ( "#chart rect" ) . each ( function ( d ) {
257306 if ( d && d . data ) {
258- const name = d . data . name || "" ;
259- const funcname = d . data . funcname || "" ;
260- const filename = d . data . filename || "" ;
307+ const name = resolveString ( d . data . name ) || "" ;
308+ const funcname = resolveString ( d . data . funcname ) || "" ;
309+ const filename = resolveString ( d . data . filename ) || "" ;
261310 const term = searchTerm . toLowerCase ( ) ;
262311 const matches =
263312 name . toLowerCase ( ) . includes ( term ) ||
@@ -315,12 +364,20 @@ function handleResize(chart, data) {
315364
316365function initFlamegraph ( ) {
317366 ensureLibraryLoaded ( ) ;
318- const tooltip = createPythonTooltip ( EMBEDDED_DATA ) ;
319- const chart = createFlamegraph ( tooltip , EMBEDDED_DATA . value ) ;
320- renderFlamegraph ( chart , EMBEDDED_DATA ) ;
367+
368+ // Extract string table if present and resolve string indices
369+ let processedData = EMBEDDED_DATA ;
370+ if ( EMBEDDED_DATA . strings ) {
371+ stringTable = EMBEDDED_DATA . strings ;
372+ processedData = resolveStringIndices ( EMBEDDED_DATA ) ;
373+ }
374+
375+ const tooltip = createPythonTooltip ( processedData ) ;
376+ const chart = createFlamegraph ( tooltip , processedData . value ) ;
377+ renderFlamegraph ( chart , processedData ) ;
321378 attachPanelControls ( ) ;
322379 initSearchHandlers ( ) ;
323- handleResize ( chart , EMBEDDED_DATA ) ;
380+ handleResize ( chart , processedData ) ;
324381}
325382
326383if ( document . readyState === "loading" ) {
@@ -336,7 +393,10 @@ function populateStats(data) {
336393 const functionMap = new Map ( ) ;
337394
338395 function collectFunctions ( node ) {
339- if ( node . filename && node . funcname ) {
396+ const filename = resolveString ( node . filename ) ;
397+ const funcname = resolveString ( node . funcname ) ;
398+
399+ if ( filename && funcname ) {
340400 // Calculate direct samples (this node's value minus children's values)
341401 let childrenValue = 0 ;
342402 if ( node . children ) {
@@ -345,23 +405,23 @@ function populateStats(data) {
345405 const directSamples = Math . max ( 0 , node . value - childrenValue ) ;
346406
347407 // Use file:line:funcname as key to ensure uniqueness
348- const funcKey = `${ node . filename } :${ node . lineno || '?' } :${ node . funcname } ` ;
408+ const funcKey = `${ filename } :${ node . lineno || '?' } :${ funcname } ` ;
349409
350410 if ( functionMap . has ( funcKey ) ) {
351411 const existing = functionMap . get ( funcKey ) ;
352412 existing . directSamples += directSamples ;
353413 existing . directPercent = ( existing . directSamples / totalSamples ) * 100 ;
354414 // Keep the most representative file/line (the one with more samples)
355415 if ( directSamples > existing . maxSingleSamples ) {
356- existing . filename = node . filename ;
416+ existing . filename = filename ;
357417 existing . lineno = node . lineno || '?' ;
358418 existing . maxSingleSamples = directSamples ;
359419 }
360420 } else {
361421 functionMap . set ( funcKey , {
362- filename : node . filename ,
422+ filename : filename ,
363423 lineno : node . lineno || '?' ,
364- funcname : node . funcname ,
424+ funcname : funcname ,
365425 directSamples,
366426 directPercent : ( directSamples / totalSamples ) * 100 ,
367427 maxSingleSamples : directSamples
0 commit comments