@parca/profile 0.14.12 → 0.14.29
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 +22 -0
- package/package.json +5 -5
- package/src/IcicleGraph.tsx +15 -1
- package/src/ProfileIcicleGraph.tsx +14 -2
- package/src/ProfileSource.tsx +13 -0
- package/src/ProfileView.tsx +107 -60
- package/src/ProfileViewWithData.tsx +79 -0
- package/src/TopTable.tsx +25 -32
- package/src/__tests__/suffix_params.test.ts +13 -0
- package/src/components/DiffLegend.tsx +13 -0
- package/src/components/ProfileShareButton/ResultBox.tsx +13 -0
- package/src/components/ProfileShareButton/index.tsx +13 -0
- package/src/index.tsx +14 -0
- package/src/useDelayedLoader.ts +39 -0
- package/src/useQuery.tsx +26 -2
- package/src/utils.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.14.29](https://github.com/parca-dev/parca/compare/ui-v0.14.28...ui-v0.14.29) (2022-08-15)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## 0.14.21 (2022-08-10)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [0.14.16](https://github.com/parca-dev/parca/compare/ui-v0.14.15...ui-v0.14.16) (2022-08-03)
|
|
19
|
+
|
|
20
|
+
**Note:** Version bump only for package @parca/profile
|
|
21
|
+
|
|
22
|
+
## [0.14.13](https://github.com/parca-dev/parca/compare/ui-v0.14.12...ui-v0.14.13) (2022-08-03)
|
|
23
|
+
|
|
24
|
+
## [0.14.7](https://github.com/parca-dev/parca/compare/ui-v0.14.5...ui-v0.14.7) (2022-07-27)
|
|
25
|
+
|
|
26
|
+
**Note:** Version bump only for package @parca/profile
|
|
27
|
+
|
|
6
28
|
## [0.14.12](https://github.com/parca-dev/parca/compare/ui-v0.14.11...ui-v0.14.12) (2022-08-02)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @parca/profile
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.29",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@iconify/react": "^3.2.2",
|
|
7
|
-
"@parca/client": "^0.14.
|
|
8
|
-
"@parca/dynamicsize": "^0.14.
|
|
9
|
-
"@parca/parser": "^0.14.
|
|
7
|
+
"@parca/client": "^0.14.16",
|
|
8
|
+
"@parca/dynamicsize": "^0.14.29",
|
|
9
|
+
"@parca/parser": "^0.14.29",
|
|
10
10
|
"d3-scale": "^4.0.2",
|
|
11
11
|
"d3-selection": "3.0.0",
|
|
12
12
|
"react-copy-to-clipboard": "^5.1.0"
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"access": "public",
|
|
23
23
|
"registry": "https://registry.npmjs.org/"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "e6dabbc59485409b1fc818d5c56c7999d9a22f17"
|
|
26
26
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import React, {MouseEvent, useEffect, useRef, useState} from 'react';
|
|
15
|
+
|
|
2
16
|
import {throttle} from 'lodash';
|
|
3
17
|
import {pointer} from 'd3-selection';
|
|
4
18
|
import {scaleLinear} from 'd3-scale';
|
|
@@ -185,7 +199,7 @@ export function IcicleGraphNodes({
|
|
|
185
199
|
const onMouseLeave = () => setHoveringNode(undefined);
|
|
186
200
|
|
|
187
201
|
return (
|
|
188
|
-
<React.Fragment>
|
|
202
|
+
<React.Fragment key={`node-${key}`}>
|
|
189
203
|
<IcicleRect
|
|
190
204
|
key={`rect-${key}`}
|
|
191
205
|
x={xStart}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {Flamegraph} from '@parca/client';
|
|
2
15
|
import {useAppSelector, selectCompareMode} from '@parca/store';
|
|
3
16
|
import {useContainerDimensions} from '@parca/dynamicsize';
|
|
@@ -20,13 +33,12 @@ const ProfileIcicleGraph = ({
|
|
|
20
33
|
sampleUnit,
|
|
21
34
|
}: ProfileIcicleGraphProps) => {
|
|
22
35
|
const compareMode = useAppSelector(selectCompareMode);
|
|
36
|
+
const {ref, dimensions} = useContainerDimensions();
|
|
23
37
|
|
|
24
38
|
if (graph === undefined) return <div>no data...</div>;
|
|
25
39
|
const total = graph.total;
|
|
26
40
|
if (parseFloat(total) === 0) return <>Profile has no samples</>;
|
|
27
41
|
|
|
28
|
-
const {ref, dimensions} = useContainerDimensions();
|
|
29
|
-
|
|
30
42
|
return (
|
|
31
43
|
<>
|
|
32
44
|
{compareMode && <DiffLegend />}
|
package/src/ProfileSource.tsx
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import React from 'react';
|
|
2
15
|
import {formatDate} from '@parca/functions';
|
|
3
16
|
import {Query, ProfileType} from '@parca/parser';
|
package/src/ProfileView.tsx
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import React, {useEffect, useMemo, useState} from 'react';
|
|
15
|
+
|
|
2
16
|
import {parseParams} from '@parca/functions';
|
|
3
|
-
import {QueryServiceClient,
|
|
17
|
+
import {QueryServiceClient, Flamegraph, Top} from '@parca/client';
|
|
4
18
|
import {Button, Card, SearchNodes, useGrpcMetadata, useParcaTheme} from '@parca/components';
|
|
5
19
|
|
|
6
20
|
import ProfileShareButton from './components/ProfileShareButton';
|
|
7
21
|
import ProfileIcicleGraph from './ProfileIcicleGraph';
|
|
8
22
|
import {ProfileSource} from './ProfileSource';
|
|
9
|
-
import {useQuery} from './useQuery';
|
|
10
23
|
import TopTable from './TopTable';
|
|
24
|
+
import useDelayedLoader from './useDelayedLoader';
|
|
11
25
|
import {downloadPprof} from './utils';
|
|
12
26
|
|
|
13
27
|
import './ProfileView.styles.css';
|
|
14
28
|
|
|
15
29
|
type NavigateFunction = (path: string, queryParams: any) => void;
|
|
16
30
|
|
|
31
|
+
interface FlamegraphData {
|
|
32
|
+
loading: boolean;
|
|
33
|
+
data?: Flamegraph;
|
|
34
|
+
error?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TopTableData {
|
|
38
|
+
loading: boolean;
|
|
39
|
+
data?: Top;
|
|
40
|
+
error?: any;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type VisualizationType = 'icicle' | 'table' | 'both';
|
|
44
|
+
|
|
45
|
+
interface ProfileVisState {
|
|
46
|
+
currentView: VisualizationType;
|
|
47
|
+
setCurrentView: (view: VisualizationType) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
17
50
|
interface ProfileViewProps {
|
|
18
|
-
|
|
19
|
-
|
|
51
|
+
flamegraphData?: FlamegraphData;
|
|
52
|
+
topTableData?: TopTableData;
|
|
53
|
+
sampleUnit: string;
|
|
54
|
+
profileVisState: ProfileVisState;
|
|
55
|
+
profileSource?: ProfileSource;
|
|
56
|
+
queryClient?: QueryServiceClient;
|
|
20
57
|
navigateTo?: NavigateFunction;
|
|
21
58
|
compare?: boolean;
|
|
22
59
|
}
|
|
@@ -29,22 +66,28 @@ function arrayEquals(a, b): boolean {
|
|
|
29
66
|
a.every((val, index) => val === b[index])
|
|
30
67
|
);
|
|
31
68
|
}
|
|
69
|
+
export const useProfileVisState = (): ProfileVisState => {
|
|
70
|
+
const router = parseParams(window.location.search);
|
|
71
|
+
const currentViewFromURL = router.currentProfileView as string;
|
|
72
|
+
const [currentView, setCurrentView] = useState<VisualizationType>(
|
|
73
|
+
(currentViewFromURL as VisualizationType) || 'icicle'
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {currentView, setCurrentView};
|
|
77
|
+
};
|
|
32
78
|
|
|
33
79
|
export const ProfileView = ({
|
|
34
|
-
|
|
80
|
+
flamegraphData,
|
|
81
|
+
topTableData,
|
|
82
|
+
sampleUnit,
|
|
35
83
|
profileSource,
|
|
84
|
+
queryClient,
|
|
36
85
|
navigateTo,
|
|
86
|
+
profileVisState,
|
|
37
87
|
}: ProfileViewProps): JSX.Element => {
|
|
38
|
-
const router = parseParams(window.location.search);
|
|
39
|
-
const currentViewFromURL = router.currentProfileView as string;
|
|
40
88
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
queryClient,
|
|
44
|
-
profileSource,
|
|
45
|
-
QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED
|
|
46
|
-
);
|
|
47
|
-
const [currentView, setCurrentView] = useState<string | undefined>(currentViewFromURL);
|
|
89
|
+
const {currentView, setCurrentView} = profileVisState;
|
|
90
|
+
|
|
48
91
|
const metadata = useGrpcMetadata();
|
|
49
92
|
const {loader} = useParcaTheme();
|
|
50
93
|
|
|
@@ -53,29 +96,39 @@ export const ProfileView = ({
|
|
|
53
96
|
setCurPath([]);
|
|
54
97
|
}, [profileSource]);
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
const isLoading = useMemo(() => {
|
|
100
|
+
if (currentView === 'icicle') {
|
|
101
|
+
return !!flamegraphData?.loading;
|
|
102
|
+
}
|
|
103
|
+
if (currentView === 'table') {
|
|
104
|
+
return !!topTableData?.loading;
|
|
105
|
+
}
|
|
106
|
+
if (currentView === 'both') {
|
|
107
|
+
return !!flamegraphData?.loading || !!topTableData?.loading;
|
|
65
108
|
}
|
|
66
|
-
return
|
|
67
|
-
}, [
|
|
109
|
+
return false;
|
|
110
|
+
}, [currentView, flamegraphData?.loading, topTableData?.loading]);
|
|
111
|
+
|
|
112
|
+
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
68
113
|
|
|
69
114
|
if (isLoaderVisible) {
|
|
70
115
|
return <>{loader}</>;
|
|
71
116
|
}
|
|
72
117
|
|
|
73
|
-
if (error
|
|
74
|
-
|
|
118
|
+
if (flamegraphData?.error != null) {
|
|
119
|
+
console.error('Error: ', flamegraphData?.error);
|
|
120
|
+
return (
|
|
121
|
+
<div className="p-10 flex justify-center">
|
|
122
|
+
An error occurred: {flamegraphData?.error.message}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
75
125
|
}
|
|
76
126
|
|
|
77
127
|
const downloadPProf = async (e: React.MouseEvent<HTMLElement>) => {
|
|
78
128
|
e.preventDefault();
|
|
129
|
+
if (!profileSource || !queryClient) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
79
132
|
|
|
80
133
|
try {
|
|
81
134
|
const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
|
|
@@ -96,16 +149,18 @@ export const ProfileView = ({
|
|
|
96
149
|
}
|
|
97
150
|
};
|
|
98
151
|
|
|
99
|
-
const switchProfileView = (view:
|
|
152
|
+
const switchProfileView = (view: VisualizationType) => {
|
|
153
|
+
if (view == null) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
100
156
|
if (navigateTo === undefined) return;
|
|
101
157
|
|
|
102
158
|
setCurrentView(view);
|
|
159
|
+
const router = parseParams(window.location.search);
|
|
103
160
|
|
|
104
161
|
navigateTo('/', {...router, ...{currentProfileView: view}});
|
|
105
162
|
};
|
|
106
163
|
|
|
107
|
-
const sampleUnit = profileSource.ProfileType().sampleUnit;
|
|
108
|
-
|
|
109
164
|
return (
|
|
110
165
|
<>
|
|
111
166
|
<div className="py-3">
|
|
@@ -114,10 +169,12 @@ export const ProfileView = ({
|
|
|
114
169
|
<div className="flex py-3 w-full">
|
|
115
170
|
<div className="w-2/5 flex space-x-4">
|
|
116
171
|
<div className="flex space-x-1">
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
172
|
+
{profileSource && queryClient ? (
|
|
173
|
+
<ProfileShareButton
|
|
174
|
+
queryRequest={profileSource.QueryRequest()}
|
|
175
|
+
queryClient={queryClient}
|
|
176
|
+
/>
|
|
177
|
+
) : null}
|
|
121
178
|
|
|
122
179
|
<Button color="neutral" onClick={downloadPProf}>
|
|
123
180
|
Download pprof
|
|
@@ -149,7 +206,7 @@ export const ProfileView = ({
|
|
|
149
206
|
|
|
150
207
|
<Button
|
|
151
208
|
variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
|
|
152
|
-
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
|
|
209
|
+
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"
|
|
153
210
|
onClick={() => switchProfileView('both')}
|
|
154
211
|
>
|
|
155
212
|
Both
|
|
@@ -166,45 +223,35 @@ export const ProfileView = ({
|
|
|
166
223
|
</div>
|
|
167
224
|
|
|
168
225
|
<div className="flex space-x-4 justify-between">
|
|
169
|
-
{currentView === 'icicle' &&
|
|
170
|
-
response !== null &&
|
|
171
|
-
response.report.oneofKind === 'flamegraph' && (
|
|
172
|
-
<div className="w-full">
|
|
173
|
-
<ProfileIcicleGraph
|
|
174
|
-
curPath={curPath}
|
|
175
|
-
setNewCurPath={setNewCurPath}
|
|
176
|
-
graph={response.report.flamegraph}
|
|
177
|
-
sampleUnit={sampleUnit}
|
|
178
|
-
/>
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
|
|
182
|
-
{currentView === 'table' && (
|
|
226
|
+
{currentView === 'icicle' && flamegraphData?.data != null && (
|
|
183
227
|
<div className="w-full">
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
228
|
+
<ProfileIcicleGraph
|
|
229
|
+
curPath={curPath}
|
|
230
|
+
setNewCurPath={setNewCurPath}
|
|
231
|
+
graph={flamegraphData.data}
|
|
187
232
|
sampleUnit={sampleUnit}
|
|
188
233
|
/>
|
|
189
234
|
</div>
|
|
190
235
|
)}
|
|
191
236
|
|
|
237
|
+
{currentView === 'table' && topTableData != null && (
|
|
238
|
+
<div className="w-full">
|
|
239
|
+
<TopTable data={topTableData.data} sampleUnit={sampleUnit} />
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
|
|
192
243
|
{currentView === 'both' && (
|
|
193
244
|
<>
|
|
194
245
|
<div className="w-1/2">
|
|
195
|
-
<TopTable
|
|
196
|
-
queryClient={queryClient}
|
|
197
|
-
profileSource={profileSource}
|
|
198
|
-
sampleUnit={sampleUnit}
|
|
199
|
-
/>
|
|
246
|
+
<TopTable data={topTableData?.data} sampleUnit={sampleUnit} />
|
|
200
247
|
</div>
|
|
201
248
|
|
|
202
249
|
<div className="w-1/2">
|
|
203
|
-
{
|
|
250
|
+
{flamegraphData != null && (
|
|
204
251
|
<ProfileIcicleGraph
|
|
205
252
|
curPath={curPath}
|
|
206
253
|
setNewCurPath={setNewCurPath}
|
|
207
|
-
graph={
|
|
254
|
+
graph={flamegraphData.data}
|
|
208
255
|
sampleUnit={sampleUnit}
|
|
209
256
|
/>
|
|
210
257
|
)}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
|
|
15
|
+
|
|
16
|
+
import {useQuery} from './useQuery';
|
|
17
|
+
import {ProfileView, useProfileVisState} from './ProfileView';
|
|
18
|
+
import {ProfileSource} from './ProfileSource';
|
|
19
|
+
|
|
20
|
+
type NavigateFunction = (path: string, queryParams: any) => void;
|
|
21
|
+
|
|
22
|
+
interface ProfileViewWithDataProps {
|
|
23
|
+
queryClient: QueryServiceClient;
|
|
24
|
+
profileSource: ProfileSource;
|
|
25
|
+
navigateTo?: NavigateFunction;
|
|
26
|
+
compare?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const ProfileViewWithData = ({
|
|
30
|
+
queryClient,
|
|
31
|
+
profileSource,
|
|
32
|
+
navigateTo,
|
|
33
|
+
}: ProfileViewWithDataProps) => {
|
|
34
|
+
const profileVisState = useProfileVisState();
|
|
35
|
+
const {currentView} = profileVisState;
|
|
36
|
+
const {
|
|
37
|
+
isLoading: flamegraphLoading,
|
|
38
|
+
response: flamegraphResponse,
|
|
39
|
+
error: flamegraphError,
|
|
40
|
+
} = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED, {
|
|
41
|
+
skip: currentView != 'icicle' && currentView != 'both',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
isLoading: topTableLoading,
|
|
46
|
+
response: topTableResponse,
|
|
47
|
+
error: topTableError,
|
|
48
|
+
} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
|
|
49
|
+
skip: currentView != 'table' && currentView != 'both',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const sampleUnit = profileSource.ProfileType().sampleUnit;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ProfileView
|
|
56
|
+
flamegraphData={{
|
|
57
|
+
loading: flamegraphLoading,
|
|
58
|
+
data:
|
|
59
|
+
flamegraphResponse?.report.oneofKind === 'flamegraph'
|
|
60
|
+
? flamegraphResponse?.report?.flamegraph
|
|
61
|
+
: undefined,
|
|
62
|
+
error: flamegraphError,
|
|
63
|
+
}}
|
|
64
|
+
topTableData={{
|
|
65
|
+
loading: topTableLoading,
|
|
66
|
+
data:
|
|
67
|
+
topTableResponse?.report.oneofKind === 'top' ? topTableResponse.report.top : undefined,
|
|
68
|
+
error: topTableError,
|
|
69
|
+
}}
|
|
70
|
+
profileVisState={profileVisState}
|
|
71
|
+
sampleUnit={sampleUnit}
|
|
72
|
+
profileSource={profileSource}
|
|
73
|
+
queryClient={queryClient}
|
|
74
|
+
navigateTo={navigateTo}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default ProfileViewWithData;
|
package/src/TopTable.tsx
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import React from 'react';
|
|
2
|
-
|
|
15
|
+
|
|
16
|
+
import {getLastItem, valueFormatter, isSearchMatch} from '@parca/functions';
|
|
3
17
|
import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
QueryServiceClient,
|
|
7
|
-
QueryRequest_ReportType,
|
|
8
|
-
TopNodeMeta,
|
|
9
|
-
} from '@parca/client';
|
|
10
|
-
import {ProfileSource} from './ProfileSource';
|
|
11
|
-
import {useQuery} from './useQuery';
|
|
18
|
+
import {TopNodeMeta, Top} from '@parca/client';
|
|
19
|
+
|
|
12
20
|
import {hexifyAddress} from './utils';
|
|
13
21
|
|
|
14
22
|
import './TopTable.styles.css';
|
|
15
23
|
|
|
16
|
-
interface
|
|
17
|
-
|
|
18
|
-
profileSource: ProfileSource;
|
|
24
|
+
interface TopTableProps {
|
|
25
|
+
data?: Top;
|
|
19
26
|
sampleUnit: string;
|
|
20
27
|
}
|
|
21
28
|
|
|
@@ -29,21 +36,17 @@ const Arrow = ({direction}: {direction: string | undefined}) => {
|
|
|
29
36
|
width="11"
|
|
30
37
|
xmlns="http://www.w3.org/2000/svg"
|
|
31
38
|
>
|
|
32
|
-
<path
|
|
39
|
+
<path clipRule="evenodd" d="m.573997 0 5.000003 10 5-10h-9.999847z" fillRule="evenodd" />
|
|
33
40
|
</svg>
|
|
34
41
|
);
|
|
35
42
|
};
|
|
36
43
|
|
|
37
|
-
const useSortableData = (
|
|
38
|
-
response: QueryResponse | null,
|
|
39
|
-
config = {key: 'cumulative', direction: 'desc'}
|
|
40
|
-
) => {
|
|
44
|
+
const useSortableData = (top?: Top, config = {key: 'cumulative', direction: 'desc'}) => {
|
|
41
45
|
const [sortConfig, setSortConfig] = React.useState<{key: string; direction: string} | null>(
|
|
42
46
|
config
|
|
43
47
|
);
|
|
44
48
|
|
|
45
|
-
const rawTableReport =
|
|
46
|
-
response !== null && response.report.oneofKind === 'top' ? response.report.top.list : [];
|
|
49
|
+
const rawTableReport = top ? top.list : [];
|
|
47
50
|
|
|
48
51
|
const items = rawTableReport.map(node => ({
|
|
49
52
|
...node,
|
|
@@ -98,25 +101,15 @@ export const RowLabel = (meta: TopNodeMeta | undefined): string => {
|
|
|
98
101
|
return fallback === '' ? '<unknown>' : fallback;
|
|
99
102
|
};
|
|
100
103
|
|
|
101
|
-
export const TopTable = ({
|
|
102
|
-
|
|
103
|
-
profileSource,
|
|
104
|
-
sampleUnit,
|
|
105
|
-
}: ProfileViewProps): JSX.Element => {
|
|
106
|
-
const {response, error} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP);
|
|
107
|
-
const {items, requestSort, sortConfig} = useSortableData(response);
|
|
104
|
+
export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element => {
|
|
105
|
+
const {items, requestSort, sortConfig} = useSortableData(top);
|
|
108
106
|
const currentSearchString = useAppSelector(selectSearchNodeString);
|
|
109
107
|
|
|
110
108
|
const compareMode = useAppSelector(selectCompareMode);
|
|
111
109
|
|
|
112
110
|
const unit = sampleUnit;
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const total =
|
|
119
|
-
response !== null && response.report.oneofKind === 'top' ? response.report.top.list.length : 0;
|
|
112
|
+
const total = top ? top.list.length : 0;
|
|
120
113
|
if (total === 0) return <>Profile has no samples</>;
|
|
121
114
|
|
|
122
115
|
const getClassNamesFor = name => {
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {SuffixParams, ParseLabels} from '../ProfileSource';
|
|
2
15
|
|
|
3
16
|
test('prefixes keys', () => {
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {Fragment, useState} from 'react';
|
|
2
15
|
import {Popover, Transition} from '@headlessui/react';
|
|
3
16
|
import {useAppSelector, selectDarkMode} from '@parca/store';
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {useState} from 'react';
|
|
2
15
|
import cx from 'classnames';
|
|
3
16
|
import {Icon} from '@iconify/react';
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {useState} from 'react';
|
|
2
15
|
import {Button, Modal, useGrpcMetadata} from '@parca/components';
|
|
3
16
|
import {Icon} from '@iconify/react';
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
export * from './IcicleGraph';
|
|
2
15
|
export * from './ProfileIcicleGraph';
|
|
3
16
|
export * from './ProfileSource';
|
|
4
17
|
export * from './ProfileView';
|
|
18
|
+
export * from './ProfileViewWithData';
|
|
5
19
|
export * from './utils';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useEffect, useState} from 'react';
|
|
15
|
+
|
|
16
|
+
interface DelayedLoaderOptions {
|
|
17
|
+
delay?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const useDelayedLoader = (isLoading = false, options?: DelayedLoaderOptions) => {
|
|
21
|
+
const {delay = 500} = options || {};
|
|
22
|
+
const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let showLoaderTimeout;
|
|
25
|
+
if (isLoading && !isLoaderVisible) {
|
|
26
|
+
// if the request takes longer than half a second, show the loading icon
|
|
27
|
+
showLoaderTimeout = setTimeout(() => {
|
|
28
|
+
setIsLoaderVisible(true);
|
|
29
|
+
}, delay);
|
|
30
|
+
} else {
|
|
31
|
+
setIsLoaderVisible(false);
|
|
32
|
+
}
|
|
33
|
+
return () => clearTimeout(showLoaderTimeout);
|
|
34
|
+
}, [isLoading]);
|
|
35
|
+
|
|
36
|
+
return isLoaderVisible;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default useDelayedLoader;
|
package/src/useQuery.tsx
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useEffect, useState} from 'react';
|
|
15
|
+
|
|
2
16
|
import {QueryServiceClient, QueryResponse, QueryRequest_ReportType} from '@parca/client';
|
|
3
17
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
4
18
|
import {useGrpcMetadata} from '@parca/components';
|
|
19
|
+
|
|
5
20
|
import {ProfileSource} from './ProfileSource';
|
|
6
21
|
|
|
7
22
|
export interface IQueryResult {
|
|
@@ -10,11 +25,17 @@ export interface IQueryResult {
|
|
|
10
25
|
isLoading: boolean;
|
|
11
26
|
}
|
|
12
27
|
|
|
28
|
+
interface UseQueryOptions {
|
|
29
|
+
skip?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
export const useQuery = (
|
|
14
33
|
client: QueryServiceClient,
|
|
15
34
|
profileSource: ProfileSource,
|
|
16
|
-
reportType: QueryRequest_ReportType
|
|
35
|
+
reportType: QueryRequest_ReportType,
|
|
36
|
+
options?: UseQueryOptions
|
|
17
37
|
): IQueryResult => {
|
|
38
|
+
const {skip = false} = options || {};
|
|
18
39
|
const [result, setResult] = useState<IQueryResult>({
|
|
19
40
|
response: null,
|
|
20
41
|
error: null,
|
|
@@ -23,6 +44,9 @@ export const useQuery = (
|
|
|
23
44
|
const metadata = useGrpcMetadata();
|
|
24
45
|
|
|
25
46
|
useEffect(() => {
|
|
47
|
+
if (skip) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
26
50
|
setResult({
|
|
27
51
|
response: null,
|
|
28
52
|
error: null,
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
1
14
|
import {QueryRequest, QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
|
|
2
15
|
import {RpcMetadata} from '@protobuf-ts/runtime-rpc';
|
|
3
16
|
|