@parca/profile 0.19.12 → 0.19.14

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.js +1 -1
  3. package/dist/GraphTooltipArrow/index.js +2 -2
  4. package/dist/MatchersInput/index.d.ts +3 -1
  5. package/dist/MatchersInput/index.d.ts.map +1 -1
  6. package/dist/MatchersInput/index.js +8 -4
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
  20. package/dist/ProfileIcicleGraph/index.d.ts +3 -1
  21. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  22. package/dist/ProfileIcicleGraph/index.js +6 -4
  23. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
  24. package/dist/ProfileSelector/QueryControls.js +1 -1
  25. package/dist/ProfileSelector/index.d.ts.map +1 -1
  26. package/dist/ProfileSelector/index.js +4 -2
  27. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/DashboardItems/index.js +1 -1
  29. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  30. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  31. package/dist/ProfileView/components/Toolbars/index.d.ts +0 -2
  32. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/Toolbars/index.js +4 -5
  34. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  35. package/dist/ProfileView/components/ViewSelector/index.js +18 -11
  36. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  37. package/dist/ProfileView/context/DashboardContext.js +5 -0
  38. package/dist/ProfileView/index.d.ts.map +1 -1
  39. package/dist/ProfileView/index.js +4 -3
  40. package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
  41. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
  42. package/dist/Sandwich/components/CalleesSection.js +2 -6
  43. package/dist/Sandwich/components/CallersSection.d.ts +4 -2
  44. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
  45. package/dist/Sandwich/components/CallersSection.js +45 -9
  46. package/dist/Sandwich/components/TableSection.js +1 -1
  47. package/dist/Sandwich/index.d.ts +0 -1
  48. package/dist/Sandwich/index.d.ts.map +1 -1
  49. package/dist/Sandwich/index.js +27 -79
  50. package/dist/SimpleMatchers/index.d.ts +2 -0
  51. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  52. package/dist/SimpleMatchers/index.js +16 -6
  53. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  54. package/dist/Table/MoreDropdown.js +1 -2
  55. package/dist/Table/TableContextMenu.d.ts +9 -0
  56. package/dist/Table/TableContextMenu.d.ts.map +1 -0
  57. package/dist/Table/TableContextMenu.js +38 -0
  58. package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
  59. package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
  60. package/dist/Table/TableContextMenuWrapper.js +30 -0
  61. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  62. package/dist/Table/hooks/useTableConfiguration.js +2 -20
  63. package/dist/Table/index.d.ts.map +1 -1
  64. package/dist/Table/index.js +65 -5
  65. package/dist/ViewMatchers/index.d.ts +2 -0
  66. package/dist/ViewMatchers/index.d.ts.map +1 -1
  67. package/dist/ViewMatchers/index.js +14 -4
  68. package/dist/contexts/MatchersInputLabelsContext.d.ts +3 -1
  69. package/dist/contexts/MatchersInputLabelsContext.d.ts.map +1 -1
  70. package/dist/contexts/MatchersInputLabelsContext.js +3 -3
  71. package/dist/contexts/SimpleMatchersLabelContext.d.ts +3 -1
  72. package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +1 -1
  73. package/dist/contexts/SimpleMatchersLabelContext.js +2 -2
  74. package/dist/styles.css +1 -1
  75. package/package.json +3 -3
  76. package/src/GraphTooltipArrow/Content.tsx +3 -3
  77. package/src/GraphTooltipArrow/index.tsx +2 -2
  78. package/src/MatchersInput/index.tsx +17 -4
  79. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
  80. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
  81. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
  82. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
  83. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
  84. package/src/ProfileIcicleGraph/index.tsx +20 -2
  85. package/src/ProfileSelector/QueryControls.tsx +6 -0
  86. package/src/ProfileSelector/index.tsx +12 -2
  87. package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
  88. package/src/ProfileView/components/ShareButton/index.tsx +9 -3
  89. package/src/ProfileView/components/Toolbars/index.tsx +7 -23
  90. package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
  91. package/src/ProfileView/context/DashboardContext.tsx +6 -0
  92. package/src/ProfileView/index.tsx +12 -4
  93. package/src/Sandwich/components/CalleesSection.tsx +1 -7
  94. package/src/Sandwich/components/CallersSection.tsx +92 -35
  95. package/src/Sandwich/components/TableSection.tsx +2 -2
  96. package/src/Sandwich/index.tsx +20 -107
  97. package/src/SimpleMatchers/index.tsx +20 -4
  98. package/src/Table/MoreDropdown.tsx +1 -2
  99. package/src/Table/TableContextMenu.tsx +70 -0
  100. package/src/Table/TableContextMenuWrapper.tsx +48 -0
  101. package/src/Table/hooks/useTableConfiguration.tsx +2 -25
  102. package/src/Table/index.tsx +84 -5
  103. package/src/ViewMatchers/index.tsx +17 -3
  104. package/src/contexts/MatchersInputLabelsContext.tsx +10 -2
  105. package/src/contexts/SimpleMatchersLabelContext.tsx +5 -1
@@ -15,12 +15,11 @@ import {useEffect, useMemo, useState} from 'react';
15
15
 
16
16
  import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
17
 
18
- import {useParcaContext, useURLState} from '@parca/components';
18
+ import {useURLState} from '@parca/components';
19
19
  import {valueFormatter} from '@parca/utilities';
20
20
 
21
21
  import {type Row} from '..';
22
22
  import {ColorCell} from '../ColorCell';
23
- import MoreDropdown from '../MoreDropdown';
24
23
  import {addPlusSign, ratioString, type ColumnName} from '../utils/functions';
25
24
 
26
25
  interface UseTableConfigurationProps {
@@ -47,10 +46,6 @@ export function useTableConfiguration({
47
46
  const [tableColumns] = useURLState<string[]>('table_columns', {
48
47
  alwaysReturnArray: true,
49
48
  });
50
- const [dashboardItems] = useURLState<string[]>('dashboard_items', {
51
- alwaysReturnArray: true,
52
- });
53
- const {enableSandwichView} = useParcaContext();
54
49
 
55
50
  const [columnVisibility, setColumnVisibility] = useState(() => {
56
51
  return {
@@ -204,26 +199,8 @@ export function useTableConfiguration({
204
199
  }),
205
200
  ];
206
201
 
207
- if (
208
- dashboardItems.length === 1 &&
209
- dashboardItems[0] === 'table' &&
210
- enableSandwichView === true
211
- ) {
212
- baseColumns.unshift(
213
- columnHelper.accessor('moreActions', {
214
- id: 'moreActions',
215
- header: '',
216
- cell: info => {
217
- return <MoreDropdown functionName={info.row.original.name} />;
218
- },
219
- size: 10,
220
- enableSorting: false,
221
- })
222
- );
223
- }
224
-
225
202
  return baseColumns;
226
- }, [unit, total, filtered, columnHelper, dashboardItems, enableSandwichView]);
203
+ }, [unit, total, filtered, columnHelper]);
227
204
 
228
205
  const initialSorting = useMemo(() => {
229
206
  return [
@@ -11,10 +11,11 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useCallback, useEffect, useMemo, useState} from 'react';
14
+ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {tableFromIPC} from 'apache-arrow';
17
17
  import {AnimatePresence, motion} from 'framer-motion';
18
+ import {useContextMenu} from 'react-contexify';
18
19
 
19
20
  import {
20
21
  Table as TableComponent,
@@ -30,6 +31,7 @@ import useMappingList, {
30
31
  useFilenamesList,
31
32
  } from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
32
33
  import {useProfileViewContext} from '../ProfileView/context/ProfileViewContext';
34
+ import TableContextMenuWrapper, {TableContextMenuWrapperRef} from './TableContextMenuWrapper';
33
35
  import {useColorManagement} from './hooks/useColorManagement';
34
36
  import {useTableConfiguration} from './hooks/useTableConfiguration';
35
37
  import {DataRow, ROW_HEIGHT, RowName, getRowColor} from './utils/functions';
@@ -78,13 +80,19 @@ export const Table = React.memo(function Table({
78
80
  const [dashboardItems] = useURLState<string[]>('dashboard_items', {
79
81
  alwaysReturnArray: true,
80
82
  });
81
-
83
+ const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
82
84
  const [colorBy, setColorBy] = useURLState('color_by');
83
85
  const {isDarkMode} = useParcaContext();
84
86
  const [scrollToIndex, setScrollToIndex] = useState<number | undefined>(undefined);
85
87
 
86
88
  const {compareMode} = useProfileViewContext();
87
89
 
90
+ const MENU_ID = 'table-context-menu';
91
+ const contextMenuRef = useRef<TableContextMenuWrapperRef>(null);
92
+ const {show} = useContextMenu({
93
+ id: MENU_ID,
94
+ });
95
+
88
96
  const table = useMemo(() => {
89
97
  if (loading || data == null) {
90
98
  return null;
@@ -130,9 +138,13 @@ export const Table = React.memo(function Table({
130
138
 
131
139
  const selectSpan = useCallback(
132
140
  (span: string): void => {
133
- setSearchString(span.trim());
141
+ if (dashboardItems.includes('icicle')) {
142
+ setSearchString(span.trim());
143
+ } else {
144
+ setSandwichFunctionName(span.trim());
145
+ }
134
146
  },
135
- [setSearchString]
147
+ [setSearchString, setSandwichFunctionName, dashboardItems]
136
148
  );
137
149
 
138
150
  const onRowClick = useCallback(
@@ -213,6 +225,72 @@ export const Table = React.memo(function Table({
213
225
  return rows;
214
226
  }, [table, colorByColors, colorBy]);
215
227
 
228
+ const handleTableContextMenu = useCallback(
229
+ (e: React.MouseEvent) => {
230
+ e.preventDefault();
231
+
232
+ // Find the closest table row element
233
+ const target = e.target as Element;
234
+ const rowElement = target.closest('tr');
235
+
236
+ if (rowElement !== null) {
237
+ // Look for a data attribute that might contain the actual row ID
238
+ const rowId = rowElement.getAttribute('data-row-id') ?? rowElement.getAttribute('data-id');
239
+
240
+ if (rowId != null && rowId.length > 0) {
241
+ // Find the row by ID
242
+ const actualRowIndex = parseInt(rowId, 10);
243
+
244
+ if (actualRowIndex >= 0 && actualRowIndex < rows.length) {
245
+ const row = rows[actualRowIndex];
246
+
247
+ contextMenuRef.current?.setRow(row, () => {
248
+ show({
249
+ event: e,
250
+ });
251
+ });
252
+ return;
253
+ }
254
+ }
255
+
256
+ // Fallback: try to find row by matching text content
257
+ const nameCell = rowElement.querySelector('td:last-child'); // Name is usually the last column
258
+ if (nameCell !== null) {
259
+ const cellText = nameCell.textContent?.trim();
260
+
261
+ if (cellText != null && cellText.length > 0) {
262
+ // First try exact match
263
+ let matchingRow = rows.find(row => row.name === cellText);
264
+
265
+ // If no exact match, try partial match (in case of truncation)
266
+ if (matchingRow == null) {
267
+ matchingRow = rows.find(
268
+ row => row.name.includes(cellText) || cellText.includes(row.name)
269
+ );
270
+ }
271
+
272
+ // If still no match, try matching the end of the name (for cases like package.function)
273
+ if (matchingRow == null) {
274
+ matchingRow = rows.find(
275
+ row =>
276
+ row.name.endsWith(cellText) || cellText.endsWith(row.name.split('.').pop() ?? '')
277
+ );
278
+ }
279
+
280
+ if (matchingRow != null) {
281
+ contextMenuRef.current?.setRow(matchingRow, () => {
282
+ show({
283
+ event: e,
284
+ });
285
+ });
286
+ }
287
+ }
288
+ }
289
+ }
290
+ },
291
+ [rows, show]
292
+ );
293
+
216
294
  useEffect(() => {
217
295
  setTimeout(() => {
218
296
  if (currentSearchString == null || rows.length === 0) return;
@@ -249,7 +327,8 @@ export const Table = React.memo(function Table({
249
327
  transition={{duration: 0.5}}
250
328
  >
251
329
  <div className="relative">
252
- <div className="font-robotoMono h-[80vh] w-full">
330
+ <TableContextMenuWrapper ref={contextMenuRef} menuId={MENU_ID} />
331
+ <div className="font-robotoMono h-[80vh] w-full" onContextMenu={handleTableContextMenu}>
253
332
  <TableComponent
254
333
  data={rows}
255
334
  columns={columns}
@@ -19,7 +19,7 @@ import cx from 'classnames';
19
19
  import {QueryServiceClient} from '@parca/client';
20
20
  import {useGrpcMetadata} from '@parca/components';
21
21
  import {Query} from '@parca/parser';
22
- import {sanitizeLabelValue} from '@parca/utilities';
22
+ import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
23
23
 
24
24
  import CustomSelect, {SelectItem} from '../SimpleMatchers/Select';
25
25
 
@@ -30,6 +30,8 @@ interface Props {
30
30
  currentQuery: Query;
31
31
  queryClient: QueryServiceClient;
32
32
  setMatchersString: (arg: string) => void;
33
+ start?: number;
34
+ end?: number;
33
35
  }
34
36
 
35
37
  const ViewMatchers: React.FC<Props> = ({
@@ -38,6 +40,8 @@ const ViewMatchers: React.FC<Props> = ({
38
40
  queryClient,
39
41
  runQuery,
40
42
  setMatchersString,
43
+ start,
44
+ end,
41
45
  currentQuery,
42
46
  }) => {
43
47
  const [labelValuesMap, setLabelValuesMap] = useState<Record<string, string[]>>({});
@@ -77,7 +81,17 @@ const ViewMatchers: React.FC<Props> = ({
77
81
  async (labelName: string): Promise<string[]> => {
78
82
  try {
79
83
  const response = await queryClient.values(
80
- {labelName, match: [], profileType},
84
+ {
85
+ labelName,
86
+ match: [],
87
+ profileType,
88
+ ...(start !== undefined && end !== undefined
89
+ ? {
90
+ start: millisToProtoTimestamp(start),
91
+ end: millisToProtoTimestamp(end),
92
+ }
93
+ : {}),
94
+ },
81
95
  {meta: metadata}
82
96
  ).response;
83
97
  return sanitizeLabelValue(response.labelValues);
@@ -86,7 +100,7 @@ const ViewMatchers: React.FC<Props> = ({
86
100
  return [];
87
101
  }
88
102
  },
89
- [queryClient, metadata, profileType]
103
+ [queryClient, metadata, profileType, start, end]
90
104
  );
91
105
 
92
106
  useEffect(() => {
@@ -40,6 +40,8 @@ interface LabelsProviderProps {
40
40
  children: React.ReactNode;
41
41
  queryClient: QueryServiceClient;
42
42
  profileType: string;
43
+ start?: number;
44
+ end?: number;
43
45
  }
44
46
 
45
47
  // With there being the possibility of having utilization labels, we need to be able to determine whether the labels to be used are utilization labels or profiling data labels.
@@ -48,13 +50,17 @@ export function LabelsProvider({
48
50
  children,
49
51
  queryClient,
50
52
  profileType,
53
+ start,
54
+ end,
51
55
  }: LabelsProviderProps): JSX.Element {
52
56
  const [currentLabelName, setCurrentLabelName] = React.useState<string | null>(null);
53
57
  const utilizationLabels = useUtilizationLabels();
54
58
 
55
59
  const {result: labelNamesResponse, loading: isLabelNamesLoading} = useLabelNames(
56
60
  queryClient,
57
- profileType
61
+ profileType,
62
+ start,
63
+ end
58
64
  );
59
65
 
60
66
  const labelNamesFromAPI = useMemo(() => {
@@ -68,7 +74,9 @@ export function LabelsProvider({
68
74
  const {result: labelValuesOriginal, loading: isLabelValuesLoading} = useLabelValues(
69
75
  queryClient,
70
76
  currentLabelName ?? '',
71
- profileType
77
+ profileType,
78
+ start,
79
+ end
72
80
  );
73
81
 
74
82
  const utilizationLabelValues = useFetchUtilizationLabelValues(
@@ -38,6 +38,8 @@ interface LabelProviderProps {
38
38
  queryClient: QueryServiceClient;
39
39
  profileType: string;
40
40
  labelNameFromMatchers: string[];
41
+ start?: number;
42
+ end?: number;
41
43
  }
42
44
 
43
45
  // With there being the possibility of having utilization labels, we need to be able to determine whether the labels to be used are utilization labels or profiling data labels.
@@ -48,9 +50,11 @@ export function LabelProvider({
48
50
  queryClient,
49
51
  profileType,
50
52
  labelNameFromMatchers,
53
+ start,
54
+ end,
51
55
  }: LabelProviderProps): JSX.Element {
52
56
  const utilizationLabelResponse = useUtilizationLabels();
53
- const {loading, result} = useLabelNames(queryClient, profileType);
57
+ const {loading, result} = useLabelNames(queryClient, profileType, start, end);
54
58
 
55
59
  const profileValues = useMemo(() => {
56
60
  const profileLabelNames =