1111// See the License for the specific language governing permissions and
1212// limitations under the License.
1313
14- import React , { useMemo } from 'react' ;
14+ import React , { useCallback , useMemo } from 'react' ;
1515
1616import { Table } from 'apache-arrow' ;
1717import cx from 'classnames' ;
@@ -101,36 +101,52 @@ export const FlameNode = React.memo(
101101 effectiveDepth,
102102 tooltipId = 'default' ,
103103 } : FlameNodeProps ) : React . JSX . Element {
104- // get the columns to read from
105- const mappingColumn = table . getChild ( FIELD_MAPPING_FILE ) ;
106- const functionNameColumn = table . getChild ( FIELD_FUNCTION_NAME ) ;
107- const cumulativeColumn = table . getChild ( FIELD_CUMULATIVE ) ;
108- const depthColumn = table . getChild ( FIELD_DEPTH ) ;
109- const diffColumn = table . getChild ( FIELD_DIFF ) ;
110- const filenameColumn = table . getChild ( FIELD_FUNCTION_FILE_NAME ) ;
111- const valueOffsetColumn = table . getChild ( FIELD_VALUE_OFFSET ) ;
112- const tsColumn = table . getChild ( FIELD_TIMESTAMP ) ;
104+ // Memoize column references - only changes when table changes
105+ const columns = useMemo (
106+ ( ) => ( {
107+ mapping : table . getChild ( FIELD_MAPPING_FILE ) ,
108+ functionName : table . getChild ( FIELD_FUNCTION_NAME ) ,
109+ cumulative : table . getChild ( FIELD_CUMULATIVE ) ,
110+ depth : table . getChild ( FIELD_DEPTH ) ,
111+ diff : table . getChild ( FIELD_DIFF ) ,
112+ filename : table . getChild ( FIELD_FUNCTION_FILE_NAME ) ,
113+ valueOffset : table . getChild ( FIELD_VALUE_OFFSET ) ,
114+ ts : table . getChild ( FIELD_TIMESTAMP ) ,
115+ } ) ,
116+ [ table ]
117+ ) ;
113118
114119 // get the actual values from the columns
115120 const binaries = useAppSelector ( selectBinaries ) ;
116121
117- const mappingFile : string | null = arrowToString ( mappingColumn ?. get ( row ) ) ;
118- const functionName : string | null = arrowToString ( functionNameColumn ?. get ( row ) ) ;
119- const cumulative = cumulativeColumn ?. get ( row ) != null ? BigInt ( cumulativeColumn ?. get ( row ) ) : 0n ;
120- const diff : bigint | null = diffColumn ?. get ( row ) != null ? BigInt ( diffColumn ?. get ( row ) ) : null ;
121- const filename : string | null = arrowToString ( filenameColumn ?. get ( row ) ) ;
122- const depth : number = depthColumn ?. get ( row ) ?? 0 ;
123-
124- const valueOffset : bigint =
125- valueOffsetColumn ?. get ( row ) !== null && valueOffsetColumn ?. get ( row ) !== undefined
126- ? BigInt ( valueOffsetColumn ?. get ( row ) )
127- : 0n ;
122+ // Memoize row data extraction - only changes when table or row changes
123+ const rowData = useMemo ( ( ) => {
124+ const mappingFile : string | null = arrowToString ( columns . mapping ?. get ( row ) ) ;
125+ const functionName : string | null = arrowToString ( columns . functionName ?. get ( row ) ) ;
126+ const cumulative =
127+ columns . cumulative ?. get ( row ) != null ? BigInt ( columns . cumulative ?. get ( row ) ) : 0n ;
128+ const diff : bigint | null =
129+ columns . diff ?. get ( row ) != null ? BigInt ( columns . diff ?. get ( row ) ) : null ;
130+ const filename : string | null = arrowToString ( columns . filename ?. get ( row ) ) ;
131+ const depth : number = columns . depth ?. get ( row ) ?? 0 ;
132+ const valueOffset : bigint =
133+ columns . valueOffset ?. get ( row ) !== null && columns . valueOffset ?. get ( row ) !== undefined
134+ ? BigInt ( columns . valueOffset ?. get ( row ) )
135+ : 0n ;
136+
137+ return { mappingFile, functionName, cumulative, diff, filename, depth, valueOffset} ;
138+ } , [ columns , row ] ) ;
139+
140+ const { mappingFile, functionName, cumulative, diff, filename, depth, valueOffset} = rowData ;
128141
129142 const colorAttribute =
130143 colorBy === 'filename' ? filename : colorBy === 'binary' ? mappingFile : null ;
131144
132- const hoveringName =
133- hoveringRow !== undefined ? arrowToString ( functionNameColumn ?. get ( hoveringRow ) ) : '' ;
145+ // Memoize hovering name lookup
146+ const hoveringName = useMemo ( ( ) => {
147+ return hoveringRow !== undefined ? arrowToString ( columns . functionName ?. get ( hoveringRow ) ) : '' ;
148+ } , [ columns . functionName , hoveringRow ] ) ;
149+
134150 const shouldBeHighlighted =
135151 functionName != null && hoveringName != null && functionName === hoveringName ;
136152
@@ -147,18 +163,59 @@ export const FlameNode = React.memo(
147163 return row === 0 ? 'root' : nodeLabel ( table , row , binaries . length > 1 ) ;
148164 } , [ table , row , binaries ] ) ;
149165
166+ // Memoize selection data - only changes when selectedRow changes
167+ const selectionData = useMemo ( ( ) => {
168+ const selectionOffset =
169+ columns . valueOffset ?. get ( selectedRow ) !== null &&
170+ columns . valueOffset ?. get ( selectedRow ) !== undefined
171+ ? BigInt ( columns . valueOffset ?. get ( selectedRow ) )
172+ : 0n ;
173+ const selectionCumulative =
174+ columns . cumulative ?. get ( selectedRow ) !== null
175+ ? BigInt ( columns . cumulative ?. get ( selectedRow ) )
176+ : 0n ;
177+ const selectedDepth = columns . depth ?. get ( selectedRow ) ;
178+ const total = columns . cumulative ?. get ( selectedRow ) ;
179+ return { selectionOffset, selectionCumulative, selectedDepth, total} ;
180+ } , [ columns , selectedRow ] ) ;
181+
182+ const { selectionOffset, selectionCumulative, selectedDepth, total} = selectionData ;
183+
184+ // Memoize tsBounds - only changes when profileSource changes
185+ const tsBounds = useMemo ( ( ) => boundsFromProfileSource ( profileSource ) , [ profileSource ] ) ;
186+
187+ // Memoize event handlers
188+ const onMouseEnter = useCallback ( ( ) : void => {
189+ setHoveringRow ( row ) ;
190+ window . dispatchEvent (
191+ new CustomEvent ( `flame-tooltip-update-${ tooltipId } ` , {
192+ detail : { row} ,
193+ } )
194+ ) ;
195+ } , [ setHoveringRow , row , tooltipId ] ) ;
196+
197+ const onMouseLeave = useCallback ( ( ) : void => {
198+ setHoveringRow ( undefined ) ;
199+ window . dispatchEvent (
200+ new CustomEvent ( `flame-tooltip-update-${ tooltipId } ` , {
201+ detail : { row : null } ,
202+ } )
203+ ) ;
204+ } , [ setHoveringRow , tooltipId ] ) ;
205+
206+ const handleContextMenu = useCallback (
207+ ( e : React . MouseEvent ) : void => {
208+ onContextMenu ( e , row ) ;
209+ } ,
210+ [ onContextMenu , row ]
211+ ) ;
212+
213+ // Early returns - all hooks must be called before this point
150214 // Hide frames beyond effective depth limit
151215 if ( effectiveDepth !== undefined && depth > effectiveDepth ) {
152216 return < > </ > ;
153217 }
154218
155- const selectionOffset =
156- valueOffsetColumn ?. get ( selectedRow ) !== null &&
157- valueOffsetColumn ?. get ( selectedRow ) !== undefined
158- ? BigInt ( valueOffsetColumn ?. get ( selectedRow ) )
159- : 0n ;
160- const selectionCumulative =
161- cumulativeColumn ?. get ( selectedRow ) !== null ? BigInt ( cumulativeColumn ?. get ( selectedRow ) ) : 0n ;
162219 if (
163220 valueOffset + cumulative <= selectionOffset ||
164221 valueOffset >= selectionOffset + selectionCumulative
@@ -173,8 +230,6 @@ export const FlameNode = React.memo(
173230 }
174231
175232 // Cumulative can be larger than total when a selection is made. All parents of the selection are likely larger, but we want to only show them as 100% in the graph.
176- const tsBounds = boundsFromProfileSource ( profileSource ) ;
177- const total = cumulativeColumn ?. get ( selectedRow ) ;
178233 const totalRatio = cumulative > total ? 1 : Number ( cumulative ) / Number ( total ) ;
179234 const width : number = isFlameChart
180235 ? ( Number ( cumulative ) / ( Number ( tsBounds [ 1 ] ) - Number ( tsBounds [ 0 ] ) ) ) * totalWidth
@@ -184,35 +239,12 @@ export const FlameNode = React.memo(
184239 return < > </ > ;
185240 }
186241
187- const selectedDepth = depthColumn ?. get ( selectedRow ) ;
188242 const styles =
189243 selectedDepth !== undefined && selectedDepth > depth ? fadedFlameRectStyles : flameRectStyles ;
190244
191- const onMouseEnter = ( ) : void => {
192- setHoveringRow ( row ) ;
193- window . dispatchEvent (
194- new CustomEvent ( `flame-tooltip-update-${ tooltipId } ` , {
195- detail : { row} ,
196- } )
197- ) ;
198- } ;
199-
200- const onMouseLeave = ( ) : void => {
201- setHoveringRow ( undefined ) ;
202- window . dispatchEvent (
203- new CustomEvent ( `flame-tooltip-update-${ tooltipId } ` , {
204- detail : { row : null } ,
205- } )
206- ) ;
207- } ;
208-
209- const handleContextMenu = ( e : React . MouseEvent ) : void => {
210- onContextMenu ( e , row ) ;
211- } ;
212-
213- const ts = tsColumn !== null ? Number ( tsColumn . get ( row ) ) : 0 ;
245+ const ts = columns . ts !== null ? Number ( columns . ts . get ( row ) ) : 0 ;
214246 const x =
215- isFlameChart && tsColumn !== null
247+ isFlameChart && columns . ts !== null
216248 ? ( ( ts - Number ( tsBounds [ 0 ] ) ) / ( Number ( tsBounds [ 1 ] ) - Number ( tsBounds [ 0 ] ) ) ) * totalWidth
217249 : selectedDepth > depth
218250 ? 0
0 commit comments