@parca/profile 0.19.24 → 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.
- 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 -40
- 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/ColorStackLegend.d.ts.map +1 -1
- package/dist/ProfileView/components/ColorStackLegend.js +0 -1
- package/dist/ProfileView/components/DashboardItems/index.d.ts +3 -2
- package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
- package/dist/ProfileView/components/DashboardItems/index.js +2 -2
- package/dist/ProfileView/index.d.ts +1 -1
- package/dist/ProfileView/index.d.ts.map +1 -1
- package/dist/ProfileView/index.js +2 -1
- package/dist/ProfileView/types/visualization.d.ts +6 -10
- package/dist/ProfileView/types/visualization.d.ts.map +1 -1
- package/dist/ProfileViewWithData.d.ts.map +1 -1
- package/dist/ProfileViewWithData.js +52 -22
- package/dist/Sandwich/components/CalleesSection.d.ts +3 -12
- package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CalleesSection.js +2 -4
- package/dist/Sandwich/components/CallersSection.d.ts +3 -13
- package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CallersSection.js +5 -8
- package/dist/Sandwich/index.d.ts +2 -10
- package/dist/Sandwich/index.d.ts.map +1 -1
- package/dist/Sandwich/index.js +5 -103
- package/dist/styles.css +1 -1
- package/package.json +6 -6
- package/src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx +214 -200
- package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +75 -76
- 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/ColorStackLegend.tsx +0 -2
- package/src/ProfileView/components/DashboardItems/index.tsx +4 -12
- package/src/ProfileView/index.tsx +2 -0
- package/src/ProfileView/types/visualization.ts +7 -18
- package/src/ProfileViewWithData.tsx +65 -30
- package/src/Sandwich/components/CalleesSection.tsx +10 -28
- package/src/Sandwich/components/CallersSection.tsx +13 -34
- package/src/Sandwich/index.tsx +8 -170
|
@@ -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();
|
|
@@ -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
|
|
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
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
);
|