@parca/profile 0.16.375 → 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 +4 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts +4 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +27 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +5 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +51 -35
- package/dist/ProfileIcicleGraph/index.d.ts +2 -1
- package/dist/ProfileIcicleGraph/index.js +36 -19
- 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 +36 -7
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +66 -52
- package/src/ProfileIcicleGraph/index.tsx +83 -52
- package/src/ProfileView/index.tsx +2 -1
- package/src/Table/index.tsx +1 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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
|
+
|
|
6
10
|
## [0.16.375](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.374...@parca/profile@0.16.375) (2024-05-23)
|
|
7
11
|
|
|
8
12
|
**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
|
+
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,14 +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
26
|
const [currentSearchString, setSearchString] = useURLState({
|
|
23
27
|
param: 'binary_frame_filter',
|
|
24
28
|
navigateTo,
|
|
25
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]);
|
|
26
49
|
const stackColorArray = useMemo(() => {
|
|
27
50
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
28
51
|
if (featureA === EVERYTHING_ELSE) {
|
|
@@ -34,7 +57,7 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
34
57
|
return featureA?.localeCompare(featureB ?? '') ?? 0;
|
|
35
58
|
});
|
|
36
59
|
}, [mappingColors]);
|
|
37
|
-
if (mappingColors === undefined) {
|
|
60
|
+
if (mappingColors === undefined && mappingsLoading === false) {
|
|
38
61
|
return _jsx(_Fragment, {});
|
|
39
62
|
}
|
|
40
63
|
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,9 @@ interface IcicleGraphArrowProps {
|
|
|
28
29
|
setCurPath: (path: string[]) => void;
|
|
29
30
|
navigateTo?: NavigateFunction;
|
|
30
31
|
sortBy: string;
|
|
31
|
-
|
|
32
|
+
flamegraphLoading: boolean;
|
|
33
|
+
isHalfScreen: boolean;
|
|
32
34
|
}
|
|
35
|
+
export declare const getMappingColors: (mappingsList: string[], isDarkMode: boolean, currentColorProfile: ColorConfig) => mappingColors;
|
|
33
36
|
export declare const IcicleGraphArrow: React.NamedExoticComponent<IcicleGraphArrowProps>;
|
|
34
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.
|
|
@@ -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, }) {
|
|
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,47 @@ 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';
|
|
73
|
+
// const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
67
74
|
const currentColorProfile = useCurrentColorProfile();
|
|
68
75
|
const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
|
|
69
76
|
const mappingsList = useMemo(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
// Read the mappings from the dictionary that contains all mapping strings.
|
|
78
|
+
// This is great, as might only have a dozen or so mappings,
|
|
79
|
+
// and don't need to read through all the rows (potentially thousands).
|
|
80
|
+
const mappingsDict = table.getChild(FIELD_MAPPING_FILE);
|
|
81
|
+
const mappings = mappingsDict?.data
|
|
82
|
+
.map(mapping => {
|
|
83
|
+
if (mapping.dictionary == null) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const len = mapping.dictionary.length;
|
|
87
|
+
const entries = [];
|
|
88
|
+
for (let i = 0; i < len; i++) {
|
|
89
|
+
const fn = arrowToString(mapping.dictionary.get(i));
|
|
90
|
+
entries.push(getLastItem(fn) ?? '');
|
|
91
|
+
}
|
|
92
|
+
return entries;
|
|
73
93
|
})
|
|
74
94
|
.flat() ?? [];
|
|
75
95
|
// We add a EVERYTHING ELSE mapping to the list.
|
|
76
|
-
|
|
96
|
+
mappings.push('');
|
|
77
97
|
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
78
|
-
|
|
79
|
-
return
|
|
80
|
-
}, [
|
|
98
|
+
mappings.sort((a, b) => a.localeCompare(b));
|
|
99
|
+
return mappings;
|
|
100
|
+
}, [table]);
|
|
81
101
|
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
|
-
});
|
|
102
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
87
103
|
return colors;
|
|
88
|
-
}, [
|
|
104
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
89
105
|
useEffect(() => {
|
|
90
106
|
if (ref.current != null) {
|
|
91
107
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
92
108
|
}
|
|
93
|
-
}, [width]);
|
|
109
|
+
}, [width, flamegraphLoading]);
|
|
94
110
|
const xScale = useMemo(() => {
|
|
111
|
+
if (total === 0n) {
|
|
112
|
+
return () => 0;
|
|
113
|
+
}
|
|
95
114
|
if (width === undefined) {
|
|
96
115
|
return () => 0;
|
|
97
116
|
}
|
|
@@ -125,29 +144,26 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
125
144
|
const root = useMemo(() => {
|
|
126
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 }) }) }) }));
|
|
127
146
|
}, [
|
|
128
|
-
|
|
129
|
-
curPath,
|
|
130
|
-
currentSearchString,
|
|
147
|
+
width,
|
|
131
148
|
height,
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
displayMenu,
|
|
150
|
+
table,
|
|
134
151
|
mappingColors,
|
|
135
152
|
setCurPath,
|
|
136
|
-
|
|
137
|
-
table,
|
|
153
|
+
curPath,
|
|
138
154
|
total,
|
|
139
|
-
width,
|
|
140
155
|
xScale,
|
|
156
|
+
currentSearchString,
|
|
157
|
+
sortBy,
|
|
158
|
+
isDarkMode,
|
|
159
|
+
compareMode,
|
|
160
|
+
profileType,
|
|
141
161
|
isContextMenuOpen,
|
|
142
|
-
displayMenu,
|
|
143
|
-
colorForSimilarNodes,
|
|
144
|
-
highlightSimilarStacksPreference,
|
|
145
162
|
hoveringName,
|
|
146
163
|
hoveringRow,
|
|
164
|
+
colorForSimilarNodes,
|
|
165
|
+
highlightSimilarStacksPreference,
|
|
147
166
|
]);
|
|
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] }) }));
|
|
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] }) }));
|
|
152
168
|
});
|
|
153
169
|
export default IcicleGraphArrow;
|
|
@@ -18,6 +18,7 @@ interface ProfileIcicleGraphProps {
|
|
|
18
18
|
error?: any;
|
|
19
19
|
isHalfScreen: boolean;
|
|
20
20
|
mappings?: string[];
|
|
21
|
+
mappingsLoading?: boolean;
|
|
21
22
|
}
|
|
22
|
-
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, }: 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;
|
|
23
24
|
export default ProfileIcicleGraph;
|
|
@@ -14,15 +14,16 @@ 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';
|
|
26
27
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
27
28
|
const ErrorContent = ({ errorMessage }) => {
|
|
28
29
|
return _jsx("div", { className: "flex justify-center p-10", children: errorMessage });
|
|
@@ -76,10 +77,11 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
|
76
77
|
}, [groupBy, setGroupBy]);
|
|
77
78
|
return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy })] }));
|
|
78
79
|
};
|
|
79
|
-
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, }) {
|
|
80
|
+
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, mappingsLoading, }) {
|
|
80
81
|
const { onError, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
81
82
|
const { compareMode } = useProfileViewContext();
|
|
82
83
|
const [isLoading, setIsLoading] = useState(true);
|
|
84
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
83
85
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
84
86
|
param: 'sort_by',
|
|
85
87
|
navigateTo,
|
|
@@ -108,14 +110,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
108
110
|
];
|
|
109
111
|
}, [graph, arrow, filtered, total]);
|
|
110
112
|
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 })] }))] }) }));
|
|
113
|
+
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
114
|
}, [
|
|
120
115
|
navigateTo,
|
|
121
116
|
isInvert,
|
|
@@ -136,9 +131,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
136
131
|
setIsLoading(true);
|
|
137
132
|
}
|
|
138
133
|
}, [loading, arrow, graph]);
|
|
139
|
-
if (isLoading) {
|
|
140
|
-
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
141
|
-
}
|
|
142
134
|
if (error != null) {
|
|
143
135
|
onError?.(error);
|
|
144
136
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
@@ -146,13 +138,38 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
146
138
|
}
|
|
147
139
|
return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
|
|
148
140
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
142
|
+
const icicleGraph = useMemo(() => {
|
|
143
|
+
if (isLoading) {
|
|
144
|
+
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
145
|
+
}
|
|
146
|
+
if (graph === undefined && arrow === undefined)
|
|
147
|
+
return _jsx("div", { className: "mx-auto text-center", children: "No data..." });
|
|
148
|
+
if (total === 0n && !loading)
|
|
149
|
+
return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
|
|
150
|
+
if (graph !== undefined)
|
|
151
|
+
return (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, profileType: profileType, navigateTo: navigateTo }));
|
|
152
|
+
if (arrow !== undefined)
|
|
153
|
+
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 }));
|
|
154
|
+
}, [
|
|
155
|
+
isLoading,
|
|
156
|
+
graph,
|
|
157
|
+
arrow,
|
|
158
|
+
total,
|
|
159
|
+
filtered,
|
|
160
|
+
curPath,
|
|
161
|
+
setNewCurPath,
|
|
162
|
+
profileType,
|
|
163
|
+
navigateTo,
|
|
164
|
+
width,
|
|
165
|
+
storeSortBy,
|
|
166
|
+
isHalfScreen,
|
|
167
|
+
isDarkMode,
|
|
168
|
+
loading,
|
|
169
|
+
]);
|
|
153
170
|
if (isTrimmed) {
|
|
154
171
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
155
172
|
}
|
|
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,
|
|
173
|
+
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, mappingsLoading: mappingsLoading })), _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
174
|
};
|
|
158
175
|
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.376",
|
|
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": "6f30d0d2d958ea4a32f58671bfc97bf72eb232d7"
|
|
75
75
|
}
|
|
@@ -17,23 +17,27 @@ 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 {getLastItem, type NavigateFunction} from '@parca/utilities';
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {getMappingColors} from '.';
|
|
25
25
|
|
|
26
26
|
interface Props {
|
|
27
|
-
|
|
27
|
+
mappings?: string[];
|
|
28
|
+
mappingsLoading?: boolean;
|
|
28
29
|
navigateTo?: NavigateFunction;
|
|
29
30
|
compareMode?: boolean;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const ColorStackLegend = ({
|
|
33
|
-
|
|
34
|
+
mappings,
|
|
34
35
|
navigateTo,
|
|
35
36
|
compareMode = false,
|
|
37
|
+
mappingsLoading,
|
|
36
38
|
}: Props): React.JSX.Element => {
|
|
39
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
40
|
+
const currentColorProfile = useCurrentColorProfile();
|
|
37
41
|
const [colorProfileName] = useUserPreference<string>(
|
|
38
42
|
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
39
43
|
);
|
|
@@ -42,6 +46,31 @@ const ColorStackLegend = ({
|
|
|
42
46
|
navigateTo,
|
|
43
47
|
});
|
|
44
48
|
|
|
49
|
+
const mappingsList = useMemo(() => {
|
|
50
|
+
if (mappings === undefined) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const list =
|
|
54
|
+
mappings
|
|
55
|
+
?.map(mapping => {
|
|
56
|
+
return getLastItem(mapping) as string;
|
|
57
|
+
})
|
|
58
|
+
.flat() ?? [];
|
|
59
|
+
|
|
60
|
+
// We add a EVERYTHING ELSE mapping to the list.
|
|
61
|
+
list.push('');
|
|
62
|
+
|
|
63
|
+
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
64
|
+
list.sort((a, b) => a.localeCompare(b));
|
|
65
|
+
|
|
66
|
+
return list;
|
|
67
|
+
}, [mappings]);
|
|
68
|
+
|
|
69
|
+
const mappingColors = useMemo(() => {
|
|
70
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
71
|
+
return colors;
|
|
72
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
73
|
+
|
|
45
74
|
const stackColorArray = useMemo(() => {
|
|
46
75
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
47
76
|
if (featureA === EVERYTHING_ELSE) {
|
|
@@ -54,7 +83,7 @@ const ColorStackLegend = ({
|
|
|
54
83
|
});
|
|
55
84
|
}, [mappingColors]);
|
|
56
85
|
|
|
57
|
-
if (mappingColors === undefined) {
|
|
86
|
+
if (mappingColors === undefined && mappingsLoading === false) {
|
|
58
87
|
return <></>;
|
|
59
88
|
}
|
|
60
89
|
|
|
@@ -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,24 @@ interface IcicleGraphArrowProps {
|
|
|
65
70
|
setCurPath: (path: string[]) => void;
|
|
66
71
|
navigateTo?: NavigateFunction;
|
|
67
72
|
sortBy: string;
|
|
68
|
-
|
|
73
|
+
flamegraphLoading: boolean;
|
|
74
|
+
isHalfScreen: boolean;
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
export const getMappingColors = (
|
|
78
|
+
mappingsList: string[],
|
|
79
|
+
isDarkMode: boolean,
|
|
80
|
+
currentColorProfile: ColorConfig
|
|
81
|
+
): mappingColors => {
|
|
82
|
+
const mappingFeatures = mappingsList.map(mapping => extractFeature(mapping));
|
|
83
|
+
|
|
84
|
+
const colors: mappingColors = {};
|
|
85
|
+
Object.entries(mappingFeatures).forEach(([_, feature]) => {
|
|
86
|
+
colors[feature.name] = getColorForFeature(feature.name, isDarkMode, currentColorProfile.colors);
|
|
87
|
+
});
|
|
88
|
+
return colors;
|
|
89
|
+
};
|
|
90
|
+
|
|
71
91
|
export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
72
92
|
arrow,
|
|
73
93
|
total,
|
|
@@ -78,7 +98,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
78
98
|
profileType,
|
|
79
99
|
navigateTo,
|
|
80
100
|
sortBy,
|
|
81
|
-
|
|
101
|
+
flamegraphLoading,
|
|
82
102
|
}: IcicleGraphArrowProps): React.JSX.Element {
|
|
83
103
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState<boolean>(false);
|
|
84
104
|
const dispatch = useAppDispatch();
|
|
@@ -106,50 +126,55 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
106
126
|
|
|
107
127
|
const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
108
128
|
const {compareMode} = useProfileViewContext();
|
|
109
|
-
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
129
|
+
// const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
110
130
|
const currentColorProfile = useCurrentColorProfile();
|
|
111
131
|
const colorForSimilarNodes = currentColorProfile.colorForSimilarNodes;
|
|
112
132
|
|
|
113
133
|
const mappingsList = useMemo(() => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
// Read the mappings from the dictionary that contains all mapping strings.
|
|
135
|
+
// This is great, as might only have a dozen or so mappings,
|
|
136
|
+
// and don't need to read through all the rows (potentially thousands).
|
|
137
|
+
const mappingsDict: Vector<Dictionary> | null = table.getChild(FIELD_MAPPING_FILE);
|
|
138
|
+
const mappings =
|
|
139
|
+
mappingsDict?.data
|
|
140
|
+
.map(mapping => {
|
|
141
|
+
if (mapping.dictionary == null) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const len = mapping.dictionary.length;
|
|
145
|
+
const entries: string[] = [];
|
|
146
|
+
for (let i = 0; i < len; i++) {
|
|
147
|
+
const fn = arrowToString(mapping.dictionary.get(i));
|
|
148
|
+
entries.push(getLastItem(fn) ?? '');
|
|
149
|
+
}
|
|
150
|
+
return entries;
|
|
118
151
|
})
|
|
119
152
|
.flat() ?? [];
|
|
120
153
|
|
|
121
154
|
// We add a EVERYTHING ELSE mapping to the list.
|
|
122
|
-
|
|
155
|
+
mappings.push('');
|
|
123
156
|
|
|
124
157
|
// We sort the mappings alphabetically to make sure that the order is always the same.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, [mappings]);
|
|
158
|
+
mappings.sort((a, b) => a.localeCompare(b));
|
|
159
|
+
return mappings;
|
|
160
|
+
}, [table]);
|
|
129
161
|
|
|
130
162
|
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
|
-
|
|
163
|
+
const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
|
|
143
164
|
return colors;
|
|
144
|
-
}, [
|
|
165
|
+
}, [isDarkMode, mappingsList, currentColorProfile]);
|
|
145
166
|
|
|
146
167
|
useEffect(() => {
|
|
147
168
|
if (ref.current != null) {
|
|
148
169
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
149
170
|
}
|
|
150
|
-
}, [width]);
|
|
171
|
+
}, [width, flamegraphLoading]);
|
|
151
172
|
|
|
152
173
|
const xScale = useMemo(() => {
|
|
174
|
+
if (total === 0n) {
|
|
175
|
+
return () => 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
153
178
|
if (width === undefined) {
|
|
154
179
|
return () => 0;
|
|
155
180
|
}
|
|
@@ -235,31 +260,27 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
235
260
|
</svg>
|
|
236
261
|
);
|
|
237
262
|
}, [
|
|
238
|
-
|
|
239
|
-
curPath,
|
|
240
|
-
currentSearchString,
|
|
263
|
+
width,
|
|
241
264
|
height,
|
|
242
|
-
|
|
243
|
-
|
|
265
|
+
displayMenu,
|
|
266
|
+
table,
|
|
244
267
|
mappingColors,
|
|
245
268
|
setCurPath,
|
|
246
|
-
|
|
247
|
-
table,
|
|
269
|
+
curPath,
|
|
248
270
|
total,
|
|
249
|
-
width,
|
|
250
271
|
xScale,
|
|
272
|
+
currentSearchString,
|
|
273
|
+
sortBy,
|
|
274
|
+
isDarkMode,
|
|
275
|
+
compareMode,
|
|
276
|
+
profileType,
|
|
251
277
|
isContextMenuOpen,
|
|
252
|
-
displayMenu,
|
|
253
|
-
colorForSimilarNodes,
|
|
254
|
-
highlightSimilarStacksPreference,
|
|
255
278
|
hoveringName,
|
|
256
279
|
hoveringRow,
|
|
280
|
+
colorForSimilarNodes,
|
|
281
|
+
highlightSimilarStacksPreference,
|
|
257
282
|
]);
|
|
258
283
|
|
|
259
|
-
if (table.numRows === 0 || width === undefined) {
|
|
260
|
-
return <></>;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
284
|
return (
|
|
264
285
|
<>
|
|
265
286
|
<div onMouseLeave={() => dispatch(setHoveringNode(undefined))}>
|
|
@@ -278,13 +299,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
278
299
|
hideMenu={hideAll}
|
|
279
300
|
hideBinary={hideBinary}
|
|
280
301
|
/>
|
|
281
|
-
{isColorStackLegendEnabled && (
|
|
282
|
-
<ColorStackLegend
|
|
283
|
-
mappingColors={mappingColors}
|
|
284
|
-
navigateTo={navigateTo}
|
|
285
|
-
compareMode={compareMode}
|
|
286
|
-
/>
|
|
287
|
-
)}
|
|
288
302
|
{dockedMetainfo ? (
|
|
289
303
|
<DockedGraphTooltip
|
|
290
304
|
table={table}
|
|
@@ -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,7 @@ 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';
|
|
38
43
|
|
|
39
44
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
40
45
|
|
|
@@ -55,6 +60,7 @@ interface ProfileIcicleGraphProps {
|
|
|
55
60
|
error?: any;
|
|
56
61
|
isHalfScreen: boolean;
|
|
57
62
|
mappings?: string[];
|
|
63
|
+
mappingsLoading?: boolean;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
|
|
@@ -214,10 +220,12 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
214
220
|
width,
|
|
215
221
|
isHalfScreen,
|
|
216
222
|
mappings,
|
|
223
|
+
mappingsLoading,
|
|
217
224
|
}: ProfileIcicleGraphProps): JSX.Element {
|
|
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';
|
|
221
229
|
|
|
222
230
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
223
231
|
param: 'sort_by',
|
|
@@ -262,20 +270,11 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
262
270
|
}, [graph, arrow, filtered, total]);
|
|
263
271
|
|
|
264
272
|
useEffect(() => {
|
|
265
|
-
|
|
266
|
-
setActionButtons(<IcicleActionButtonPlaceholder isHalfScreen={isHalfScreen} />);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (setActionButtons === undefined) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
setActionButtons(
|
|
273
|
+
setActionButtons?.(
|
|
275
274
|
<div className="flex w-full justify-end gap-2 pb-2">
|
|
276
275
|
<div className="ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end">
|
|
277
|
-
{
|
|
278
|
-
{
|
|
276
|
+
{<GroupAndSortActionButtons navigateTo={navigateTo} />}
|
|
277
|
+
{isHalfScreen ? (
|
|
279
278
|
<IconButton
|
|
280
279
|
icon={isInvert ? 'ph:sort-ascending' : 'ph:sort-descending'}
|
|
281
280
|
toolTipText={isInvert ? 'Original Call Stack' : 'Invert Call Stack'}
|
|
@@ -336,14 +335,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
336
335
|
}
|
|
337
336
|
}, [loading, arrow, graph]);
|
|
338
337
|
|
|
339
|
-
if (isLoading) {
|
|
340
|
-
return (
|
|
341
|
-
<div className="h-auto overflow-clip">
|
|
342
|
-
<IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
343
|
-
</div>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
338
|
if (error != null) {
|
|
348
339
|
onError?.(error);
|
|
349
340
|
|
|
@@ -354,11 +345,68 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
354
345
|
return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(error.message)} />;
|
|
355
346
|
}
|
|
356
347
|
|
|
357
|
-
|
|
358
|
-
|
|
348
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
349
|
+
const icicleGraph = useMemo(() => {
|
|
350
|
+
if (isLoading) {
|
|
351
|
+
return (
|
|
352
|
+
<div className="h-auto overflow-clip">
|
|
353
|
+
<IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
359
357
|
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
if (graph === undefined && arrow === undefined)
|
|
359
|
+
return <div className="mx-auto text-center">No data...</div>;
|
|
360
|
+
|
|
361
|
+
if (total === 0n && !loading)
|
|
362
|
+
return <div className="mx-auto text-center">Profile has no samples</div>;
|
|
363
|
+
|
|
364
|
+
if (graph !== undefined)
|
|
365
|
+
return (
|
|
366
|
+
<IcicleGraph
|
|
367
|
+
width={width}
|
|
368
|
+
graph={graph}
|
|
369
|
+
total={total}
|
|
370
|
+
filtered={filtered}
|
|
371
|
+
curPath={curPath}
|
|
372
|
+
setCurPath={setNewCurPath}
|
|
373
|
+
profileType={profileType}
|
|
374
|
+
navigateTo={navigateTo}
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (arrow !== undefined)
|
|
379
|
+
return (
|
|
380
|
+
<IcicleGraphArrow
|
|
381
|
+
width={width}
|
|
382
|
+
arrow={arrow}
|
|
383
|
+
total={total}
|
|
384
|
+
filtered={filtered}
|
|
385
|
+
curPath={curPath}
|
|
386
|
+
setCurPath={setNewCurPath}
|
|
387
|
+
profileType={profileType}
|
|
388
|
+
navigateTo={navigateTo}
|
|
389
|
+
sortBy={storeSortBy as string}
|
|
390
|
+
flamegraphLoading={isLoading}
|
|
391
|
+
isHalfScreen={isHalfScreen}
|
|
392
|
+
/>
|
|
393
|
+
);
|
|
394
|
+
}, [
|
|
395
|
+
isLoading,
|
|
396
|
+
graph,
|
|
397
|
+
arrow,
|
|
398
|
+
total,
|
|
399
|
+
filtered,
|
|
400
|
+
curPath,
|
|
401
|
+
setNewCurPath,
|
|
402
|
+
profileType,
|
|
403
|
+
navigateTo,
|
|
404
|
+
width,
|
|
405
|
+
storeSortBy,
|
|
406
|
+
isHalfScreen,
|
|
407
|
+
isDarkMode,
|
|
408
|
+
loading,
|
|
409
|
+
]);
|
|
362
410
|
|
|
363
411
|
if (isTrimmed) {
|
|
364
412
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
@@ -374,33 +422,16 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
374
422
|
transition={{duration: 0.5}}
|
|
375
423
|
>
|
|
376
424
|
{compareMode ? <DiffLegend /> : null}
|
|
425
|
+
{isColorStackLegendEnabled && (
|
|
426
|
+
<ColorStackLegend
|
|
427
|
+
navigateTo={navigateTo}
|
|
428
|
+
compareMode={compareMode}
|
|
429
|
+
mappings={mappings}
|
|
430
|
+
mappingsLoading={mappingsLoading}
|
|
431
|
+
/>
|
|
432
|
+
)}
|
|
377
433
|
<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
|
-
)}
|
|
434
|
+
<>{icicleGraph}</>
|
|
404
435
|
</div>
|
|
405
436
|
<p className="my-2 text-xs">
|
|
406
437
|
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}
|