@parca/profile 0.16.89 → 0.16.90
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/IcicleGraph.js +1 -1
- package/dist/ProfileIcicleGraph/index.d.ts +3 -1
- package/dist/ProfileIcicleGraph/index.js +26 -2
- package/dist/ProfileView/index.d.ts +3 -1
- package/dist/ProfileView/index.js +4 -3
- package/dist/ProfileViewWithData.js +27 -8
- package/dist/useQuery.d.ts +1 -0
- package/dist/useQuery.js +2 -1
- package/package.json +5 -5
- package/src/IcicleGraph.tsx +1 -1
- package/src/ProfileIcicleGraph/index.tsx +39 -0
- package/src/ProfileView/index.tsx +8 -2
- package/src/ProfileViewWithData.tsx +24 -1
- package/src/useQuery.tsx +3 -1
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.90 (2022-12-22)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## 0.16.89 (2022-12-20)
|
|
7
11
|
|
|
8
12
|
# 0.15.0 (2022-12-20)
|
package/dist/IcicleGraph.js
CHANGED
|
@@ -152,7 +152,7 @@ export default function IcicleGraph(_a) {
|
|
|
152
152
|
if (ref.current != null) {
|
|
153
153
|
setHeight(ref === null || ref === void 0 ? void 0 : ref.current.getBoundingClientRect().height);
|
|
154
154
|
}
|
|
155
|
-
}, [width]);
|
|
155
|
+
}, [width, graph]);
|
|
156
156
|
var total = useMemo(function () { return parseFloat(graph.total); }, [graph.total]);
|
|
157
157
|
var xScale = useMemo(function () {
|
|
158
158
|
if (width === undefined) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { Flamegraph } from '@parca/client';
|
|
3
|
+
export type ResizeHandler = (width: number, height: number) => void;
|
|
3
4
|
interface ProfileIcicleGraphProps {
|
|
4
5
|
width?: number;
|
|
5
6
|
graph: Flamegraph | undefined;
|
|
6
7
|
sampleUnit: string;
|
|
7
8
|
curPath: string[] | [];
|
|
8
9
|
setNewCurPath: (path: string[]) => void;
|
|
10
|
+
onContainerResize?: ResizeHandler;
|
|
9
11
|
}
|
|
10
|
-
declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
12
|
+
declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, onContainerResize, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
11
13
|
export default ProfileIcicleGraph;
|
|
@@ -14,15 +14,39 @@ import { useAppSelector, selectCompareMode } from '@parca/store';
|
|
|
14
14
|
import { useContainerDimensions } from '@parca/dynamicsize';
|
|
15
15
|
import DiffLegend from '../components/DiffLegend';
|
|
16
16
|
import IcicleGraph from '../IcicleGraph';
|
|
17
|
+
import { useEffect, useMemo } from 'react';
|
|
18
|
+
var numberFormatter = new Intl.NumberFormat('en-US');
|
|
17
19
|
var ProfileIcicleGraph = function (_a) {
|
|
18
|
-
var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit;
|
|
20
|
+
var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit, onContainerResize = _a.onContainerResize;
|
|
19
21
|
var compareMode = useAppSelector(selectCompareMode);
|
|
20
22
|
var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
|
|
23
|
+
useEffect(function () {
|
|
24
|
+
if (dimensions === undefined)
|
|
25
|
+
return;
|
|
26
|
+
if (onContainerResize === undefined)
|
|
27
|
+
return;
|
|
28
|
+
onContainerResize(dimensions.width, dimensions.height);
|
|
29
|
+
}, [dimensions, onContainerResize]);
|
|
30
|
+
var _c = useMemo(function () {
|
|
31
|
+
if (graph === undefined || graph.untrimmedTotal === '0') {
|
|
32
|
+
return [BigInt(0), '0'];
|
|
33
|
+
}
|
|
34
|
+
var untrimmedTotal = BigInt(graph.untrimmedTotal);
|
|
35
|
+
var total = BigInt(graph.total);
|
|
36
|
+
var trimDifference = untrimmedTotal - total;
|
|
37
|
+
var trimmedPercentage = (total * BigInt(100)) / untrimmedTotal;
|
|
38
|
+
return [
|
|
39
|
+
trimDifference,
|
|
40
|
+
trimmedPercentage.toString(),
|
|
41
|
+
numberFormatter.format(total),
|
|
42
|
+
numberFormatter.format(untrimmedTotal),
|
|
43
|
+
];
|
|
44
|
+
}, [graph]), trimDifference = _c[0], trimmedPercentage = _c[1], formattedTotal = _c[2], formattedUntrimmedTotal = _c[3];
|
|
21
45
|
if (graph === undefined)
|
|
22
46
|
return _jsx("div", { children: "no data..." });
|
|
23
47
|
var total = graph.total;
|
|
24
48
|
if (parseFloat(total) === 0)
|
|
25
49
|
return _jsx(_Fragment, { children: "Profile has no samples" });
|
|
26
|
-
return (_jsxs(_Fragment, { children: [compareMode && _jsx(DiffLegend, {}), _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit }) }))] }));
|
|
50
|
+
return (_jsxs(_Fragment, { children: [compareMode && _jsx(DiffLegend, {}), trimDifference > BigInt(0) ? (_jsxs("p", __assign({ className: "my-2 text-sm" }, { children: ["Showing ", formattedTotal, "(", trimmedPercentage, "%) out of ", formattedUntrimmedTotal, " samples"] }))) : null, _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit }) }))] }));
|
|
27
51
|
};
|
|
28
52
|
export default ProfileIcicleGraph;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { QueryServiceClient, Flamegraph, Top, Callgraph as CallgraphType } from '@parca/client';
|
|
3
|
+
import { ResizeHandler } from '../ProfileIcicleGraph';
|
|
3
4
|
import { ProfileSource } from '../ProfileSource';
|
|
4
5
|
import '../ProfileView.styles.css';
|
|
5
6
|
type NavigateFunction = (path: string, queryParams: any) => void;
|
|
@@ -34,7 +35,8 @@ export interface ProfileViewProps {
|
|
|
34
35
|
navigateTo?: NavigateFunction;
|
|
35
36
|
compare?: boolean;
|
|
36
37
|
onDownloadPProf: () => void;
|
|
38
|
+
onFlamegraphContainerResize?: ResizeHandler;
|
|
37
39
|
}
|
|
38
40
|
export declare const useProfileVisState: () => ProfileVisState;
|
|
39
|
-
export declare const ProfileView: ({ flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, profileVisState, onDownloadPProf, }: ProfileViewProps) => JSX.Element;
|
|
41
|
+
export declare const ProfileView: ({ flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, profileVisState, onDownloadPProf, onFlamegraphContainerResize, }: ProfileViewProps) => JSX.Element;
|
|
40
42
|
export {};
|
|
@@ -36,6 +36,7 @@ import ProfileIcicleGraph from '../ProfileIcicleGraph';
|
|
|
36
36
|
import TopTable from '../TopTable';
|
|
37
37
|
import useDelayedLoader from '../useDelayedLoader';
|
|
38
38
|
import '../ProfileView.styles.css';
|
|
39
|
+
import useUserPreference, { USER_PREFERENCES } from '@parca/functions/useUserPreference';
|
|
39
40
|
function arrayEquals(a, b) {
|
|
40
41
|
return (Array.isArray(a) &&
|
|
41
42
|
Array.isArray(b) &&
|
|
@@ -57,7 +58,7 @@ export var useProfileVisState = function () {
|
|
|
57
58
|
return { currentView: currentView, setCurrentView: setCurrentView };
|
|
58
59
|
};
|
|
59
60
|
export var ProfileView = function (_a) {
|
|
60
|
-
var flamegraphData = _a.flamegraphData, topTableData = _a.topTableData, callgraphData = _a.callgraphData, sampleUnit = _a.sampleUnit, profileSource = _a.profileSource, queryClient = _a.queryClient, navigateTo = _a.navigateTo, profileVisState = _a.profileVisState, onDownloadPProf = _a.onDownloadPProf;
|
|
61
|
+
var flamegraphData = _a.flamegraphData, topTableData = _a.topTableData, callgraphData = _a.callgraphData, sampleUnit = _a.sampleUnit, profileSource = _a.profileSource, queryClient = _a.queryClient, navigateTo = _a.navigateTo, profileVisState = _a.profileVisState, onDownloadPProf = _a.onDownloadPProf, onFlamegraphContainerResize = _a.onFlamegraphContainerResize;
|
|
61
62
|
var dispatch = useAppDispatch();
|
|
62
63
|
var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
|
|
63
64
|
var _c = useState([]), curPath = _c[0], setCurPath = _c[1];
|
|
@@ -66,7 +67,7 @@ export var ProfileView = function (_a) {
|
|
|
66
67
|
var currentSearchString = useAppSelector(selectSearchNodeString);
|
|
67
68
|
var filterByFunctionString = useAppSelector(selectFilterByFunction);
|
|
68
69
|
var callgraphEnabled = useUIFeatureFlag('callgraph')[0];
|
|
69
|
-
var highlightAfterFilteringEnabled =
|
|
70
|
+
var highlightAfterFilteringEnabled = useUserPreference(USER_PREFERENCES.HIGHTLIGHT_AFTER_FILTERING.key)[0];
|
|
70
71
|
var _d = useParcaContext(), loader = _d.loader, perf = _d.perf;
|
|
71
72
|
useEffect(function () {
|
|
72
73
|
// Reset the current path when the profile source changes
|
|
@@ -137,5 +138,5 @@ export var ProfileView = function (_a) {
|
|
|
137
138
|
return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient, disabled: isLoading })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
|
|
138
139
|
e.preventDefault();
|
|
139
140
|
onDownloadPProf();
|
|
140
|
-
}, disabled: isLoading }, { children: "Download pprof" }))] })), _jsx(FilterByFunctionButton, {})] })), _jsxs("div", __assign({ className: "flex ml-auto gap-2" }, { children: [_jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })), callgraphEnabled ? (_jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Callgraph" }))) : null, _jsxs("div", __assign({ className: "flex" }, { children: [_jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] }))] })), isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(Profiler, __assign({ id: "icicleGraph", onRender: perf === null || perf === void 0 ? void 0 : perf.onRender }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) })) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] })))] }) }) })) }));
|
|
141
|
+
}, disabled: isLoading }, { children: "Download pprof" }))] })), _jsx(FilterByFunctionButton, {})] })), _jsxs("div", __assign({ className: "flex ml-auto gap-2" }, { children: [_jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })), callgraphEnabled ? (_jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Callgraph" }))) : null, _jsxs("div", __assign({ className: "flex" }, { children: [_jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] }))] })), isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(Profiler, __assign({ id: "icicleGraph", onRender: perf === null || perf === void 0 ? void 0 : perf.onRender }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit, onContainerResize: onFlamegraphContainerResize }) })) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] })))] }) }) })) }));
|
|
141
142
|
};
|
|
@@ -53,16 +53,35 @@ import { ProfileView, useProfileVisState } from './ProfileView';
|
|
|
53
53
|
import { downloadPprof } from './utils';
|
|
54
54
|
import { useGrpcMetadata, useParcaContext } from '@parca/components';
|
|
55
55
|
import { saveAsBlob } from '@parca/functions';
|
|
56
|
-
import { useEffect } from 'react';
|
|
56
|
+
import { useEffect, useState } from 'react';
|
|
57
|
+
import useUserPreference, { USER_PREFERENCES } from '@parca/functions/useUserPreference';
|
|
57
58
|
export var ProfileViewWithData = function (_a) {
|
|
58
59
|
var _b, _c;
|
|
59
60
|
var queryClient = _a.queryClient, profileSource = _a.profileSource, navigateTo = _a.navigateTo;
|
|
60
61
|
var profileVisState = useProfileVisState();
|
|
61
62
|
var metadata = useGrpcMetadata();
|
|
62
63
|
var currentView = profileVisState.currentView;
|
|
63
|
-
var _d =
|
|
64
|
+
var _d = useState(0), nodeTrimThreshold = _d[0], setNodeTrimThreshold = _d[1];
|
|
65
|
+
var disableTrimming = useUserPreference(USER_PREFERENCES.DISABLE_GRAPH_TRIMMING.key)[0];
|
|
66
|
+
useEffect(function () {
|
|
67
|
+
if (disableTrimming) {
|
|
68
|
+
setNodeTrimThreshold(0);
|
|
69
|
+
}
|
|
70
|
+
}, [disableTrimming]);
|
|
71
|
+
var onFlamegraphContainerResize = function (width) {
|
|
72
|
+
if (disableTrimming || width === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
var threshold = (1 / width) * 100;
|
|
76
|
+
if (threshold === nodeTrimThreshold) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
setNodeTrimThreshold(threshold);
|
|
80
|
+
};
|
|
81
|
+
var _e = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
|
|
64
82
|
skip: currentView !== 'icicle' && currentView !== 'both',
|
|
65
|
-
|
|
83
|
+
nodeTrimThreshold: nodeTrimThreshold,
|
|
84
|
+
}), flamegraphLoading = _e.isLoading, flamegraphResponse = _e.response, flamegraphError = _e.error;
|
|
66
85
|
var perf = useParcaContext().perf;
|
|
67
86
|
useEffect(function () {
|
|
68
87
|
var _a;
|
|
@@ -74,12 +93,12 @@ export var ProfileViewWithData = function (_a) {
|
|
|
74
93
|
}
|
|
75
94
|
perf === null || perf === void 0 ? void 0 : perf.markInteraction('Flamegraph Render', (_a = flamegraphResponse === null || flamegraphResponse === void 0 ? void 0 : flamegraphResponse.report) === null || _a === void 0 ? void 0 : _a.flamegraph.total);
|
|
76
95
|
}, [flamegraphLoading, flamegraphResponse, perf]);
|
|
77
|
-
var
|
|
96
|
+
var _f = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
|
|
78
97
|
skip: currentView !== 'table' && currentView !== 'both',
|
|
79
|
-
}), topTableLoading =
|
|
80
|
-
var
|
|
98
|
+
}), topTableLoading = _f.isLoading, topTableResponse = _f.response, topTableError = _f.error;
|
|
99
|
+
var _g = useQuery(queryClient, profileSource, QueryRequest_ReportType.CALLGRAPH, {
|
|
81
100
|
skip: currentView !== 'callgraph',
|
|
82
|
-
}), callgraphLoading =
|
|
101
|
+
}), callgraphLoading = _g.isLoading, callgraphResponse = _g.response, callgraphError = _g.error;
|
|
83
102
|
var sampleUnit = profileSource.ProfileType().sampleUnit;
|
|
84
103
|
var downloadPProfClick = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
85
104
|
var blob, error_1;
|
|
@@ -121,6 +140,6 @@ export var ProfileViewWithData = function (_a) {
|
|
|
121
140
|
? (_c = callgraphResponse === null || callgraphResponse === void 0 ? void 0 : callgraphResponse.report) === null || _c === void 0 ? void 0 : _c.callgraph
|
|
122
141
|
: undefined,
|
|
123
142
|
error: callgraphError,
|
|
124
|
-
}, profileVisState: profileVisState, sampleUnit: sampleUnit, profileSource: profileSource, queryClient: queryClient, navigateTo: navigateTo, onDownloadPProf: function () { return void downloadPProfClick(); } }));
|
|
143
|
+
}, profileVisState: profileVisState, sampleUnit: sampleUnit, profileSource: profileSource, queryClient: queryClient, navigateTo: navigateTo, onDownloadPProf: function () { return void downloadPProfClick(); }, onFlamegraphContainerResize: onFlamegraphContainerResize }));
|
|
125
144
|
};
|
|
126
145
|
export default ProfileViewWithData;
|
package/dist/useQuery.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface IQueryResult {
|
|
|
8
8
|
}
|
|
9
9
|
interface UseQueryOptions {
|
|
10
10
|
skip?: boolean;
|
|
11
|
+
nodeTrimThreshold?: number;
|
|
11
12
|
}
|
|
12
13
|
export declare const useQuery: (client: QueryServiceClient, profileSource: ProfileSource, reportType: QueryRequest_ReportType, options?: UseQueryOptions) => IQueryResult;
|
|
13
14
|
export {};
|
package/dist/useQuery.js
CHANGED
|
@@ -52,7 +52,7 @@ export var useQuery = function (client, profileSource, reportType, options) {
|
|
|
52
52
|
var _a = (options !== null && options !== void 0 ? options : {}).skip, skip = _a === void 0 ? false : _a;
|
|
53
53
|
var metadata = useGrpcMetadata();
|
|
54
54
|
var _b = useGrpcQuery({
|
|
55
|
-
key: ['query', profileSource, reportType],
|
|
55
|
+
key: ['query', profileSource, reportType, options === null || options === void 0 ? void 0 : options.nodeTrimThreshold],
|
|
56
56
|
queryFn: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
57
57
|
var req, response;
|
|
58
58
|
return __generator(this, function (_a) {
|
|
@@ -60,6 +60,7 @@ export var useQuery = function (client, profileSource, reportType, options) {
|
|
|
60
60
|
case 0:
|
|
61
61
|
req = profileSource.QueryRequest();
|
|
62
62
|
req.reportType = reportType;
|
|
63
|
+
req.nodeTrimThreshold = options === null || options === void 0 ? void 0 : options.nodeTrimThreshold;
|
|
63
64
|
return [4 /*yield*/, client.query(req, { meta: metadata })];
|
|
64
65
|
case 1:
|
|
65
66
|
response = (_a.sent()).response;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.90",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@parca/client": "^0.16.
|
|
7
|
-
"@parca/components": "^0.16.
|
|
6
|
+
"@parca/client": "^0.16.62",
|
|
7
|
+
"@parca/components": "^0.16.79",
|
|
8
8
|
"@parca/dynamicsize": "^0.16.52",
|
|
9
|
-
"@parca/functions": "^0.16.
|
|
9
|
+
"@parca/functions": "^0.16.55",
|
|
10
10
|
"@parca/parser": "^0.16.52",
|
|
11
11
|
"@parca/store": "^0.16.51",
|
|
12
12
|
"d3": "7.7.0",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"access": "public",
|
|
43
43
|
"registry": "https://registry.npmjs.org/"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "4a9035ae2d347b9e419e81ed27d587658c8f3fdd"
|
|
46
46
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -383,7 +383,7 @@ export default function IcicleGraph({
|
|
|
383
383
|
if (ref.current != null) {
|
|
384
384
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
385
385
|
}
|
|
386
|
-
}, [width]);
|
|
386
|
+
}, [width, graph]);
|
|
387
387
|
|
|
388
388
|
const total = useMemo(() => parseFloat(graph.total), [graph.total]);
|
|
389
389
|
const xScale = useMemo(() => {
|
|
@@ -17,6 +17,11 @@ import {useContainerDimensions} from '@parca/dynamicsize';
|
|
|
17
17
|
|
|
18
18
|
import DiffLegend from '../components/DiffLegend';
|
|
19
19
|
import IcicleGraph from '../IcicleGraph';
|
|
20
|
+
import {useEffect, useMemo} from 'react';
|
|
21
|
+
|
|
22
|
+
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
23
|
+
|
|
24
|
+
export type ResizeHandler = (width: number, height: number) => void;
|
|
20
25
|
|
|
21
26
|
interface ProfileIcicleGraphProps {
|
|
22
27
|
width?: number;
|
|
@@ -24,6 +29,7 @@ interface ProfileIcicleGraphProps {
|
|
|
24
29
|
sampleUnit: string;
|
|
25
30
|
curPath: string[] | [];
|
|
26
31
|
setNewCurPath: (path: string[]) => void;
|
|
32
|
+
onContainerResize?: ResizeHandler;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
const ProfileIcicleGraph = ({
|
|
@@ -31,10 +37,38 @@ const ProfileIcicleGraph = ({
|
|
|
31
37
|
curPath,
|
|
32
38
|
setNewCurPath,
|
|
33
39
|
sampleUnit,
|
|
40
|
+
onContainerResize,
|
|
34
41
|
}: ProfileIcicleGraphProps): JSX.Element => {
|
|
35
42
|
const compareMode = useAppSelector(selectCompareMode);
|
|
36
43
|
const {ref, dimensions} = useContainerDimensions();
|
|
37
44
|
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (dimensions === undefined) return;
|
|
47
|
+
if (onContainerResize === undefined) return;
|
|
48
|
+
|
|
49
|
+
onContainerResize(dimensions.width, dimensions.height);
|
|
50
|
+
}, [dimensions, onContainerResize]);
|
|
51
|
+
|
|
52
|
+
const [trimDifference, trimmedPercentage, formattedTotal, formattedUntrimmedTotal] =
|
|
53
|
+
useMemo(() => {
|
|
54
|
+
if (graph === undefined || graph.untrimmedTotal === '0') {
|
|
55
|
+
return [BigInt(0), '0'];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const untrimmedTotal = BigInt(graph.untrimmedTotal);
|
|
59
|
+
const total = BigInt(graph.total);
|
|
60
|
+
|
|
61
|
+
const trimDifference = untrimmedTotal - total;
|
|
62
|
+
const trimmedPercentage = (total * BigInt(100)) / untrimmedTotal;
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
trimDifference,
|
|
66
|
+
trimmedPercentage.toString(),
|
|
67
|
+
numberFormatter.format(total),
|
|
68
|
+
numberFormatter.format(untrimmedTotal),
|
|
69
|
+
];
|
|
70
|
+
}, [graph]);
|
|
71
|
+
|
|
38
72
|
if (graph === undefined) return <div>no data...</div>;
|
|
39
73
|
const total = graph.total;
|
|
40
74
|
if (parseFloat(total) === 0) return <>Profile has no samples</>;
|
|
@@ -42,6 +76,11 @@ const ProfileIcicleGraph = ({
|
|
|
42
76
|
return (
|
|
43
77
|
<>
|
|
44
78
|
{compareMode && <DiffLegend />}
|
|
79
|
+
{trimDifference > BigInt(0) ? (
|
|
80
|
+
<p className="my-2 text-sm">
|
|
81
|
+
Showing {formattedTotal}({trimmedPercentage}%) out of {formattedUntrimmedTotal} samples
|
|
82
|
+
</p>
|
|
83
|
+
) : null}
|
|
45
84
|
<div ref={ref}>
|
|
46
85
|
<IcicleGraph
|
|
47
86
|
width={dimensions?.width}
|
|
@@ -31,12 +31,13 @@ import {
|
|
|
31
31
|
import {Callgraph} from '../';
|
|
32
32
|
import ProfileShareButton from '../components/ProfileShareButton';
|
|
33
33
|
import FilterByFunctionButton from './FilterByFunctionButton';
|
|
34
|
-
import ProfileIcicleGraph from '../ProfileIcicleGraph';
|
|
34
|
+
import ProfileIcicleGraph, {ResizeHandler} from '../ProfileIcicleGraph';
|
|
35
35
|
import {ProfileSource} from '../ProfileSource';
|
|
36
36
|
import TopTable from '../TopTable';
|
|
37
37
|
import useDelayedLoader from '../useDelayedLoader';
|
|
38
38
|
|
|
39
39
|
import '../ProfileView.styles.css';
|
|
40
|
+
import useUserPreference, {USER_PREFERENCES} from '@parca/functions/useUserPreference';
|
|
40
41
|
|
|
41
42
|
type NavigateFunction = (path: string, queryParams: any) => void;
|
|
42
43
|
|
|
@@ -76,6 +77,7 @@ export interface ProfileViewProps {
|
|
|
76
77
|
navigateTo?: NavigateFunction;
|
|
77
78
|
compare?: boolean;
|
|
78
79
|
onDownloadPProf: () => void;
|
|
80
|
+
onFlamegraphContainerResize?: ResizeHandler;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
function arrayEquals<T>(a: T[], b: T[]): boolean {
|
|
@@ -113,6 +115,7 @@ export const ProfileView = ({
|
|
|
113
115
|
navigateTo,
|
|
114
116
|
profileVisState,
|
|
115
117
|
onDownloadPProf,
|
|
118
|
+
onFlamegraphContainerResize,
|
|
116
119
|
}: ProfileViewProps): JSX.Element => {
|
|
117
120
|
const dispatch = useAppDispatch();
|
|
118
121
|
const {ref, dimensions} = useContainerDimensions();
|
|
@@ -123,7 +126,9 @@ export const ProfileView = ({
|
|
|
123
126
|
const filterByFunctionString = useAppSelector(selectFilterByFunction);
|
|
124
127
|
|
|
125
128
|
const [callgraphEnabled] = useUIFeatureFlag('callgraph');
|
|
126
|
-
const [highlightAfterFilteringEnabled] =
|
|
129
|
+
const [highlightAfterFilteringEnabled] = useUserPreference<boolean>(
|
|
130
|
+
USER_PREFERENCES.HIGHTLIGHT_AFTER_FILTERING.key
|
|
131
|
+
);
|
|
127
132
|
|
|
128
133
|
const {loader, perf} = useParcaContext();
|
|
129
134
|
|
|
@@ -305,6 +310,7 @@ export const ProfileView = ({
|
|
|
305
310
|
setNewCurPath={setNewCurPath}
|
|
306
311
|
graph={flamegraphData.data}
|
|
307
312
|
sampleUnit={sampleUnit}
|
|
313
|
+
onContainerResize={onFlamegraphContainerResize}
|
|
308
314
|
/>
|
|
309
315
|
</Profiler>
|
|
310
316
|
</div>
|
|
@@ -19,7 +19,8 @@ import {ProfileSource} from './ProfileSource';
|
|
|
19
19
|
import {downloadPprof} from './utils';
|
|
20
20
|
import {useGrpcMetadata, useParcaContext} from '@parca/components';
|
|
21
21
|
import {saveAsBlob} from '@parca/functions';
|
|
22
|
-
import {useEffect} from 'react';
|
|
22
|
+
import {useEffect, useState} from 'react';
|
|
23
|
+
import useUserPreference, {USER_PREFERENCES} from '@parca/functions/useUserPreference';
|
|
23
24
|
|
|
24
25
|
export type NavigateFunction = (path: string, queryParams: any) => void;
|
|
25
26
|
|
|
@@ -38,12 +39,33 @@ export const ProfileViewWithData = ({
|
|
|
38
39
|
const profileVisState = useProfileVisState();
|
|
39
40
|
const metadata = useGrpcMetadata();
|
|
40
41
|
const {currentView} = profileVisState;
|
|
42
|
+
const [nodeTrimThreshold, setNodeTrimThreshold] = useState<number>(0);
|
|
43
|
+
const [disableTrimming] = useUserPreference<boolean>(USER_PREFERENCES.DISABLE_GRAPH_TRIMMING.key);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (disableTrimming) {
|
|
47
|
+
setNodeTrimThreshold(0);
|
|
48
|
+
}
|
|
49
|
+
}, [disableTrimming]);
|
|
50
|
+
|
|
51
|
+
const onFlamegraphContainerResize = (width: number): void => {
|
|
52
|
+
if (disableTrimming || width === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const threshold = (1 / width) * 100;
|
|
56
|
+
if (threshold === nodeTrimThreshold) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setNodeTrimThreshold(threshold);
|
|
60
|
+
};
|
|
61
|
+
|
|
41
62
|
const {
|
|
42
63
|
isLoading: flamegraphLoading,
|
|
43
64
|
response: flamegraphResponse,
|
|
44
65
|
error: flamegraphError,
|
|
45
66
|
} = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
|
|
46
67
|
skip: currentView !== 'icicle' && currentView !== 'both',
|
|
68
|
+
nodeTrimThreshold,
|
|
47
69
|
});
|
|
48
70
|
const {perf} = useParcaContext();
|
|
49
71
|
|
|
@@ -120,6 +142,7 @@ export const ProfileViewWithData = ({
|
|
|
120
142
|
queryClient={queryClient}
|
|
121
143
|
navigateTo={navigateTo}
|
|
122
144
|
onDownloadPProf={() => void downloadPProfClick()}
|
|
145
|
+
onFlamegraphContainerResize={onFlamegraphContainerResize}
|
|
123
146
|
/>
|
|
124
147
|
);
|
|
125
148
|
};
|
package/src/useQuery.tsx
CHANGED
|
@@ -26,6 +26,7 @@ export interface IQueryResult {
|
|
|
26
26
|
|
|
27
27
|
interface UseQueryOptions {
|
|
28
28
|
skip?: boolean;
|
|
29
|
+
nodeTrimThreshold?: number;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export const useQuery = (
|
|
@@ -37,10 +38,11 @@ export const useQuery = (
|
|
|
37
38
|
const {skip = false} = options ?? {};
|
|
38
39
|
const metadata = useGrpcMetadata();
|
|
39
40
|
const {data, isLoading, error} = useGrpcQuery<QueryResponse | undefined>({
|
|
40
|
-
key: ['query', profileSource, reportType],
|
|
41
|
+
key: ['query', profileSource, reportType, options?.nodeTrimThreshold],
|
|
41
42
|
queryFn: async () => {
|
|
42
43
|
const req = profileSource.QueryRequest();
|
|
43
44
|
req.reportType = reportType;
|
|
45
|
+
req.nodeTrimThreshold = options?.nodeTrimThreshold;
|
|
44
46
|
|
|
45
47
|
const {response} = await client.query(req, {meta: metadata});
|
|
46
48
|
return response;
|