@parca/profile 0.16.373 → 0.16.375

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 (42) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.js +1 -1
  3. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +1 -1
  4. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +1 -1
  5. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +1 -1
  6. package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +1 -5
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +23 -9
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -1
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +3 -2
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +0 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +1 -0
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +29 -47
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +1 -2
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +2 -7
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +0 -3
  16. package/dist/ProfileIcicleGraph/index.d.ts +2 -1
  17. package/dist/ProfileIcicleGraph/index.js +26 -22
  18. package/dist/ProfileSource.js +2 -0
  19. package/dist/ProfileView/index.d.ts +2 -0
  20. package/dist/ProfileView/index.js +2 -2
  21. package/dist/ProfileViewWithData.js +17 -10
  22. package/dist/useQuery.d.ts +1 -3
  23. package/dist/useQuery.js +21 -8
  24. package/package.json +7 -7
  25. package/src/GraphTooltipArrow/Content.tsx +1 -5
  26. package/src/GraphTooltipArrow/DockedGraphTooltip/index.tsx +1 -1
  27. package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +1 -1
  28. package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +1 -7
  29. package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +26 -9
  30. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +21 -1
  31. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +0 -1
  32. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +39 -48
  33. package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +1 -8
  34. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +0 -5
  35. package/src/ProfileIcicleGraph/index.tsx +65 -50
  36. package/src/ProfileSource.tsx +2 -0
  37. package/src/ProfileView/index.tsx +4 -1
  38. package/src/ProfileViewWithData.tsx +26 -10
  39. package/src/useQuery.tsx +22 -11
  40. package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +0 -9
  41. package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +0 -23
  42. package/src/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.tsx +0 -123
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.375](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.374...@parca/profile@0.16.375) (2024-05-23)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## 0.16.374 (2024-05-21)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.373](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.372...@parca/profile@0.16.373) (2024-05-16)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -32,6 +32,6 @@ const TooltipMetaInfo = ({ table, row, navigateTo, }) => {
32
32
  const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
33
33
  const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== '';
34
34
  const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no';
35
- return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsx("div", { className: "flex gap-4", children: _jsx("div", { className: "whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? _jsx(NoData, {}) : _jsx("div", { children: hexifyAddress(locationAddress) }) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Inlined" }), _jsx("td", { className: "w-3/4 break-all", children: inlinedText })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: isMappingBuildIDAvailable ? (_jsx("div", { children: truncateString(getLastItem(mappingBuildID), 28) })) : (_jsx(NoData, {})) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
35
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsx("div", { className: "flex gap-4", children: _jsx("div", { className: "whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? _jsx(NoData, {}) : _jsx("div", { children: hexifyAddress(locationAddress) }) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Inlined" }), _jsx("td", { className: "w-3/4 break-all", children: inlinedText })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: isMappingBuildIDAvailable ? _jsx("div", { children: truncateString(mappingBuildID, 28) }) : _jsx(NoData, {}) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
36
36
  };
37
37
  export default GraphTooltipArrowContent;
@@ -51,5 +51,5 @@ export const DockedGraphTooltip = ({ table, total, totalUnfiltered, row, level,
51
51
  ? name
52
52
  : locationAddress !== 0n
53
53
  ? hexifyAddress(locationAddress)
54
- : 'unknown' })) }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx(InfoSection, { title: "Cumulative", value: cumulativeText, minWidth: "w-44" }), diff !== 0n ? _jsx(InfoSection, { title: "Diff", value: diffText, minWidth: "w-44" }) : null, _jsx(InfoSection, { title: "File", value: functionFilename !== '' ? truncateStringReverse(file, 45) : _jsx(NoData, {}), minWidth: 'w-[460px]' }), _jsx(InfoSection, { title: "Address", value: addressText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Inlined", value: inlinedText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Binary", value: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}), minWidth: "w-44" }), _jsx(InfoSection, { title: "Build ID", value: isMappingBuildIDAvailable ? (_jsx("div", { children: truncateString(getLastItem(mappingBuildID), 28) })) : (_jsx(NoData, {})) })] }), _jsx("div", { children: _jsx("div", { className: "flex h-5 gap-1", children: labels }) })] }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }));
54
+ : 'unknown' })) }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx(InfoSection, { title: "Cumulative", value: cumulativeText, minWidth: "w-44" }), diff !== 0n ? _jsx(InfoSection, { title: "Diff", value: diffText, minWidth: "w-44" }) : null, _jsx(InfoSection, { title: "File", value: functionFilename !== '' ? truncateStringReverse(file, 45) : _jsx(NoData, {}), minWidth: 'w-[460px]' }), _jsx(InfoSection, { title: "Address", value: addressText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Inlined", value: inlinedText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Binary", value: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}), minWidth: "w-44" }), _jsx(InfoSection, { title: "Build ID", value: isMappingBuildIDAvailable ? (_jsx("div", { children: truncateString(mappingBuildID, 28) })) : (_jsx(NoData, {})) })] }), _jsx("div", { children: _jsx("div", { className: "flex h-5 gap-1", children: labels }) })] }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }));
55
55
  };
@@ -25,7 +25,7 @@ const colorNodes = (nodes, strings, mappings, locations, functions, features) =>
25
25
  if (node.children != null) {
26
26
  coloredNode.children = colorNodes(node.children, strings, mappings, locations, functions, features);
27
27
  }
28
- const feature = extractFeature(node, mappings, locations, strings, functions);
28
+ const feature = extractFeature(node, mappings, locations, strings);
29
29
  coloredNode.feature = feature.name;
30
30
  features[feature.name] = feature.type;
31
31
  return coloredNode;
@@ -3,4 +3,4 @@ import { Location, Mapping, Function as ParcaFunction } from '@parca/client/dist
3
3
  import { type Feature } from '@parca/store';
4
4
  export declare const getBinaryName: (node: FlamegraphNode, mappings: Mapping[], locations: Location[], strings: string[]) => string | undefined;
5
5
  export declare function nodeLabel(node: FlamegraphNode, strings: string[], mappings: Mapping[], locations: Location[], functions: ParcaFunction[], showBinaryName: boolean): string;
6
- export declare const extractFeature: (data: FlamegraphNode, mappings: Mapping[], locations: Location[], strings: string[], functions: ParcaFunction[]) => Feature;
6
+ export declare const extractFeature: (data: FlamegraphNode, mappings: Mapping[], locations: Location[], strings: string[]) => Feature;
@@ -57,11 +57,7 @@ export function nodeLabel(node, strings, mappings, locations, functions, showBin
57
57
  const fallback = `${mappingString}${address}`;
58
58
  return fallback === '' ? '<unknown>' : fallback;
59
59
  }
60
- export const extractFeature = (data, mappings, locations, strings, functions) => {
61
- const name = nodeLabel(data, strings, mappings, locations, functions, false).trim();
62
- if (name.startsWith('runtime') || name === 'root') {
63
- return { name: 'runtime', type: FEATURE_TYPES.Runtime };
64
- }
60
+ export const extractFeature = (data, mappings, locations, strings) => {
65
61
  const binaryName = getBinaryName(data, mappings, locations, strings);
66
62
  if (binaryName != null) {
67
63
  return { name: binaryName, type: FEATURE_TYPES.Binary };
@@ -19,7 +19,10 @@ import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
19
19
  import { EVERYTHING_ELSE } from '@parca/store';
20
20
  const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) => {
21
21
  const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
22
- const [currentSearchString, setSearchString] = useURLState({ param: 'search_string', navigateTo });
22
+ const [currentSearchString, setSearchString] = useURLState({
23
+ param: 'binary_frame_filter',
24
+ navigateTo,
25
+ });
23
26
  const stackColorArray = useMemo(() => {
24
27
  return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
25
28
  if (featureA === EVERYTHING_ELSE) {
@@ -42,21 +45,32 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
42
45
  }
43
46
  return (_jsx("div", { className: "my-4 flex w-full flex-wrap justify-start", children: stackColorArray.map(([feature, color]) => {
44
47
  const filteringAllowed = feature !== EVERYTHING_ELSE;
45
- const isHighlighted = currentSearchString === feature;
48
+ const isHighlighted = currentSearchString !== undefined ? currentSearchString.includes(feature) : false;
46
49
  return (_jsxs("div", { className: cx('flex-no-wrap mb-1 flex w-1/5 items-center justify-between text-ellipsis p-1', {
47
50
  'cursor-pointer': filteringAllowed,
48
51
  'bg-gray-200 dark:bg-gray-800': isHighlighted,
49
52
  }), onClick: () => {
50
- if (!filteringAllowed) {
53
+ if (!filteringAllowed || isHighlighted) {
51
54
  return;
52
55
  }
53
- if (isHighlighted) {
54
- setSearchString('');
55
- return;
56
- }
57
- setSearchString(feature);
56
+ // Check if the current search string is defined and an array
57
+ const updatedSearchString = Array.isArray(currentSearchString)
58
+ ? [...currentSearchString, feature] // If array, append the feature
59
+ : // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
60
+ currentSearchString // If not array, preserve current value
61
+ ? currentSearchString.split(',') // If string, split by commas
62
+ : [feature]; // If undefined, initialize array with feature
63
+ setSearchString(updatedSearchString);
58
64
  }, children: [_jsxs("div", { className: "flex w-11/12 items-center justify-start", children: [_jsx("div", { className: "flex w-5 items-center", children: _jsx("div", { className: "mr-1 inline-block h-4 w-4", style: { backgroundColor: color } }) }), _jsx("div", { className: "shrink overflow-hidden text-ellipsis whitespace-nowrap text-sm hover:whitespace-normal", children: feature })] }), _jsx("div", { className: "flex w-1/12 justify-end", children: isHighlighted && (_jsx(Icon, { icon: "radix-icons:cross-circled", onClick: e => {
59
- setSearchString('');
65
+ let searchString = [];
66
+ if (typeof currentSearchString === 'string') {
67
+ searchString.push(currentSearchString);
68
+ }
69
+ else {
70
+ searchString = currentSearchString;
71
+ }
72
+ // remove the current feature from the search string array of strings
73
+ setSearchString(searchString.filter((f) => f !== feature));
60
74
  e.stopPropagation();
61
75
  } })) })] }, feature));
62
76
  }) }));
@@ -15,6 +15,7 @@ interface ContextMenuProps {
15
15
  curPath: string[];
16
16
  setCurPath: (path: string[]) => void;
17
17
  hideMenu: () => void;
18
+ hideBinary: (binaryToRemove: string) => void;
18
19
  }
19
- declare const ContextMenu: ({ menuId, table, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, profileType, }: ContextMenuProps) => JSX.Element;
20
+ declare const ContextMenu: ({ menuId, table, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, profileType, hideBinary, }: ContextMenuProps) => JSX.Element;
20
21
  export default ContextMenu;
@@ -16,10 +16,11 @@ import { Item, Menu, Separator, Submenu } from 'react-contexify';
16
16
  import { Tooltip } from 'react-tooltip';
17
17
  import { useParcaContext } from '@parca/components';
18
18
  import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
19
+ import { getLastItem } from '@parca/utilities';
19
20
  import { useGraphTooltip } from '../../GraphTooltipArrow/useGraphTooltip';
20
21
  import { useGraphTooltipMetaInfo } from '../../GraphTooltipArrow/useGraphTooltipMetaInfo';
21
22
  import { hexifyAddress, truncateString } from '../../utils';
22
- const ContextMenu = ({ menuId, table, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, profileType, }) => {
23
+ const ContextMenu = ({ menuId, table, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, profileType, hideBinary, }) => {
23
24
  const { isDarkMode } = useParcaContext();
24
25
  const { enableSourcesView } = useParcaContext();
25
26
  const [isGraphTooltipDocked, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
@@ -75,6 +76,6 @@ const ContextMenu = ({ menuId, table, total, totalUnfiltered, row, level, naviga
75
76
  { id: 'Build Id', value: buildIdText },
76
77
  ];
77
78
  const nonEmptyValuesToCopy = valuesToCopy.filter(({ value }) => value !== '');
78
- return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, theme: isDarkMode ? 'dark' : '', children: [_jsxs(Item, { id: "view-source-file", onClick: handleViewSourceFile, disabled: enableSourcesView === false || !isSourceAvailable, children: [_jsx("div", { "data-tooltip-id": "view-source-file-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "wpf:view-file" }), _jsx("div", { children: "View source file" })] }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "view-source-file-help" }) : null] }), _jsx(Item, { id: "reset-view", onClick: handleResetView, disabled: curPath.length === 0, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "system-uicons:reset" }), _jsx("div", { children: "Reset view" })] }) }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:copy" }), _jsx("div", { children: "Copy" })] }), children: _jsx("div", { className: "max-h-[300px] overflow-scroll", children: nonEmptyValuesToCopy.map(({ id, value }) => (_jsx(Item, { id: id, onClick: () => handleCopyItem(value), className: "dark:bg-gray-800", children: _jsxs("div", { className: "flex flex-col dark:text-gray-300 hover:dark:text-gray-100", children: [_jsx("div", { className: "text-sm", children: id }), _jsx("div", { className: "text-xs", children: truncateString(value, 30) })] }) }, id))) }) }), _jsx(Separator, {}), _jsx(Item, { id: "dock-tooltip", onClick: handleDockTooltip, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:dock-bottom" }), isGraphTooltipDocked ? 'Undock tooltip' : 'Dock tooltip'] }) })] }));
79
+ return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, theme: isDarkMode ? 'dark' : '', children: [_jsxs(Item, { id: "view-source-file", onClick: handleViewSourceFile, disabled: enableSourcesView === false || !isSourceAvailable, children: [_jsx("div", { "data-tooltip-id": "view-source-file-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "wpf:view-file" }), _jsx("div", { children: "View source file" })] }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "view-source-file-help" }) : null] }), _jsx(Item, { id: "reset-view", onClick: handleResetView, disabled: curPath.length === 0, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "system-uicons:reset" }), _jsx("div", { children: "Reset view" })] }) }), _jsxs(Item, { id: "hide-binary", onClick: () => hideBinary(getLastItem(mappingFile)), disabled: mappingFile === null || mappingFile === '', children: [_jsx("div", { "data-tooltip-id": "hide-binary-help", "data-tooltip-content": "Hide all frames for this binary", children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:bxs-hide" }), _jsxs("div", { children: ["Hide Binary ", mappingFile !== null && `(${getLastItem(mappingFile)})`] })] }) }), _jsx(Tooltip, { place: "left", id: "hide-binary-help" })] }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:copy" }), _jsx("div", { children: "Copy" })] }), children: _jsx("div", { className: "max-h-[300px] overflow-scroll", children: nonEmptyValuesToCopy.map(({ id, value }) => (_jsx(Item, { id: id, onClick: () => handleCopyItem(value), className: "dark:bg-gray-800", children: _jsxs("div", { className: "flex flex-col dark:text-gray-300 hover:dark:text-gray-100", children: [_jsx("div", { className: "text-sm", children: id }), _jsx("div", { className: "text-xs", children: truncateString(value, 30) })] }) }, id))) }) }), _jsx(Separator, {}), _jsx(Item, { id: "dock-tooltip", onClick: handleDockTooltip, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:dock-bottom" }), isGraphTooltipDocked ? 'Undock tooltip' : 'Dock tooltip'] }) })] }));
79
80
  };
80
81
  export default ContextMenu;
@@ -178,7 +178,6 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
178
178
  diffPerSecond,
179
179
  mappingColors,
180
180
  mappingFile,
181
- functionName,
182
181
  });
183
182
  const name = useMemo(() => {
184
183
  return isRoot ? 'root' : nodeLabel(table, row, level, binaries.length > 1);
@@ -28,6 +28,7 @@ interface IcicleGraphArrowProps {
28
28
  setCurPath: (path: string[]) => void;
29
29
  navigateTo?: NavigateFunction;
30
30
  sortBy: string;
31
+ mappings?: string[];
31
32
  }
32
33
  export declare const IcicleGraphArrow: React.NamedExoticComponent<IcicleGraphArrowProps>;
33
34
  export default IcicleGraphArrow;
@@ -14,6 +14,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
14
14
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
  import { tableFromIPC } from 'apache-arrow';
16
16
  import { useContextMenu } from 'react-contexify';
17
+ import { useURLState } from '@parca/components';
17
18
  import { USER_PREFERENCES, useCurrentColorProfile, useUserPreference } from '@parca/hooks';
18
19
  import { getColorForFeature, selectDarkMode, setHoveringNode, useAppDispatch, useAppSelector, } from '@parca/store';
19
20
  import { getLastItem, scaleLinear, selectQueryParam } from '@parca/utilities';
@@ -24,7 +25,7 @@ import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
24
25
  import ColorStackLegend from './ColorStackLegend';
25
26
  import ContextMenu from './ContextMenu';
26
27
  import { IcicleNode, RowHeight } from './IcicleGraphNodes';
27
- import { arrowToString, extractFeature } from './utils';
28
+ import { extractFeature } from './utils';
28
29
  export const FIELD_LABELS_ONLY = 'labels_only';
29
30
  export const FIELD_MAPPING_FILE = 'mapping_file';
30
31
  export const FIELD_MAPPING_BUILD_ID = 'mapping_build_id';
@@ -41,7 +42,7 @@ export const FIELD_CUMULATIVE = 'cumulative';
41
42
  export const FIELD_CUMULATIVE_PER_SECOND = 'cumulative_per_second';
42
43
  export const FIELD_DIFF = 'diff';
43
44
  export const FIELD_DIFF_PER_SECOND = 'diff_per_second';
44
- export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, profileType, navigateTo, sortBy, }) {
45
+ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, profileType, navigateTo, sortBy, mappings, }) {
45
46
  const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
46
47
  const dispatch = useAppDispatch();
47
48
  const [highlightSimilarStacksPreference] = useUserPreference(USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key);
@@ -56,66 +57,35 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
56
57
  const [hoveringName, setHoveringName] = useState(null);
57
58
  const svg = useRef(null);
58
59
  const ref = useRef(null);
60
+ const [binaryFrameFilter, setBinaryFrameFilter] = useURLState({
61
+ param: 'binary_frame_filter',
62
+ navigateTo,
63
+ });
59
64
  const currentSearchString = selectQueryParam('search_string') ?? '';
60
65
  const { compareMode } = useProfileViewContext();
61
66
  const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
62
67
  const currentColorProfile = useCurrentColorProfile();
63
68
  const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
64
- const mappings = useMemo(() => {
65
- // Read the mappings from the dictionary that contains all mapping strings.
66
- // This is great, as might only have a dozen or so mappings,
67
- // and don't need to read through all the rows (potentially thousands).
68
- const mappingsDict = table.getChild(FIELD_MAPPING_FILE);
69
- const mappings = mappingsDict?.data
70
- .map(mapping => {
71
- if (mapping.dictionary == null) {
72
- return [];
73
- }
74
- const len = mapping.dictionary.length;
75
- const entries = [];
76
- for (let i = 0; i < len; i++) {
77
- const fn = arrowToString(mapping.dictionary.get(i));
78
- entries.push(getLastItem(fn) ?? '');
79
- }
80
- return entries;
69
+ const mappingsList = useMemo(() => {
70
+ const list = mappings
71
+ ?.map(mapping => {
72
+ return getLastItem(mapping);
81
73
  })
82
74
  .flat() ?? [];
83
75
  // We add a EVERYTHING ELSE mapping to the list.
84
- mappings.push('');
85
- // We look through the function names to find out if there's a runtime function.
86
- // Again, we only read through the dictionary, which is much faster than reading through all the rows.
87
- // We stop as soon as we find a runtime function.
88
- const functionNamesDict = table.getChild(FIELD_FUNCTION_NAME);
89
- functionNamesDict?.data.forEach(fn => {
90
- if (fn.dictionary == null) {
91
- return;
92
- }
93
- const len = fn.dictionary.length;
94
- for (let i = 0; i < len; i++) {
95
- const fn = arrowToString(functionNamesDict?.get(i));
96
- if (fn?.startsWith('runtime') === true) {
97
- mappings.push('runtime');
98
- break;
99
- }
100
- }
101
- });
76
+ list.push('');
102
77
  // We sort the mappings alphabetically to make sure that the order is always the same.
103
- mappings.sort((a, b) => a.localeCompare(b));
104
- return mappings;
105
- }, [table]);
106
- // TODO: Somehow figure out how to add runtime to this, if stacks are present.
107
- // Potentially read the function name dictionary and check if it contains strings starting with runtime.
108
- const mappingFeatures = useMemo(() => {
109
- return mappings.map(mapping => extractFeature(mapping));
78
+ list.sort((a, b) => a.localeCompare(b));
79
+ return list;
110
80
  }, [mappings]);
111
- // TODO: Unify with mappingFeatures
112
81
  const mappingColors = useMemo(() => {
82
+ const mappingFeatures = mappingsList.map(mapping => extractFeature(mapping));
113
83
  const colors = {};
114
84
  Object.entries(mappingFeatures).forEach(([_, feature]) => {
115
85
  colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
116
86
  });
117
87
  return colors;
118
- }, [isDarkMode, mappingFeatures, currentColorProfile]);
88
+ }, [mappingsList, isDarkMode, currentColorProfile]);
119
89
  useEffect(() => {
120
90
  if (ref.current != null) {
121
91
  setHeight(ref?.current.getBoundingClientRect().height);
@@ -139,6 +109,18 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
139
109
  const trackVisibility = (isVisible) => {
140
110
  setIsContextMenuOpen(isVisible);
141
111
  };
112
+ const hideBinary = (binaryToRemove) => {
113
+ // second/subsequent time filtering out a binary i.e. a binary has already been hidden
114
+ // and we want to hide more binaries, we simply remove the binary from the binaryFrameFilter array in the URL.
115
+ if (Array.isArray(binaryFrameFilter) && binaryFrameFilter.length > 0) {
116
+ const newMappingsList = binaryFrameFilter.filter(mapping => mapping !== binaryToRemove);
117
+ setBinaryFrameFilter(newMappingsList);
118
+ return;
119
+ }
120
+ // first time hiding a binary
121
+ const newMappingsList = mappingsList.filter(mapping => mapping !== binaryToRemove);
122
+ setBinaryFrameFilter(newMappingsList);
123
+ };
142
124
  // useMemo for the root graph as it otherwise renders the whole graph if the hoveringRow changes.
143
125
  const root = useMemo(() => {
144
126
  return (_jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, onContextMenu: displayMenu, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { table: table, row: 0, mappingColors: mappingColors, x: 0, y: 0, totalWidth: width ?? 1, height: RowHeight, setCurPath: setCurPath, curPath: curPath, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, sortBy: sortBy, darkMode: isDarkMode, compareMode: compareMode, profileType: profileType, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }) }) }) }));
@@ -166,6 +148,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
166
148
  if (table.numRows === 0 || width === undefined) {
167
149
  return _jsx(_Fragment, {});
168
150
  }
169
- return (_jsx(_Fragment, { children: _jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ContextMenu, { menuId: MENU_ID, table: table, row: hoveringRow ?? 0, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, profileType: profileType, navigateTo: navigateTo, trackVisibility: trackVisibility, curPath: curPath, setCurPath: setCurPath, hideMenu: hideAll }), isColorStackLegendEnabled && (_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode })), dockedMetainfo ? (_jsx(DockedGraphTooltip, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, profileType: profileType })) : (!isContextMenuOpen && (_jsx(GraphTooltipArrow, { contextElement: svg.current, isContextMenuOpen: isContextMenuOpen, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, profileType: profileType, navigateTo: navigateTo }) }))), root] }) }));
151
+ return (_jsx(_Fragment, { children: _jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ContextMenu, { menuId: MENU_ID, table: table, row: hoveringRow ?? 0, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, profileType: profileType, navigateTo: navigateTo, trackVisibility: trackVisibility, curPath: curPath, setCurPath: setCurPath, hideMenu: hideAll, hideBinary: hideBinary }), isColorStackLegendEnabled && (_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode })), dockedMetainfo ? (_jsx(DockedGraphTooltip, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, profileType: profileType })) : (!isContextMenuOpen && (_jsx(GraphTooltipArrow, { contextElement: svg.current, isContextMenuOpen: isContextMenuOpen, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, profileType: profileType, navigateTo: navigateTo }) }))), root] }) }));
170
152
  });
171
153
  export default IcicleGraphArrow;
@@ -9,8 +9,7 @@ interface Props {
9
9
  diff: bigint | null;
10
10
  diffPerSecond: number | null;
11
11
  mappingColors: mappingColors;
12
- functionName: string | null;
13
12
  mappingFile: string | null;
14
13
  }
15
- declare const useNodeColor: ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, functionName, mappingFile, }: Props) => string;
14
+ declare const useNodeColor: ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, mappingFile, }: Props) => string;
16
15
  export default useNodeColor;
@@ -12,18 +12,13 @@
12
12
  // limitations under the License.
13
13
  import { EVERYTHING_ELSE } from '@parca/store';
14
14
  import { diffColor, diffColorPerSecond, getLastItem } from '@parca/utilities';
15
- const useNodeColor = ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, functionName, mappingFile, }) => {
15
+ const useNodeColor = ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, mappingFile, }) => {
16
16
  if (compareMode) {
17
17
  if (cumulativePerSecond !== null && diffPerSecond !== null) {
18
18
  return diffColorPerSecond(diffPerSecond, cumulativePerSecond, isDarkMode);
19
19
  }
20
20
  return diffColor(diff ?? 0n, cumulative, isDarkMode);
21
21
  }
22
- // To get the color we first check if the function name starts with 'runtime'.
23
- // If it does, we color it as runtime. Otherwise, we check the mapping file.
24
- // If there is no mapping file, we color it as 'everything else'.
25
- return functionName?.startsWith('runtime') === true
26
- ? mappingColors.runtime
27
- : mappingColors[getLastItem(mappingFile ?? '') ?? EVERYTHING_ELSE];
22
+ return mappingColors[getLastItem(mappingFile ?? '') ?? EVERYTHING_ELSE];
28
23
  };
29
24
  export default useNodeColor;
@@ -45,9 +45,6 @@ export function nodeLabel(table, row, level, showBinaryName) {
45
45
  return fallback === '' ? '<unknown>' : fallback;
46
46
  }
47
47
  export const extractFeature = (mapping) => {
48
- if (mapping === 'runtime' || mapping === 'root') {
49
- return { name: 'runtime', type: FEATURE_TYPES.Runtime };
50
- }
51
48
  if (mapping != null && mapping !== '') {
52
49
  return { name: mapping, type: FEATURE_TYPES.Binary };
53
50
  }
@@ -17,6 +17,7 @@ interface ProfileIcicleGraphProps {
17
17
  setActionButtons?: (buttons: React.JSX.Element) => void;
18
18
  error?: any;
19
19
  isHalfScreen: boolean;
20
+ mappings?: string[];
20
21
  }
21
- declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }: ProfileIcicleGraphProps) => JSX.Element;
22
+ declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, }: ProfileIcicleGraphProps) => JSX.Element;
22
23
  export default ProfileIcicleGraph;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } 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.
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import { useCallback, useEffect, useMemo } from 'react';
14
+ import { useCallback, useEffect, useMemo, useState } from 'react';
15
15
  import { Icon } from '@iconify/react';
16
16
  import { AnimatePresence, motion } from 'framer-motion';
17
17
  import { Button, IcicleActionButtonPlaceholder, IcicleGraphSkeleton, IconButton, useParcaContext, useURLState, } from '@parca/components';
@@ -20,7 +20,6 @@ import { capitalizeOnlyFirstLetter, divide } from '@parca/utilities';
20
20
  import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
21
21
  import DiffLegend from '../components/DiffLegend';
22
22
  import GroupByDropdown from './ActionButtons/GroupByDropdown';
23
- import RuntimeFilterDropdown from './ActionButtons/RuntimeFilterDropdown';
24
23
  import SortBySelect from './ActionButtons/SortBySelect';
25
24
  import IcicleGraph from './IcicleGraph';
26
25
  import IcicleGraphArrow, { FIELD_FUNCTION_NAME } from './IcicleGraphArrow';
@@ -33,13 +32,20 @@ const ShowHideLegendButton = ({ navigateTo, isHalfScreen, }) => {
33
32
  param: 'color_stack_legend',
34
33
  navigateTo,
35
34
  });
35
+ const [binaryFrameFilter, setBinaryFrameFilter] = useURLState({
36
+ param: 'binary_frame_filter',
37
+ navigateTo,
38
+ });
36
39
  const { compareMode } = useProfileViewContext();
37
40
  const isColorStackLegendEnabled = colorStackLegend === 'true';
38
41
  const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
39
42
  const setColorStackLegend = useCallback((value) => {
40
43
  setStoreColorStackLegend(value);
41
44
  }, [setStoreColorStackLegend]);
42
- return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsx(_Fragment, { children: isHalfScreen ? (_jsx(IconButton, { className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]", icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', toolTipText: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button" })) : (_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button", children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] })) })) }));
45
+ const resetLegend = () => {
46
+ setBinaryFrameFilter([]);
47
+ };
48
+ return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsx(_Fragment, { children: isHalfScreen ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]", icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', toolTipText: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button" }), binaryFrameFilter !== undefined && binaryFrameFilter.length > 0 && (_jsx(IconButton, { className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]", icon: "system-uicons:reset", toolTipText: "Reset the legend selection", onClick: () => resetLegend(), id: "h-reset-legend-button" }))] })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button", children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] }), binaryFrameFilter !== undefined && binaryFrameFilter.length > 0 && (_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => resetLegend(), id: "h-reset-legend-button", children: ["Reset Legend", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] })) })) }));
43
49
  };
44
50
  const GroupAndSortActionButtons = ({ navigateTo }) => {
45
51
  const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
@@ -68,23 +74,12 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
68
74
  ? setGroupBy(groupBy.filter(v => v !== key)) // remove
69
75
  : setGroupBy([...groupBy, key]); // add
70
76
  }, [groupBy, setGroupBy]);
71
- const [showRuntimeRubyStr, setShowRuntimeRuby] = useURLState({
72
- param: 'show_runtime_ruby',
73
- navigateTo,
74
- });
75
- const [showRuntimePythonStr, setShowRuntimePython] = useURLState({
76
- param: 'show_runtime_python',
77
- navigateTo,
78
- });
79
- const [showInterpretedOnlyStr, setShowInterpretedOnly] = useURLState({
80
- param: 'show_interpreted_only',
81
- navigateTo,
82
- });
83
- return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy }), _jsx(RuntimeFilterDropdown, { showRuntimeRuby: showRuntimeRubyStr === 'true', toggleShowRuntimeRuby: () => setShowRuntimeRuby(showRuntimeRubyStr === 'true' ? 'false' : 'true'), showRuntimePython: showRuntimePythonStr === 'true', toggleShowRuntimePython: () => setShowRuntimePython(showRuntimePythonStr === 'true' ? 'false' : 'true'), showInterpretedOnly: showInterpretedOnlyStr === 'true', toggleShowInterpretedOnly: () => setShowInterpretedOnly(showInterpretedOnlyStr === 'true' ? 'false' : 'true') })] }));
77
+ return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy })] }));
84
78
  };
85
- const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }) {
79
+ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, }) {
86
80
  const { onError, authenticationErrorMessage, isDarkMode } = useParcaContext();
87
81
  const { compareMode } = useProfileViewContext();
82
+ const [isLoading, setIsLoading] = useState(true);
88
83
  const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
89
84
  param: 'sort_by',
90
85
  navigateTo,
@@ -113,14 +108,14 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
113
108
  ];
114
109
  }, [graph, arrow, filtered, total]);
115
110
  useEffect(() => {
116
- if (loading && setActionButtons !== undefined) {
111
+ if (isLoading && setActionButtons !== undefined) {
117
112
  setActionButtons(_jsx(IcicleActionButtonPlaceholder, { isHalfScreen: isHalfScreen }));
118
113
  return;
119
114
  }
120
115
  if (setActionButtons === undefined) {
121
116
  return;
122
117
  }
123
- setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
118
+ setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), arrow !== undefined && isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
124
119
  }, [
125
120
  navigateTo,
126
121
  isInvert,
@@ -131,8 +126,17 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
131
126
  setActionButtons,
132
127
  loading,
133
128
  isHalfScreen,
129
+ isLoading,
134
130
  ]);
135
- if (loading) {
131
+ useEffect(() => {
132
+ if (!loading && (arrow !== undefined || graph !== undefined)) {
133
+ setIsLoading(false);
134
+ }
135
+ else {
136
+ setIsLoading(true);
137
+ }
138
+ }, [loading, arrow, graph]);
139
+ if (isLoading) {
136
140
  return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
137
141
  }
138
142
  if (error != null) {
@@ -149,6 +153,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
149
153
  if (isTrimmed) {
150
154
  console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
151
155
  }
152
- return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", id: "h-icicle-graph", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo, sortBy: storeSortBy }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
156
+ return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", id: "h-icicle-graph", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo, sortBy: storeSortBy, mappings: mappings }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
153
157
  };
154
158
  export default ProfileIcicleGraph;
@@ -108,6 +108,7 @@ export class ProfileDiffSource {
108
108
  reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
109
109
  mode: QueryRequest_Mode.DIFF,
110
110
  filterQuery: this.filterQuery,
111
+ filter: [],
111
112
  };
112
113
  }
113
114
  ProfileType() {
@@ -158,6 +159,7 @@ export class MergedProfileSource {
158
159
  reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
159
160
  mode: QueryRequest_Mode.MERGE,
160
161
  filterQuery: this.filterQuery,
162
+ filter: [],
161
163
  };
162
164
  }
163
165
  ProfileType() {
@@ -11,6 +11,8 @@ export interface FlamegraphData {
11
11
  total?: bigint;
12
12
  filtered?: bigint;
13
13
  error?: any;
14
+ mappings?: string[];
15
+ mappingsLoading: boolean;
14
16
  }
15
17
  export interface TopTableData {
16
18
  loading: boolean;
@@ -107,11 +107,11 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
107
107
  return (_jsx(ConditionalWrapper, { condition: perf?.onRender != null, WrapperComponent: Profiler, wrapperProps: {
108
108
  id: 'icicleGraph',
109
109
  onRender: perf?.onRender,
110
- }, children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, arrow: flamegraphData?.arrow, graph: flamegraphData?.data, total: total, filtered: filtered, profileType: profileSource?.ProfileType(), navigateTo: navigateTo, loading: flamegraphData.loading, setActionButtons: setActionButtons, error: flamegraphData.error, isHalfScreen: isHalfScreen, width: dimensions?.width !== undefined
110
+ }, children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, arrow: flamegraphData?.arrow, graph: flamegraphData?.data, total: total, filtered: filtered, profileType: profileSource?.ProfileType(), navigateTo: navigateTo, loading: flamegraphData.loading && flamegraphData.mappingsLoading, setActionButtons: setActionButtons, error: flamegraphData.error, isHalfScreen: isHalfScreen, width: dimensions?.width !== undefined
111
111
  ? isHalfScreen
112
112
  ? (dimensions.width - 40) / 2
113
113
  : dimensions.width - 16
114
- : 0 }) }));
114
+ : 0, mappings: flamegraphData.mappings }) }));
115
115
  }
116
116
  case 'callgraph': {
117
117
  return callgraphData?.data !== undefined &&
@@ -25,14 +25,12 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
25
25
  const [sourceBuildID] = useURLState({ param: 'source_buildid', navigateTo });
26
26
  const [sourceFilename] = useURLState({ param: 'source_filename', navigateTo });
27
27
  const [groupBy = [FIELD_FUNCTION_NAME]] = useURLState({ param: 'group_by', navigateTo });
28
- const [showRuntimeRubyStr] = useURLState({ param: 'show_runtime_ruby', navigateTo });
29
- const showRuntimeRuby = showRuntimeRubyStr === 'true';
30
- const [showRuntimePythonStr] = useURLState({ param: 'show_runtime_python', navigateTo });
31
- const showRuntimePython = showRuntimePythonStr === 'true';
32
- const [showInterpretedOnlyStr] = useURLState({ param: 'show_interpreted_only', navigateTo });
33
- const showInterpretedOnly = showInterpretedOnlyStr === 'true';
34
28
  const [invertStack] = useURLState({ param: 'invert_call_stack', navigateTo });
35
29
  const invertCallStack = invertStack === 'true';
30
+ const [binaryFrameFilterStr] = useURLState({ param: 'binary_frame_filter', navigateTo });
31
+ const binaryFrameFilter = typeof binaryFrameFilterStr === 'string'
32
+ ? binaryFrameFilterStr.split(',')
33
+ : binaryFrameFilterStr;
36
34
  const [pprofDownloading, setPprofDownloading] = useState(false);
37
35
  const nodeTrimThreshold = useMemo(() => {
38
36
  let width =
@@ -48,10 +46,15 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
48
46
  skip: !dashboardItems.includes('icicle'),
49
47
  nodeTrimThreshold,
50
48
  groupBy: groupByParam,
51
- showRuntimeRuby,
52
- showRuntimePython,
53
- showInterpretedOnly,
54
49
  invertCallStack,
50
+ binaryFrameFilter,
51
+ });
52
+ const { isLoading: profilemetadataLoading, response: profilemetadataResponse } = useQuery(queryClient, profileSource, QueryRequest_ReportType.PROFILE_METADATA, {
53
+ skip: !dashboardItems.includes('icicle'),
54
+ nodeTrimThreshold,
55
+ groupBy: groupByParam,
56
+ invertCallStack,
57
+ binaryFrameFilter: undefined,
55
58
  });
56
59
  const { perf } = useParcaContext();
57
60
  const { isLoading: tableLoading, response: tableResponse, error: tableError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TABLE_ARROW, {
@@ -126,7 +129,7 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
126
129
  filtered = BigInt(sourceResponse.filtered);
127
130
  }
128
131
  return (_jsx(ProfileView, { total: total, filtered: filtered, flamegraphData: {
129
- loading: flamegraphLoading,
132
+ loading: flamegraphLoading && profilemetadataLoading,
130
133
  data: flamegraphResponse?.report.oneofKind === 'flamegraph'
131
134
  ? flamegraphResponse?.report?.flamegraph
132
135
  : undefined,
@@ -136,6 +139,10 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
136
139
  total: BigInt(flamegraphResponse?.total ?? '0'),
137
140
  filtered: BigInt(flamegraphResponse?.filtered ?? '0'),
138
141
  error: flamegraphError,
142
+ mappings: profilemetadataResponse?.report.oneofKind === 'profileMetadata'
143
+ ? profilemetadataResponse?.report?.profileMetadata?.mappingFiles
144
+ : undefined,
145
+ mappingsLoading: profilemetadataLoading,
139
146
  }, topTableData: {
140
147
  loading: tableLoading,
141
148
  arrow: tableResponse?.report.oneofKind === 'tableArrow'