@parca/profile 0.16.379 → 0.16.381
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/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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.16.381](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.380...@parca/profile@0.16.381) (2024-06-04)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.16.380](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.379...@parca/profile@0.16.380) (2024-06-04)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## 0.16.379 (2024-06-03)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
package/dist/ProfileSource.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare class MergedProfileSelection implements ProfileSelection {
|
|
|
30
30
|
mergeTo: number;
|
|
31
31
|
query: Query;
|
|
32
32
|
filterQuery: string | undefined;
|
|
33
|
+
profileSource: ProfileSource;
|
|
33
34
|
constructor(mergeFrom: number, mergeTo: number, query: Query, filterQuery?: string);
|
|
34
35
|
ProfileName(): string;
|
|
35
36
|
HistoryParams(): {
|
|
@@ -42,6 +43,7 @@ export declare class ProfileDiffSource implements ProfileSource {
|
|
|
42
43
|
a: ProfileSource;
|
|
43
44
|
b: ProfileSource;
|
|
44
45
|
filterQuery: string | undefined;
|
|
46
|
+
profileType: ProfileType;
|
|
45
47
|
constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string);
|
|
46
48
|
DiffSelection(): ProfileDiffSelection;
|
|
47
49
|
QueryRequest(): QueryRequest;
|
|
@@ -54,6 +56,7 @@ export declare class MergedProfileSource implements ProfileSource {
|
|
|
54
56
|
mergeTo: number;
|
|
55
57
|
query: Query;
|
|
56
58
|
filterQuery: string | undefined;
|
|
59
|
+
profileType: ProfileType;
|
|
57
60
|
constructor(mergeFrom: number, mergeTo: number, query: Query, filterQuery?: string);
|
|
58
61
|
DiffSelection(): ProfileDiffSelection;
|
|
59
62
|
QueryRequest(): QueryRequest;
|
package/dist/ProfileSource.js
CHANGED
|
@@ -67,6 +67,7 @@ export class MergedProfileSelection {
|
|
|
67
67
|
this.mergeTo = mergeTo;
|
|
68
68
|
this.query = query;
|
|
69
69
|
this.filterQuery = filterQuery;
|
|
70
|
+
this.profileSource = new MergedProfileSource(this.mergeFrom, this.mergeTo, this.query, this.filterQuery);
|
|
70
71
|
}
|
|
71
72
|
ProfileName() {
|
|
72
73
|
return this.query.profileName();
|
|
@@ -84,7 +85,7 @@ export class MergedProfileSelection {
|
|
|
84
85
|
return 'merge';
|
|
85
86
|
}
|
|
86
87
|
ProfileSource() {
|
|
87
|
-
return
|
|
88
|
+
return this.profileSource;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
export class ProfileDiffSource {
|
|
@@ -92,6 +93,7 @@ export class ProfileDiffSource {
|
|
|
92
93
|
this.a = a;
|
|
93
94
|
this.b = b;
|
|
94
95
|
this.filterQuery = filterQuery;
|
|
96
|
+
this.profileType = a.ProfileType();
|
|
95
97
|
}
|
|
96
98
|
DiffSelection() {
|
|
97
99
|
throw new Error('Method not implemented.');
|
|
@@ -112,7 +114,7 @@ export class ProfileDiffSource {
|
|
|
112
114
|
};
|
|
113
115
|
}
|
|
114
116
|
ProfileType() {
|
|
115
|
-
return this.
|
|
117
|
+
return this.profileType;
|
|
116
118
|
}
|
|
117
119
|
Describe() {
|
|
118
120
|
return (_jsx(_Fragment, { children: _jsx("p", { children: "Browse the comparison" }) }));
|
|
@@ -132,6 +134,7 @@ export class MergedProfileSource {
|
|
|
132
134
|
this.mergeTo = mergeTo;
|
|
133
135
|
this.query = query;
|
|
134
136
|
this.filterQuery = filterQuery;
|
|
137
|
+
this.profileType = ProfileType.fromString(Query.parse(this.query.toString()).profileName());
|
|
135
138
|
}
|
|
136
139
|
DiffSelection() {
|
|
137
140
|
return {
|
|
@@ -163,7 +166,7 @@ export class MergedProfileSource {
|
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
168
|
ProfileType() {
|
|
166
|
-
return
|
|
169
|
+
return this.profileType;
|
|
167
170
|
}
|
|
168
171
|
stringMatchers() {
|
|
169
172
|
return this.query.matchers
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type VisibilityState } from '@tanstack/react-table';
|
|
2
|
-
import {
|
|
1
|
+
import { type ColumnDef, type VisibilityState } from '@tanstack/react-table';
|
|
2
|
+
import type { Row } from './';
|
|
3
3
|
declare const ColumnsVisibility: ({ columns, visibility, setVisibility, }: {
|
|
4
|
-
columns: ColumnDef
|
|
4
|
+
columns: Array<ColumnDef<Row>>;
|
|
5
5
|
visibility: VisibilityState;
|
|
6
6
|
setVisibility: (id: string, visible: boolean) => void;
|
|
7
7
|
}) => React.JSX.Element;
|
package/dist/Table/index.d.ts
CHANGED
|
@@ -2,18 +2,29 @@ import React from 'react';
|
|
|
2
2
|
import { Vector } from 'apache-arrow';
|
|
3
3
|
import { ProfileType } from '@parca/parser';
|
|
4
4
|
import { type NavigateFunction } from '@parca/utilities';
|
|
5
|
-
export interface
|
|
6
|
-
id:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
export interface DataRow {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
flat: bigint;
|
|
9
|
+
flatDiff: bigint;
|
|
10
|
+
cumulative: bigint;
|
|
11
|
+
cumulativeDiff: bigint;
|
|
12
|
+
mappingFile: string;
|
|
13
|
+
functionSystemName: string;
|
|
14
|
+
functionFileName: string;
|
|
15
|
+
callers?: DataRow[];
|
|
16
|
+
callees?: DataRow[];
|
|
17
|
+
subRows?: Row[];
|
|
18
|
+
isTopSubRow?: boolean;
|
|
19
|
+
isBottomSubRow?: boolean;
|
|
16
20
|
}
|
|
21
|
+
interface DummyRow {
|
|
22
|
+
size: number;
|
|
23
|
+
message?: string;
|
|
24
|
+
isTopSubRow?: boolean;
|
|
25
|
+
isBottomSubRow?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type Row = DataRow | DummyRow;
|
|
17
28
|
interface TableProps {
|
|
18
29
|
data?: Uint8Array;
|
|
19
30
|
total: bigint;
|
package/dist/Table/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// Copyright 2022 The Parca Authors
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
// you may not use this file except in compliance with the License.
|
|
@@ -12,13 +12,17 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
|
-
import {
|
|
15
|
+
import { flexRender } from '@tanstack/react-table';
|
|
16
|
+
import { createColumnHelper, } from '@tanstack/table-core';
|
|
17
|
+
import { tableFromIPC, vectorFromArray } from 'apache-arrow';
|
|
18
|
+
import cx from 'classnames';
|
|
16
19
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
20
|
import { Button, Table as TableComponent, TableSkeleton, useParcaContext, useURLState, } from '@parca/components';
|
|
18
21
|
import { getLastItem, isSearchMatch, parseParams, valueFormatter, } from '@parca/utilities';
|
|
19
22
|
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
20
23
|
import { hexifyAddress } from '../utils';
|
|
21
24
|
import ColumnsVisibility from './ColumnsVisibility';
|
|
25
|
+
import { getTopAndBottomExpandedRowModel } from './utils/topAndBottomExpandedRowModel';
|
|
22
26
|
const FIELD_MAPPING_FILE = 'mapping_file';
|
|
23
27
|
const FIELD_LOCATION_ADDRESS = 'location_address';
|
|
24
28
|
const FIELD_FUNCTION_NAME = 'function_name';
|
|
@@ -28,11 +32,100 @@ const FIELD_FLAT = 'flat';
|
|
|
28
32
|
const FIELD_FLAT_DIFF = 'flat_diff';
|
|
29
33
|
const FIELD_CUMULATIVE = 'cumulative';
|
|
30
34
|
const FIELD_CUMULATIVE_DIFF = 'cumulative_diff';
|
|
35
|
+
const FIELD_CALLERS = 'callers';
|
|
36
|
+
const FIELD_CALLEES = 'callees';
|
|
37
|
+
const isDummyRow = (row) => {
|
|
38
|
+
return 'size' in row;
|
|
39
|
+
};
|
|
40
|
+
const rowBgClassNames = (isExpanded, isSubRow) => {
|
|
41
|
+
return {
|
|
42
|
+
'bg-indigo-100 dark:bg-gray-600': isSubRow,
|
|
43
|
+
'bg-indigo-50 dark:bg-gray-700': isExpanded,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const ROW_HEIGHT = 29;
|
|
47
|
+
const sizeToHeightStyle = (size) => {
|
|
48
|
+
return {
|
|
49
|
+
height: `${size * ROW_HEIGHT}px`,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const sizeToWidthStyle = (size) => {
|
|
53
|
+
return {
|
|
54
|
+
width: `${size * ROW_HEIGHT}px`,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const sizeToTopStyle = (size) => {
|
|
58
|
+
return {
|
|
59
|
+
top: `${size * ROW_HEIGHT + 10}px`,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const getCallerLabelWidthStyle = (subRows) => {
|
|
63
|
+
let callerRows = subRows.filter(row => row.isTopSubRow).length;
|
|
64
|
+
if (callerRows < 3) {
|
|
65
|
+
callerRows = 3;
|
|
66
|
+
}
|
|
67
|
+
return sizeToWidthStyle(callerRows);
|
|
68
|
+
};
|
|
69
|
+
const getCalleeLabelWidthStyle = (subRows) => {
|
|
70
|
+
let calleeRows = subRows.filter(row => row.isBottomSubRow).length;
|
|
71
|
+
if (calleeRows < 3) {
|
|
72
|
+
calleeRows = 3;
|
|
73
|
+
}
|
|
74
|
+
return { ...sizeToWidthStyle(calleeRows), ...sizeToTopStyle(calleeRows) };
|
|
75
|
+
};
|
|
76
|
+
const CustomRowRenderer = ({ row, usePointerCursor, onRowClick, onRowDoubleClick, enableHighlighting, shouldHighlightRow, rows, }) => {
|
|
77
|
+
const data = row.original;
|
|
78
|
+
const isExpanded = row.getIsExpanded();
|
|
79
|
+
const _isSubRow = isSubRow(data);
|
|
80
|
+
const bgClassNames = rowBgClassNames(isExpanded, _isSubRow);
|
|
81
|
+
if (isDummyRow(data)) {
|
|
82
|
+
return (_jsx("tr", { className: cx(bgClassNames), children: _jsx("td", { colSpan: 100, className: `text-center`, style: sizeToHeightStyle(data.size), children: data.message }) }, row.id));
|
|
83
|
+
}
|
|
84
|
+
return (_jsx("tr", { className: cx(usePointerCursor === true ? 'cursor-pointer' : 'cursor-auto', 'relative', bgClassNames, {
|
|
85
|
+
'hover:bg-[#62626212] dark:hover:bg-[#ffffff12] ': !isExpanded && !_isSubRow,
|
|
86
|
+
'hover:bg-indigo-200 dark:hover:bg-indigo-500': isExpanded || _isSubRow,
|
|
87
|
+
}), onClick: onRowClick != null ? () => onRowClick(row.original) : undefined, onDoubleClick: onRowDoubleClick != null ? () => onRowDoubleClick(row, rows) : undefined, style: enableHighlighting !== true || shouldHighlightRow === undefined
|
|
88
|
+
? undefined
|
|
89
|
+
: { opacity: shouldHighlightRow(row.original) ? 1 : 0.5 }, children: row.getVisibleCells().map((cell, idx) => {
|
|
90
|
+
return (_jsxs("td", { className: cx('p-1.5 align-top', {
|
|
91
|
+
/* @ts-expect-error */
|
|
92
|
+
'text-right': cell.column.columnDef.meta?.align === 'right',
|
|
93
|
+
/* @ts-expect-error */
|
|
94
|
+
'text-left': cell.column.columnDef.meta?.align === 'left',
|
|
95
|
+
}), children: [idx === 0 && isExpanded ? (_jsxs(_Fragment, { children: [_jsxs("div", { 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 `, style: getCallerLabelWidthStyle(row.originalSubRows ?? []), children: ["Callers ", '->'] }), _jsxs("div", { 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 `, style: getCalleeLabelWidthStyle(row.originalSubRows ?? []), children: ['<-', " Callees"] })] })) : null, flexRender(cell.column.columnDef.cell, cell.getContext())] }, cell.id));
|
|
96
|
+
}) }, row.id));
|
|
97
|
+
};
|
|
98
|
+
const getCallerRows = (callers) => {
|
|
99
|
+
if (callers.length === 0) {
|
|
100
|
+
return [{ size: 3, message: 'No callers.', isTopSubRow: true }];
|
|
101
|
+
}
|
|
102
|
+
const rows = callers.map(row => {
|
|
103
|
+
return { ...row, isTopSubRow: true };
|
|
104
|
+
});
|
|
105
|
+
if (rows.length >= 3) {
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
return [...rows, { size: 3 - rows.length, message: '', isTopSubRow: true }];
|
|
109
|
+
};
|
|
110
|
+
const getCalleeRows = (callees) => {
|
|
111
|
+
if (callees.length === 0) {
|
|
112
|
+
return [{ size: 3, message: 'No callees.', isBottomSubRow: true }];
|
|
113
|
+
}
|
|
114
|
+
const rows = callees.map(row => {
|
|
115
|
+
return { ...row, isBottomSubRow: true };
|
|
116
|
+
});
|
|
117
|
+
if (rows.length >= 3) {
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
return [{ size: 3 - rows.length, message: '', isBottomSubRow: true }, ...rows];
|
|
121
|
+
};
|
|
31
122
|
export const Table = React.memo(function Table({ data, total, filtered, profileType, navigateTo, loading, currentSearchString, setActionButtons, isHalfScreen, }) {
|
|
32
123
|
const router = parseParams(window?.location.search);
|
|
33
124
|
const [rawDashboardItems] = useURLState({ param: 'dashboard_items' });
|
|
34
125
|
const [filterByFunctionInput] = useURLState({ param: 'filter_by_function' });
|
|
35
126
|
const { isDarkMode } = useParcaContext();
|
|
127
|
+
const [expanded, setExpanded] = useState({});
|
|
128
|
+
const [scrollToIndex, setScrollToIndex] = useState(undefined);
|
|
36
129
|
const { compareMode } = useProfileViewContext();
|
|
37
130
|
const dashboardItems = useMemo(() => {
|
|
38
131
|
if (rawDashboardItems !== undefined) {
|
|
@@ -53,11 +146,11 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
53
146
|
}
|
|
54
147
|
return `${percentageString(value, total)} / ${percentageString(value, filtered)}`;
|
|
55
148
|
};
|
|
149
|
+
const columnHelper = createColumnHelper();
|
|
56
150
|
const columns = useMemo(() => {
|
|
57
151
|
return [
|
|
58
|
-
{
|
|
152
|
+
columnHelper.accessor('flat', {
|
|
59
153
|
id: 'flat',
|
|
60
|
-
accessorKey: 'flat',
|
|
61
154
|
header: 'Flat',
|
|
62
155
|
cell: info => valueFormatter(info.getValue(), profileType?.sampleUnit ?? '', 2),
|
|
63
156
|
size: 80,
|
|
@@ -65,21 +158,24 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
65
158
|
align: 'right',
|
|
66
159
|
},
|
|
67
160
|
invertSorting: true,
|
|
68
|
-
},
|
|
69
|
-
{
|
|
161
|
+
}),
|
|
162
|
+
columnHelper.accessor('flat', {
|
|
70
163
|
id: 'flatPercentage',
|
|
71
|
-
accessorKey: 'flat',
|
|
72
164
|
header: 'Flat (%)',
|
|
73
|
-
cell: info =>
|
|
165
|
+
cell: info => {
|
|
166
|
+
if (isDummyRow(info.row.original)) {
|
|
167
|
+
return '';
|
|
168
|
+
}
|
|
169
|
+
return ratioString(info.getValue());
|
|
170
|
+
},
|
|
74
171
|
size: 120,
|
|
75
172
|
meta: {
|
|
76
173
|
align: 'right',
|
|
77
174
|
},
|
|
78
175
|
invertSorting: true,
|
|
79
|
-
},
|
|
80
|
-
{
|
|
176
|
+
}),
|
|
177
|
+
columnHelper.accessor('flatDiff', {
|
|
81
178
|
id: 'flatDiff',
|
|
82
|
-
accessorKey: 'flatDiff',
|
|
83
179
|
header: 'Flat Diff',
|
|
84
180
|
cell: info => addPlusSign(valueFormatter(info.getValue(), profileType?.sampleUnit ?? '', 2)),
|
|
85
181
|
size: 120,
|
|
@@ -87,21 +183,24 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
87
183
|
align: 'right',
|
|
88
184
|
},
|
|
89
185
|
invertSorting: true,
|
|
90
|
-
},
|
|
91
|
-
{
|
|
186
|
+
}),
|
|
187
|
+
columnHelper.accessor('flatDiff', {
|
|
92
188
|
id: 'flatDiffPercentage',
|
|
93
|
-
accessorKey: 'flatDiff',
|
|
94
189
|
header: 'Flat Diff (%)',
|
|
95
|
-
cell: info =>
|
|
190
|
+
cell: info => {
|
|
191
|
+
if (isDummyRow(info.row.original)) {
|
|
192
|
+
return '';
|
|
193
|
+
}
|
|
194
|
+
return ratioString(info.getValue());
|
|
195
|
+
},
|
|
96
196
|
size: 120,
|
|
97
197
|
meta: {
|
|
98
198
|
align: 'right',
|
|
99
199
|
},
|
|
100
200
|
invertSorting: true,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
201
|
+
}),
|
|
202
|
+
columnHelper.accessor('cumulative', {
|
|
103
203
|
id: 'cumulative',
|
|
104
|
-
accessorKey: 'cumulative',
|
|
105
204
|
header: 'Cumulative',
|
|
106
205
|
cell: info => valueFormatter(info.getValue(), profileType?.sampleUnit ?? '', 2),
|
|
107
206
|
size: 150,
|
|
@@ -109,21 +208,24 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
109
208
|
align: 'right',
|
|
110
209
|
},
|
|
111
210
|
invertSorting: true,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
211
|
+
}),
|
|
212
|
+
columnHelper.accessor('cumulative', {
|
|
114
213
|
id: 'cumulativePercentage',
|
|
115
|
-
accessorKey: 'cumulative',
|
|
116
214
|
header: 'Cumulative (%)',
|
|
117
|
-
cell: info =>
|
|
215
|
+
cell: info => {
|
|
216
|
+
if (isDummyRow(info.row.original)) {
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
return ratioString(info.getValue());
|
|
220
|
+
},
|
|
118
221
|
size: 150,
|
|
119
222
|
meta: {
|
|
120
223
|
align: 'right',
|
|
121
224
|
},
|
|
122
225
|
invertSorting: true,
|
|
123
|
-
},
|
|
124
|
-
{
|
|
226
|
+
}),
|
|
227
|
+
columnHelper.accessor('cumulativeDiff', {
|
|
125
228
|
id: 'cumulativeDiff',
|
|
126
|
-
accessorKey: 'cumulativeDiff',
|
|
127
229
|
header: 'Cumulative Diff',
|
|
128
230
|
cell: info => addPlusSign(valueFormatter(info.getValue(), profileType?.sampleUnit ?? '', 2)),
|
|
129
231
|
size: 170,
|
|
@@ -131,39 +233,42 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
131
233
|
align: 'right',
|
|
132
234
|
},
|
|
133
235
|
invertSorting: true,
|
|
134
|
-
},
|
|
135
|
-
{
|
|
236
|
+
}),
|
|
237
|
+
columnHelper.accessor('cumulativeDiff', {
|
|
136
238
|
id: 'cumulativeDiffPercentage',
|
|
137
|
-
accessorKey: 'cumulativeDiff',
|
|
138
239
|
header: 'Cumulative Diff (%)',
|
|
139
|
-
cell: info =>
|
|
240
|
+
cell: info => {
|
|
241
|
+
if (isDummyRow(info.row.original)) {
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
return ratioString(info.getValue());
|
|
245
|
+
},
|
|
140
246
|
size: 170,
|
|
141
247
|
meta: {
|
|
142
248
|
align: 'right',
|
|
143
249
|
},
|
|
144
250
|
invertSorting: true,
|
|
145
|
-
},
|
|
146
|
-
{
|
|
251
|
+
}),
|
|
252
|
+
columnHelper.accessor('name', {
|
|
147
253
|
id: 'name',
|
|
148
|
-
accessorKey: 'name',
|
|
149
254
|
header: 'Name',
|
|
150
255
|
cell: info => info.getValue(),
|
|
151
|
-
},
|
|
152
|
-
{
|
|
256
|
+
}),
|
|
257
|
+
columnHelper.accessor('functionSystemName', {
|
|
153
258
|
id: 'functionSystemName',
|
|
154
|
-
accessorKey: 'functionSystemName',
|
|
155
259
|
header: 'Function System Name',
|
|
156
|
-
|
|
157
|
-
|
|
260
|
+
cell: info => info.getValue(),
|
|
261
|
+
}),
|
|
262
|
+
columnHelper.accessor('functionFileName', {
|
|
158
263
|
id: 'functionFileName',
|
|
159
|
-
accessorKey: 'functionFileName',
|
|
160
264
|
header: 'Function File Name',
|
|
161
|
-
|
|
162
|
-
|
|
265
|
+
cell: info => info.getValue(),
|
|
266
|
+
}),
|
|
267
|
+
columnHelper.accessor('mappingFile', {
|
|
163
268
|
id: 'mappingFile',
|
|
164
|
-
accessorKey: 'mappingFile',
|
|
165
269
|
header: 'Mapping File',
|
|
166
|
-
|
|
270
|
+
cell: info => info.getValue(),
|
|
271
|
+
}),
|
|
167
272
|
];
|
|
168
273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
274
|
}, [profileType]);
|
|
@@ -192,13 +297,45 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
192
297
|
}
|
|
193
298
|
}, [navigateTo, router]);
|
|
194
299
|
const onRowClick = useCallback((row) => {
|
|
300
|
+
if (isDummyRow(row)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
195
303
|
// If there is only one dashboard item, we don't want to select a span
|
|
196
304
|
if (dashboardItems.length <= 1) {
|
|
197
305
|
return;
|
|
198
306
|
}
|
|
199
307
|
selectSpan(row.name);
|
|
200
308
|
}, [selectSpan, dashboardItems.length]);
|
|
309
|
+
const onRowDoubleClick = useCallback((row, rows) => {
|
|
310
|
+
if (isDummyRow(row.original)) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!isSubRow(row.original)) {
|
|
314
|
+
row.toggleExpanded();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// find the original row for this subrow and toggle it
|
|
318
|
+
const newRow = rows.find(r => !isDummyRow(r.original) &&
|
|
319
|
+
!isDummyRow(row.original) &&
|
|
320
|
+
r.original.name === row.original.name &&
|
|
321
|
+
!isSubRow(r.original));
|
|
322
|
+
const parentRow = rows.find(r => {
|
|
323
|
+
const parent = row.getParentRow();
|
|
324
|
+
if (isDummyRow(parent.original) || isDummyRow(r.original)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
return r.original.name === parent.original.name;
|
|
328
|
+
});
|
|
329
|
+
if (parentRow == null || newRow == null) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
newRow.toggleExpanded();
|
|
333
|
+
setScrollToIndex(getScrollTargetIndex(rows, parentRow, newRow));
|
|
334
|
+
}, []);
|
|
201
335
|
const shouldHighlightRow = useCallback((row) => {
|
|
336
|
+
if (!('name' in row)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
202
339
|
const name = row.name;
|
|
203
340
|
return isSearchMatch(currentSearchString, name);
|
|
204
341
|
}, [currentSearchString]);
|
|
@@ -234,44 +371,80 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
234
371
|
},
|
|
235
372
|
];
|
|
236
373
|
}, [compareMode]);
|
|
237
|
-
|
|
374
|
+
const table = useMemo(() => {
|
|
375
|
+
if (loading || data == null) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return tableFromIPC(data);
|
|
379
|
+
}, [data, loading]);
|
|
380
|
+
const rows = useMemo(() => {
|
|
381
|
+
if (table == null || table.numRows === 0) {
|
|
382
|
+
return [];
|
|
383
|
+
}
|
|
384
|
+
const flatColumn = table.getChild(FIELD_FLAT);
|
|
385
|
+
const flatDiffColumn = table.getChild(FIELD_FLAT_DIFF);
|
|
386
|
+
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
387
|
+
const cumulativeDiffColumn = table.getChild(FIELD_CUMULATIVE_DIFF);
|
|
388
|
+
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
389
|
+
const functionSystemNameColumn = table.getChild(FIELD_FUNCTION_SYSTEM_NAME);
|
|
390
|
+
const functionFileNameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
|
|
391
|
+
const mappingFileColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
392
|
+
const locationAddressColumn = table.getChild(FIELD_LOCATION_ADDRESS);
|
|
393
|
+
const callersColumn = table.getChild(FIELD_CALLERS);
|
|
394
|
+
const calleesColumn = table.getChild(FIELD_CALLEES);
|
|
395
|
+
const getRow = (i) => {
|
|
396
|
+
const flat = flatColumn?.get(i) ?? 0n;
|
|
397
|
+
const flatDiff = flatDiffColumn?.get(i) ?? 0n;
|
|
398
|
+
const cumulative = cumulativeColumn?.get(i) ?? 0n;
|
|
399
|
+
const cumulativeDiff = cumulativeDiffColumn?.get(i) ?? 0n;
|
|
400
|
+
const functionSystemName = functionSystemNameColumn?.get(i) ?? '';
|
|
401
|
+
const functionFileName = functionFileNameColumn?.get(i) ?? '';
|
|
402
|
+
const mappingFile = mappingFileColumn?.get(i) ?? '';
|
|
403
|
+
return {
|
|
404
|
+
id: i,
|
|
405
|
+
name: RowName(mappingFileColumn, locationAddressColumn, functionNameColumn, i),
|
|
406
|
+
flat,
|
|
407
|
+
flatDiff,
|
|
408
|
+
cumulative,
|
|
409
|
+
cumulativeDiff,
|
|
410
|
+
functionSystemName,
|
|
411
|
+
functionFileName,
|
|
412
|
+
mappingFile,
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
const rows = [];
|
|
416
|
+
for (let i = 0; i < table.numRows; i++) {
|
|
417
|
+
const row = getRow(i);
|
|
418
|
+
const callerIndices = callersColumn?.get(i) ?? vectorFromArray([]);
|
|
419
|
+
const callers = Array.from(callerIndices.toArray().values()).map(rowIdx => {
|
|
420
|
+
return getRow(Number(rowIdx));
|
|
421
|
+
});
|
|
422
|
+
const calleeIndices = calleesColumn?.get(i) ?? vectorFromArray([]);
|
|
423
|
+
const callees = Array.from(calleeIndices.toArray().values()).map(rowIdx => {
|
|
424
|
+
return getRow(Number(rowIdx));
|
|
425
|
+
});
|
|
426
|
+
row.callers = callers;
|
|
427
|
+
row.callees = callees;
|
|
428
|
+
row.subRows = [...getCallerRows(callers), ...getCalleeRows(callees)];
|
|
429
|
+
rows.push(row);
|
|
430
|
+
}
|
|
431
|
+
return rows;
|
|
432
|
+
}, [table]);
|
|
433
|
+
if (loading) {
|
|
238
434
|
return (_jsx("div", { className: "overflow-clip h-[700px] min-h-[700px]", children: _jsx(TableSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const table = tableFromIPC(data);
|
|
242
|
-
if (table.numRows === 0)
|
|
435
|
+
}
|
|
436
|
+
if (rows.length === 0) {
|
|
243
437
|
return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
|
|
244
|
-
const flatColumn = table.getChild(FIELD_FLAT);
|
|
245
|
-
const flatDiffColumn = table.getChild(FIELD_FLAT_DIFF);
|
|
246
|
-
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
247
|
-
const cumulativeDiffColumn = table.getChild(FIELD_CUMULATIVE_DIFF);
|
|
248
|
-
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
249
|
-
const functionSystemNameColumn = table.getChild(FIELD_FUNCTION_SYSTEM_NAME);
|
|
250
|
-
const functionFileNameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
|
|
251
|
-
const mappingFileColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
252
|
-
const locationAddressColumn = table.getChild(FIELD_LOCATION_ADDRESS);
|
|
253
|
-
const rows = [];
|
|
254
|
-
// TODO: Figure out how to only read the data of the columns we need for the virtualized table
|
|
255
|
-
for (let i = 0; i < table.numRows; i++) {
|
|
256
|
-
const flat = flatColumn?.get(i) ?? 0n;
|
|
257
|
-
const flatDiff = flatDiffColumn?.get(i) ?? 0n;
|
|
258
|
-
const cumulative = cumulativeColumn?.get(i) ?? 0n;
|
|
259
|
-
const cumulativeDiff = cumulativeDiffColumn?.get(i) ?? 0n;
|
|
260
|
-
const functionSystemName = functionSystemNameColumn?.get(i) ?? '';
|
|
261
|
-
const functionFileName = functionFileNameColumn?.get(i) ?? '';
|
|
262
|
-
const mappingFile = mappingFileColumn?.get(i) ?? '';
|
|
263
|
-
rows.push({
|
|
264
|
-
name: RowName(mappingFileColumn, locationAddressColumn, functionNameColumn, i),
|
|
265
|
-
flat,
|
|
266
|
-
flatDiff,
|
|
267
|
-
cumulative,
|
|
268
|
-
cumulativeDiff,
|
|
269
|
-
functionSystemName,
|
|
270
|
-
functionFileName,
|
|
271
|
-
mappingFile,
|
|
272
|
-
});
|
|
273
438
|
}
|
|
274
|
-
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx("div", { className: "relative", children: _jsx("div", { className: "font-robotoMono h-[80vh] w-full", children: _jsx(TableComponent, { data: rows, columns: columns, initialSorting: initialSorting, columnVisibility: columnVisibility, onRowClick: onRowClick, enableHighlighting: enableHighlighting, shouldHighlightRow: shouldHighlightRow, usePointerCursor: dashboardItems.length > 1
|
|
439
|
+
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx("div", { className: "relative", children: _jsx("div", { className: "font-robotoMono h-[80vh] w-full", children: _jsx(TableComponent, { data: rows, columns: columns, initialSorting: initialSorting, columnVisibility: columnVisibility, onRowClick: onRowClick, enableHighlighting: enableHighlighting, shouldHighlightRow: shouldHighlightRow, usePointerCursor: dashboardItems.length > 1, onRowDoubleClick: onRowDoubleClick, getSubRows: row => (isDummyRow(row) ? [] : row.subRows ?? []), getCustomExpandedRowModel: getTopAndBottomExpandedRowModel, expandedState: expanded, onExpandedChange: getNewState => {
|
|
440
|
+
// We only want the new expanded row so passing the exisitng state as empty
|
|
441
|
+
// @ts-expect-error
|
|
442
|
+
let newState = getNewState({});
|
|
443
|
+
if (Object.keys(newState)[0] === Object.keys(expanded)[0]) {
|
|
444
|
+
newState = {};
|
|
445
|
+
}
|
|
446
|
+
setExpanded(newState);
|
|
447
|
+
}, CustomRowRenderer: CustomRowRenderer, scrollToIndex: scrollToIndex, estimatedRowHeight: ROW_HEIGHT }) }) }) }, "table-loaded") }));
|
|
275
448
|
});
|
|
276
449
|
const addPlusSign = (num) => {
|
|
277
450
|
if (num.charAt(0) === '0' || num.charAt(0) === '-') {
|
|
@@ -297,4 +470,30 @@ export const RowName = (mappingFileColumn, locationAddressColumn, functionNameCo
|
|
|
297
470
|
const address = locationAddressColumn?.get(row) ?? 0;
|
|
298
471
|
return hexifyAddress(address);
|
|
299
472
|
};
|
|
473
|
+
const getRowsCount = (rows) => {
|
|
474
|
+
if (rows.length < 6) {
|
|
475
|
+
return 6;
|
|
476
|
+
}
|
|
477
|
+
return rows.length;
|
|
478
|
+
};
|
|
479
|
+
function getScrollTargetIndex(rows, parentRow, newRow) {
|
|
480
|
+
const parentIndex = rows.indexOf(parentRow);
|
|
481
|
+
const newRowIndex = rows.indexOf(newRow);
|
|
482
|
+
let targetIndex = newRowIndex;
|
|
483
|
+
if (parentIndex > newRowIndex) {
|
|
484
|
+
// Adjusting the number of subs rows to scroll to the main row after expansion.
|
|
485
|
+
targetIndex -= getRowsCount(newRow.subRows);
|
|
486
|
+
}
|
|
487
|
+
if (parentIndex < newRowIndex) {
|
|
488
|
+
// If the parent row is above the new row, we need to adjust the number of subrows of the parent.
|
|
489
|
+
targetIndex += getRowsCount(parentRow.subRows);
|
|
490
|
+
}
|
|
491
|
+
if (targetIndex < 0) {
|
|
492
|
+
targetIndex = 0;
|
|
493
|
+
}
|
|
494
|
+
return targetIndex;
|
|
495
|
+
}
|
|
496
|
+
function isSubRow(row) {
|
|
497
|
+
return row.isTopSubRow === true || row.isBottomSubRow === true;
|
|
498
|
+
}
|
|
300
499
|
export default Table;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type RowData, type RowModel, type Table } from '@tanstack/table-core';
|
|
2
|
+
export declare function getTopAndBottomExpandedRowModel<TData extends RowData>(): (table: Table<TData>) => () => RowModel<TData>;
|
|
3
|
+
export declare function expandRows<TData extends RowData>(rowModel: RowModel<TData>): RowModel<TData>;
|