@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 CHANGED
@@ -3,6 +3,10 @@
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.380](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.379...@parca/profile@0.16.380) (2024-06-04)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## 0.16.379 (2024-06-03)
7
11
 
8
12
  **Note:** Version bump only for package @parca/profile
@@ -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;
@@ -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 new MergedProfileSource(this.mergeFrom, this.mergeTo, this.query, this.filterQuery);
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.a.ProfileType();
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 ProfileType.fromString(Query.parse(this.query.toString()).profileName());
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 { ColumnDef } from '.';
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;
@@ -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 ColumnDef {
6
- id: string;
7
- header: string;
8
- accessorKey: string;
9
- footer?: string;
10
- cell?: (info: any) => string | number;
11
- meta?: {
12
- align: 'right' | 'left';
13
- };
14
- invertSorting?: boolean;
15
- size?: number;
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;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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 { tableFromIPC } from 'apache-arrow';
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 => ratioString(info.getValue()),
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 => ratioString(info.getValue()),
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 => ratioString(info.getValue()),
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 => ratioString(info.getValue()),
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
- if (loading)
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
- if (data === undefined)
240
- return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
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 }) }) }) }, "table-loaded") }));
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>;