@parca/profile 0.15.17 → 0.15.24
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 +51 -0
- package/package.json +7 -4
- package/src/IcicleGraph.tsx +3 -2
- package/src/ProfileView.tsx +26 -30
- package/src/ProfileViewWithData.tsx +20 -0
- package/src/TopTable.tsx +27 -10
- package/src/components/ProfileShareButton/index.tsx +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,57 @@
|
|
|
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.15.24](https://github.com/parca-dev/parca/compare/ui-v0.15.23...ui-v0.15.24) (2022-09-13)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.19) (2022-09-08)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **ui:** type all the things (string mode) ([e91ac9d](https://github.com/parca-dev/parca/commit/e91ac9deb8a200ed3c9a9c5d81ae71e13d49466e))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## 0.15.10 (2022-08-30)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.18...ui-v0.15.19) (2022-09-08)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
|
|
38
|
+
|
|
39
|
+
**Note:** Version bump only for package @parca/profile
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## [0.15.18](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.18) (2022-09-08)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
|
|
50
|
+
|
|
51
|
+
**Note:** Version bump only for package @parca/profile
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
6
57
|
## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.16...ui-v0.15.17) (2022-09-07)
|
|
7
58
|
|
|
8
59
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.24",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@iconify/react": "^3.2.2",
|
|
7
7
|
"@parca/client": "^0.15.11",
|
|
8
8
|
"@parca/dynamicsize": "^0.15.17",
|
|
9
|
-
"@parca/functions": "^0.15.
|
|
10
|
-
"@parca/parser": "^0.15.
|
|
9
|
+
"@parca/functions": "^0.15.24",
|
|
10
|
+
"@parca/parser": "^0.15.24",
|
|
11
11
|
"d3-scale": "^4.0.2",
|
|
12
12
|
"d3-selection": "3.0.0",
|
|
13
13
|
"react-copy-to-clipboard": "^5.1.0"
|
|
14
14
|
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react-copy-to-clipboard": "5.0.4"
|
|
17
|
+
},
|
|
15
18
|
"main": "src/index.tsx",
|
|
16
19
|
"scripts": {
|
|
17
20
|
"test": "jest --coverage --config ../../../jest.config.js ./src/*"
|
|
@@ -23,5 +26,5 @@
|
|
|
23
26
|
"access": "public",
|
|
24
27
|
"registry": "https://registry.npmjs.org/"
|
|
25
28
|
},
|
|
26
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "3cc65f6eecfb39e80acd6ec0888834c67a4f89d1"
|
|
27
30
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {getLastItem, diffColor, isSearchMatch} from '@parca/functions';
|
|
|
22
22
|
import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
|
|
23
23
|
|
|
24
24
|
import {hexifyAddress} from './utils';
|
|
25
|
+
import {HoveringNode} from '@parca/components/src/GraphTooltip';
|
|
25
26
|
|
|
26
27
|
interface IcicleGraphProps {
|
|
27
28
|
graph: Flamegraph;
|
|
@@ -260,7 +261,7 @@ export function IcicleGraphRootNode({
|
|
|
260
261
|
const onClick = (): void => setCurPath([]);
|
|
261
262
|
const onMouseEnter = (): void => setHoveringNode(node);
|
|
262
263
|
const onMouseLeave = (): void => setHoveringNode(undefined);
|
|
263
|
-
const path = [];
|
|
264
|
+
const path: string[] = [];
|
|
264
265
|
|
|
265
266
|
return (
|
|
266
267
|
<g transform={'translate(0, 0)'}>
|
|
@@ -336,7 +337,7 @@ export default function IcicleGraph({
|
|
|
336
337
|
total={total}
|
|
337
338
|
x={pos[0]}
|
|
338
339
|
y={pos[1]}
|
|
339
|
-
hoveringNode={hoveringNode}
|
|
340
|
+
hoveringNode={hoveringNode as HoveringNode}
|
|
340
341
|
contextElement={svg.current}
|
|
341
342
|
/>
|
|
342
343
|
<svg
|
package/src/ProfileView.tsx
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
Button,
|
|
21
21
|
Card,
|
|
22
22
|
SearchNodes,
|
|
23
|
-
useGrpcMetadata,
|
|
24
23
|
useParcaTheme,
|
|
25
24
|
Callgraph as CallgraphComponent,
|
|
26
25
|
} from '@parca/components';
|
|
@@ -31,7 +30,6 @@ import ProfileIcicleGraph from './ProfileIcicleGraph';
|
|
|
31
30
|
import {ProfileSource} from './ProfileSource';
|
|
32
31
|
import TopTable from './TopTable';
|
|
33
32
|
import useDelayedLoader from './useDelayedLoader';
|
|
34
|
-
import {downloadPprof} from './utils';
|
|
35
33
|
|
|
36
34
|
import './ProfileView.styles.css';
|
|
37
35
|
|
|
@@ -55,7 +53,7 @@ interface CallgraphData {
|
|
|
55
53
|
error?: any;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
|
|
56
|
+
export type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
|
|
59
57
|
|
|
60
58
|
interface ProfileVisState {
|
|
61
59
|
currentView: VisualizationType;
|
|
@@ -72,6 +70,7 @@ interface ProfileViewProps {
|
|
|
72
70
|
queryClient?: QueryServiceClient;
|
|
73
71
|
navigateTo?: NavigateFunction;
|
|
74
72
|
compare?: boolean;
|
|
73
|
+
onDownloadPProf: () => void;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
function arrayEquals<T>(a: T[], b: T[]): boolean {
|
|
@@ -83,11 +82,18 @@ function arrayEquals<T>(a: T[], b: T[]): boolean {
|
|
|
83
82
|
);
|
|
84
83
|
}
|
|
85
84
|
export const useProfileVisState = (): ProfileVisState => {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const [currentView, setCurrentView] = useState<VisualizationType>(() => {
|
|
86
|
+
if (typeof window === 'undefined') {
|
|
87
|
+
return 'icicle';
|
|
88
|
+
}
|
|
89
|
+
const router = parseParams(window.location.search);
|
|
90
|
+
const currentViewFromURL = router.currentProfileView as string;
|
|
91
|
+
|
|
92
|
+
if (currentViewFromURL != null) {
|
|
93
|
+
return currentViewFromURL as VisualizationType;
|
|
94
|
+
}
|
|
95
|
+
return 'icicle';
|
|
96
|
+
});
|
|
91
97
|
|
|
92
98
|
return {currentView, setCurrentView};
|
|
93
99
|
};
|
|
@@ -101,6 +107,7 @@ export const ProfileView = ({
|
|
|
101
107
|
queryClient,
|
|
102
108
|
navigateTo,
|
|
103
109
|
profileVisState,
|
|
110
|
+
onDownloadPProf,
|
|
104
111
|
}: ProfileViewProps): JSX.Element => {
|
|
105
112
|
const {ref, dimensions} = useContainerDimensions();
|
|
106
113
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
@@ -108,7 +115,6 @@ export const ProfileView = ({
|
|
|
108
115
|
|
|
109
116
|
const [callgraphEnabled] = useUIFeatureFlag('callgraph');
|
|
110
117
|
|
|
111
|
-
const metadata = useGrpcMetadata();
|
|
112
118
|
const {loader} = useParcaTheme();
|
|
113
119
|
|
|
114
120
|
useEffect(() => {
|
|
@@ -147,23 +153,6 @@ export const ProfileView = ({
|
|
|
147
153
|
);
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
const downloadPProf = async (e: React.MouseEvent<HTMLElement>): Promise<void> => {
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
if (profileSource == null || queryClient == null) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
|
|
158
|
-
const link = document.createElement('a');
|
|
159
|
-
link.href = window.URL.createObjectURL(blob);
|
|
160
|
-
link.download = 'profile.pb.gz';
|
|
161
|
-
link.click();
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('Error while querying', error);
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
156
|
const resetIcicleGraph = (): void => setCurPath([]);
|
|
168
157
|
|
|
169
158
|
const setNewCurPath = (path: string[]): void => {
|
|
@@ -176,11 +165,12 @@ export const ProfileView = ({
|
|
|
176
165
|
if (view == null) {
|
|
177
166
|
return;
|
|
178
167
|
}
|
|
179
|
-
if (navigateTo === undefined) return;
|
|
180
|
-
|
|
181
168
|
setCurrentView(view);
|
|
182
|
-
const router = parseParams(window.location.search);
|
|
183
169
|
|
|
170
|
+
if (navigateTo === undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const router = parseParams(window.location.search);
|
|
184
174
|
navigateTo('/', {...router, ...{currentProfileView: view}});
|
|
185
175
|
};
|
|
186
176
|
|
|
@@ -199,7 +189,13 @@ export const ProfileView = ({
|
|
|
199
189
|
/>
|
|
200
190
|
) : null}
|
|
201
191
|
|
|
202
|
-
<Button
|
|
192
|
+
<Button
|
|
193
|
+
color="neutral"
|
|
194
|
+
onClick={e => {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
onDownloadPProf();
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
203
199
|
Download pprof
|
|
204
200
|
</Button>
|
|
205
201
|
</div>
|
|
@@ -16,6 +16,8 @@ import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
|
|
|
16
16
|
import {useQuery} from './useQuery';
|
|
17
17
|
import {ProfileView, useProfileVisState} from './ProfileView';
|
|
18
18
|
import {ProfileSource} from './ProfileSource';
|
|
19
|
+
import {downloadPprof} from './utils';
|
|
20
|
+
import {useGrpcMetadata} from '@parca/components';
|
|
19
21
|
|
|
20
22
|
type NavigateFunction = (path: string, queryParams: any) => void;
|
|
21
23
|
|
|
@@ -32,6 +34,7 @@ export const ProfileViewWithData = ({
|
|
|
32
34
|
navigateTo,
|
|
33
35
|
}: ProfileViewWithDataProps): JSX.Element => {
|
|
34
36
|
const profileVisState = useProfileVisState();
|
|
37
|
+
const metadata = useGrpcMetadata();
|
|
35
38
|
const {currentView} = profileVisState;
|
|
36
39
|
const {
|
|
37
40
|
isLoading: flamegraphLoading,
|
|
@@ -59,6 +62,22 @@ export const ProfileViewWithData = ({
|
|
|
59
62
|
|
|
60
63
|
const sampleUnit = profileSource.ProfileType().sampleUnit;
|
|
61
64
|
|
|
65
|
+
const downloadPProfClick = async (): Promise<void> => {
|
|
66
|
+
if (profileSource == null || queryClient == null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
|
|
72
|
+
const link = document.createElement('a');
|
|
73
|
+
link.href = window.URL.createObjectURL(blob);
|
|
74
|
+
link.download = 'profile.pb.gz';
|
|
75
|
+
link.click();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error while querying', error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
62
81
|
return (
|
|
63
82
|
<ProfileView
|
|
64
83
|
flamegraphData={{
|
|
@@ -88,6 +107,7 @@ export const ProfileViewWithData = ({
|
|
|
88
107
|
profileSource={profileSource}
|
|
89
108
|
queryClient={queryClient}
|
|
90
109
|
navigateTo={navigateTo}
|
|
110
|
+
onDownloadPProf={() => void downloadPProfClick()}
|
|
91
111
|
/>
|
|
92
112
|
);
|
|
93
113
|
};
|
package/src/TopTable.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import React from 'react';
|
|
|
15
15
|
|
|
16
16
|
import {getLastItem, valueFormatter, isSearchMatch} from '@parca/functions';
|
|
17
17
|
import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
|
|
18
|
-
import {TopNodeMeta, Top} from '@parca/client';
|
|
18
|
+
import {TopNode, TopNodeMeta, Top} from '@parca/client';
|
|
19
19
|
|
|
20
20
|
import {hexifyAddress} from './utils';
|
|
21
21
|
|
|
@@ -43,7 +43,10 @@ const Arrow = ({direction}: {direction: string | undefined}): JSX.Element => {
|
|
|
43
43
|
|
|
44
44
|
const useSortableData = (
|
|
45
45
|
top?: Top,
|
|
46
|
-
config
|
|
46
|
+
config: {key: keyof TopNode | 'name'; direction: 'asc' | 'desc'} = {
|
|
47
|
+
key: 'cumulative',
|
|
48
|
+
direction: 'desc',
|
|
49
|
+
}
|
|
47
50
|
): {
|
|
48
51
|
items:
|
|
49
52
|
| Array<{
|
|
@@ -54,17 +57,20 @@ const useSortableData = (
|
|
|
54
57
|
meta?: TopNodeMeta | undefined;
|
|
55
58
|
}>
|
|
56
59
|
| undefined;
|
|
57
|
-
requestSort: (key:
|
|
58
|
-
sortConfig: {key:
|
|
60
|
+
requestSort: (key: keyof TopNode | 'name') => void;
|
|
61
|
+
sortConfig: {key: keyof TopNode | 'name'; direction: string} | null;
|
|
59
62
|
} => {
|
|
60
|
-
const [sortConfig, setSortConfig] = React.useState<{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const [sortConfig, setSortConfig] = React.useState<{
|
|
64
|
+
key: keyof TopNode | 'name';
|
|
65
|
+
direction: string;
|
|
66
|
+
} | null>(config);
|
|
63
67
|
|
|
64
68
|
const rawTableReport = top != null ? top.list : [];
|
|
65
69
|
|
|
66
70
|
const items = rawTableReport.map(node => ({
|
|
67
71
|
...node,
|
|
72
|
+
// Warning: string to number can overflow
|
|
73
|
+
// https://github.com/timostamm/protobuf-ts/blob/master/MANUAL.md#bigint-support
|
|
68
74
|
diff: Number(node.diff),
|
|
69
75
|
cumulative: Number(node.cumulative),
|
|
70
76
|
flat: Number(node.flat),
|
|
@@ -77,10 +83,21 @@ const useSortableData = (
|
|
|
77
83
|
const sortableItems = [...items];
|
|
78
84
|
if (sortConfig !== null) {
|
|
79
85
|
sortableItems.sort((a, b) => {
|
|
80
|
-
|
|
86
|
+
const itemA = a[sortConfig.key];
|
|
87
|
+
const itemB = b[sortConfig.key];
|
|
88
|
+
if (itemA === undefined && itemB === undefined) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
if (itemA === undefined) {
|
|
92
|
+
return sortConfig.direction === 'asc' ? -1 : 1;
|
|
93
|
+
}
|
|
94
|
+
if (itemB === undefined) {
|
|
95
|
+
return sortConfig.direction === 'asc' ? 1 : -1;
|
|
96
|
+
}
|
|
97
|
+
if (itemA < itemB) {
|
|
81
98
|
return sortConfig.direction === 'asc' ? -1 : 1;
|
|
82
99
|
}
|
|
83
|
-
if (
|
|
100
|
+
if (itemA > itemB) {
|
|
84
101
|
return sortConfig.direction === 'asc' ? 1 : -1;
|
|
85
102
|
}
|
|
86
103
|
return 0;
|
|
@@ -89,7 +106,7 @@ const useSortableData = (
|
|
|
89
106
|
return sortableItems;
|
|
90
107
|
}, [items, sortConfig]);
|
|
91
108
|
|
|
92
|
-
const requestSort = (key:
|
|
109
|
+
const requestSort = (key: keyof TopNode | 'name'): void => {
|
|
93
110
|
let direction = 'desc';
|
|
94
111
|
if (sortConfig != null && sortConfig.key === key && sortConfig.direction === 'desc') {
|
|
95
112
|
direction = 'asc';
|
|
@@ -54,9 +54,13 @@ const ProfileShareModal = ({
|
|
|
54
54
|
setLoading(false);
|
|
55
55
|
setIsShared(true);
|
|
56
56
|
} catch (err) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (err instanceof Error) {
|
|
58
|
+
console.error(err);
|
|
59
|
+
setLoading(false);
|
|
60
|
+
// https://github.com/microsoft/TypeScript/issues/38347
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
62
|
+
setError(err.toString());
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
65
|
};
|
|
62
66
|
|