@parca/profile 0.16.374 → 0.16.376
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +1 -5
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts +4 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +50 -13
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +3 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +0 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +5 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +48 -50
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +1 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +2 -7
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +0 -3
- package/dist/ProfileIcicleGraph/index.d.ts +3 -1
- package/dist/ProfileIcicleGraph/index.js +57 -36
- package/dist/ProfileSource.js +2 -0
- package/dist/ProfileView/index.d.ts +2 -0
- package/dist/ProfileView/index.js +1 -1
- package/dist/ProfileViewWithData.js +17 -10
- package/dist/Table/index.js +2 -9
- package/dist/useQuery.d.ts +1 -3
- package/dist/useQuery.js +21 -8
- package/package.json +7 -7
- package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +1 -1
- package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +1 -7
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +62 -16
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +21 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +0 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +66 -61
- package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +1 -8
- package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +0 -5
- package/src/ProfileIcicleGraph/index.tsx +142 -96
- package/src/ProfileSource.tsx +2 -0
- package/src/ProfileView/index.tsx +4 -0
- package/src/ProfileViewWithData.tsx +26 -10
- package/src/Table/index.tsx +1 -11
- package/src/useQuery.tsx +22 -11
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +0 -9
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +0 -23
- package/src/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.tsx +0 -123
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Copyright 2022 The Parca Authors
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
// you may not use this file except in compliance with the License.
|
|
@@ -11,19 +11,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
11
11
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
|
-
import { useCallback, useEffect, useMemo } from 'react';
|
|
14
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
|
-
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
|
-
import RuntimeFilterDropdown from './ActionButtons/RuntimeFilterDropdown';
|
|
24
23
|
import SortBySelect from './ActionButtons/SortBySelect';
|
|
25
24
|
import IcicleGraph from './IcicleGraph';
|
|
26
25
|
import IcicleGraphArrow, { FIELD_FUNCTION_NAME } from './IcicleGraphArrow';
|
|
26
|
+
import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
|
|
27
27
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
28
28
|
const ErrorContent = ({ errorMessage }) => {
|
|
29
29
|
return _jsx("div", { className: "flex justify-center p-10", children: errorMessage });
|
|
@@ -33,13 +33,20 @@ const ShowHideLegendButton = ({ navigateTo, isHalfScreen, }) => {
|
|
|
33
33
|
param: 'color_stack_legend',
|
|
34
34
|
navigateTo,
|
|
35
35
|
});
|
|
36
|
+
const [binaryFrameFilter, setBinaryFrameFilter] = useURLState({
|
|
37
|
+
param: 'binary_frame_filter',
|
|
38
|
+
navigateTo,
|
|
39
|
+
});
|
|
36
40
|
const { compareMode } = useProfileViewContext();
|
|
37
41
|
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
38
42
|
const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
39
43
|
const setColorStackLegend = useCallback((value) => {
|
|
40
44
|
setStoreColorStackLegend(value);
|
|
41
45
|
}, [setStoreColorStackLegend]);
|
|
42
|
-
|
|
46
|
+
const resetLegend = () => {
|
|
47
|
+
setBinaryFrameFilter([]);
|
|
48
|
+
};
|
|
49
|
+
return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsx(_Fragment, { children: isHalfScreen ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]", icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', toolTipText: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button" }), binaryFrameFilter !== undefined && binaryFrameFilter.length > 0 && (_jsx(IconButton, { className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]", icon: "system-uicons:reset", toolTipText: "Reset the legend selection", onClick: () => resetLegend(), id: "h-reset-legend-button" }))] })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), id: "h-show-legend-button", children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] }), binaryFrameFilter !== undefined && binaryFrameFilter.length > 0 && (_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => resetLegend(), id: "h-reset-legend-button", children: ["Reset Legend", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] })) })) }));
|
|
43
50
|
};
|
|
44
51
|
const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
45
52
|
const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
|
|
@@ -68,23 +75,13 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
|
68
75
|
? setGroupBy(groupBy.filter(v => v !== key)) // remove
|
|
69
76
|
: setGroupBy([...groupBy, key]); // add
|
|
70
77
|
}, [groupBy, setGroupBy]);
|
|
71
|
-
|
|
72
|
-
param: 'show_runtime_ruby',
|
|
73
|
-
navigateTo,
|
|
74
|
-
});
|
|
75
|
-
const [showRuntimePythonStr, setShowRuntimePython] = useURLState({
|
|
76
|
-
param: 'show_runtime_python',
|
|
77
|
-
navigateTo,
|
|
78
|
-
});
|
|
79
|
-
const [showInterpretedOnlyStr, setShowInterpretedOnly] = useURLState({
|
|
80
|
-
param: 'show_interpreted_only',
|
|
81
|
-
navigateTo,
|
|
82
|
-
});
|
|
83
|
-
return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy }), _jsx(RuntimeFilterDropdown, { showRuntimeRuby: showRuntimeRubyStr === 'true', toggleShowRuntimeRuby: () => setShowRuntimeRuby(showRuntimeRubyStr === 'true' ? 'false' : 'true'), showRuntimePython: showRuntimePythonStr === 'true', toggleShowRuntimePython: () => setShowRuntimePython(showRuntimePythonStr === 'true' ? 'false' : 'true'), showInterpretedOnly: showInterpretedOnlyStr === 'true', toggleShowInterpretedOnly: () => setShowInterpretedOnly(showInterpretedOnlyStr === 'true' ? 'false' : 'true') })] }));
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy })] }));
|
|
84
79
|
};
|
|
85
|
-
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }) {
|
|
80
|
+
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, profileType, navigateTo, loading, setActionButtons, error, width, isHalfScreen, mappings, mappingsLoading, }) {
|
|
86
81
|
const { onError, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
87
82
|
const { compareMode } = useProfileViewContext();
|
|
83
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
84
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
88
85
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
89
86
|
param: 'sort_by',
|
|
90
87
|
navigateTo,
|
|
@@ -113,14 +110,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
113
110
|
];
|
|
114
111
|
}, [graph, arrow, filtered, total]);
|
|
115
112
|
useEffect(() => {
|
|
116
|
-
|
|
117
|
-
setActionButtons(_jsx(IcicleActionButtonPlaceholder, { isHalfScreen: isHalfScreen }));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (setActionButtons === undefined) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
|
|
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 })] }))] }) }));
|
|
124
114
|
}, [
|
|
125
115
|
navigateTo,
|
|
126
116
|
isInvert,
|
|
@@ -131,10 +121,16 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
131
121
|
setActionButtons,
|
|
132
122
|
loading,
|
|
133
123
|
isHalfScreen,
|
|
124
|
+
isLoading,
|
|
134
125
|
]);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!loading && (arrow !== undefined || graph !== undefined)) {
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
setIsLoading(true);
|
|
132
|
+
}
|
|
133
|
+
}, [loading, arrow, graph]);
|
|
138
134
|
if (error != null) {
|
|
139
135
|
onError?.(error);
|
|
140
136
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
@@ -142,13 +138,38 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
142
138
|
}
|
|
143
139
|
return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
|
|
144
140
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
]);
|
|
149
170
|
if (isTrimmed) {
|
|
150
171
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
151
172
|
}
|
|
152
|
-
return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null,
|
|
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") }));
|
|
153
174
|
};
|
|
154
175
|
export default ProfileIcicleGraph;
|
package/dist/ProfileSource.js
CHANGED
|
@@ -108,6 +108,7 @@ export class ProfileDiffSource {
|
|
|
108
108
|
reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
|
|
109
109
|
mode: QueryRequest_Mode.DIFF,
|
|
110
110
|
filterQuery: this.filterQuery,
|
|
111
|
+
filter: [],
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
ProfileType() {
|
|
@@ -158,6 +159,7 @@ export class MergedProfileSource {
|
|
|
158
159
|
reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
|
|
159
160
|
mode: QueryRequest_Mode.MERGE,
|
|
160
161
|
filterQuery: this.filterQuery,
|
|
162
|
+
filter: [],
|
|
161
163
|
};
|
|
162
164
|
}
|
|
163
165
|
ProfileType() {
|
|
@@ -111,7 +111,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
111
111
|
? isHalfScreen
|
|
112
112
|
? (dimensions.width - 40) / 2
|
|
113
113
|
: dimensions.width - 16
|
|
114
|
-
: 0 }) }));
|
|
114
|
+
: 0, mappings: flamegraphData.mappings, mappingsLoading: flamegraphData.mappingsLoading }) }));
|
|
115
115
|
}
|
|
116
116
|
case 'callgraph': {
|
|
117
117
|
return callgraphData?.data !== undefined &&
|
|
@@ -25,14 +25,12 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
|
|
|
25
25
|
const [sourceBuildID] = useURLState({ param: 'source_buildid', navigateTo });
|
|
26
26
|
const [sourceFilename] = useURLState({ param: 'source_filename', navigateTo });
|
|
27
27
|
const [groupBy = [FIELD_FUNCTION_NAME]] = useURLState({ param: 'group_by', navigateTo });
|
|
28
|
-
const [showRuntimeRubyStr] = useURLState({ param: 'show_runtime_ruby', navigateTo });
|
|
29
|
-
const showRuntimeRuby = showRuntimeRubyStr === 'true';
|
|
30
|
-
const [showRuntimePythonStr] = useURLState({ param: 'show_runtime_python', navigateTo });
|
|
31
|
-
const showRuntimePython = showRuntimePythonStr === 'true';
|
|
32
|
-
const [showInterpretedOnlyStr] = useURLState({ param: 'show_interpreted_only', navigateTo });
|
|
33
|
-
const showInterpretedOnly = showInterpretedOnlyStr === 'true';
|
|
34
28
|
const [invertStack] = useURLState({ param: 'invert_call_stack', navigateTo });
|
|
35
29
|
const invertCallStack = invertStack === 'true';
|
|
30
|
+
const [binaryFrameFilterStr] = useURLState({ param: 'binary_frame_filter', navigateTo });
|
|
31
|
+
const binaryFrameFilter = typeof binaryFrameFilterStr === 'string'
|
|
32
|
+
? binaryFrameFilterStr.split(',')
|
|
33
|
+
: binaryFrameFilterStr;
|
|
36
34
|
const [pprofDownloading, setPprofDownloading] = useState(false);
|
|
37
35
|
const nodeTrimThreshold = useMemo(() => {
|
|
38
36
|
let width =
|
|
@@ -48,10 +46,15 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
|
|
|
48
46
|
skip: !dashboardItems.includes('icicle'),
|
|
49
47
|
nodeTrimThreshold,
|
|
50
48
|
groupBy: groupByParam,
|
|
51
|
-
showRuntimeRuby,
|
|
52
|
-
showRuntimePython,
|
|
53
|
-
showInterpretedOnly,
|
|
54
49
|
invertCallStack,
|
|
50
|
+
binaryFrameFilter,
|
|
51
|
+
});
|
|
52
|
+
const { isLoading: profilemetadataLoading, response: profilemetadataResponse } = useQuery(queryClient, profileSource, QueryRequest_ReportType.PROFILE_METADATA, {
|
|
53
|
+
skip: !dashboardItems.includes('icicle'),
|
|
54
|
+
nodeTrimThreshold,
|
|
55
|
+
groupBy: groupByParam,
|
|
56
|
+
invertCallStack,
|
|
57
|
+
binaryFrameFilter: undefined,
|
|
55
58
|
});
|
|
56
59
|
const { perf } = useParcaContext();
|
|
57
60
|
const { isLoading: tableLoading, response: tableResponse, error: tableError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TABLE_ARROW, {
|
|
@@ -126,7 +129,7 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
|
|
|
126
129
|
filtered = BigInt(sourceResponse.filtered);
|
|
127
130
|
}
|
|
128
131
|
return (_jsx(ProfileView, { total: total, filtered: filtered, flamegraphData: {
|
|
129
|
-
loading: flamegraphLoading,
|
|
132
|
+
loading: flamegraphLoading && profilemetadataLoading,
|
|
130
133
|
data: flamegraphResponse?.report.oneofKind === 'flamegraph'
|
|
131
134
|
? flamegraphResponse?.report?.flamegraph
|
|
132
135
|
: undefined,
|
|
@@ -136,6 +139,10 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
|
|
|
136
139
|
total: BigInt(flamegraphResponse?.total ?? '0'),
|
|
137
140
|
filtered: BigInt(flamegraphResponse?.filtered ?? '0'),
|
|
138
141
|
error: flamegraphError,
|
|
142
|
+
mappings: profilemetadataResponse?.report.oneofKind === 'profileMetadata'
|
|
143
|
+
? profilemetadataResponse?.report?.profileMetadata?.mappingFiles
|
|
144
|
+
: undefined,
|
|
145
|
+
mappingsLoading: profilemetadataLoading,
|
|
139
146
|
}, topTableData: {
|
|
140
147
|
loading: tableLoading,
|
|
141
148
|
arrow: tableResponse?.report.oneofKind === 'tableArrow'
|
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/dist/useQuery.d.ts
CHANGED
|
@@ -13,10 +13,8 @@ interface UseQueryOptions {
|
|
|
13
13
|
sourceBuildID?: string;
|
|
14
14
|
sourceFilename?: string;
|
|
15
15
|
sourceOnly?: boolean;
|
|
16
|
-
showRuntimeRuby?: boolean;
|
|
17
|
-
showRuntimePython?: boolean;
|
|
18
|
-
showInterpretedOnly?: boolean;
|
|
19
16
|
invertCallStack?: boolean;
|
|
17
|
+
binaryFrameFilter?: string[];
|
|
20
18
|
}
|
|
21
19
|
export declare const useQuery: (client: QueryServiceClient, profileSource: ProfileSource, reportType: QueryRequest_ReportType, options?: UseQueryOptions) => IQueryResult;
|
|
22
20
|
export {};
|
package/dist/useQuery.js
CHANGED
|
@@ -25,10 +25,8 @@ export const useQuery = (client, profileSource, reportType, options) => {
|
|
|
25
25
|
options?.sourceBuildID,
|
|
26
26
|
options?.sourceOnly,
|
|
27
27
|
options?.sourceOnly === true ? '' : options?.sourceFilename,
|
|
28
|
-
options?.showRuntimeRuby ?? false,
|
|
29
|
-
options?.showRuntimePython ?? false,
|
|
30
|
-
options?.showInterpretedOnly ?? false,
|
|
31
28
|
options?.invertCallStack ?? false,
|
|
29
|
+
options?.binaryFrameFilter ?? '',
|
|
32
30
|
],
|
|
33
31
|
queryFn: async () => {
|
|
34
32
|
const req = profileSource.QueryRequest();
|
|
@@ -44,12 +42,27 @@ export const useQuery = (client, profileSource, reportType, options) => {
|
|
|
44
42
|
sourceOnly: options?.sourceOnly ?? false,
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
|
-
req.runtimeFilter = {
|
|
48
|
-
showRuby: options?.showRuntimeRuby ?? false,
|
|
49
|
-
showPython: options?.showRuntimePython ?? false,
|
|
50
|
-
showInterpretedOnly: options?.showInterpretedOnly ?? false,
|
|
51
|
-
};
|
|
52
45
|
req.invertCallStack = options?.invertCallStack ?? false;
|
|
46
|
+
if (options?.binaryFrameFilter !== undefined && options?.binaryFrameFilter.length > 0) {
|
|
47
|
+
req.filter = [
|
|
48
|
+
{
|
|
49
|
+
filter: {
|
|
50
|
+
oneofKind: 'frameFilter',
|
|
51
|
+
frameFilter: {
|
|
52
|
+
filter: {
|
|
53
|
+
oneofKind: 'binaryFrameFilter',
|
|
54
|
+
binaryFrameFilter: {
|
|
55
|
+
includeBinaries: options?.binaryFrameFilter ?? [],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
req.filter = [];
|
|
65
|
+
}
|
|
53
66
|
try {
|
|
54
67
|
const { response } = await client.query(req, { meta: metadata });
|
|
55
68
|
return response;
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
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",
|
|
7
7
|
"@iconify/react": "^4.0.0",
|
|
8
|
-
"@parca/client": "^0.16.
|
|
9
|
-
"@parca/components": "^0.16.
|
|
8
|
+
"@parca/client": "^0.16.115",
|
|
9
|
+
"@parca/components": "^0.16.278",
|
|
10
10
|
"@parca/dynamicsize": "^0.16.65",
|
|
11
|
-
"@parca/hooks": "^0.0.
|
|
11
|
+
"@parca/hooks": "^0.0.57",
|
|
12
12
|
"@parca/icons": "^0.16.69",
|
|
13
13
|
"@parca/parser": "^0.16.74",
|
|
14
|
-
"@parca/store": "^0.16.
|
|
15
|
-
"@parca/utilities": "^0.0.
|
|
14
|
+
"@parca/store": "^0.16.146",
|
|
15
|
+
"@parca/utilities": "^0.0.74",
|
|
16
16
|
"@popperjs/core": "^2.11.8",
|
|
17
17
|
"@protobuf-ts/runtime-rpc": "^2.5.0",
|
|
18
18
|
"@tanstack/react-query": "^4.0.5",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"access": "public",
|
|
72
72
|
"registry": "https://registry.npmjs.org/"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "6f30d0d2d958ea4a32f58671bfc97bf72eb232d7"
|
|
75
75
|
}
|
|
@@ -71,7 +71,7 @@ const colorNodes = (
|
|
|
71
71
|
features
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
-
const feature = extractFeature(node, mappings, locations, strings
|
|
74
|
+
const feature = extractFeature(node, mappings, locations, strings);
|
|
75
75
|
coloredNode.feature = feature.name;
|
|
76
76
|
features[feature.name] = feature.type;
|
|
77
77
|
return coloredNode;
|
|
@@ -91,14 +91,8 @@ export const extractFeature = (
|
|
|
91
91
|
data: FlamegraphNode,
|
|
92
92
|
mappings: Mapping[],
|
|
93
93
|
locations: Location[],
|
|
94
|
-
strings: string[]
|
|
95
|
-
functions: ParcaFunction[]
|
|
94
|
+
strings: string[]
|
|
96
95
|
): Feature => {
|
|
97
|
-
const name = nodeLabel(data, strings, mappings, locations, functions, false).trim();
|
|
98
|
-
if (name.startsWith('runtime') || name === 'root') {
|
|
99
|
-
return {name: 'runtime', type: FEATURE_TYPES.Runtime};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
96
|
const binaryName = getBinaryName(data, mappings, locations, strings);
|
|
103
97
|
if (binaryName != null) {
|
|
104
98
|
return {name: binaryName, type: FEATURE_TYPES.Binary};
|
|
@@ -17,27 +17,59 @@ 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
|
);
|
|
40
|
-
const [currentSearchString, setSearchString] = useURLState({
|
|
44
|
+
const [currentSearchString, setSearchString] = useURLState({
|
|
45
|
+
param: 'binary_frame_filter',
|
|
46
|
+
navigateTo,
|
|
47
|
+
});
|
|
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]);
|
|
41
73
|
|
|
42
74
|
const stackColorArray = useMemo(() => {
|
|
43
75
|
return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
|
|
@@ -51,7 +83,7 @@ const ColorStackLegend = ({
|
|
|
51
83
|
});
|
|
52
84
|
}, [mappingColors]);
|
|
53
85
|
|
|
54
|
-
if (mappingColors === undefined) {
|
|
86
|
+
if (mappingColors === undefined && mappingsLoading === false) {
|
|
55
87
|
return <></>;
|
|
56
88
|
}
|
|
57
89
|
|
|
@@ -67,7 +99,8 @@ const ColorStackLegend = ({
|
|
|
67
99
|
<div className="my-4 flex w-full flex-wrap justify-start">
|
|
68
100
|
{stackColorArray.map(([feature, color]) => {
|
|
69
101
|
const filteringAllowed = feature !== EVERYTHING_ELSE;
|
|
70
|
-
const isHighlighted =
|
|
102
|
+
const isHighlighted =
|
|
103
|
+
currentSearchString !== undefined ? currentSearchString.includes(feature) : false;
|
|
71
104
|
return (
|
|
72
105
|
<div
|
|
73
106
|
key={feature}
|
|
@@ -79,14 +112,19 @@ const ColorStackLegend = ({
|
|
|
79
112
|
}
|
|
80
113
|
)}
|
|
81
114
|
onClick={() => {
|
|
82
|
-
if (!filteringAllowed) {
|
|
115
|
+
if (!filteringAllowed || isHighlighted) {
|
|
83
116
|
return;
|
|
84
117
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
118
|
+
|
|
119
|
+
// Check if the current search string is defined and an array
|
|
120
|
+
const updatedSearchString = Array.isArray(currentSearchString)
|
|
121
|
+
? [...currentSearchString, feature] // If array, append the feature
|
|
122
|
+
: // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
123
|
+
currentSearchString // If not array, preserve current value
|
|
124
|
+
? currentSearchString.split(',') // If string, split by commas
|
|
125
|
+
: [feature]; // If undefined, initialize array with feature
|
|
126
|
+
|
|
127
|
+
setSearchString(updatedSearchString);
|
|
90
128
|
}}
|
|
91
129
|
>
|
|
92
130
|
<div className="flex w-11/12 items-center justify-start">
|
|
@@ -102,7 +140,15 @@ const ColorStackLegend = ({
|
|
|
102
140
|
<Icon
|
|
103
141
|
icon="radix-icons:cross-circled"
|
|
104
142
|
onClick={e => {
|
|
105
|
-
|
|
143
|
+
let searchString: string[] = [];
|
|
144
|
+
if (typeof currentSearchString === 'string') {
|
|
145
|
+
searchString.push(currentSearchString);
|
|
146
|
+
} else {
|
|
147
|
+
searchString = currentSearchString;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// remove the current feature from the search string array of strings
|
|
151
|
+
setSearchString(searchString.filter((f: string) => f !== feature));
|
|
106
152
|
e.stopPropagation();
|
|
107
153
|
}}
|
|
108
154
|
/>
|
|
@@ -19,7 +19,7 @@ import {Tooltip} from 'react-tooltip';
|
|
|
19
19
|
import {useParcaContext} from '@parca/components';
|
|
20
20
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
21
21
|
import {ProfileType} from '@parca/parser';
|
|
22
|
-
import {type NavigateFunction} from '@parca/utilities';
|
|
22
|
+
import {getLastItem, type NavigateFunction} from '@parca/utilities';
|
|
23
23
|
|
|
24
24
|
import {useGraphTooltip} from '../../GraphTooltipArrow/useGraphTooltip';
|
|
25
25
|
import {useGraphTooltipMetaInfo} from '../../GraphTooltipArrow/useGraphTooltipMetaInfo';
|
|
@@ -38,6 +38,7 @@ interface ContextMenuProps {
|
|
|
38
38
|
curPath: string[];
|
|
39
39
|
setCurPath: (path: string[]) => void;
|
|
40
40
|
hideMenu: () => void;
|
|
41
|
+
hideBinary: (binaryToRemove: string) => void;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const ContextMenu = ({
|
|
@@ -53,6 +54,7 @@ const ContextMenu = ({
|
|
|
53
54
|
setCurPath,
|
|
54
55
|
hideMenu,
|
|
55
56
|
profileType,
|
|
57
|
+
hideBinary,
|
|
56
58
|
}: ContextMenuProps): JSX.Element => {
|
|
57
59
|
const {isDarkMode} = useParcaContext();
|
|
58
60
|
const {enableSourcesView} = useParcaContext();
|
|
@@ -157,6 +159,24 @@ const ContextMenu = ({
|
|
|
157
159
|
<div>Reset view</div>
|
|
158
160
|
</div>
|
|
159
161
|
</Item>
|
|
162
|
+
<Item
|
|
163
|
+
id="hide-binary"
|
|
164
|
+
onClick={() => hideBinary(getLastItem(mappingFile) as string)}
|
|
165
|
+
disabled={mappingFile === null || mappingFile === ''}
|
|
166
|
+
>
|
|
167
|
+
<div
|
|
168
|
+
data-tooltip-id="hide-binary-help"
|
|
169
|
+
data-tooltip-content="Hide all frames for this binary"
|
|
170
|
+
>
|
|
171
|
+
<div className="flex w-full items-center gap-2">
|
|
172
|
+
<Icon icon="bx:bxs-hide" />
|
|
173
|
+
<div>
|
|
174
|
+
Hide Binary {mappingFile !== null && `(${getLastItem(mappingFile) as string})`}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<Tooltip place="left" id="hide-binary-help" />
|
|
179
|
+
</Item>
|
|
160
180
|
<Submenu
|
|
161
181
|
label={
|
|
162
182
|
<div className="flex w-full items-center gap-2">
|
|
@@ -362,7 +362,6 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
362
362
|
diffPerSecond,
|
|
363
363
|
mappingColors,
|
|
364
364
|
mappingFile,
|
|
365
|
-
functionName,
|
|
366
365
|
});
|
|
367
366
|
const name = useMemo(() => {
|
|
368
367
|
return isRoot ? 'root' : nodeLabel(table, row, level, binaries.length > 1);
|