@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.
- package/CHANGELOG.md +8 -0
- package/dist/GraphTooltipArrow/Content.js +1 -1
- package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +1 -5
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +23 -9
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +3 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +0 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +1 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +29 -47
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +1 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +2 -7
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +0 -3
- package/dist/ProfileIcicleGraph/index.d.ts +2 -1
- package/dist/ProfileIcicleGraph/index.js +26 -22
- package/dist/ProfileSource.js +2 -0
- package/dist/ProfileView/index.d.ts +2 -0
- package/dist/ProfileView/index.js +2 -2
- package/dist/ProfileViewWithData.js +17 -10
- package/dist/useQuery.d.ts +1 -3
- package/dist/useQuery.js +21 -8
- package/package.json +7 -7
- package/src/GraphTooltipArrow/Content.tsx +1 -5
- package/src/GraphTooltipArrow/DockedGraphTooltip/index.tsx +1 -1
- package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +1 -1
- package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +1 -7
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +26 -9
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +21 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +0 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +39 -48
- package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +1 -8
- package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +0 -5
- package/src/ProfileIcicleGraph/index.tsx +65 -50
- package/src/ProfileSource.tsx +2 -0
- package/src/ProfileView/index.tsx +4 -1
- package/src/ProfileViewWithData.tsx +26 -10
- package/src/useQuery.tsx +22 -11
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +0 -9
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +0 -23
- 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 ?
|
|
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(
|
|
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
|
|
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[]
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
return
|
|
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
|
-
}, [
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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;
|
package/dist/ProfileSource.js
CHANGED
|
@@ -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() {
|
|
@@ -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'
|