@parca/profile 0.16.374 → 0.16.376
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/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.d.ts +4 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +50 -13
- 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 +5 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +48 -50
- 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 +3 -1
- package/dist/ProfileIcicleGraph/index.js +57 -36
- package/dist/ProfileSource.js +2 -0
- package/dist/ProfileView/index.d.ts +2 -0
- package/dist/ProfileView/index.js +1 -1
- package/dist/ProfileViewWithData.js +17 -10
- package/dist/Table/index.js +2 -9
- package/dist/useQuery.d.ts +1 -3
- package/dist/useQuery.js +21 -8
- package/package.json +7 -7
- package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +1 -1
- package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +1 -7
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +62 -16
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +21 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +0 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +66 -61
- package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +1 -8
- package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +0 -5
- package/src/ProfileIcicleGraph/index.tsx +142 -96
- package/src/ProfileSource.tsx +2 -0
- package/src/ProfileView/index.tsx +4 -0
- package/src/ProfileViewWithData.tsx +26 -10
- package/src/Table/index.tsx +1 -11
- 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.376 (2024-05-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.16.375](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.374...@parca/profile@0.16.375) (2024-05-23)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## 0.16.374 (2024-05-21)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -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 };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type
|
|
3
|
-
import { mappingColors } from './IcicleGraphNodes';
|
|
2
|
+
import { type NavigateFunction } from '@parca/utilities';
|
|
4
3
|
interface Props {
|
|
5
|
-
|
|
4
|
+
mappings?: string[];
|
|
5
|
+
mappingsLoading?: boolean;
|
|
6
6
|
navigateTo?: NavigateFunction;
|
|
7
7
|
compareMode?: boolean;
|
|
8
8
|
}
|
|
9
|
-
declare const ColorStackLegend: ({
|
|
9
|
+
declare const ColorStackLegend: ({ mappings, navigateTo, compareMode, mappingsLoading, }: Props) => React.JSX.Element;
|
|
10
10
|
export default ColorStackLegend;
|
|
@@ -15,11 +15,37 @@ import { useMemo } from 'react';
|
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import cx from 'classnames';
|
|
17
17
|
import { useURLState } from '@parca/components';
|
|
18
|
-
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
19
|
-
import { EVERYTHING_ELSE } from '@parca/store';
|
|
20
|
-
|
|
18
|
+
import { USER_PREFERENCES, useCurrentColorProfile, useUserPreference } from '@parca/hooks';
|
|
19
|
+
import { EVERYTHING_ELSE, selectDarkMode, useAppSelector } from '@parca/store';
|
|
20
|
+
import { getLastItem } from '@parca/utilities';
|
|
21
|
+
import { getMappingColors } from '.';
|
|
22
|
+
const ColorStackLegend = ({ mappings, navigateTo, compareMode = false, mappingsLoading, }) => {
|
|
23
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
24
|
+
const currentColorProfile = useCurrentColorProfile();
|
|
21
25
|
const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
22
|
-
const [currentSearchString, setSearchString] = useURLState({
|
|
26
|
+
const [currentSearchString, setSearchString] = useURLState({
|
|
27
|
+
param: 'binary_frame_filter',
|
|
28
|
+
navigateTo,
|
|
29
|
+
});
|
|
30
|
+
const mappingsList = useMemo(() => {
|
|
31
|
+
if (mappings === undefined) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const list = mappings
|
|
35
|
+
?.map(mapping => {
|
|
36
|
+
return getLastItem(mapping);
|
|
37
|
+
})
|
|
38
|
+
.flat() ?? [];
|
|
39
|
+
// We add a EVERYTHING ELSE mapping to the list.
|
|
40
|
+
list.push('');
|
|
41
|
+
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
42
|
+
list.sort((a, b) => a.localeCompare(b));
|
|
43
|
+
return list;
|
|
44
|
+
}, [mappings]);
|
|
45
|
+
const mappingColors = useMemo(() => {
|
|
46
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
47
|
+
return colors;
|
|
48
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
23
49
|
const stackColorArray = useMemo(() => {
|
|
24
50
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
25
51
|
if (featureA === EVERYTHING_ELSE) {
|
|
@@ -31,7 +57,7 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
31
57
|
return featureA?.localeCompare(featureB ?? '') ?? 0;
|
|
32
58
|
});
|
|
33
59
|
}, [mappingColors]);
|
|
34
|
-
if (mappingColors === undefined) {
|
|
60
|
+
if (mappingColors === undefined && mappingsLoading === false) {
|
|
35
61
|
return _jsx(_Fragment, {});
|
|
36
62
|
}
|
|
37
63
|
if (Object.entries(mappingColors).length === 0) {
|
|
@@ -42,21 +68,32 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
42
68
|
}
|
|
43
69
|
return (_jsx("div", { className: "my-4 flex w-full flex-wrap justify-start", children: stackColorArray.map(([feature, color]) => {
|
|
44
70
|
const filteringAllowed = feature !== EVERYTHING_ELSE;
|
|
45
|
-
const isHighlighted = currentSearchString
|
|
71
|
+
const isHighlighted = currentSearchString !== undefined ? currentSearchString.includes(feature) : false;
|
|
46
72
|
return (_jsxs("div", { className: cx('flex-no-wrap mb-1 flex w-1/5 items-center justify-between text-ellipsis p-1', {
|
|
47
73
|
'cursor-pointer': filteringAllowed,
|
|
48
74
|
'bg-gray-200 dark:bg-gray-800': isHighlighted,
|
|
49
75
|
}), onClick: () => {
|
|
50
|
-
if (!filteringAllowed) {
|
|
76
|
+
if (!filteringAllowed || isHighlighted) {
|
|
51
77
|
return;
|
|
52
78
|
}
|
|
53
|
-
if
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
// Check if the current search string is defined and an array
|
|
80
|
+
const updatedSearchString = Array.isArray(currentSearchString)
|
|
81
|
+
? [...currentSearchString, feature] // If array, append the feature
|
|
82
|
+
: // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
83
|
+
currentSearchString // If not array, preserve current value
|
|
84
|
+
? currentSearchString.split(',') // If string, split by commas
|
|
85
|
+
: [feature]; // If undefined, initialize array with feature
|
|
86
|
+
setSearchString(updatedSearchString);
|
|
58
87
|
}, 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
|
-
|
|
88
|
+
let searchString = [];
|
|
89
|
+
if (typeof currentSearchString === 'string') {
|
|
90
|
+
searchString.push(currentSearchString);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
searchString = currentSearchString;
|
|
94
|
+
}
|
|
95
|
+
// remove the current feature from the search string array of strings
|
|
96
|
+
setSearchString(searchString.filter((f) => f !== feature));
|
|
60
97
|
e.stopPropagation();
|
|
61
98
|
} })) })] }, feature));
|
|
62
99
|
}) }));
|
|
@@ -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);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FlamegraphArrow } from '@parca/client';
|
|
3
3
|
import { ProfileType } from '@parca/parser';
|
|
4
|
-
import { type NavigateFunction } from '@parca/utilities';
|
|
4
|
+
import { type ColorConfig, type NavigateFunction } from '@parca/utilities';
|
|
5
|
+
import { mappingColors } from './IcicleGraphNodes';
|
|
5
6
|
export declare const FIELD_LABELS_ONLY = "labels_only";
|
|
6
7
|
export declare const FIELD_MAPPING_FILE = "mapping_file";
|
|
7
8
|
export declare const FIELD_MAPPING_BUILD_ID = "mapping_build_id";
|
|
@@ -28,6 +29,9 @@ interface IcicleGraphArrowProps {
|
|
|
28
29
|
setCurPath: (path: string[]) => void;
|
|
29
30
|
navigateTo?: NavigateFunction;
|
|
30
31
|
sortBy: string;
|
|
32
|
+
flamegraphLoading: boolean;
|
|
33
|
+
isHalfScreen: boolean;
|
|
31
34
|
}
|
|
35
|
+
export declare const getMappingColors: (mappingsList: string[], isDarkMode: boolean, currentColorProfile: ColorConfig) => mappingColors;
|
|
32
36
|
export declare const IcicleGraphArrow: React.NamedExoticComponent<IcicleGraphArrowProps>;
|
|
33
37
|
export default IcicleGraphArrow;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// Copyright 2022 The Parca Authors
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
// you may not use this file except in compliance with the License.
|
|
@@ -14,14 +14,14 @@ 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
|
-
import { getLastItem, scaleLinear, selectQueryParam } from '@parca/utilities';
|
|
20
|
+
import { getLastItem, scaleLinear, selectQueryParam, } from '@parca/utilities';
|
|
20
21
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
21
22
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
22
23
|
import { DockedGraphTooltip } from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
23
24
|
import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
|
|
24
|
-
import ColorStackLegend from './ColorStackLegend';
|
|
25
25
|
import ContextMenu from './ContextMenu';
|
|
26
26
|
import { IcicleNode, RowHeight } from './IcicleGraphNodes';
|
|
27
27
|
import { arrowToString, extractFeature } from './utils';
|
|
@@ -41,7 +41,15 @@ export const FIELD_CUMULATIVE = 'cumulative';
|
|
|
41
41
|
export const FIELD_CUMULATIVE_PER_SECOND = 'cumulative_per_second';
|
|
42
42
|
export const FIELD_DIFF = 'diff';
|
|
43
43
|
export const FIELD_DIFF_PER_SECOND = 'diff_per_second';
|
|
44
|
-
export const
|
|
44
|
+
export const getMappingColors = (mappingsList, isDarkMode, currentColorProfile) => {
|
|
45
|
+
const mappingFeatures = mappingsList.map(mapping => extractFeature(mapping));
|
|
46
|
+
const colors = {};
|
|
47
|
+
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
48
|
+
colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
|
|
49
|
+
});
|
|
50
|
+
return colors;
|
|
51
|
+
};
|
|
52
|
+
export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, profileType, navigateTo, sortBy, flamegraphLoading, }) {
|
|
45
53
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
|
46
54
|
const dispatch = useAppDispatch();
|
|
47
55
|
const [highlightSimilarStacksPreference] = useUserPreference(USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key);
|
|
@@ -56,12 +64,16 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
56
64
|
const [hoveringName, setHoveringName] = useState(null);
|
|
57
65
|
const svg = useRef(null);
|
|
58
66
|
const ref = useRef(null);
|
|
67
|
+
const [binaryFrameFilter, setBinaryFrameFilter] = useURLState({
|
|
68
|
+
param: 'binary_frame_filter',
|
|
69
|
+
navigateTo,
|
|
70
|
+
});
|
|
59
71
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
60
72
|
const { compareMode } = useProfileViewContext();
|
|
61
|
-
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
73
|
+
// const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
62
74
|
const currentColorProfile = useCurrentColorProfile();
|
|
63
75
|
const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
|
|
64
|
-
const
|
|
76
|
+
const mappingsList = useMemo(() => {
|
|
65
77
|
// Read the mappings from the dictionary that contains all mapping strings.
|
|
66
78
|
// This is great, as might only have a dozen or so mappings,
|
|
67
79
|
// and don't need to read through all the rows (potentially thousands).
|
|
@@ -82,46 +94,23 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
82
94
|
.flat() ?? [];
|
|
83
95
|
// We add a EVERYTHING ELSE mapping to the list.
|
|
84
96
|
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
|
-
});
|
|
102
97
|
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
103
98
|
mappings.sort((a, b) => a.localeCompare(b));
|
|
104
99
|
return mappings;
|
|
105
100
|
}, [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));
|
|
110
|
-
}, [mappings]);
|
|
111
|
-
// TODO: Unify with mappingFeatures
|
|
112
101
|
const mappingColors = useMemo(() => {
|
|
113
|
-
const colors =
|
|
114
|
-
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
115
|
-
colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
|
|
116
|
-
});
|
|
102
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
117
103
|
return colors;
|
|
118
|
-
}, [isDarkMode,
|
|
104
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
119
105
|
useEffect(() => {
|
|
120
106
|
if (ref.current != null) {
|
|
121
107
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
122
108
|
}
|
|
123
|
-
}, [width]);
|
|
109
|
+
}, [width, flamegraphLoading]);
|
|
124
110
|
const xScale = useMemo(() => {
|
|
111
|
+
if (total === 0n) {
|
|
112
|
+
return () => 0;
|
|
113
|
+
}
|
|
125
114
|
if (width === undefined) {
|
|
126
115
|
return () => 0;
|
|
127
116
|
}
|
|
@@ -139,33 +128,42 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
139
128
|
const trackVisibility = (isVisible) => {
|
|
140
129
|
setIsContextMenuOpen(isVisible);
|
|
141
130
|
};
|
|
131
|
+
const hideBinary = (binaryToRemove) => {
|
|
132
|
+
// second/subsequent time filtering out a binary i.e. a binary has already been hidden
|
|
133
|
+
// and we want to hide more binaries, we simply remove the binary from the binaryFrameFilter array in the URL.
|
|
134
|
+
if (Array.isArray(binaryFrameFilter) && binaryFrameFilter.length > 0) {
|
|
135
|
+
const newMappingsList = binaryFrameFilter.filter(mapping => mapping !== binaryToRemove);
|
|
136
|
+
setBinaryFrameFilter(newMappingsList);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// first time hiding a binary
|
|
140
|
+
const newMappingsList = mappingsList.filter(mapping => mapping !== binaryToRemove);
|
|
141
|
+
setBinaryFrameFilter(newMappingsList);
|
|
142
|
+
};
|
|
142
143
|
// useMemo for the root graph as it otherwise renders the whole graph if the hoveringRow changes.
|
|
143
144
|
const root = useMemo(() => {
|
|
144
145
|
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 }) }) }) }));
|
|
145
146
|
}, [
|
|
146
|
-
|
|
147
|
-
curPath,
|
|
148
|
-
currentSearchString,
|
|
147
|
+
width,
|
|
149
148
|
height,
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
displayMenu,
|
|
150
|
+
table,
|
|
152
151
|
mappingColors,
|
|
153
152
|
setCurPath,
|
|
154
|
-
|
|
155
|
-
table,
|
|
153
|
+
curPath,
|
|
156
154
|
total,
|
|
157
|
-
width,
|
|
158
155
|
xScale,
|
|
156
|
+
currentSearchString,
|
|
157
|
+
sortBy,
|
|
158
|
+
isDarkMode,
|
|
159
|
+
compareMode,
|
|
160
|
+
profileType,
|
|
159
161
|
isContextMenuOpen,
|
|
160
|
-
displayMenu,
|
|
161
|
-
colorForSimilarNodes,
|
|
162
|
-
highlightSimilarStacksPreference,
|
|
163
162
|
hoveringName,
|
|
164
163
|
hoveringRow,
|
|
164
|
+
colorForSimilarNodes,
|
|
165
|
+
highlightSimilarStacksPreference,
|
|
165
166
|
]);
|
|
166
|
-
|
|
167
|
-
return _jsx(_Fragment, {});
|
|
168
|
-
}
|
|
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] }) }));
|
|
167
|
+
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 }), 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
168
|
});
|
|
171
169
|
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,8 @@ interface ProfileIcicleGraphProps {
|
|
|
17
17
|
setActionButtons?: (buttons: React.JSX.Element) => void;
|
|
18
18
|
error?: any;
|
|
19
19
|
isHalfScreen: boolean;
|
|
20
|
+
mappings?: string[];
|
|
21
|
+
mappingsLoading?: boolean;
|
|
20
22
|
}
|
|
21
|
-
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
23
|
+
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, mappingsLoading, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
22
24
|
export default ProfileIcicleGraph;
|