@parca/profile 0.19.25 → 0.19.27
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.
- package/CHANGELOG.md +8 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +8 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +33 -42
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.d.ts +8 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.d.ts.map +1 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +70 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.d.ts +24 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.d.ts.map +1 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +111 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/utils.d.ts +2 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/utils.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/utils.js +11 -0
- package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.js +2 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts +7 -4
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +39 -50
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -5
- package/src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx +214 -200
- package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +90 -90
- package/src/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.ts +89 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.ts +167 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/utils.ts +12 -1
- package/src/ProfileView/components/ProfileFilters/filterPresets.ts +4 -2
- package/src/ProfileView/components/ProfileFilters/useProfileFilters.ts +57 -76
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +1 -1
- package/src/ProfileView/components/Toolbars/index.tsx +1 -1
- package/src/ProfileView/components/ViewSelector/index.tsx +1 -1
|
@@ -77,212 +77,226 @@ export const fadedFlameRectStyles = {
|
|
|
77
77
|
opacity: '0.5',
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
export const FlameNode = React.memo(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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();
|
|
@@ -279,20 +247,27 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
279
247
|
excludeBinary(binaryToRemove);
|
|
280
248
|
};
|
|
281
249
|
|
|
282
|
-
const handleRowClick = (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
250
|
+
const handleRowClick = useCallback(
|
|
251
|
+
(row: number): void => {
|
|
252
|
+
if (isFlameChart) {
|
|
253
|
+
// In flame charts, we don't want to expand the node, so we return early.
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Walk down the stack starting at row until we reach the root (row 0).
|
|
257
|
+
const path: CurrentPathFrame[] = [];
|
|
258
|
+
let currentRow = row;
|
|
259
|
+
while (currentRow > 0) {
|
|
260
|
+
const frame = getCurrentPathFrameData(table, currentRow);
|
|
261
|
+
path.push(frame);
|
|
262
|
+
currentRow = table.getChild(FIELD_PARENT)?.get(currentRow) ?? 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Reverse the path so that the root is first.
|
|
266
|
+
path.reverse();
|
|
267
|
+
setCurPath(path);
|
|
268
|
+
},
|
|
269
|
+
[table, setCurPath, isFlameChart]
|
|
270
|
+
);
|
|
296
271
|
|
|
297
272
|
const depthColumn = table.getChild(FIELD_DEPTH);
|
|
298
273
|
const maxDepth = getMaxDepth(depthColumn);
|
|
@@ -304,10 +279,13 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
304
279
|
// Use deferred value to prevent UI blocking when expanding frames
|
|
305
280
|
const deferredEffectiveDepth = useDeferredValue(effectiveDepth);
|
|
306
281
|
|
|
307
|
-
const
|
|
282
|
+
const totalHeight = isInSandwichView
|
|
308
283
|
? deferredEffectiveDepth * RowHeight
|
|
309
284
|
: (deferredEffectiveDepth + 1) * RowHeight;
|
|
310
285
|
|
|
286
|
+
// Get the viewport of the container, this is used to determine which rows are visible.
|
|
287
|
+
const viewport = useScrollViewport(containerRef);
|
|
288
|
+
|
|
311
289
|
// To find the selected row, we must walk the current path and look at which
|
|
312
290
|
// children of the current frame matches the path element exactly. Until the
|
|
313
291
|
// end, the row we find at the end is our selected row.
|
|
@@ -333,6 +311,25 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
333
311
|
}
|
|
334
312
|
const selectedRow = currentRow;
|
|
335
313
|
|
|
314
|
+
const visibleNodes = useVisibleNodes({
|
|
315
|
+
table,
|
|
316
|
+
viewport,
|
|
317
|
+
total,
|
|
318
|
+
width: width ?? 1,
|
|
319
|
+
selectedRow,
|
|
320
|
+
effectiveDepth: deferredEffectiveDepth,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (perf?.markInteraction != null) {
|
|
325
|
+
renderStartTime.current = performance.now();
|
|
326
|
+
}
|
|
327
|
+
}, [table, width, curPath, perf]);
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
setSvgElement(svg.current);
|
|
331
|
+
}, [tooltipId]);
|
|
332
|
+
|
|
336
333
|
return (
|
|
337
334
|
<TooltipProvider
|
|
338
335
|
table={table}
|
|
@@ -359,46 +356,49 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
359
356
|
isInSandwichView={isInSandwichView}
|
|
360
357
|
/>
|
|
361
358
|
<MemoizedTooltip contextElement={svgElement} dockedMetainfo={dockedMetainfo} />
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
359
|
+
<div
|
|
360
|
+
ref={containerRef}
|
|
361
|
+
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"
|
|
362
|
+
style={{
|
|
363
|
+
width: width ?? '100%',
|
|
364
|
+
contain: 'layout style paint',
|
|
365
|
+
}}
|
|
368
366
|
>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
367
|
+
<svg
|
|
368
|
+
className="font-robotoMono"
|
|
369
|
+
width={width ?? 0}
|
|
370
|
+
height={totalHeight}
|
|
371
|
+
preserveAspectRatio="xMinYMid"
|
|
372
|
+
ref={svg}
|
|
373
|
+
>
|
|
374
|
+
{visibleNodes.map(row => (
|
|
375
|
+
<FlameNode
|
|
376
|
+
key={row}
|
|
377
|
+
table={table}
|
|
378
|
+
row={row}
|
|
379
|
+
colors={colorByColors}
|
|
380
|
+
colorBy={colorByValue}
|
|
381
|
+
totalWidth={width ?? 1}
|
|
382
|
+
height={RowHeight}
|
|
383
|
+
darkMode={isDarkMode}
|
|
384
|
+
compareMode={compareMode}
|
|
385
|
+
colorForSimilarNodes={colorForSimilarNodes}
|
|
386
|
+
selectedRow={selectedRow}
|
|
387
|
+
onClick={() => handleRowClick(row)}
|
|
388
|
+
onContextMenu={displayMenu}
|
|
389
|
+
hoveringRow={highlightSimilarStacksPreference ? hoveringRow : undefined}
|
|
390
|
+
setHoveringRow={highlightSimilarStacksPreference ? setHoveringRow : noop}
|
|
391
|
+
isFlameChart={isFlameChart}
|
|
392
|
+
profileSource={profileSource}
|
|
393
|
+
isRenderedAsFlamegraph={isRenderedAsFlamegraph}
|
|
394
|
+
isInSandwichView={isInSandwichView}
|
|
395
|
+
maxDepth={maxDepth}
|
|
396
|
+
effectiveDepth={deferredEffectiveDepth}
|
|
397
|
+
tooltipId={tooltipId}
|
|
398
|
+
/>
|
|
399
|
+
))}
|
|
400
|
+
</svg>
|
|
401
|
+
</div>
|
|
402
402
|
</div>
|
|
403
403
|
</TooltipProvider>
|
|
404
404
|
);
|