@parca/profile 0.16.375 → 0.16.377
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/IcicleGraphArrow/ColorStackLegend.d.ts +4 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +13 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +6 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +51 -36
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.d.ts +2 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.js +33 -0
- package/dist/ProfileIcicleGraph/index.d.ts +1 -0
- package/dist/ProfileIcicleGraph/index.js +41 -20
- package/dist/ProfileView/index.js +2 -2
- package/dist/Table/index.js +2 -9
- package/package.json +2 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +19 -7
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +68 -53
- package/src/ProfileIcicleGraph/IcicleGraphArrow/useMappingList.ts +42 -0
- package/src/ProfileIcicleGraph/index.tsx +92 -54
- package/src/ProfileView/index.tsx +2 -1
- package/src/Table/index.tsx +1 -11
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.377](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.376...@parca/profile@0.16.377) (2024-05-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.376 (2024-05-30)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.375](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.374...@parca/profile@0.16.375) (2024-05-23)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -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
|
+
loading?: boolean;
|
|
6
6
|
navigateTo?: NavigateFunction;
|
|
7
7
|
compareMode?: boolean;
|
|
8
8
|
}
|
|
9
|
-
declare const ColorStackLegend: ({
|
|
9
|
+
declare const ColorStackLegend: ({ mappings, navigateTo, compareMode, loading, }: Props) => React.JSX.Element;
|
|
10
10
|
export default ColorStackLegend;
|
|
@@ -15,14 +15,23 @@ 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 { getMappingColors } from '.';
|
|
21
|
+
import useMappingList from './useMappingList';
|
|
22
|
+
const ColorStackLegend = ({ mappings, navigateTo, compareMode = false, loading, }) => {
|
|
23
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
24
|
+
const currentColorProfile = useCurrentColorProfile();
|
|
21
25
|
const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
22
26
|
const [currentSearchString, setSearchString] = useURLState({
|
|
23
27
|
param: 'binary_frame_filter',
|
|
24
28
|
navigateTo,
|
|
25
29
|
});
|
|
30
|
+
const mappingsList = useMappingList(mappings);
|
|
31
|
+
const mappingColors = useMemo(() => {
|
|
32
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
33
|
+
return colors;
|
|
34
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
26
35
|
const stackColorArray = useMemo(() => {
|
|
27
36
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
28
37
|
if (featureA === EVERYTHING_ELSE) {
|
|
@@ -34,7 +43,7 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
34
43
|
return featureA?.localeCompare(featureB ?? '') ?? 0;
|
|
35
44
|
});
|
|
36
45
|
}, [mappingColors]);
|
|
37
|
-
if (
|
|
46
|
+
if (stackColorArray.length === 0 && loading === false) {
|
|
38
47
|
return _jsx(_Fragment, {});
|
|
39
48
|
}
|
|
40
49
|
if (Object.entries(mappingColors).length === 0) {
|
|
@@ -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,7 +29,10 @@ interface IcicleGraphArrowProps {
|
|
|
28
29
|
setCurPath: (path: string[]) => void;
|
|
29
30
|
navigateTo?: NavigateFunction;
|
|
30
31
|
sortBy: string;
|
|
31
|
-
|
|
32
|
+
flamegraphLoading: boolean;
|
|
33
|
+
isHalfScreen: boolean;
|
|
34
|
+
mappingsListFromMetadata: string[];
|
|
32
35
|
}
|
|
36
|
+
export declare const getMappingColors: (mappingsList: string[], isDarkMode: boolean, currentColorProfile: ColorConfig) => mappingColors;
|
|
33
37
|
export declare const IcicleGraphArrow: React.NamedExoticComponent<IcicleGraphArrowProps>;
|
|
34
38
|
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.
|
|
@@ -17,15 +17,14 @@ import { useContextMenu } from 'react-contexify';
|
|
|
17
17
|
import { useURLState } from '@parca/components';
|
|
18
18
|
import { USER_PREFERENCES, useCurrentColorProfile, useUserPreference } from '@parca/hooks';
|
|
19
19
|
import { getColorForFeature, selectDarkMode, setHoveringNode, useAppDispatch, useAppSelector, } from '@parca/store';
|
|
20
|
-
import { getLastItem, scaleLinear, selectQueryParam } from '@parca/utilities';
|
|
20
|
+
import { getLastItem, scaleLinear, selectQueryParam, } from '@parca/utilities';
|
|
21
21
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
22
22
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
23
23
|
import { DockedGraphTooltip } from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
24
24
|
import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
|
|
25
|
-
import ColorStackLegend from './ColorStackLegend';
|
|
26
25
|
import ContextMenu from './ContextMenu';
|
|
27
26
|
import { IcicleNode, RowHeight } from './IcicleGraphNodes';
|
|
28
|
-
import { extractFeature } from './utils';
|
|
27
|
+
import { arrowToString, extractFeature } from './utils';
|
|
29
28
|
export const FIELD_LABELS_ONLY = 'labels_only';
|
|
30
29
|
export const FIELD_MAPPING_FILE = 'mapping_file';
|
|
31
30
|
export const FIELD_MAPPING_BUILD_ID = 'mapping_build_id';
|
|
@@ -42,7 +41,15 @@ export const FIELD_CUMULATIVE = 'cumulative';
|
|
|
42
41
|
export const FIELD_CUMULATIVE_PER_SECOND = 'cumulative_per_second';
|
|
43
42
|
export const FIELD_DIFF = 'diff';
|
|
44
43
|
export const FIELD_DIFF_PER_SECOND = 'diff_per_second';
|
|
45
|
-
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, mappingsListFromMetadata, }) {
|
|
46
53
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
|
47
54
|
const dispatch = useAppDispatch();
|
|
48
55
|
const [highlightSimilarStacksPreference] = useUserPreference(USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key);
|
|
@@ -63,35 +70,46 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
63
70
|
});
|
|
64
71
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
65
72
|
const { compareMode } = useProfileViewContext();
|
|
66
|
-
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
67
73
|
const currentColorProfile = useCurrentColorProfile();
|
|
68
74
|
const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
|
|
69
75
|
const mappingsList = useMemo(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
// Read the mappings from the dictionary that contains all mapping strings.
|
|
77
|
+
// This is great, as might only have a dozen or so mappings,
|
|
78
|
+
// and don't need to read through all the rows (potentially thousands).
|
|
79
|
+
const mappingsDict = table.getChild(FIELD_MAPPING_FILE);
|
|
80
|
+
const mappings = mappingsDict?.data
|
|
81
|
+
.map(mapping => {
|
|
82
|
+
if (mapping.dictionary == null) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const len = mapping.dictionary.length;
|
|
86
|
+
const entries = [];
|
|
87
|
+
for (let i = 0; i < len; i++) {
|
|
88
|
+
const fn = arrowToString(mapping.dictionary.get(i));
|
|
89
|
+
entries.push(getLastItem(fn) ?? '');
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
73
92
|
})
|
|
74
93
|
.flat() ?? [];
|
|
75
94
|
// We add a EVERYTHING ELSE mapping to the list.
|
|
76
|
-
|
|
95
|
+
mappings.push('');
|
|
77
96
|
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
78
|
-
|
|
79
|
-
return
|
|
80
|
-
}, [
|
|
97
|
+
mappings.sort((a, b) => a.localeCompare(b));
|
|
98
|
+
return mappings;
|
|
99
|
+
}, [table]);
|
|
81
100
|
const mappingColors = useMemo(() => {
|
|
82
|
-
const
|
|
83
|
-
const colors = {};
|
|
84
|
-
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
85
|
-
colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
|
|
86
|
-
});
|
|
101
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
87
102
|
return colors;
|
|
88
|
-
}, [
|
|
103
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
89
104
|
useEffect(() => {
|
|
90
105
|
if (ref.current != null) {
|
|
91
106
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
92
107
|
}
|
|
93
|
-
}, [width]);
|
|
108
|
+
}, [width, flamegraphLoading]);
|
|
94
109
|
const xScale = useMemo(() => {
|
|
110
|
+
if (total === 0n) {
|
|
111
|
+
return () => 0;
|
|
112
|
+
}
|
|
95
113
|
if (width === undefined) {
|
|
96
114
|
return () => 0;
|
|
97
115
|
}
|
|
@@ -118,36 +136,33 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
118
136
|
return;
|
|
119
137
|
}
|
|
120
138
|
// first time hiding a binary
|
|
121
|
-
const newMappingsList =
|
|
139
|
+
const newMappingsList = mappingsListFromMetadata.filter(mapping => mapping !== binaryToRemove);
|
|
122
140
|
setBinaryFrameFilter(newMappingsList);
|
|
123
141
|
};
|
|
124
142
|
// useMemo for the root graph as it otherwise renders the whole graph if the hoveringRow changes.
|
|
125
143
|
const root = useMemo(() => {
|
|
126
144
|
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 }) }) }) }));
|
|
127
145
|
}, [
|
|
128
|
-
|
|
129
|
-
curPath,
|
|
130
|
-
currentSearchString,
|
|
146
|
+
width,
|
|
131
147
|
height,
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
displayMenu,
|
|
149
|
+
table,
|
|
134
150
|
mappingColors,
|
|
135
151
|
setCurPath,
|
|
136
|
-
|
|
137
|
-
table,
|
|
152
|
+
curPath,
|
|
138
153
|
total,
|
|
139
|
-
width,
|
|
140
154
|
xScale,
|
|
155
|
+
currentSearchString,
|
|
156
|
+
sortBy,
|
|
157
|
+
isDarkMode,
|
|
158
|
+
compareMode,
|
|
159
|
+
profileType,
|
|
141
160
|
isContextMenuOpen,
|
|
142
|
-
displayMenu,
|
|
143
|
-
colorForSimilarNodes,
|
|
144
|
-
highlightSimilarStacksPreference,
|
|
145
161
|
hoveringName,
|
|
146
162
|
hoveringRow,
|
|
163
|
+
colorForSimilarNodes,
|
|
164
|
+
highlightSimilarStacksPreference,
|
|
147
165
|
]);
|
|
148
|
-
|
|
149
|
-
return _jsx(_Fragment, {});
|
|
150
|
-
}
|
|
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] }) }));
|
|
166
|
+
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] }) }));
|
|
152
167
|
});
|
|
153
168
|
export default IcicleGraphArrow;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { getLastItem } from '@parca/utilities';
|
|
15
|
+
const useMappingList = (mappings) => {
|
|
16
|
+
const mappingsList = useMemo(() => {
|
|
17
|
+
if (mappings === undefined) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const list = mappings
|
|
21
|
+
?.map(mapping => {
|
|
22
|
+
return getLastItem(mapping);
|
|
23
|
+
})
|
|
24
|
+
.flat() ?? [];
|
|
25
|
+
// We add a EVERYTHING ELSE mapping to the list.
|
|
26
|
+
list.push('');
|
|
27
|
+
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
28
|
+
list.sort((a, b) => a.localeCompare(b));
|
|
29
|
+
return list;
|
|
30
|
+
}, [mappings]);
|
|
31
|
+
return mappingsList;
|
|
32
|
+
};
|
|
33
|
+
export default useMappingList;
|
|
@@ -18,6 +18,7 @@ interface ProfileIcicleGraphProps {
|
|
|
18
18
|
error?: any;
|
|
19
19
|
isHalfScreen: boolean;
|
|
20
20
|
mappings?: string[];
|
|
21
|
+
mappingsLoading?: boolean;
|
|
21
22
|
}
|
|
22
23
|
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
23
24
|
export default ProfileIcicleGraph;
|
|
@@ -14,15 +14,17 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
14
14
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
|
-
import { Button,
|
|
17
|
+
import { Button, IcicleGraphSkeleton, IconButton, useParcaContext, useURLState, } from '@parca/components';
|
|
18
18
|
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
19
|
-
import { capitalizeOnlyFirstLetter, divide } from '@parca/utilities';
|
|
19
|
+
import { capitalizeOnlyFirstLetter, divide, selectQueryParam, } 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
23
|
import SortBySelect from './ActionButtons/SortBySelect';
|
|
24
24
|
import IcicleGraph from './IcicleGraph';
|
|
25
25
|
import IcicleGraphArrow, { FIELD_FUNCTION_NAME } from './IcicleGraphArrow';
|
|
26
|
+
import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
|
|
27
|
+
import useMappingList from './IcicleGraphArrow/useMappingList';
|
|
26
28
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
27
29
|
const ErrorContent = ({ errorMessage }) => {
|
|
28
30
|
return _jsx("div", { className: "flex justify-center p-10", children: errorMessage });
|
|
@@ -80,6 +82,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
80
82
|
const { onError, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
81
83
|
const { compareMode } = useProfileViewContext();
|
|
82
84
|
const [isLoading, setIsLoading] = useState(true);
|
|
85
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
86
|
+
const mappingsList = useMappingList(mappings);
|
|
83
87
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
84
88
|
param: 'sort_by',
|
|
85
89
|
navigateTo,
|
|
@@ -108,14 +112,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
108
112
|
];
|
|
109
113
|
}, [graph, arrow, filtered, total]);
|
|
110
114
|
useEffect(() => {
|
|
111
|
-
|
|
112
|
-
setActionButtons(_jsx(IcicleActionButtonPlaceholder, { isHalfScreen: isHalfScreen }));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (setActionButtons === undefined) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
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 })] }))] }) }));
|
|
115
|
+
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: [_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 })] }))] }) }));
|
|
119
116
|
}, [
|
|
120
117
|
navigateTo,
|
|
121
118
|
isInvert,
|
|
@@ -128,17 +125,15 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
128
125
|
isHalfScreen,
|
|
129
126
|
isLoading,
|
|
130
127
|
]);
|
|
128
|
+
const loadingState = !loading && (arrow !== undefined || graph !== undefined) && mappings !== undefined;
|
|
131
129
|
useEffect(() => {
|
|
132
|
-
if (
|
|
130
|
+
if (loadingState) {
|
|
133
131
|
setIsLoading(false);
|
|
134
132
|
}
|
|
135
133
|
else {
|
|
136
134
|
setIsLoading(true);
|
|
137
135
|
}
|
|
138
|
-
}, [
|
|
139
|
-
if (isLoading) {
|
|
140
|
-
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
141
|
-
}
|
|
136
|
+
}, [loadingState]);
|
|
142
137
|
if (error != null) {
|
|
143
138
|
onError?.(error);
|
|
144
139
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
@@ -146,13 +141,39 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
146
141
|
}
|
|
147
142
|
return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
|
|
148
143
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
145
|
+
const icicleGraph = useMemo(() => {
|
|
146
|
+
if (isLoading) {
|
|
147
|
+
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
148
|
+
}
|
|
149
|
+
if (graph === undefined && arrow === undefined)
|
|
150
|
+
return _jsx("div", { className: "mx-auto text-center", children: "No data..." });
|
|
151
|
+
if (total === 0n && !loading)
|
|
152
|
+
return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
|
|
153
|
+
if (graph !== undefined)
|
|
154
|
+
return (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo }));
|
|
155
|
+
if (arrow !== undefined)
|
|
156
|
+
return (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo, sortBy: storeSortBy, flamegraphLoading: isLoading, isHalfScreen: isHalfScreen, mappingsListFromMetadata: mappingsList }));
|
|
157
|
+
}, [
|
|
158
|
+
isLoading,
|
|
159
|
+
graph,
|
|
160
|
+
arrow,
|
|
161
|
+
total,
|
|
162
|
+
loading,
|
|
163
|
+
width,
|
|
164
|
+
filtered,
|
|
165
|
+
curPath,
|
|
166
|
+
setNewCurPath,
|
|
167
|
+
profileType,
|
|
168
|
+
navigateTo,
|
|
169
|
+
storeSortBy,
|
|
170
|
+
isHalfScreen,
|
|
171
|
+
isDarkMode,
|
|
172
|
+
mappingsList,
|
|
173
|
+
]);
|
|
153
174
|
if (isTrimmed) {
|
|
154
175
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
155
176
|
}
|
|
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,
|
|
177
|
+
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, isColorStackLegendEnabled && (_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode, mappings: mappings, loading: isLoading })), _jsx("div", { className: "min-h-48", id: "h-icicle-graph", children: _jsx(_Fragment, { children: icicleGraph }) }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
|
|
157
178
|
};
|
|
158
179
|
export default ProfileIcicleGraph;
|
|
@@ -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
|
|
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
|
|
111
111
|
? isHalfScreen
|
|
112
112
|
? (dimensions.width - 40) / 2
|
|
113
113
|
: dimensions.width - 16
|
|
114
|
-
: 0, mappings: flamegraphData.mappings }) }));
|
|
114
|
+
: 0, mappings: flamegraphData.mappings, mappingsLoading: flamegraphData.mappingsLoading }) }));
|
|
115
115
|
}
|
|
116
116
|
case 'callgraph': {
|
|
117
117
|
return callgraphData?.data !== undefined &&
|
package/dist/Table/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
14
14
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { tableFromIPC } from 'apache-arrow';
|
|
16
16
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
|
-
import { Button,
|
|
17
|
+
import { Button, Table as TableComponent, TableSkeleton, useParcaContext, useURLState, } from '@parca/components';
|
|
18
18
|
import { getLastItem, isSearchMatch, parseParams, valueFormatter, } from '@parca/utilities';
|
|
19
19
|
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
20
20
|
import { hexifyAddress } from '../utils';
|
|
@@ -214,14 +214,7 @@ export const Table = React.memo(function Table({ data, total, filtered, profileT
|
|
|
214
214
|
}
|
|
215
215
|
}, [navigateTo, router, filterByFunctionInput]);
|
|
216
216
|
useEffect(() => {
|
|
217
|
-
|
|
218
|
-
setActionButtons(_jsx(TableActionButtonPlaceholder, {}));
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (setActionButtons === undefined) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
setActionButtons(_jsxs(_Fragment, { children: [_jsx(ColumnsVisibility, { columns: columns, visibility: columnVisibility, setVisibility: (id, visible) => {
|
|
217
|
+
setActionButtons?.(_jsxs(_Fragment, { children: [_jsx(ColumnsVisibility, { columns: columns, visibility: columnVisibility, setVisibility: (id, visible) => {
|
|
225
218
|
setColumnVisibility({ ...columnVisibility, [id]: visible });
|
|
226
219
|
} }), dashboardItems.length > 1 && (_jsx(Button, { color: "neutral", onClick: clearSelection, className: "w-auto", variant: "neutral", disabled: currentSearchString === undefined || currentSearchString.length === 0, children: "Clear selection" }))] }));
|
|
227
220
|
}, [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.377",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@headlessui/react": "^1.7.19",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"access": "public",
|
|
72
72
|
"registry": "https://registry.npmjs.org/"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "7f48ce8d53a8f749508d0b002c04bf1257c4bbde"
|
|
75
75
|
}
|
|
@@ -17,23 +17,28 @@ import {Icon} from '@iconify/react';
|
|
|
17
17
|
import cx from 'classnames';
|
|
18
18
|
|
|
19
19
|
import {useURLState} from '@parca/components';
|
|
20
|
-
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
21
|
-
import {EVERYTHING_ELSE} from '@parca/store';
|
|
22
|
-
import type
|
|
20
|
+
import {USER_PREFERENCES, useCurrentColorProfile, useUserPreference} from '@parca/hooks';
|
|
21
|
+
import {EVERYTHING_ELSE, selectDarkMode, useAppSelector} from '@parca/store';
|
|
22
|
+
import {type NavigateFunction} from '@parca/utilities';
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {getMappingColors} from '.';
|
|
25
|
+
import useMappingList from './useMappingList';
|
|
25
26
|
|
|
26
27
|
interface Props {
|
|
27
|
-
|
|
28
|
+
mappings?: string[];
|
|
29
|
+
loading?: boolean;
|
|
28
30
|
navigateTo?: NavigateFunction;
|
|
29
31
|
compareMode?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const ColorStackLegend = ({
|
|
33
|
-
|
|
35
|
+
mappings,
|
|
34
36
|
navigateTo,
|
|
35
37
|
compareMode = false,
|
|
38
|
+
loading,
|
|
36
39
|
}: Props): React.JSX.Element => {
|
|
40
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
41
|
+
const currentColorProfile = useCurrentColorProfile();
|
|
37
42
|
const [colorProfileName] = useUserPreference<string>(
|
|
38
43
|
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
39
44
|
);
|
|
@@ -42,6 +47,13 @@ const ColorStackLegend = ({
|
|
|
42
47
|
navigateTo,
|
|
43
48
|
});
|
|
44
49
|
|
|
50
|
+
const mappingsList = useMappingList(mappings);
|
|
51
|
+
|
|
52
|
+
const mappingColors = useMemo(() => {
|
|
53
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
54
|
+
return colors;
|
|
55
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
56
|
+
|
|
45
57
|
const stackColorArray = useMemo(() => {
|
|
46
58
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
47
59
|
if (featureA === EVERYTHING_ELSE) {
|
|
@@ -54,7 +66,7 @@ const ColorStackLegend = ({
|
|
|
54
66
|
});
|
|
55
67
|
}, [mappingColors]);
|
|
56
68
|
|
|
57
|
-
if (
|
|
69
|
+
if (stackColorArray.length === 0 && loading === false) {
|
|
58
70
|
return <></>;
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
|
-
import {Table, tableFromIPC} from 'apache-arrow';
|
|
16
|
+
import {Dictionary, Table, Vector, tableFromIPC} from 'apache-arrow';
|
|
17
17
|
import {useContextMenu} from 'react-contexify';
|
|
18
18
|
|
|
19
19
|
import {FlamegraphArrow} from '@parca/client';
|
|
@@ -27,16 +27,21 @@ import {
|
|
|
27
27
|
useAppDispatch,
|
|
28
28
|
useAppSelector,
|
|
29
29
|
} from '@parca/store';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
getLastItem,
|
|
32
|
+
scaleLinear,
|
|
33
|
+
selectQueryParam,
|
|
34
|
+
type ColorConfig,
|
|
35
|
+
type NavigateFunction,
|
|
36
|
+
} from '@parca/utilities';
|
|
31
37
|
|
|
32
38
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
33
39
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
34
40
|
import {DockedGraphTooltip} from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
35
41
|
import {useProfileViewContext} from '../../ProfileView/ProfileViewContext';
|
|
36
|
-
import ColorStackLegend from './ColorStackLegend';
|
|
37
42
|
import ContextMenu from './ContextMenu';
|
|
38
43
|
import {IcicleNode, RowHeight, mappingColors} from './IcicleGraphNodes';
|
|
39
|
-
import {extractFeature} from './utils';
|
|
44
|
+
import {arrowToString, extractFeature} from './utils';
|
|
40
45
|
|
|
41
46
|
export const FIELD_LABELS_ONLY = 'labels_only';
|
|
42
47
|
export const FIELD_MAPPING_FILE = 'mapping_file';
|
|
@@ -65,9 +70,25 @@ interface IcicleGraphArrowProps {
|
|
|
65
70
|
setCurPath: (path: string[]) => void;
|
|
66
71
|
navigateTo?: NavigateFunction;
|
|
67
72
|
sortBy: string;
|
|
68
|
-
|
|
73
|
+
flamegraphLoading: boolean;
|
|
74
|
+
isHalfScreen: boolean;
|
|
75
|
+
mappingsListFromMetadata: string[];
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
export const getMappingColors = (
|
|
79
|
+
mappingsList: string[],
|
|
80
|
+
isDarkMode: boolean,
|
|
81
|
+
currentColorProfile: ColorConfig
|
|
82
|
+
): mappingColors => {
|
|
83
|
+
const mappingFeatures = mappingsList.map(mapping => extractFeature(mapping));
|
|
84
|
+
|
|
85
|
+
const colors: mappingColors = {};
|
|
86
|
+
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
87
|
+
colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
|
|
88
|
+
});
|
|
89
|
+
return colors;
|
|
90
|
+
};
|
|
91
|
+
|
|
71
92
|
export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
72
93
|
arrow,
|
|
73
94
|
total,
|
|
@@ -78,7 +99,8 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
78
99
|
profileType,
|
|
79
100
|
navigateTo,
|
|
80
101
|
sortBy,
|
|
81
|
-
|
|
102
|
+
flamegraphLoading,
|
|
103
|
+
mappingsListFromMetadata,
|
|
82
104
|
}: IcicleGraphArrowProps): React.JSX.Element {
|
|
83
105
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState<boolean>(false);
|
|
84
106
|
const dispatch = useAppDispatch();
|
|
@@ -106,50 +128,54 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
106
128
|
|
|
107
129
|
const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
108
130
|
const {compareMode} = useProfileViewContext();
|
|
109
|
-
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
110
131
|
const currentColorProfile = useCurrentColorProfile();
|
|
111
132
|
const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
|
|
112
133
|
|
|
113
134
|
const mappingsList = useMemo(() => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
// Read the mappings from the dictionary that contains all mapping strings.
|
|
136
|
+
// This is great, as might only have a dozen or so mappings,
|
|
137
|
+
// and don't need to read through all the rows (potentially thousands).
|
|
138
|
+
const mappingsDict: Vector<Dictionary> | null = table.getChild(FIELD_MAPPING_FILE);
|
|
139
|
+
const mappings =
|
|
140
|
+
mappingsDict?.data
|
|
141
|
+
.map(mapping => {
|
|
142
|
+
if (mapping.dictionary == null) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const len = mapping.dictionary.length;
|
|
146
|
+
const entries: string[] = [];
|
|
147
|
+
for (let i = 0; i < len; i++) {
|
|
148
|
+
const fn = arrowToString(mapping.dictionary.get(i));
|
|
149
|
+
entries.push(getLastItem(fn) ?? '');
|
|
150
|
+
}
|
|
151
|
+
return entries;
|
|
118
152
|
})
|
|
119
153
|
.flat() ?? [];
|
|
120
154
|
|
|
121
155
|
// We add a EVERYTHING ELSE mapping to the list.
|
|
122
|
-
|
|
156
|
+
mappings.push('');
|
|
123
157
|
|
|
124
158
|
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, [mappings]);
|
|
159
|
+
mappings.sort((a, b) => a.localeCompare(b));
|
|
160
|
+
return mappings;
|
|
161
|
+
}, [table]);
|
|
129
162
|
|
|
130
163
|
const mappingColors = useMemo(() => {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const colors: mappingColors = {};
|
|
134
|
-
|
|
135
|
-
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
136
|
-
colors[feature.name] = getColorForFeature(
|
|
137
|
-
feature.name,
|
|
138
|
-
isDarkMode,
|
|
139
|
-
currentColorProfile.colors
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
164
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
143
165
|
return colors;
|
|
144
|
-
}, [
|
|
166
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
145
167
|
|
|
146
168
|
useEffect(() => {
|
|
147
169
|
if (ref.current != null) {
|
|
148
170
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
149
171
|
}
|
|
150
|
-
}, [width]);
|
|
172
|
+
}, [width, flamegraphLoading]);
|
|
151
173
|
|
|
152
174
|
const xScale = useMemo(() => {
|
|
175
|
+
if (total === 0n) {
|
|
176
|
+
return () => 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
if (width === undefined) {
|
|
154
180
|
return () => 0;
|
|
155
181
|
}
|
|
@@ -184,7 +210,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
// first time hiding a binary
|
|
187
|
-
const newMappingsList =
|
|
213
|
+
const newMappingsList = mappingsListFromMetadata.filter(mapping => mapping !== binaryToRemove);
|
|
188
214
|
setBinaryFrameFilter(newMappingsList);
|
|
189
215
|
};
|
|
190
216
|
|
|
@@ -235,31 +261,27 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
235
261
|
</svg>
|
|
236
262
|
);
|
|
237
263
|
}, [
|
|
238
|
-
|
|
239
|
-
curPath,
|
|
240
|
-
currentSearchString,
|
|
264
|
+
width,
|
|
241
265
|
height,
|
|
242
|
-
|
|
243
|
-
|
|
266
|
+
displayMenu,
|
|
267
|
+
table,
|
|
244
268
|
mappingColors,
|
|
245
269
|
setCurPath,
|
|
246
|
-
|
|
247
|
-
table,
|
|
270
|
+
curPath,
|
|
248
271
|
total,
|
|
249
|
-
width,
|
|
250
272
|
xScale,
|
|
273
|
+
currentSearchString,
|
|
274
|
+
sortBy,
|
|
275
|
+
isDarkMode,
|
|
276
|
+
compareMode,
|
|
277
|
+
profileType,
|
|
251
278
|
isContextMenuOpen,
|
|
252
|
-
displayMenu,
|
|
253
|
-
colorForSimilarNodes,
|
|
254
|
-
highlightSimilarStacksPreference,
|
|
255
279
|
hoveringName,
|
|
256
280
|
hoveringRow,
|
|
281
|
+
colorForSimilarNodes,
|
|
282
|
+
highlightSimilarStacksPreference,
|
|
257
283
|
]);
|
|
258
284
|
|
|
259
|
-
if (table.numRows === 0 || width === undefined) {
|
|
260
|
-
return <></>;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
285
|
return (
|
|
264
286
|
<>
|
|
265
287
|
<div onMouseLeave={() => dispatch(setHoveringNode(undefined))}>
|
|
@@ -278,13 +300,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
278
300
|
hideMenu={hideAll}
|
|
279
301
|
hideBinary={hideBinary}
|
|
280
302
|
/>
|
|
281
|
-
{isColorStackLegendEnabled && (
|
|
282
|
-
<ColorStackLegend
|
|
283
|
-
mappingColors={mappingColors}
|
|
284
|
-
navigateTo={navigateTo}
|
|
285
|
-
compareMode={compareMode}
|
|
286
|
-
/>
|
|
287
|
-
)}
|
|
288
303
|
{dockedMetainfo ? (
|
|
289
304
|
<DockedGraphTooltip
|
|
290
305
|
table={table}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useMemo} from 'react';
|
|
15
|
+
|
|
16
|
+
import {getLastItem} from '@parca/utilities';
|
|
17
|
+
|
|
18
|
+
const useMappingList = (mappings: string[] | undefined): string[] => {
|
|
19
|
+
const mappingsList = useMemo(() => {
|
|
20
|
+
if (mappings === undefined) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const list =
|
|
24
|
+
mappings
|
|
25
|
+
?.map(mapping => {
|
|
26
|
+
return getLastItem(mapping) as string;
|
|
27
|
+
})
|
|
28
|
+
.flat() ?? [];
|
|
29
|
+
|
|
30
|
+
// We add a EVERYTHING ELSE mapping to the list.
|
|
31
|
+
list.push('');
|
|
32
|
+
|
|
33
|
+
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
34
|
+
list.sort((a, b) => a.localeCompare(b));
|
|
35
|
+
|
|
36
|
+
return list;
|
|
37
|
+
}, [mappings]);
|
|
38
|
+
|
|
39
|
+
return mappingsList;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default useMappingList;
|
|
@@ -19,7 +19,6 @@ import {AnimatePresence, motion} from 'framer-motion';
|
|
|
19
19
|
import {Flamegraph, FlamegraphArrow} from '@parca/client';
|
|
20
20
|
import {
|
|
21
21
|
Button,
|
|
22
|
-
IcicleActionButtonPlaceholder,
|
|
23
22
|
IcicleGraphSkeleton,
|
|
24
23
|
IconButton,
|
|
25
24
|
useParcaContext,
|
|
@@ -27,7 +26,12 @@ import {
|
|
|
27
26
|
} from '@parca/components';
|
|
28
27
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
29
28
|
import {ProfileType} from '@parca/parser';
|
|
30
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
capitalizeOnlyFirstLetter,
|
|
31
|
+
divide,
|
|
32
|
+
selectQueryParam,
|
|
33
|
+
type NavigateFunction,
|
|
34
|
+
} from '@parca/utilities';
|
|
31
35
|
|
|
32
36
|
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
33
37
|
import DiffLegend from '../components/DiffLegend';
|
|
@@ -35,6 +39,8 @@ import GroupByDropdown from './ActionButtons/GroupByDropdown';
|
|
|
35
39
|
import SortBySelect from './ActionButtons/SortBySelect';
|
|
36
40
|
import IcicleGraph from './IcicleGraph';
|
|
37
41
|
import IcicleGraphArrow, {FIELD_FUNCTION_NAME} from './IcicleGraphArrow';
|
|
42
|
+
import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
|
|
43
|
+
import useMappingList from './IcicleGraphArrow/useMappingList';
|
|
38
44
|
|
|
39
45
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
40
46
|
|
|
@@ -55,6 +61,7 @@ interface ProfileIcicleGraphProps {
|
|
|
55
61
|
error?: any;
|
|
56
62
|
isHalfScreen: boolean;
|
|
57
63
|
mappings?: string[];
|
|
64
|
+
mappingsLoading?: boolean;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
|
|
@@ -218,6 +225,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
218
225
|
const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
|
|
219
226
|
const {compareMode} = useProfileViewContext();
|
|
220
227
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
228
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
229
|
+
|
|
230
|
+
const mappingsList = useMappingList(mappings);
|
|
221
231
|
|
|
222
232
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
223
233
|
param: 'sort_by',
|
|
@@ -262,20 +272,11 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
262
272
|
}, [graph, arrow, filtered, total]);
|
|
263
273
|
|
|
264
274
|
useEffect(() => {
|
|
265
|
-
|
|
266
|
-
setActionButtons(<IcicleActionButtonPlaceholder isHalfScreen={isHalfScreen} />);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (setActionButtons === undefined) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
setActionButtons(
|
|
275
|
+
setActionButtons?.(
|
|
275
276
|
<div className="flex w-full justify-end gap-2 pb-2">
|
|
276
277
|
<div className="ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end">
|
|
277
|
-
{
|
|
278
|
-
{
|
|
278
|
+
{<GroupAndSortActionButtons navigateTo={navigateTo} />}
|
|
279
|
+
{isHalfScreen ? (
|
|
279
280
|
<IconButton
|
|
280
281
|
icon={isInvert ? 'ph:sort-ascending' : 'ph:sort-descending'}
|
|
281
282
|
toolTipText={isInvert ? 'Original Call Stack' : 'Invert Call Stack'}
|
|
@@ -328,21 +329,16 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
328
329
|
isLoading,
|
|
329
330
|
]);
|
|
330
331
|
|
|
332
|
+
const loadingState =
|
|
333
|
+
!loading && (arrow !== undefined || graph !== undefined) && mappings !== undefined;
|
|
334
|
+
|
|
331
335
|
useEffect(() => {
|
|
332
|
-
if (
|
|
336
|
+
if (loadingState) {
|
|
333
337
|
setIsLoading(false);
|
|
334
338
|
} else {
|
|
335
339
|
setIsLoading(true);
|
|
336
340
|
}
|
|
337
|
-
}, [
|
|
338
|
-
|
|
339
|
-
if (isLoading) {
|
|
340
|
-
return (
|
|
341
|
-
<div className="h-auto overflow-clip">
|
|
342
|
-
<IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
343
|
-
</div>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
341
|
+
}, [loadingState]);
|
|
346
342
|
|
|
347
343
|
if (error != null) {
|
|
348
344
|
onError?.(error);
|
|
@@ -354,11 +350,70 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
354
350
|
return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(error.message)} />;
|
|
355
351
|
}
|
|
356
352
|
|
|
357
|
-
|
|
358
|
-
|
|
353
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
354
|
+
const icicleGraph = useMemo(() => {
|
|
355
|
+
if (isLoading) {
|
|
356
|
+
return (
|
|
357
|
+
<div className="h-auto overflow-clip">
|
|
358
|
+
<IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
359
362
|
|
|
360
|
-
|
|
361
|
-
|
|
363
|
+
if (graph === undefined && arrow === undefined)
|
|
364
|
+
return <div className="mx-auto text-center">No data...</div>;
|
|
365
|
+
|
|
366
|
+
if (total === 0n && !loading)
|
|
367
|
+
return <div className="mx-auto text-center">Profile has no samples</div>;
|
|
368
|
+
|
|
369
|
+
if (graph !== undefined)
|
|
370
|
+
return (
|
|
371
|
+
<IcicleGraph
|
|
372
|
+
width={width}
|
|
373
|
+
graph={graph}
|
|
374
|
+
total={total}
|
|
375
|
+
filtered={filtered}
|
|
376
|
+
curPath={curPath}
|
|
377
|
+
setCurPath={setNewCurPath}
|
|
378
|
+
profileType={profileType}
|
|
379
|
+
navigateTo={navigateTo}
|
|
380
|
+
/>
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (arrow !== undefined)
|
|
384
|
+
return (
|
|
385
|
+
<IcicleGraphArrow
|
|
386
|
+
width={width}
|
|
387
|
+
arrow={arrow}
|
|
388
|
+
total={total}
|
|
389
|
+
filtered={filtered}
|
|
390
|
+
curPath={curPath}
|
|
391
|
+
setCurPath={setNewCurPath}
|
|
392
|
+
profileType={profileType}
|
|
393
|
+
navigateTo={navigateTo}
|
|
394
|
+
sortBy={storeSortBy as string}
|
|
395
|
+
flamegraphLoading={isLoading}
|
|
396
|
+
isHalfScreen={isHalfScreen}
|
|
397
|
+
mappingsListFromMetadata={mappingsList}
|
|
398
|
+
/>
|
|
399
|
+
);
|
|
400
|
+
}, [
|
|
401
|
+
isLoading,
|
|
402
|
+
graph,
|
|
403
|
+
arrow,
|
|
404
|
+
total,
|
|
405
|
+
loading,
|
|
406
|
+
width,
|
|
407
|
+
filtered,
|
|
408
|
+
curPath,
|
|
409
|
+
setNewCurPath,
|
|
410
|
+
profileType,
|
|
411
|
+
navigateTo,
|
|
412
|
+
storeSortBy,
|
|
413
|
+
isHalfScreen,
|
|
414
|
+
isDarkMode,
|
|
415
|
+
mappingsList,
|
|
416
|
+
]);
|
|
362
417
|
|
|
363
418
|
if (isTrimmed) {
|
|
364
419
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
@@ -374,33 +429,16 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
374
429
|
transition={{duration: 0.5}}
|
|
375
430
|
>
|
|
376
431
|
{compareMode ? <DiffLegend /> : null}
|
|
432
|
+
{isColorStackLegendEnabled && (
|
|
433
|
+
<ColorStackLegend
|
|
434
|
+
navigateTo={navigateTo}
|
|
435
|
+
compareMode={compareMode}
|
|
436
|
+
mappings={mappings}
|
|
437
|
+
loading={isLoading}
|
|
438
|
+
/>
|
|
439
|
+
)}
|
|
377
440
|
<div className="min-h-48" id="h-icicle-graph">
|
|
378
|
-
{
|
|
379
|
-
<IcicleGraph
|
|
380
|
-
width={width}
|
|
381
|
-
graph={graph}
|
|
382
|
-
total={total}
|
|
383
|
-
filtered={filtered}
|
|
384
|
-
curPath={curPath}
|
|
385
|
-
setCurPath={setNewCurPath}
|
|
386
|
-
profileType={profileType}
|
|
387
|
-
navigateTo={navigateTo}
|
|
388
|
-
/>
|
|
389
|
-
)}
|
|
390
|
-
{arrow !== undefined && (
|
|
391
|
-
<IcicleGraphArrow
|
|
392
|
-
width={width}
|
|
393
|
-
arrow={arrow}
|
|
394
|
-
total={total}
|
|
395
|
-
filtered={filtered}
|
|
396
|
-
curPath={curPath}
|
|
397
|
-
setCurPath={setNewCurPath}
|
|
398
|
-
profileType={profileType}
|
|
399
|
-
navigateTo={navigateTo}
|
|
400
|
-
sortBy={storeSortBy as string}
|
|
401
|
-
mappings={mappings}
|
|
402
|
-
/>
|
|
403
|
-
)}
|
|
441
|
+
<>{icicleGraph}</>
|
|
404
442
|
</div>
|
|
405
443
|
<p className="my-2 text-xs">
|
|
406
444
|
Showing {totalFormatted}{' '}
|
|
@@ -240,7 +240,7 @@ export const ProfileView = ({
|
|
|
240
240
|
filtered={filtered}
|
|
241
241
|
profileType={profileSource?.ProfileType()}
|
|
242
242
|
navigateTo={navigateTo}
|
|
243
|
-
loading={flamegraphData.loading
|
|
243
|
+
loading={flamegraphData.loading}
|
|
244
244
|
setActionButtons={setActionButtons}
|
|
245
245
|
error={flamegraphData.error}
|
|
246
246
|
isHalfScreen={isHalfScreen}
|
|
@@ -252,6 +252,7 @@ export const ProfileView = ({
|
|
|
252
252
|
: 0
|
|
253
253
|
}
|
|
254
254
|
mappings={flamegraphData.mappings}
|
|
255
|
+
mappingsLoading={flamegraphData.mappingsLoading}
|
|
255
256
|
/>
|
|
256
257
|
</ConditionalWrapper>
|
|
257
258
|
);
|
package/src/Table/index.tsx
CHANGED
|
@@ -18,7 +18,6 @@ import {AnimatePresence, motion} from 'framer-motion';
|
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
Button,
|
|
21
|
-
TableActionButtonPlaceholder,
|
|
22
21
|
Table as TableComponent,
|
|
23
22
|
TableSkeleton,
|
|
24
23
|
useParcaContext,
|
|
@@ -310,16 +309,7 @@ export const Table = React.memo(function Table({
|
|
|
310
309
|
}, [navigateTo, router, filterByFunctionInput]);
|
|
311
310
|
|
|
312
311
|
useEffect(() => {
|
|
313
|
-
|
|
314
|
-
setActionButtons(<TableActionButtonPlaceholder />);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (setActionButtons === undefined) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
setActionButtons(
|
|
312
|
+
setActionButtons?.(
|
|
323
313
|
<>
|
|
324
314
|
<ColumnsVisibility
|
|
325
315
|
columns={columns}
|