@parca/profile 0.16.438 → 0.16.440

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 (44) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +2 -2
  3. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts.map +1 -1
  4. package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +3 -3
  5. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts.map +1 -1
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +4 -2
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +5 -3
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +20 -8
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +3 -2
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +25 -3
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.d.ts +2 -0
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.d.ts.map +1 -1
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.js +25 -0
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +4 -4
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts.map +1 -1
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +2 -2
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +3 -2
  20. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts.map +1 -1
  21. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +9 -3
  22. package/dist/ProfileIcicleGraph/index.d.ts +3 -3
  23. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  24. package/dist/ProfileIcicleGraph/index.js +20 -5
  25. package/dist/ProfileView/index.d.ts +3 -3
  26. package/dist/ProfileView/index.d.ts.map +1 -1
  27. package/dist/ProfileView/index.js +2 -2
  28. package/dist/ProfileViewWithData.js +4 -4
  29. package/dist/components/VisualisationToolbar/MultiLevelDropdown.d.ts.map +1 -1
  30. package/dist/components/VisualisationToolbar/MultiLevelDropdown.js +52 -6
  31. package/dist/styles.css +1 -1
  32. package/package.json +5 -5
  33. package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +5 -5
  34. package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +4 -4
  35. package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +7 -2
  36. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +32 -10
  37. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +42 -6
  38. package/src/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.ts +32 -0
  39. package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +6 -6
  40. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +18 -4
  41. package/src/ProfileIcicleGraph/index.tsx +32 -7
  42. package/src/ProfileView/index.tsx +6 -6
  43. package/src/ProfileViewWithData.tsx +4 -4
  44. package/src/components/VisualisationToolbar/MultiLevelDropdown.tsx +85 -13
@@ -14,7 +14,7 @@
14
14
  import {EVERYTHING_ELSE} from '@parca/store';
15
15
  import {diffColor, getLastItem} from '@parca/utilities';
16
16
 
17
- interface mappingColors {
17
+ interface colors {
18
18
  [key: string]: string;
19
19
  }
20
20
 
@@ -23,8 +23,8 @@ interface Props {
23
23
  compareMode: boolean;
24
24
  cumulative: bigint;
25
25
  diff: bigint | null;
26
- mappingColors: mappingColors;
27
- mappingFile: string | null;
26
+ colorsMap: colors;
27
+ colorAttribute: string | null;
28
28
  }
29
29
 
30
30
  const useNodeColor = ({
@@ -32,14 +32,14 @@ const useNodeColor = ({
32
32
  compareMode,
33
33
  cumulative,
34
34
  diff,
35
- mappingColors,
36
- mappingFile,
35
+ colorsMap,
36
+ colorAttribute,
37
37
  }: Props): string => {
38
38
  if (compareMode) {
39
39
  return diffColor(diff ?? 0n, cumulative, isDarkMode);
40
40
  }
41
41
 
42
- return mappingColors[getLastItem(mappingFile ?? '') ?? EVERYTHING_ELSE];
42
+ return colorsMap[getLastItem(colorAttribute ?? '') ?? EVERYTHING_ELSE];
43
43
  };
44
44
 
45
45
  export default useNodeColor;
@@ -13,7 +13,13 @@
13
13
 
14
14
  import {Table} from 'apache-arrow';
15
15
 
16
- import {EVERYTHING_ELSE, FEATURE_TYPES, type Feature} from '@parca/store';
16
+ import {
17
+ BINARY_FEATURE_TYPES,
18
+ EVERYTHING_ELSE,
19
+ FILENAMES_FEATURE_TYPES,
20
+ type BinaryFeature,
21
+ type FilenameFeature,
22
+ } from '@parca/store';
17
23
  import {divide, getLastItem, valueFormatter} from '@parca/utilities';
18
24
 
19
25
  import {hexifyAddress} from '../../utils';
@@ -65,12 +71,20 @@ export function nodeLabel(
65
71
  return fallback === '' ? '<unknown>' : fallback;
66
72
  }
67
73
 
68
- export const extractFeature = (mapping: string): Feature => {
74
+ export const extractFeature = (mapping: string): BinaryFeature => {
69
75
  if (mapping != null && mapping !== '') {
70
- return {name: mapping, type: FEATURE_TYPES.Binary};
76
+ return {name: mapping, type: BINARY_FEATURE_TYPES.Binary};
77
+ }
78
+
79
+ return {name: EVERYTHING_ELSE, type: BINARY_FEATURE_TYPES.Misc};
80
+ };
81
+
82
+ export const extractFilenameFeature = (filename: string): FilenameFeature => {
83
+ if (filename != null && filename !== '') {
84
+ return {name: filename, type: FILENAMES_FEATURE_TYPES.Filename};
71
85
  }
72
86
 
73
- return {name: EVERYTHING_ELSE, type: FEATURE_TYPES.Misc};
87
+ return {name: EVERYTHING_ELSE, type: FILENAMES_FEATURE_TYPES.Misc};
74
88
  };
75
89
 
76
90
  export const getTextForCumulative = (
@@ -13,6 +13,7 @@
13
13
 
14
14
  import React, {useEffect, useMemo, useState} from 'react';
15
15
 
16
+ import {Table, tableFromIPC} from 'apache-arrow';
16
17
  import {AnimatePresence, motion} from 'framer-motion';
17
18
 
18
19
  import {Flamegraph, FlamegraphArrow} from '@parca/client';
@@ -25,7 +26,7 @@ import DiffLegend from '../components/DiffLegend';
25
26
  import {IcicleGraph} from './IcicleGraph';
26
27
  import {FIELD_FUNCTION_NAME, IcicleGraphArrow} from './IcicleGraphArrow';
27
28
  import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
28
- import useMappingList from './IcicleGraphArrow/useMappingList';
29
+ import useMappingList, {useFilenamesList} from './IcicleGraphArrow/useMappingList';
29
30
 
30
31
  const numberFormatter = new Intl.NumberFormat('en-US');
31
32
 
@@ -44,8 +45,8 @@ interface ProfileIcicleGraphProps {
44
45
  setActionButtons?: (buttons: React.JSX.Element) => void;
45
46
  error?: any;
46
47
  isHalfScreen: boolean;
47
- mappings?: string[];
48
- mappingsLoading?: boolean;
48
+ metadataMappingFiles?: string[];
49
+ metadataLoading?: boolean;
49
50
  }
50
51
 
51
52
  const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
@@ -64,16 +65,22 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
64
65
  error,
65
66
  width,
66
67
  isHalfScreen,
67
- mappings,
68
+ metadataMappingFiles,
68
69
  }: ProfileIcicleGraphProps): JSX.Element {
69
70
  const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
70
71
  const {compareMode} = useProfileViewContext();
71
72
  const [isLoading, setIsLoading] = useState<boolean>(true);
72
73
  const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
73
74
 
74
- const mappingsList = useMappingList(mappings);
75
+ const table: Table<any> | null = useMemo(() => {
76
+ return arrow !== undefined ? tableFromIPC(arrow.record) : null;
77
+ }, [arrow]);
78
+
79
+ const mappingsList = useMappingList(metadataMappingFiles);
80
+ const filenamesList = useFilenamesList(table);
75
81
 
76
82
  const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState('sort_by');
83
+ const [colorBy, setColorBy] = useURLState('color_by');
77
84
 
78
85
  // By default, we want delta profiles (CPU) to be relatively compared.
79
86
  // For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
@@ -82,6 +89,12 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
82
89
  const [compareAbsolute = compareAbsoluteDefault] = useURLState('compare_absolute');
83
90
  const isCompareAbsolute = compareAbsolute === 'true';
84
91
 
92
+ const colorByValue = colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string);
93
+ const mappingsListCount = useMemo(
94
+ () => mappingsList.filter(m => m !== '').length,
95
+ [mappingsList]
96
+ );
97
+
85
98
  const [
86
99
  totalFormatted,
87
100
  totalUnfilteredFormatted,
@@ -113,7 +126,15 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
113
126
  }, [graph, arrow, filtered, total]);
114
127
 
115
128
  const loadingState =
116
- !loading && (arrow !== undefined || graph !== undefined) && mappings !== undefined;
129
+ !loading && (arrow !== undefined || graph !== undefined) && metadataMappingFiles !== undefined;
130
+
131
+ // If there is only one mapping file, we want to color by filename by default.
132
+ useEffect(() => {
133
+ if (mappingsListCount === 1 && colorBy !== 'filename') {
134
+ setColorBy('filename');
135
+ }
136
+ // eslint-disable-next-line react-hooks/exhaustive-deps
137
+ }, [mappingsListCount]);
117
138
 
118
139
  useEffect(() => {
119
140
  if (loadingState) {
@@ -211,7 +232,11 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
211
232
  >
212
233
  {compareMode ? <DiffLegend /> : null}
213
234
  {isColorStackLegendEnabled && (
214
- <ColorStackLegend compareMode={compareMode} mappings={mappings} loading={isLoading} />
235
+ <ColorStackLegend
236
+ compareMode={compareMode}
237
+ mappings={colorByValue === 'binary' ? mappingsList : filenamesList}
238
+ loading={isLoading}
239
+ />
215
240
  )}
216
241
  <div className="min-h-48" id="h-icicle-graph">
217
242
  <>{icicleGraph}</>
@@ -56,9 +56,9 @@ export interface FlamegraphData {
56
56
  total?: bigint;
57
57
  filtered?: bigint;
58
58
  error?: any;
59
- mappings?: string[];
60
- mappingsLoading: boolean;
61
- groupByLabels: string[];
59
+ metadataMappingFiles?: string[];
60
+ metadataLoading: boolean;
61
+ metadataLabels?: string[];
62
62
  }
63
63
 
64
64
  export interface TopTableData {
@@ -231,8 +231,8 @@ export const ProfileView = ({
231
231
  : dimensions.width - 16
232
232
  : 0
233
233
  }
234
- mappings={flamegraphData.mappings}
235
- mappingsLoading={flamegraphData.mappingsLoading}
234
+ metadataMappingFiles={flamegraphData.metadataMappingFiles}
235
+ metadataLoading={flamegraphData.metadataLoading}
236
236
  />
237
237
  </ConditionalWrapper>
238
238
  );
@@ -397,7 +397,7 @@ export const ProfileView = ({
397
397
  filtered={filtered}
398
398
  currentSearchString={currentSearchString}
399
399
  setSearchString={setSearchString}
400
- groupByLabels={flamegraphData.groupByLabels ?? []}
400
+ groupByLabels={flamegraphData.metadataLabels ?? []}
401
401
  />
402
402
 
403
403
  <div className="w-full" ref={ref}>
@@ -199,15 +199,15 @@ export const ProfileViewWithData = ({
199
199
  total: BigInt(flamegraphResponse?.total ?? '0'),
200
200
  filtered: BigInt(flamegraphResponse?.filtered ?? '0'),
201
201
  error: flamegraphError,
202
- mappings:
202
+ metadataMappingFiles:
203
203
  profileMetadataResponse?.report.oneofKind === 'profileMetadata'
204
204
  ? profileMetadataResponse?.report?.profileMetadata?.mappingFiles
205
205
  : undefined,
206
- mappingsLoading: profileMetadataLoading,
207
- groupByLabels:
206
+ metadataLabels:
208
207
  profileMetadataResponse?.report.oneofKind === 'profileMetadata'
209
208
  ? profileMetadataResponse?.report?.profileMetadata?.labels
210
- : [],
209
+ : undefined,
210
+ metadataLoading: profileMetadataLoading,
211
211
  }}
212
212
  topTableData={{
213
213
  loading: tableLoading,
@@ -37,6 +37,7 @@ interface MenuItemType {
37
37
  active?: boolean;
38
38
  value?: string;
39
39
  icon?: string;
40
+ customSubmenu?: React.ReactNode;
40
41
  }
41
42
 
42
43
  type MenuItemProps = MenuItemType & {
@@ -44,7 +45,8 @@ type MenuItemProps = MenuItemType & {
44
45
  path?: string[];
45
46
  closeDropdown: () => void;
46
47
  isNested?: boolean;
47
- activeValue?: string;
48
+ activeValueForSortBy?: string;
49
+ activeValueForColorBy?: string;
48
50
  icon?: string;
49
51
  };
50
52
 
@@ -57,12 +59,22 @@ const MenuItem: React.FC<MenuItemProps> = ({
57
59
  id,
58
60
  closeDropdown,
59
61
  isNested = false,
60
- activeValue,
62
+ activeValueForSortBy,
63
+ activeValueForColorBy,
61
64
  value,
62
65
  disabled = false,
63
66
  icon,
67
+ customSubmenu,
64
68
  }) => {
65
- const isActive = isNested && value === activeValue;
69
+ let isActive = false;
70
+ if (isNested) {
71
+ if (activeValueForSortBy !== undefined && value === activeValueForSortBy) {
72
+ isActive = true;
73
+ }
74
+ if (activeValueForColorBy !== undefined && value === activeValueForColorBy) {
75
+ isActive = true;
76
+ }
77
+ }
66
78
 
67
79
  const handleSelect = (): void => {
68
80
  if (items === undefined) {
@@ -93,13 +105,17 @@ const MenuItem: React.FC<MenuItemProps> = ({
93
105
  id={id}
94
106
  disabled={disabled}
95
107
  >
96
- <span className="flex items-center">
97
- <div className="flex items-center">
98
- <span>{label}</span>
99
- {icon !== undefined && <Icon icon={icon} className="ml-2 h-4 w-4" />}
100
- </div>
101
- {isActive && <Icon icon="heroicons-solid:check" className="ml-2 h-4 w-4" />}
102
- </span>
108
+ {customSubmenu !== undefined ? (
109
+ customSubmenu
110
+ ) : (
111
+ <span className="flex items-center">
112
+ <div className="flex items-center">
113
+ <span>{label}</span>
114
+ {icon !== undefined && <Icon icon={icon} className="ml-2 h-4 w-4" />}
115
+ </div>
116
+ {isActive && <Icon icon="heroicons-solid:check" className="ml-2 h-4 w-4" />}
117
+ </span>
118
+ )}
103
119
  {items !== undefined && (
104
120
  <Icon icon="flowbite:caret-right-solid" className="h-[14px] w-[14px]" />
105
121
  )}
@@ -118,7 +134,8 @@ const MenuItem: React.FC<MenuItemProps> = ({
118
134
  path={[...path, label]}
119
135
  closeDropdown={closeDropdown}
120
136
  isNested={true}
121
- activeValue={activeValue}
137
+ activeValueForSortBy={activeValueForSortBy}
138
+ activeValueForColorBy={activeValueForColorBy}
122
139
  />
123
140
  ))}
124
141
  </Menu.Items>
@@ -141,6 +158,11 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
141
158
  });
142
159
  const [colorStackLegend, setStoreColorStackLegend] = useURLState('color_stack_legend');
143
160
  const [binaryFrameFilter, setBinaryFrameFilter] = useURLState('binary_frame_filter');
161
+ const [colorBy, setColorBy] = useURLState('color_by');
162
+ const [hiddenBinaries, setHiddenBinaries] = useURLState('binary_frame_filter', {
163
+ defaultValue: [],
164
+ alwaysReturnArray: true,
165
+ });
144
166
  const {compareMode} = useProfileViewContext();
145
167
  const [colorProfileName] = useUserPreference<string>(
146
168
  USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
@@ -157,6 +179,12 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
157
179
  useURLState('compare_absolute');
158
180
  const isCompareAbsolute = compareAbsolute === 'true';
159
181
 
182
+ const handleBinaryToggle = (index: number): void => {
183
+ const updatedBinaries = [...(hiddenBinaries as string[])];
184
+ updatedBinaries.splice(index, 1);
185
+ setHiddenBinaries(updatedBinaries);
186
+ };
187
+
160
188
  const setColorStackLegend = useCallback(
161
189
  (value: string): void => {
162
190
  setStoreColorStackLegend(value);
@@ -193,6 +221,25 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
193
221
  hide: false,
194
222
  icon: 'material-symbols:sort',
195
223
  },
224
+ {
225
+ label: 'Color by',
226
+ id: 'h-solor-by-filter',
227
+ items: [
228
+ {
229
+ label: 'Binary',
230
+ onclick: () => setColorBy('binary'),
231
+ value: 'binary',
232
+ },
233
+ {
234
+ label: 'Filename',
235
+ onclick: () => setColorBy('filename'),
236
+ value: 'filename',
237
+ },
238
+ ],
239
+ hide: false,
240
+ icon: 'carbon:color-palette',
241
+ },
242
+
196
243
  {
197
244
  label: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend',
198
245
  onclick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'),
@@ -219,6 +266,28 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
219
266
  id: 'h-reset-legend-button',
220
267
  icon: 'system-uicons:reset',
221
268
  },
269
+ {
270
+ label: 'Hidden Binaries',
271
+ id: 'h-hidden-binaries',
272
+ items: (hiddenBinaries as string[])?.map((binary, index) => ({
273
+ label: binary,
274
+ customSubmenu: (
275
+ <div className="flex items-center gap-2 w-full">
276
+ <input
277
+ id={binary}
278
+ name={binary}
279
+ type="checkbox"
280
+ className="h-4 w-4 rounded-md border-2 border-gray-300 text-indigo-600 focus:ring-indigo-600 focus:ring-offset-0 checked:bg-indigo-600 checked:border-indigo-600"
281
+ checked={hiddenBinaries?.includes(binary)}
282
+ onChange={() => handleBinaryToggle(index)}
283
+ />
284
+ <span>{binary}</span>
285
+ </div>
286
+ ),
287
+ })),
288
+ hide: hiddenBinaries === undefined || hiddenBinaries.length === 0,
289
+ icon: 'ph:eye-closed',
290
+ },
222
291
  ];
223
292
 
224
293
  return (
@@ -233,7 +302,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
233
302
  />
234
303
  </Menu.Button>
235
304
  {open && (
236
- <Menu.Items className="absolute z-30 right-0 w-56 mt-2 py-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none border dark:bg-gray-900 dark:border-gray-600">
305
+ <Menu.Items className="absolute z-30 left-0 w-56 mt-2 py-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none border dark:bg-gray-900 dark:border-gray-600">
237
306
  <span className="text-xs text-gray-400 capitalize px-4 py-3">actions</span>
238
307
  {menuItems
239
308
  .filter(item => item.hide !== undefined && !item.hide)
@@ -243,7 +312,10 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
243
312
  {...item}
244
313
  onSelect={onSelect}
245
314
  closeDropdown={close}
246
- activeValue={storeSortBy as string}
315
+ activeValueForSortBy={storeSortBy as string}
316
+ activeValueForColorBy={
317
+ colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string)
318
+ }
247
319
  />
248
320
  ))}
249
321
  </Menu.Items>