@parca/profile 0.16.379 → 0.16.380
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 +4 -0
- package/dist/ProfileSource.d.ts +3 -0
- package/dist/ProfileSource.js +6 -3
- package/dist/Table/ColumnsVisibility.d.ts +3 -3
- package/dist/Table/index.d.ts +22 -11
- package/dist/Table/index.js +276 -77
- package/dist/Table/utils/topAndBottomExpandedRowModel.d.ts +3 -0
- package/dist/Table/utils/topAndBottomExpandedRowModel.js +62 -0
- package/dist/styles.css +1 -1
- package/package.json +11 -11
- package/src/ProfileSource.tsx +14 -3
- package/src/Table/ColumnsVisibility.tsx +4 -4
- package/src/Table/index.tsx +432 -94
- package/src/Table/utils/topAndBottomExpandedRowModel.tsx +89 -0
package/src/Table/index.tsx
CHANGED
|
@@ -13,7 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {flexRender} from '@tanstack/react-table';
|
|
17
|
+
import {
|
|
18
|
+
createColumnHelper,
|
|
19
|
+
type CellContext,
|
|
20
|
+
type ColumnDef,
|
|
21
|
+
type ExpandedState,
|
|
22
|
+
type Row as RowType,
|
|
23
|
+
} from '@tanstack/table-core';
|
|
24
|
+
import {Int64, Vector, tableFromIPC, vectorFromArray} from 'apache-arrow';
|
|
25
|
+
import cx from 'classnames';
|
|
17
26
|
import {AnimatePresence, motion} from 'framer-motion';
|
|
18
27
|
|
|
19
28
|
import {
|
|
@@ -23,6 +32,7 @@ import {
|
|
|
23
32
|
useParcaContext,
|
|
24
33
|
useURLState,
|
|
25
34
|
} from '@parca/components';
|
|
35
|
+
import {type RowRendererProps} from '@parca/components/dist/Table';
|
|
26
36
|
import {ProfileType} from '@parca/parser';
|
|
27
37
|
import {
|
|
28
38
|
getLastItem,
|
|
@@ -35,6 +45,7 @@ import {
|
|
|
35
45
|
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
36
46
|
import {hexifyAddress} from '../utils';
|
|
37
47
|
import ColumnsVisibility from './ColumnsVisibility';
|
|
48
|
+
import {getTopAndBottomExpandedRowModel} from './utils/topAndBottomExpandedRowModel';
|
|
38
49
|
|
|
39
50
|
const FIELD_MAPPING_FILE = 'mapping_file';
|
|
40
51
|
const FIELD_LOCATION_ADDRESS = 'location_address';
|
|
@@ -45,8 +56,11 @@ const FIELD_FLAT = 'flat';
|
|
|
45
56
|
const FIELD_FLAT_DIFF = 'flat_diff';
|
|
46
57
|
const FIELD_CUMULATIVE = 'cumulative';
|
|
47
58
|
const FIELD_CUMULATIVE_DIFF = 'cumulative_diff';
|
|
59
|
+
const FIELD_CALLERS = 'callers';
|
|
60
|
+
const FIELD_CALLEES = 'callees';
|
|
48
61
|
|
|
49
|
-
interface
|
|
62
|
+
export interface DataRow {
|
|
63
|
+
id: number;
|
|
50
64
|
name: string;
|
|
51
65
|
flat: bigint;
|
|
52
66
|
flatDiff: bigint;
|
|
@@ -55,19 +69,26 @@ interface row {
|
|
|
55
69
|
mappingFile: string;
|
|
56
70
|
functionSystemName: string;
|
|
57
71
|
functionFileName: string;
|
|
72
|
+
callers?: DataRow[];
|
|
73
|
+
callees?: DataRow[];
|
|
74
|
+
subRows?: Row[];
|
|
75
|
+
isTopSubRow?: boolean;
|
|
76
|
+
isBottomSubRow?: boolean;
|
|
58
77
|
}
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
cell?: (info: any) => string | number;
|
|
66
|
-
meta?: {align: 'right' | 'left'};
|
|
67
|
-
invertSorting?: boolean;
|
|
68
|
-
size?: number;
|
|
79
|
+
interface DummyRow {
|
|
80
|
+
size: number;
|
|
81
|
+
message?: string;
|
|
82
|
+
isTopSubRow?: boolean;
|
|
83
|
+
isBottomSubRow?: boolean;
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
export type Row = DataRow | DummyRow;
|
|
87
|
+
|
|
88
|
+
const isDummyRow = (row: Row): row is DummyRow => {
|
|
89
|
+
return 'size' in row;
|
|
90
|
+
};
|
|
91
|
+
|
|
71
92
|
interface TableProps {
|
|
72
93
|
data?: Uint8Array;
|
|
73
94
|
total: bigint;
|
|
@@ -80,6 +101,159 @@ interface TableProps {
|
|
|
80
101
|
isHalfScreen: boolean;
|
|
81
102
|
}
|
|
82
103
|
|
|
104
|
+
const rowBgClassNames = (isExpanded: boolean, isSubRow: boolean): Record<string, boolean> => {
|
|
105
|
+
return {
|
|
106
|
+
'bg-indigo-100 dark:bg-gray-600': isSubRow,
|
|
107
|
+
'bg-indigo-50 dark:bg-gray-700': isExpanded,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const ROW_HEIGHT = 29;
|
|
112
|
+
|
|
113
|
+
const sizeToHeightStyle = (size: number): Record<string, string> => {
|
|
114
|
+
return {
|
|
115
|
+
height: `${size * ROW_HEIGHT}px`,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const sizeToWidthStyle = (size: number): Record<string, string> => {
|
|
120
|
+
return {
|
|
121
|
+
width: `${size * ROW_HEIGHT}px`,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const sizeToTopStyle = (size: number): Record<string, string> => {
|
|
126
|
+
return {
|
|
127
|
+
top: `${size * ROW_HEIGHT + 10}px`,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const getCallerLabelWidthStyle = (subRows: Row[]): Record<string, string> => {
|
|
132
|
+
let callerRows = subRows.filter(row => row.isTopSubRow).length;
|
|
133
|
+
if (callerRows < 3) {
|
|
134
|
+
callerRows = 3;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return sizeToWidthStyle(callerRows);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getCalleeLabelWidthStyle = (subRows: Row[]): Record<string, string> => {
|
|
141
|
+
let calleeRows = subRows.filter(row => row.isBottomSubRow).length;
|
|
142
|
+
if (calleeRows < 3) {
|
|
143
|
+
calleeRows = 3;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {...sizeToWidthStyle(calleeRows), ...sizeToTopStyle(calleeRows)};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const CustomRowRenderer = ({
|
|
150
|
+
row,
|
|
151
|
+
usePointerCursor,
|
|
152
|
+
onRowClick,
|
|
153
|
+
onRowDoubleClick,
|
|
154
|
+
enableHighlighting,
|
|
155
|
+
shouldHighlightRow,
|
|
156
|
+
rows,
|
|
157
|
+
}: RowRendererProps<Row>): React.JSX.Element => {
|
|
158
|
+
const data = row.original;
|
|
159
|
+
const isExpanded = row.getIsExpanded();
|
|
160
|
+
const _isSubRow = isSubRow(data);
|
|
161
|
+
const bgClassNames = rowBgClassNames(isExpanded, _isSubRow);
|
|
162
|
+
if (isDummyRow(data)) {
|
|
163
|
+
return (
|
|
164
|
+
<tr key={row.id} className={cx(bgClassNames)}>
|
|
165
|
+
<td colSpan={100} className={`text-center`} style={sizeToHeightStyle(data.size)}>
|
|
166
|
+
{data.message}
|
|
167
|
+
</td>
|
|
168
|
+
</tr>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<tr
|
|
174
|
+
key={row.id}
|
|
175
|
+
className={cx(
|
|
176
|
+
usePointerCursor === true ? 'cursor-pointer' : 'cursor-auto',
|
|
177
|
+
'relative',
|
|
178
|
+
bgClassNames,
|
|
179
|
+
{
|
|
180
|
+
'hover:bg-[#62626212] dark:hover:bg-[#ffffff12] ': !isExpanded && !_isSubRow,
|
|
181
|
+
'hover:bg-indigo-200 dark:hover:bg-indigo-500': isExpanded || _isSubRow,
|
|
182
|
+
}
|
|
183
|
+
)}
|
|
184
|
+
onClick={onRowClick != null ? () => onRowClick(row.original) : undefined}
|
|
185
|
+
onDoubleClick={onRowDoubleClick != null ? () => onRowDoubleClick(row, rows) : undefined}
|
|
186
|
+
style={
|
|
187
|
+
enableHighlighting !== true || shouldHighlightRow === undefined
|
|
188
|
+
? undefined
|
|
189
|
+
: {opacity: shouldHighlightRow(row.original) ? 1 : 0.5}
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
{row.getVisibleCells().map((cell, idx) => {
|
|
193
|
+
return (
|
|
194
|
+
<td
|
|
195
|
+
key={cell.id}
|
|
196
|
+
className={cx('p-1.5 align-top', {
|
|
197
|
+
/* @ts-expect-error */
|
|
198
|
+
'text-right': cell.column.columnDef.meta?.align === 'right',
|
|
199
|
+
/* @ts-expect-error */
|
|
200
|
+
'text-left': cell.column.columnDef.meta?.align === 'left',
|
|
201
|
+
})}
|
|
202
|
+
>
|
|
203
|
+
{idx === 0 && isExpanded ? (
|
|
204
|
+
<>
|
|
205
|
+
<div
|
|
206
|
+
className={`absolute top-0 left-0 bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-top-left z-10 text-[10px] border-l border-y border-gray-200 dark:border-gray-700 text-left `}
|
|
207
|
+
style={getCallerLabelWidthStyle(row.originalSubRows ?? [])}
|
|
208
|
+
>
|
|
209
|
+
Callers {'->'}
|
|
210
|
+
</div>
|
|
211
|
+
<div
|
|
212
|
+
className={`absolute left-[18px] bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-bottom-left z-10 text-[10px] border-r border-y border-gray-200 dark:border-gray-700 `}
|
|
213
|
+
style={getCalleeLabelWidthStyle(row.originalSubRows ?? [])}
|
|
214
|
+
>
|
|
215
|
+
{'<-'} Callees
|
|
216
|
+
</div>
|
|
217
|
+
</>
|
|
218
|
+
) : null}
|
|
219
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
220
|
+
</td>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</tr>
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const getCallerRows = (callers: DataRow[]): Row[] => {
|
|
228
|
+
if (callers.length === 0) {
|
|
229
|
+
return [{size: 3, message: 'No callers.', isTopSubRow: true}];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const rows = callers.map(row => {
|
|
233
|
+
return {...row, isTopSubRow: true};
|
|
234
|
+
});
|
|
235
|
+
if (rows.length >= 3) {
|
|
236
|
+
return rows;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return [...rows, {size: 3 - rows.length, message: '', isTopSubRow: true}];
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const getCalleeRows = (callees: DataRow[]): Row[] => {
|
|
243
|
+
if (callees.length === 0) {
|
|
244
|
+
return [{size: 3, message: 'No callees.', isBottomSubRow: true}];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const rows = callees.map(row => {
|
|
248
|
+
return {...row, isBottomSubRow: true};
|
|
249
|
+
});
|
|
250
|
+
if (rows.length >= 3) {
|
|
251
|
+
return rows;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return [{size: 3 - rows.length, message: '', isBottomSubRow: true}, ...rows];
|
|
255
|
+
};
|
|
256
|
+
|
|
83
257
|
export const Table = React.memo(function Table({
|
|
84
258
|
data,
|
|
85
259
|
total,
|
|
@@ -95,6 +269,8 @@ export const Table = React.memo(function Table({
|
|
|
95
269
|
const [rawDashboardItems] = useURLState({param: 'dashboard_items'});
|
|
96
270
|
const [filterByFunctionInput] = useURLState({param: 'filter_by_function'});
|
|
97
271
|
const {isDarkMode} = useParcaContext();
|
|
272
|
+
const [expanded, setExpanded] = useState<ExpandedState>({});
|
|
273
|
+
const [scrollToIndex, setScrollToIndex] = useState<number | undefined>(undefined);
|
|
98
274
|
|
|
99
275
|
const {compareMode} = useProfileViewContext();
|
|
100
276
|
|
|
@@ -122,119 +298,154 @@ export const Table = React.memo(function Table({
|
|
|
122
298
|
return `${percentageString(value, total)} / ${percentageString(value, filtered)}`;
|
|
123
299
|
};
|
|
124
300
|
|
|
125
|
-
const
|
|
301
|
+
const columnHelper = createColumnHelper<Row>();
|
|
302
|
+
|
|
303
|
+
const columns = useMemo<Array<ColumnDef<Row>>>(() => {
|
|
126
304
|
return [
|
|
127
|
-
{
|
|
305
|
+
columnHelper.accessor('flat', {
|
|
128
306
|
id: 'flat',
|
|
129
|
-
accessorKey: 'flat',
|
|
130
307
|
header: 'Flat',
|
|
131
|
-
cell: info =>
|
|
308
|
+
cell: info =>
|
|
309
|
+
valueFormatter(
|
|
310
|
+
(info as CellContext<DataRow, bigint>).getValue(),
|
|
311
|
+
profileType?.sampleUnit ?? '',
|
|
312
|
+
2
|
|
313
|
+
),
|
|
132
314
|
size: 80,
|
|
133
315
|
meta: {
|
|
134
316
|
align: 'right',
|
|
135
317
|
},
|
|
136
318
|
invertSorting: true,
|
|
137
|
-
},
|
|
138
|
-
{
|
|
319
|
+
}),
|
|
320
|
+
columnHelper.accessor('flat', {
|
|
139
321
|
id: 'flatPercentage',
|
|
140
|
-
accessorKey: 'flat',
|
|
141
322
|
header: 'Flat (%)',
|
|
142
|
-
cell: info =>
|
|
323
|
+
cell: info => {
|
|
324
|
+
if (isDummyRow(info.row.original)) {
|
|
325
|
+
return '';
|
|
326
|
+
}
|
|
327
|
+
return ratioString((info as CellContext<DataRow, bigint>).getValue());
|
|
328
|
+
},
|
|
143
329
|
size: 120,
|
|
144
330
|
meta: {
|
|
145
331
|
align: 'right',
|
|
146
332
|
},
|
|
147
333
|
invertSorting: true,
|
|
148
|
-
},
|
|
149
|
-
{
|
|
334
|
+
}),
|
|
335
|
+
columnHelper.accessor('flatDiff', {
|
|
150
336
|
id: 'flatDiff',
|
|
151
|
-
accessorKey: 'flatDiff',
|
|
152
337
|
header: 'Flat Diff',
|
|
153
338
|
cell: info =>
|
|
154
|
-
addPlusSign(
|
|
339
|
+
addPlusSign(
|
|
340
|
+
valueFormatter(
|
|
341
|
+
(info as CellContext<DataRow, bigint>).getValue(),
|
|
342
|
+
profileType?.sampleUnit ?? '',
|
|
343
|
+
2
|
|
344
|
+
)
|
|
345
|
+
),
|
|
155
346
|
size: 120,
|
|
156
347
|
meta: {
|
|
157
348
|
align: 'right',
|
|
158
349
|
},
|
|
159
350
|
invertSorting: true,
|
|
160
|
-
},
|
|
161
|
-
{
|
|
351
|
+
}),
|
|
352
|
+
columnHelper.accessor('flatDiff', {
|
|
162
353
|
id: 'flatDiffPercentage',
|
|
163
|
-
accessorKey: 'flatDiff',
|
|
164
354
|
header: 'Flat Diff (%)',
|
|
165
|
-
cell: info =>
|
|
355
|
+
cell: info => {
|
|
356
|
+
if (isDummyRow(info.row.original)) {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
return ratioString((info as CellContext<DataRow, bigint>).getValue());
|
|
360
|
+
},
|
|
166
361
|
size: 120,
|
|
167
362
|
meta: {
|
|
168
363
|
align: 'right',
|
|
169
364
|
},
|
|
170
365
|
invertSorting: true,
|
|
171
|
-
},
|
|
172
|
-
{
|
|
366
|
+
}),
|
|
367
|
+
columnHelper.accessor('cumulative', {
|
|
173
368
|
id: 'cumulative',
|
|
174
|
-
accessorKey: 'cumulative',
|
|
175
369
|
header: 'Cumulative',
|
|
176
|
-
cell: info =>
|
|
370
|
+
cell: info =>
|
|
371
|
+
valueFormatter(
|
|
372
|
+
(info as CellContext<DataRow, bigint>).getValue(),
|
|
373
|
+
profileType?.sampleUnit ?? '',
|
|
374
|
+
2
|
|
375
|
+
),
|
|
177
376
|
size: 150,
|
|
178
377
|
meta: {
|
|
179
378
|
align: 'right',
|
|
180
379
|
},
|
|
181
380
|
invertSorting: true,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
381
|
+
}),
|
|
382
|
+
columnHelper.accessor('cumulative', {
|
|
184
383
|
id: 'cumulativePercentage',
|
|
185
|
-
accessorKey: 'cumulative',
|
|
186
384
|
header: 'Cumulative (%)',
|
|
187
|
-
cell: info =>
|
|
385
|
+
cell: info => {
|
|
386
|
+
if (isDummyRow(info.row.original)) {
|
|
387
|
+
return '';
|
|
388
|
+
}
|
|
389
|
+
return ratioString((info as CellContext<DataRow, bigint>).getValue());
|
|
390
|
+
},
|
|
188
391
|
size: 150,
|
|
189
392
|
meta: {
|
|
190
393
|
align: 'right',
|
|
191
394
|
},
|
|
192
395
|
invertSorting: true,
|
|
193
|
-
},
|
|
194
|
-
{
|
|
396
|
+
}),
|
|
397
|
+
columnHelper.accessor('cumulativeDiff', {
|
|
195
398
|
id: 'cumulativeDiff',
|
|
196
|
-
accessorKey: 'cumulativeDiff',
|
|
197
399
|
header: 'Cumulative Diff',
|
|
198
400
|
cell: info =>
|
|
199
|
-
addPlusSign(
|
|
401
|
+
addPlusSign(
|
|
402
|
+
valueFormatter(
|
|
403
|
+
(info as CellContext<DataRow, bigint>).getValue(),
|
|
404
|
+
profileType?.sampleUnit ?? '',
|
|
405
|
+
2
|
|
406
|
+
)
|
|
407
|
+
),
|
|
200
408
|
size: 170,
|
|
201
409
|
meta: {
|
|
202
410
|
align: 'right',
|
|
203
411
|
},
|
|
204
412
|
invertSorting: true,
|
|
205
|
-
},
|
|
206
|
-
{
|
|
413
|
+
}),
|
|
414
|
+
columnHelper.accessor('cumulativeDiff', {
|
|
207
415
|
id: 'cumulativeDiffPercentage',
|
|
208
|
-
accessorKey: 'cumulativeDiff',
|
|
209
416
|
header: 'Cumulative Diff (%)',
|
|
210
|
-
cell: info =>
|
|
417
|
+
cell: info => {
|
|
418
|
+
if (isDummyRow(info.row.original)) {
|
|
419
|
+
return '';
|
|
420
|
+
}
|
|
421
|
+
return ratioString((info as CellContext<DataRow, bigint>).getValue());
|
|
422
|
+
},
|
|
211
423
|
size: 170,
|
|
212
424
|
meta: {
|
|
213
425
|
align: 'right',
|
|
214
426
|
},
|
|
215
427
|
invertSorting: true,
|
|
216
|
-
},
|
|
217
|
-
{
|
|
428
|
+
}),
|
|
429
|
+
columnHelper.accessor('name', {
|
|
218
430
|
id: 'name',
|
|
219
|
-
accessorKey: 'name',
|
|
220
431
|
header: 'Name',
|
|
221
432
|
cell: info => info.getValue(),
|
|
222
|
-
},
|
|
223
|
-
{
|
|
433
|
+
}),
|
|
434
|
+
columnHelper.accessor('functionSystemName', {
|
|
224
435
|
id: 'functionSystemName',
|
|
225
|
-
accessorKey: 'functionSystemName',
|
|
226
436
|
header: 'Function System Name',
|
|
227
|
-
|
|
228
|
-
|
|
437
|
+
cell: info => info.getValue(),
|
|
438
|
+
}),
|
|
439
|
+
columnHelper.accessor('functionFileName', {
|
|
229
440
|
id: 'functionFileName',
|
|
230
|
-
accessorKey: 'functionFileName',
|
|
231
441
|
header: 'Function File Name',
|
|
232
|
-
|
|
233
|
-
|
|
442
|
+
cell: info => info.getValue(),
|
|
443
|
+
}),
|
|
444
|
+
columnHelper.accessor('mappingFile', {
|
|
234
445
|
id: 'mappingFile',
|
|
235
|
-
accessorKey: 'mappingFile',
|
|
236
446
|
header: 'Mapping File',
|
|
237
|
-
|
|
447
|
+
cell: info => info.getValue(),
|
|
448
|
+
}),
|
|
238
449
|
];
|
|
239
450
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
240
451
|
}, [profileType]);
|
|
@@ -273,7 +484,11 @@ export const Table = React.memo(function Table({
|
|
|
273
484
|
);
|
|
274
485
|
|
|
275
486
|
const onRowClick = useCallback(
|
|
276
|
-
(row:
|
|
487
|
+
(row: Row) => {
|
|
488
|
+
if (isDummyRow(row)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
277
492
|
// If there is only one dashboard item, we don't want to select a span
|
|
278
493
|
if (dashboardItems.length <= 1) {
|
|
279
494
|
return;
|
|
@@ -283,8 +498,43 @@ export const Table = React.memo(function Table({
|
|
|
283
498
|
[selectSpan, dashboardItems.length]
|
|
284
499
|
);
|
|
285
500
|
|
|
501
|
+
const onRowDoubleClick = useCallback((row: RowType<Row>, rows: Array<RowType<Row>>) => {
|
|
502
|
+
if (isDummyRow(row.original)) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (!isSubRow(row.original)) {
|
|
506
|
+
row.toggleExpanded();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// find the original row for this subrow and toggle it
|
|
510
|
+
const newRow = rows.find(
|
|
511
|
+
r =>
|
|
512
|
+
!isDummyRow(r.original) &&
|
|
513
|
+
!isDummyRow(row.original) &&
|
|
514
|
+
r.original.name === row.original.name &&
|
|
515
|
+
!isSubRow(r.original)
|
|
516
|
+
);
|
|
517
|
+
const parentRow = rows.find(r => {
|
|
518
|
+
const parent = row.getParentRow()!;
|
|
519
|
+
if (isDummyRow(parent.original) || isDummyRow(r.original)) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
return r.original.name === parent.original.name;
|
|
523
|
+
});
|
|
524
|
+
if (parentRow == null || newRow == null) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
newRow.toggleExpanded();
|
|
529
|
+
|
|
530
|
+
setScrollToIndex(getScrollTargetIndex(rows, parentRow, newRow));
|
|
531
|
+
}, []);
|
|
532
|
+
|
|
286
533
|
const shouldHighlightRow = useCallback(
|
|
287
|
-
(row:
|
|
534
|
+
(row: Row) => {
|
|
535
|
+
if (!('name' in row)) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
288
538
|
const name = row.name;
|
|
289
539
|
return isSearchMatch(currentSearchString as string, name);
|
|
290
540
|
},
|
|
@@ -350,48 +600,86 @@ export const Table = React.memo(function Table({
|
|
|
350
600
|
];
|
|
351
601
|
}, [compareMode]);
|
|
352
602
|
|
|
353
|
-
|
|
603
|
+
const table = useMemo(() => {
|
|
604
|
+
if (loading || data == null) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return tableFromIPC(data);
|
|
609
|
+
}, [data, loading]);
|
|
610
|
+
|
|
611
|
+
const rows: DataRow[] = useMemo(() => {
|
|
612
|
+
if (table == null || table.numRows === 0) {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const flatColumn = table.getChild(FIELD_FLAT);
|
|
617
|
+
const flatDiffColumn = table.getChild(FIELD_FLAT_DIFF);
|
|
618
|
+
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
619
|
+
const cumulativeDiffColumn = table.getChild(FIELD_CUMULATIVE_DIFF);
|
|
620
|
+
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
621
|
+
const functionSystemNameColumn = table.getChild(FIELD_FUNCTION_SYSTEM_NAME);
|
|
622
|
+
const functionFileNameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
|
|
623
|
+
const mappingFileColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
624
|
+
const locationAddressColumn = table.getChild(FIELD_LOCATION_ADDRESS);
|
|
625
|
+
const callersColumn = table.getChild(FIELD_CALLERS);
|
|
626
|
+
const calleesColumn = table.getChild(FIELD_CALLEES);
|
|
627
|
+
|
|
628
|
+
const getRow = (i: number): DataRow => {
|
|
629
|
+
const flat: bigint = flatColumn?.get(i) ?? 0n;
|
|
630
|
+
const flatDiff: bigint = flatDiffColumn?.get(i) ?? 0n;
|
|
631
|
+
const cumulative: bigint = cumulativeColumn?.get(i) ?? 0n;
|
|
632
|
+
const cumulativeDiff: bigint = cumulativeDiffColumn?.get(i) ?? 0n;
|
|
633
|
+
const functionSystemName: string = functionSystemNameColumn?.get(i) ?? '';
|
|
634
|
+
const functionFileName: string = functionFileNameColumn?.get(i) ?? '';
|
|
635
|
+
const mappingFile: string = mappingFileColumn?.get(i) ?? '';
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
id: i,
|
|
639
|
+
name: RowName(mappingFileColumn, locationAddressColumn, functionNameColumn, i),
|
|
640
|
+
flat,
|
|
641
|
+
flatDiff,
|
|
642
|
+
cumulative,
|
|
643
|
+
cumulativeDiff,
|
|
644
|
+
functionSystemName,
|
|
645
|
+
functionFileName,
|
|
646
|
+
mappingFile,
|
|
647
|
+
};
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const rows: DataRow[] = [];
|
|
651
|
+
for (let i = 0; i < table.numRows; i++) {
|
|
652
|
+
const row = getRow(i);
|
|
653
|
+
const callerIndices: Vector<Int64> = callersColumn?.get(i) ?? vectorFromArray([]);
|
|
654
|
+
const callers: DataRow[] = Array.from(callerIndices.toArray().values()).map(rowIdx => {
|
|
655
|
+
return getRow(Number(rowIdx));
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
const calleeIndices: Vector<Int64> = calleesColumn?.get(i) ?? vectorFromArray([]);
|
|
659
|
+
const callees: DataRow[] = Array.from(calleeIndices.toArray().values()).map(rowIdx => {
|
|
660
|
+
return getRow(Number(rowIdx));
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
row.callers = callers;
|
|
664
|
+
row.callees = callees;
|
|
665
|
+
row.subRows = [...getCallerRows(callers), ...getCalleeRows(callees)];
|
|
666
|
+
|
|
667
|
+
rows.push(row);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return rows;
|
|
671
|
+
}, [table]);
|
|
672
|
+
|
|
673
|
+
if (loading) {
|
|
354
674
|
return (
|
|
355
675
|
<div className="overflow-clip h-[700px] min-h-[700px]">
|
|
356
676
|
<TableSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
357
677
|
</div>
|
|
358
678
|
);
|
|
679
|
+
}
|
|
359
680
|
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
const table = tableFromIPC(data);
|
|
363
|
-
if (table.numRows === 0) return <div className="mx-auto text-center">Profile has no samples</div>;
|
|
364
|
-
|
|
365
|
-
const flatColumn = table.getChild(FIELD_FLAT);
|
|
366
|
-
const flatDiffColumn = table.getChild(FIELD_FLAT_DIFF);
|
|
367
|
-
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
368
|
-
const cumulativeDiffColumn = table.getChild(FIELD_CUMULATIVE_DIFF);
|
|
369
|
-
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
370
|
-
const functionSystemNameColumn = table.getChild(FIELD_FUNCTION_SYSTEM_NAME);
|
|
371
|
-
const functionFileNameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
|
|
372
|
-
const mappingFileColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
373
|
-
const locationAddressColumn = table.getChild(FIELD_LOCATION_ADDRESS);
|
|
374
|
-
|
|
375
|
-
const rows: row[] = [];
|
|
376
|
-
// TODO: Figure out how to only read the data of the columns we need for the virtualized table
|
|
377
|
-
for (let i = 0; i < table.numRows; i++) {
|
|
378
|
-
const flat: bigint = flatColumn?.get(i) ?? 0n;
|
|
379
|
-
const flatDiff: bigint = flatDiffColumn?.get(i) ?? 0n;
|
|
380
|
-
const cumulative: bigint = cumulativeColumn?.get(i) ?? 0n;
|
|
381
|
-
const cumulativeDiff: bigint = cumulativeDiffColumn?.get(i) ?? 0n;
|
|
382
|
-
const functionSystemName: string = functionSystemNameColumn?.get(i) ?? '';
|
|
383
|
-
const functionFileName: string = functionFileNameColumn?.get(i) ?? '';
|
|
384
|
-
const mappingFile: string = mappingFileColumn?.get(i) ?? '';
|
|
385
|
-
rows.push({
|
|
386
|
-
name: RowName(mappingFileColumn, locationAddressColumn, functionNameColumn, i),
|
|
387
|
-
flat,
|
|
388
|
-
flatDiff,
|
|
389
|
-
cumulative,
|
|
390
|
-
cumulativeDiff,
|
|
391
|
-
functionSystemName,
|
|
392
|
-
functionFileName,
|
|
393
|
-
mappingFile,
|
|
394
|
-
});
|
|
681
|
+
if (rows.length === 0) {
|
|
682
|
+
return <div className="mx-auto text-center">Profile has no samples</div>;
|
|
395
683
|
}
|
|
396
684
|
|
|
397
685
|
return (
|
|
@@ -414,6 +702,22 @@ export const Table = React.memo(function Table({
|
|
|
414
702
|
enableHighlighting={enableHighlighting}
|
|
415
703
|
shouldHighlightRow={shouldHighlightRow}
|
|
416
704
|
usePointerCursor={dashboardItems.length > 1}
|
|
705
|
+
onRowDoubleClick={onRowDoubleClick}
|
|
706
|
+
getSubRows={row => (isDummyRow(row) ? [] : row.subRows ?? [])}
|
|
707
|
+
getCustomExpandedRowModel={getTopAndBottomExpandedRowModel}
|
|
708
|
+
expandedState={expanded}
|
|
709
|
+
onExpandedChange={getNewState => {
|
|
710
|
+
// We only want the new expanded row so passing the exisitng state as empty
|
|
711
|
+
// @ts-expect-error
|
|
712
|
+
let newState = getNewState({});
|
|
713
|
+
if (Object.keys(newState)[0] === Object.keys(expanded)[0]) {
|
|
714
|
+
newState = {};
|
|
715
|
+
}
|
|
716
|
+
setExpanded(newState);
|
|
717
|
+
}}
|
|
718
|
+
CustomRowRenderer={CustomRowRenderer}
|
|
719
|
+
scrollToIndex={scrollToIndex}
|
|
720
|
+
estimatedRowHeight={ROW_HEIGHT}
|
|
417
721
|
/>
|
|
418
722
|
</div>
|
|
419
723
|
</div>
|
|
@@ -457,4 +761,38 @@ export const RowName = (
|
|
|
457
761
|
return hexifyAddress(address);
|
|
458
762
|
};
|
|
459
763
|
|
|
764
|
+
const getRowsCount = (rows: Array<RowType<Row>>): number => {
|
|
765
|
+
if (rows.length < 6) {
|
|
766
|
+
return 6;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return rows.length;
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
function getScrollTargetIndex(
|
|
773
|
+
rows: Array<RowType<Row>>,
|
|
774
|
+
parentRow: RowType<Row>,
|
|
775
|
+
newRow: RowType<Row>
|
|
776
|
+
): number {
|
|
777
|
+
const parentIndex = rows.indexOf(parentRow);
|
|
778
|
+
const newRowIndex = rows.indexOf(newRow);
|
|
779
|
+
let targetIndex = newRowIndex;
|
|
780
|
+
if (parentIndex > newRowIndex) {
|
|
781
|
+
// Adjusting the number of subs rows to scroll to the main row after expansion.
|
|
782
|
+
targetIndex -= getRowsCount(newRow.subRows);
|
|
783
|
+
}
|
|
784
|
+
if (parentIndex < newRowIndex) {
|
|
785
|
+
// If the parent row is above the new row, we need to adjust the number of subrows of the parent.
|
|
786
|
+
targetIndex += getRowsCount(parentRow.subRows);
|
|
787
|
+
}
|
|
788
|
+
if (targetIndex < 0) {
|
|
789
|
+
targetIndex = 0;
|
|
790
|
+
}
|
|
791
|
+
return targetIndex;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function isSubRow(row: Row): boolean {
|
|
795
|
+
return row.isTopSubRow === true || row.isBottomSubRow === true;
|
|
796
|
+
}
|
|
797
|
+
|
|
460
798
|
export default Table;
|