@parca/profile 0.19.25 → 0.19.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -77,212 +77,226 @@ export const fadedFlameRectStyles = {
77
77
  opacity: '0.5',
78
78
  };
79
79
 
80
- export const FlameNode = React.memo(function FlameNodeNoMemo({
81
- table,
82
- row,
83
- colors,
84
- colorBy,
85
- height,
86
- totalWidth,
87
- darkMode,
88
- compareMode,
89
- colorForSimilarNodes,
90
- selectedRow,
91
- onClick,
92
- onContextMenu,
93
- hoveringRow,
94
- setHoveringRow,
95
- isFlameChart,
96
- profileSource,
97
- isRenderedAsFlamegraph = false,
98
- isInSandwichView = false,
99
- maxDepth = 0,
100
- effectiveDepth,
101
- tooltipId = 'default',
102
- }: FlameNodeProps): React.JSX.Element {
103
- // get the columns to read from
104
- const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
105
- const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
106
- const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
107
- const depthColumn = table.getChild(FIELD_DEPTH);
108
- const diffColumn = table.getChild(FIELD_DIFF);
109
- const filenameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
110
- const valueOffsetColumn = table.getChild(FIELD_VALUE_OFFSET);
111
- const tsColumn = table.getChild(FIELD_TIMESTAMP);
112
-
113
- // get the actual values from the columns
114
- const binaries = useAppSelector(selectBinaries);
115
-
116
- const mappingFile: string | null = arrowToString(mappingColumn?.get(row));
117
- const functionName: string | null = arrowToString(functionNameColumn?.get(row));
118
- const cumulative = cumulativeColumn?.get(row) !== null ? BigInt(cumulativeColumn?.get(row)) : 0n;
119
- const diff: bigint | null = diffColumn?.get(row) !== null ? BigInt(diffColumn?.get(row)) : null;
120
- const filename: string | null = arrowToString(filenameColumn?.get(row));
121
- const depth: number = depthColumn?.get(row) ?? 0;
122
-
123
- const valueOffset: bigint =
124
- valueOffsetColumn?.get(row) !== null && valueOffsetColumn?.get(row) !== undefined
125
- ? BigInt(valueOffsetColumn?.get(row))
126
- : 0n;
127
-
128
- const colorAttribute =
129
- colorBy === 'filename' ? filename : colorBy === 'binary' ? mappingFile : null;
130
-
131
- const colorsMap = colors;
132
-
133
- const hoveringName =
134
- hoveringRow !== undefined ? arrowToString(functionNameColumn?.get(hoveringRow)) : '';
135
- const shouldBeHighlighted =
136
- functionName != null && hoveringName != null && functionName === hoveringName;
137
-
138
- const colorResult = useNodeColor({
139
- isDarkMode: darkMode,
80
+ export const FlameNode = React.memo(
81
+ function FlameNodeNoMemo({
82
+ table,
83
+ row,
84
+ colors,
85
+ colorBy,
86
+ height,
87
+ totalWidth,
88
+ darkMode,
140
89
  compareMode,
141
- cumulative,
142
- diff,
143
- colorsMap,
144
- colorAttribute,
145
- });
146
-
147
- const name = useMemo(() => {
148
- return row === 0 ? 'root' : nodeLabel(table, row, binaries.length > 1);
149
- }, [table, row, binaries]);
150
-
151
- // Hide frames beyond effective depth limit
152
- if (effectiveDepth !== undefined && depth > effectiveDepth) {
153
- return <></>;
154
- }
155
-
156
- const selectionOffset =
157
- valueOffsetColumn?.get(selectedRow) !== null &&
158
- valueOffsetColumn?.get(selectedRow) !== undefined
159
- ? BigInt(valueOffsetColumn?.get(selectedRow))
160
- : 0n;
161
- const selectionCumulative =
162
- cumulativeColumn?.get(selectedRow) !== null ? BigInt(cumulativeColumn?.get(selectedRow)) : 0n;
163
- if (
164
- valueOffset + cumulative <= selectionOffset ||
165
- valueOffset >= selectionOffset + selectionCumulative
166
- ) {
167
- // If the end of the node is before the selection offset or the start of the node is after the selection offset + totalWidth, we don't render it.
168
- return <></>;
169
- }
170
-
171
- if (row === 0 && (isFlameChart || isInSandwichView)) {
172
- // The root node is not rendered in the flame chart or sandwich view, so we return null.
173
- return <></>;
174
- }
175
-
176
- // 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.
177
- const tsBounds = boundsFromProfileSource(profileSource);
178
- const total = cumulativeColumn?.get(selectedRow);
179
- const totalRatio = cumulative > total ? 1 : Number(cumulative) / Number(total);
180
- const width: number = isFlameChart
181
- ? (Number(cumulative) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
182
- : totalRatio * totalWidth;
183
-
184
- if (width <= 1) {
185
- return <></>;
186
- }
90
+ colorForSimilarNodes,
91
+ selectedRow,
92
+ onClick,
93
+ onContextMenu,
94
+ hoveringRow,
95
+ setHoveringRow,
96
+ isFlameChart,
97
+ profileSource,
98
+ isRenderedAsFlamegraph = false,
99
+ isInSandwichView = false,
100
+ maxDepth = 0,
101
+ effectiveDepth,
102
+ tooltipId = 'default',
103
+ }: 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);
113
+
114
+ // get the actual values from the columns
115
+ const binaries = useAppSelector(selectBinaries);
116
+
117
+ const mappingFile: string | null = arrowToString(mappingColumn?.get(row));
118
+ const functionName: string | null = arrowToString(functionNameColumn?.get(row));
119
+ const cumulative =
120
+ cumulativeColumn?.get(row) !== null ? BigInt(cumulativeColumn?.get(row)) : 0n;
121
+ const diff: bigint | null = diffColumn?.get(row) !== null ? BigInt(diffColumn?.get(row)) : null;
122
+ const filename: string | null = arrowToString(filenameColumn?.get(row));
123
+ const depth: number = depthColumn?.get(row) ?? 0;
124
+
125
+ const valueOffset: bigint =
126
+ valueOffsetColumn?.get(row) !== null && valueOffsetColumn?.get(row) !== undefined
127
+ ? BigInt(valueOffsetColumn?.get(row))
128
+ : 0n;
129
+
130
+ const colorAttribute =
131
+ colorBy === 'filename' ? filename : colorBy === 'binary' ? mappingFile : null;
132
+
133
+ const colorsMap = colors;
134
+
135
+ const hoveringName =
136
+ hoveringRow !== undefined ? arrowToString(functionNameColumn?.get(hoveringRow)) : '';
137
+ const shouldBeHighlighted =
138
+ functionName != null && hoveringName != null && functionName === hoveringName;
139
+
140
+ const colorResult = useNodeColor({
141
+ isDarkMode: darkMode,
142
+ compareMode,
143
+ cumulative,
144
+ diff,
145
+ colorsMap,
146
+ colorAttribute,
147
+ });
148
+
149
+ const name = useMemo(() => {
150
+ return row === 0 ? 'root' : nodeLabel(table, row, binaries.length > 1);
151
+ }, [table, row, binaries]);
152
+
153
+ // Hide frames beyond effective depth limit
154
+ if (effectiveDepth !== undefined && depth > effectiveDepth) {
155
+ return <></>;
156
+ }
187
157
 
188
- const selectedDepth = depthColumn?.get(selectedRow);
189
- const styles =
190
- selectedDepth !== undefined && selectedDepth > depth ? fadedFlameRectStyles : flameRectStyles;
158
+ const selectionOffset =
159
+ valueOffsetColumn?.get(selectedRow) !== null &&
160
+ valueOffsetColumn?.get(selectedRow) !== undefined
161
+ ? BigInt(valueOffsetColumn?.get(selectedRow))
162
+ : 0n;
163
+ const selectionCumulative =
164
+ cumulativeColumn?.get(selectedRow) !== null ? BigInt(cumulativeColumn?.get(selectedRow)) : 0n;
165
+ if (
166
+ valueOffset + cumulative <= selectionOffset ||
167
+ valueOffset >= selectionOffset + selectionCumulative
168
+ ) {
169
+ // If the end of the node is before the selection offset or the start of the node is after the selection offset + totalWidth, we don't render it.
170
+ return <></>;
171
+ }
191
172
 
192
- const onMouseEnter = (): void => {
193
- setHoveringRow(row);
194
- window.dispatchEvent(
195
- new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
196
- detail: {row},
197
- })
198
- );
199
- };
200
-
201
- const onMouseLeave = (): void => {
202
- setHoveringRow(undefined);
203
- window.dispatchEvent(
204
- new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
205
- detail: {row: null},
206
- })
207
- );
208
- };
209
-
210
- const handleContextMenu = (e: React.MouseEvent): void => {
211
- onContextMenu(e, row);
212
- };
213
-
214
- const ts = tsColumn !== null ? Number(tsColumn.get(row)) : 0;
215
- const x =
216
- isFlameChart && tsColumn !== null
217
- ? ((ts - Number(tsBounds[0])) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
218
- : selectedDepth > depth
219
- ? 0
220
- : ((Number(valueOffset) - Number(selectionOffset)) / Number(total)) * totalWidth;
221
-
222
- const calculateY = (
223
- isRenderedAsFlamegraph: boolean,
224
- isInSandwichView: boolean,
225
- isFlameChart: boolean,
226
- maxDepth: number,
227
- depth: number,
228
- height: number
229
- ): number => {
230
- if (isRenderedAsFlamegraph) {
231
- return (maxDepth - depth) * height; // Flamegraph is inverted
173
+ if (row === 0 && (isFlameChart || isInSandwichView)) {
174
+ // The root node is not rendered in the flame chart or sandwich view, so we return null.
175
+ return <></>;
232
176
  }
233
177
 
234
- if (isFlameChart || isInSandwichView) {
235
- return (depth - 1) * height;
178
+ // 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.
179
+ const tsBounds = boundsFromProfileSource(profileSource);
180
+ const total = cumulativeColumn?.get(selectedRow);
181
+ const totalRatio = cumulative > total ? 1 : Number(cumulative) / Number(total);
182
+ const width: number = isFlameChart
183
+ ? (Number(cumulative) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
184
+ : totalRatio * totalWidth;
185
+
186
+ if (width <= 1) {
187
+ return <></>;
236
188
  }
237
189
 
238
- return depth * height;
239
- };
190
+ const selectedDepth = depthColumn?.get(selectedRow);
191
+ const styles =
192
+ selectedDepth !== undefined && selectedDepth > depth ? fadedFlameRectStyles : flameRectStyles;
193
+
194
+ const onMouseEnter = (): void => {
195
+ setHoveringRow(row);
196
+ window.dispatchEvent(
197
+ new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
198
+ detail: {row},
199
+ })
200
+ );
201
+ };
202
+
203
+ const onMouseLeave = (): void => {
204
+ setHoveringRow(undefined);
205
+ window.dispatchEvent(
206
+ new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
207
+ detail: {row: null},
208
+ })
209
+ );
210
+ };
211
+
212
+ const handleContextMenu = (e: React.MouseEvent): void => {
213
+ onContextMenu(e, row);
214
+ };
215
+
216
+ const ts = tsColumn !== null ? Number(tsColumn.get(row)) : 0;
217
+ const x =
218
+ isFlameChart && tsColumn !== null
219
+ ? ((ts - Number(tsBounds[0])) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
220
+ : selectedDepth > depth
221
+ ? 0
222
+ : ((Number(valueOffset) - Number(selectionOffset)) / Number(total)) * totalWidth;
223
+
224
+ const calculateY = (
225
+ isRenderedAsFlamegraph: boolean,
226
+ isInSandwichView: boolean,
227
+ isFlameChart: boolean,
228
+ maxDepth: number,
229
+ depth: number,
230
+ height: number
231
+ ): number => {
232
+ if (isRenderedAsFlamegraph) {
233
+ return (maxDepth - depth) * height; // Flamegraph is inverted
234
+ }
235
+
236
+ if (isFlameChart || isInSandwichView) {
237
+ return (depth - 1) * height;
238
+ }
239
+
240
+ return depth * height;
241
+ };
242
+
243
+ const y = calculateY(
244
+ isRenderedAsFlamegraph,
245
+ isInSandwichView,
246
+ isFlameChart,
247
+ effectiveDepth ?? maxDepth,
248
+ depth,
249
+ height
250
+ );
240
251
 
241
- const y = calculateY(
242
- isRenderedAsFlamegraph,
243
- isInSandwichView,
244
- isFlameChart,
245
- effectiveDepth ?? maxDepth,
246
- depth,
247
- height
248
- );
249
-
250
- return (
251
- <>
252
- <g
253
- id={row === 0 ? 'root-span' : undefined}
254
- transform={`translate(${x + 1}, ${y + 1})`}
255
- style={styles}
256
- onMouseEnter={onMouseEnter}
257
- onMouseLeave={onMouseLeave}
258
- onClick={onClick}
259
- onContextMenu={handleContextMenu}
260
- >
261
- <rect
262
- x={0}
263
- y={0}
264
- width={width}
265
- height={height}
266
- style={{
267
- fill: colorResult,
268
- }}
269
- className={cx(
270
- shouldBeHighlighted
271
- ? `${colorForSimilarNodes} stroke-[3] [stroke-dasharray:6,4] [stroke-linecap:round] [stroke-linejoin:round] h-6`
272
- : 'stroke-white dark:stroke-gray-700'
252
+ return (
253
+ <>
254
+ <g
255
+ id={row === 0 ? 'root-span' : undefined}
256
+ transform={`translate(${x + 1}, ${y + 1})`}
257
+ style={styles}
258
+ onMouseEnter={onMouseEnter}
259
+ onMouseLeave={onMouseLeave}
260
+ onClick={onClick}
261
+ onContextMenu={handleContextMenu}
262
+ >
263
+ <rect
264
+ x={0}
265
+ y={0}
266
+ width={width}
267
+ height={height}
268
+ style={{
269
+ fill: colorResult,
270
+ }}
271
+ className={cx(
272
+ shouldBeHighlighted
273
+ ? `${colorForSimilarNodes} stroke-[3] [stroke-dasharray:6,4] [stroke-linecap:round] [stroke-linejoin:round] h-6`
274
+ : 'stroke-white dark:stroke-gray-700'
275
+ )}
276
+ />
277
+ {width > 5 && (
278
+ <svg width={width - 5} height={height}>
279
+ <TextWithEllipsis
280
+ text={name}
281
+ x={5}
282
+ y={15}
283
+ width={width - 10} // Subtract padding from available width
284
+ />
285
+ </svg>
273
286
  )}
274
- />
275
- {width > 5 && (
276
- <svg width={width - 5} height={height}>
277
- <TextWithEllipsis
278
- text={name}
279
- x={5}
280
- y={15}
281
- width={width - 10} // Subtract padding from available width
282
- />
283
- </svg>
284
- )}
285
- </g>
286
- </>
287
- );
288
- });
287
+ </g>
288
+ </>
289
+ );
290
+ },
291
+ (prevProps, nextProps) => {
292
+ // Only re-render if the relevant props have changed
293
+ return (
294
+ prevProps.row === nextProps.row &&
295
+ prevProps.selectedRow === nextProps.selectedRow &&
296
+ prevProps.hoveringRow === nextProps.hoveringRow &&
297
+ prevProps.totalWidth === nextProps.totalWidth &&
298
+ prevProps.height === nextProps.height &&
299
+ prevProps.effectiveDepth === nextProps.effectiveDepth
300
+ );
301
+ }
302
+ );
@@ -39,12 +39,15 @@ import {FlameNode, RowHeight, colorByColors} from './FlameGraphNodes';
39
39
  import {MemoizedTooltip} from './MemoizedTooltip';
40
40
  import {TooltipProvider} from './TooltipContext';
41
41
  import {useFilenamesList} from './useMappingList';
42
+ import {useScrollViewport} from './useScrollViewport';
43
+ import {useVisibleNodes} from './useVisibleNodes';
42
44
  import {
43
45
  CurrentPathFrame,
44
46
  arrowToString,
45
47
  extractFeature,
46
48
  extractFilenameFeature,
47
49
  getCurrentPathFrameData,
50
+ getMaxDepth,
48
51
  isCurrentPathFrameMatch,
49
52
  } from './utils';
50
53
 
@@ -120,17 +123,6 @@ export const getFilenameColors = (
120
123
 
121
124
  const noop = (): void => {};
122
125
 
123
- function getMaxDepth(depthColumn: Vector<any> | null): number {
124
- if (depthColumn === null) return 0;
125
-
126
- let max = 0;
127
- for (const val of depthColumn) {
128
- const numVal = Number(val);
129
- if (numVal > max) max = numVal;
130
- }
131
- return max;
132
- }
133
-
134
126
  export const FlameGraphArrow = memo(function FlameGraphArrow({
135
127
  arrow,
136
128
  total,
@@ -166,35 +158,11 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
166
158
  return result;
167
159
  }, [arrow, perf]);
168
160
  const svg = useRef(null);
161
+ const containerRef = useRef<HTMLDivElement>(null);
169
162
  const renderStartTime = useRef<number>(0);
170
163
 
171
164
  const [svgElement, setSvgElement] = useState<SVGSVGElement | null>(null);
172
165
 
173
- useEffect(() => {
174
- if (perf?.markInteraction != null) {
175
- renderStartTime.current = performance.now();
176
- }
177
- }, [table, width, curPath, perf]);
178
-
179
- useEffect(() => {
180
- if (perf?.setMeasurement != null && renderStartTime.current > 0) {
181
- const measureRenderTime = (): void => {
182
- const renderTime = performance.now() - renderStartTime.current;
183
- if (perf?.setMeasurement != null) {
184
- perf.setMeasurement('flamegraph.render_time', renderTime);
185
- }
186
-
187
- renderStartTime.current = 0;
188
- };
189
-
190
- requestAnimationFrame(measureRenderTime);
191
- }
192
- }, [table, width, curPath, perf]);
193
-
194
- useEffect(() => {
195
- setSvgElement(svg.current);
196
- }, [tooltipId]);
197
-
198
166
  const {excludeBinary} = useProfileFilters();
199
167
 
200
168
  const {compareMode} = useProfileViewContext();
@@ -304,10 +272,13 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
304
272
  // Use deferred value to prevent UI blocking when expanding frames
305
273
  const deferredEffectiveDepth = useDeferredValue(effectiveDepth);
306
274
 
307
- const height = isInSandwichView
275
+ const totalHeight = isInSandwichView
308
276
  ? deferredEffectiveDepth * RowHeight
309
277
  : (deferredEffectiveDepth + 1) * RowHeight;
310
278
 
279
+ // Get the viewport of the container, this is used to determine which rows are visible.
280
+ const viewport = useScrollViewport(containerRef);
281
+
311
282
  // To find the selected row, we must walk the current path and look at which
312
283
  // children of the current frame matches the path element exactly. Until the
313
284
  // end, the row we find at the end is our selected row.
@@ -333,6 +304,25 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
333
304
  }
334
305
  const selectedRow = currentRow;
335
306
 
307
+ const visibleNodes = useVisibleNodes({
308
+ table,
309
+ viewport,
310
+ total,
311
+ width: width ?? 1,
312
+ selectedRow,
313
+ effectiveDepth: deferredEffectiveDepth,
314
+ });
315
+
316
+ useEffect(() => {
317
+ if (perf?.markInteraction != null) {
318
+ renderStartTime.current = performance.now();
319
+ }
320
+ }, [table, width, curPath, perf]);
321
+
322
+ useEffect(() => {
323
+ setSvgElement(svg.current);
324
+ }, [tooltipId]);
325
+
336
326
  return (
337
327
  <TooltipProvider
338
328
  table={table}
@@ -359,46 +349,55 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
359
349
  isInSandwichView={isInSandwichView}
360
350
  />
361
351
  <MemoizedTooltip contextElement={svgElement} dockedMetainfo={dockedMetainfo} />
362
- <svg
363
- className="font-robotoMono"
364
- width={width}
365
- height={height}
366
- preserveAspectRatio="xMinYMid"
367
- ref={svg}
352
+ <div
353
+ ref={containerRef}
354
+ className="overflow-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100 dark:scrollbar-thumb-gray-600 dark:scrollbar-track-gray-800 will-change-transform scroll-smooth webkit-overflow-scrolling-touch contain"
355
+ style={{
356
+ width: width ?? '100%',
357
+ contain: 'layout style paint',
358
+ }}
368
359
  >
369
- {Array.from({length: table.numRows}, (_, row) => (
370
- <FlameNode
371
- key={row}
372
- table={table}
373
- row={row} // root is always row 0 in the arrow record
374
- colors={colorByColors}
375
- colorBy={colorByValue}
376
- totalWidth={width ?? 1}
377
- height={RowHeight}
378
- darkMode={isDarkMode}
379
- compareMode={compareMode}
380
- colorForSimilarNodes={colorForSimilarNodes}
381
- selectedRow={selectedRow}
382
- onClick={() => {
383
- if (isFlameChart) {
384
- // We don't want to expand in flame charts.
385
- return;
386
- }
387
- handleRowClick(row);
388
- }}
389
- onContextMenu={displayMenu}
390
- hoveringRow={highlightSimilarStacksPreference ? hoveringRow : undefined}
391
- setHoveringRow={highlightSimilarStacksPreference ? setHoveringRow : noop}
392
- isFlameChart={isFlameChart}
393
- profileSource={profileSource}
394
- isRenderedAsFlamegraph={isRenderedAsFlamegraph}
395
- isInSandwichView={isInSandwichView}
396
- maxDepth={maxDepth}
397
- effectiveDepth={deferredEffectiveDepth}
398
- tooltipId={tooltipId}
399
- />
400
- ))}
401
- </svg>
360
+ <svg
361
+ className="font-robotoMono"
362
+ width={width ?? 0}
363
+ height={totalHeight}
364
+ preserveAspectRatio="xMinYMid"
365
+ ref={svg}
366
+ >
367
+ {visibleNodes.map(row => (
368
+ <FlameNode
369
+ key={row}
370
+ table={table}
371
+ row={row}
372
+ colors={colorByColors}
373
+ colorBy={colorByValue}
374
+ totalWidth={width ?? 1}
375
+ height={RowHeight}
376
+ darkMode={isDarkMode}
377
+ compareMode={compareMode}
378
+ colorForSimilarNodes={colorForSimilarNodes}
379
+ selectedRow={selectedRow}
380
+ onClick={() => {
381
+ if (isFlameChart) {
382
+ // We don't want to expand in flame charts.
383
+ return;
384
+ }
385
+ handleRowClick(row);
386
+ }}
387
+ onContextMenu={displayMenu}
388
+ hoveringRow={highlightSimilarStacksPreference ? hoveringRow : undefined}
389
+ setHoveringRow={highlightSimilarStacksPreference ? setHoveringRow : noop}
390
+ isFlameChart={isFlameChart}
391
+ profileSource={profileSource}
392
+ isRenderedAsFlamegraph={isRenderedAsFlamegraph}
393
+ isInSandwichView={isInSandwichView}
394
+ maxDepth={maxDepth}
395
+ effectiveDepth={deferredEffectiveDepth}
396
+ tooltipId={tooltipId}
397
+ />
398
+ ))}
399
+ </svg>
400
+ </div>
402
401
  </div>
403
402
  </TooltipProvider>
404
403
  );