@parca/profile 0.10.0 → 0.12.2
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 +24 -0
- package/package.json +5 -5
- package/src/ProfileSource.tsx +17 -16
- package/src/ProfileView.tsx +30 -9
- package/src/TopTable.tsx +28 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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.12.2](https://github.com/parca-dev/parca/compare/ui-v0.12.1...ui-v0.12.2) (2022-03-14)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
# [0.12.0](https://github.com/parca-dev/parca/compare/ui-v0.11.2...ui-v0.12.0) (2022-03-09)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
14
|
+
# [0.11.0](https://github.com/parca-dev/parca/compare/ui-v0.10.4...ui-v0.11.0) (2022-03-02)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- convert moment.js to date-fns ([9431e15](https://github.com/parca-dev/parca/commit/9431e156d9438353e5542b11e33ed8e0de93547f)), closes [#613](https://github.com/parca-dev/parca/issues/613)
|
|
19
|
+
|
|
20
|
+
## [0.10.3](https://github.com/parca-dev/parca/compare/ui-v0.10.2...ui-v0.10.3) (2022-03-02)
|
|
21
|
+
|
|
22
|
+
## 0.10.1 (2022-02-28)
|
|
23
|
+
|
|
24
|
+
**Note:** Version bump only for package @parca/profile
|
|
25
|
+
|
|
26
|
+
## [0.10.2](https://github.com/parca-dev/parca/compare/ui-v0.10.1...ui-v0.10.2) (2022-03-01)
|
|
27
|
+
|
|
28
|
+
**Note:** Version bump only for package @parca/profile
|
|
29
|
+
|
|
6
30
|
# [0.10.0](https://github.com/parca-dev/parca/compare/ui-v0.9.3...ui-v0.10.0) (2022-02-28)
|
|
7
31
|
|
|
8
32
|
**Note:** Version bump only for package @parca/profile
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@parca/client": "^0.
|
|
7
|
-
"@parca/dynamicsize": "^0.
|
|
8
|
-
"@parca/parser": "^0.
|
|
6
|
+
"@parca/client": "^0.12.2",
|
|
7
|
+
"@parca/dynamicsize": "^0.12.0",
|
|
8
|
+
"@parca/parser": "^0.12.0",
|
|
9
9
|
"d3-scale": "^4.0.2"
|
|
10
10
|
},
|
|
11
11
|
"main": "src/index.tsx",
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"access": "public",
|
|
20
20
|
"registry": "https://registry.npmjs.org/"
|
|
21
21
|
},
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "55e2a33548e27fdb1efb8bb0a1598084e0d5ddf1"
|
|
23
23
|
}
|
package/src/ProfileSource.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import {formatDate} from '@parca/functions';
|
|
3
3
|
import {Query} from '@parca/parser';
|
|
4
4
|
import {
|
|
5
5
|
Label,
|
|
@@ -25,8 +25,8 @@ export interface ProfileSelection {
|
|
|
25
25
|
Type: () => string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export const timeFormat =
|
|
29
|
-
export const timeFormatShort = 'MMM
|
|
28
|
+
export const timeFormat = "MMM d, 'at' h:mm:s a '(UTC)'";
|
|
29
|
+
export const timeFormatShort = 'MMM d, h:mma';
|
|
30
30
|
|
|
31
31
|
export function ParamsString(params: {[key: string]: string}): string {
|
|
32
32
|
return Object.keys(params)
|
|
@@ -157,7 +157,7 @@ export class SingleProfileSource implements ProfileSource {
|
|
|
157
157
|
|
|
158
158
|
const singleProfile = new SingleProfile();
|
|
159
159
|
const ts = new Timestamp();
|
|
160
|
-
ts.fromDate(
|
|
160
|
+
ts.fromDate(new Date(this.time));
|
|
161
161
|
singleProfile.setTime(ts);
|
|
162
162
|
singleProfile.setQuery(this.query());
|
|
163
163
|
sel.setSingle(singleProfile);
|
|
@@ -170,7 +170,7 @@ export class SingleProfileSource implements ProfileSource {
|
|
|
170
170
|
req.setMode(QueryRequest.Mode.MODE_SINGLE_UNSPECIFIED);
|
|
171
171
|
const singleQueryRequest = new SingleProfile();
|
|
172
172
|
const ts = new Timestamp();
|
|
173
|
-
ts.fromDate(
|
|
173
|
+
ts.fromDate(new Date(this.time));
|
|
174
174
|
singleQueryRequest.setTime(ts);
|
|
175
175
|
singleQueryRequest.setQuery(this.query());
|
|
176
176
|
req.setSingle(singleQueryRequest);
|
|
@@ -201,7 +201,7 @@ export class SingleProfileSource implements ProfileSource {
|
|
|
201
201
|
</button>
|
|
202
202
|
))}
|
|
203
203
|
</p>
|
|
204
|
-
<p>{
|
|
204
|
+
<p>{formatDate(this.time, timeFormat)}</p>
|
|
205
205
|
</>
|
|
206
206
|
);
|
|
207
207
|
}
|
|
@@ -215,7 +215,7 @@ export class SingleProfileSource implements ProfileSource {
|
|
|
215
215
|
toString(): string {
|
|
216
216
|
return `single profile of type ${this.profileName()} with labels ${this.stringLabels().join(
|
|
217
217
|
', '
|
|
218
|
-
)} collected at ${
|
|
218
|
+
)} collected at ${formatDate(this.time, timeFormat)}`;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -275,11 +275,11 @@ export class MergedProfileSource implements ProfileSource {
|
|
|
275
275
|
const mergeProfile = new MergeProfile();
|
|
276
276
|
|
|
277
277
|
const startTs = new Timestamp();
|
|
278
|
-
startTs.fromDate(
|
|
278
|
+
startTs.fromDate(new Date(this.from));
|
|
279
279
|
mergeProfile.setStart(startTs);
|
|
280
280
|
|
|
281
281
|
const endTs = new Timestamp();
|
|
282
|
-
endTs.fromDate(
|
|
282
|
+
endTs.fromDate(new Date(this.to));
|
|
283
283
|
mergeProfile.setEnd(endTs);
|
|
284
284
|
|
|
285
285
|
mergeProfile.setQuery(this.query);
|
|
@@ -296,11 +296,11 @@ export class MergedProfileSource implements ProfileSource {
|
|
|
296
296
|
const mergeQueryRequest = new MergeProfile();
|
|
297
297
|
|
|
298
298
|
const startTs = new Timestamp();
|
|
299
|
-
startTs.fromDate(
|
|
299
|
+
startTs.fromDate(new Date(this.from));
|
|
300
300
|
mergeQueryRequest.setStart(startTs);
|
|
301
301
|
|
|
302
302
|
const endTs = new Timestamp();
|
|
303
|
-
endTs.fromDate(
|
|
303
|
+
endTs.fromDate(new Date(this.to));
|
|
304
304
|
mergeQueryRequest.setEnd(endTs);
|
|
305
305
|
|
|
306
306
|
mergeQueryRequest.setQuery(this.query);
|
|
@@ -313,15 +313,16 @@ export class MergedProfileSource implements ProfileSource {
|
|
|
313
313
|
Describe(): JSX.Element {
|
|
314
314
|
return (
|
|
315
315
|
<a>
|
|
316
|
-
Merge of "{this.query}" from {
|
|
317
|
-
{
|
|
316
|
+
Merge of "{this.query}" from {formatDate(this.from, timeFormat)} to{' '}
|
|
317
|
+
{formatDate(this.to, timeFormat)}
|
|
318
318
|
</a>
|
|
319
319
|
);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
toString(): string {
|
|
323
|
-
return `merged profiles of query "${this.query}" from ${
|
|
324
|
-
.
|
|
325
|
-
|
|
323
|
+
return `merged profiles of query "${this.query}" from ${formatDate(
|
|
324
|
+
this.from,
|
|
325
|
+
timeFormat
|
|
326
|
+
)} to ${formatDate(this.to, timeFormat)}`;
|
|
326
327
|
}
|
|
327
328
|
}
|
package/src/ProfileView.tsx
CHANGED
|
@@ -2,12 +2,12 @@ import React, {useEffect, useState} from 'react';
|
|
|
2
2
|
import {CalcWidth} from '@parca/dynamicsize';
|
|
3
3
|
import {parseParams} from '@parca/functions';
|
|
4
4
|
import {QueryRequest, QueryResponse, QueryServiceClient, ServiceError} from '@parca/client';
|
|
5
|
-
import Button from '@parca/
|
|
5
|
+
import {Button} from '@parca/components';
|
|
6
6
|
import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
|
|
7
7
|
|
|
8
8
|
import ProfileIcicleGraph from './ProfileIcicleGraph';
|
|
9
9
|
import {ProfileSource} from './ProfileSource';
|
|
10
|
-
import Card from '
|
|
10
|
+
import {Card} from '@parca/components';
|
|
11
11
|
import TopTable from './TopTable';
|
|
12
12
|
|
|
13
13
|
import './ProfileView.styles.css';
|
|
@@ -21,6 +21,7 @@ interface ProfileViewProps {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface IQueryResult {
|
|
24
|
+
isLoading: boolean;
|
|
24
25
|
response: QueryResponse | null;
|
|
25
26
|
error: ServiceError | null;
|
|
26
27
|
}
|
|
@@ -39,16 +40,22 @@ export const useQuery = (
|
|
|
39
40
|
profileSource: ProfileSource
|
|
40
41
|
): IQueryResult => {
|
|
41
42
|
const [result, setResult] = useState<IQueryResult>({
|
|
43
|
+
isLoading: false,
|
|
42
44
|
response: null,
|
|
43
45
|
error: null,
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
useEffect(() => {
|
|
49
|
+
setResult({
|
|
50
|
+
...result,
|
|
51
|
+
isLoading: true,
|
|
52
|
+
});
|
|
47
53
|
const req = profileSource.QueryRequest();
|
|
48
54
|
req.setReportType(QueryRequest.ReportType.REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED);
|
|
49
55
|
|
|
50
56
|
client.query(req, (error: ServiceError | null, responseMessage: QueryResponse | null) => {
|
|
51
57
|
setResult({
|
|
58
|
+
isLoading: false,
|
|
52
59
|
response: responseMessage,
|
|
53
60
|
error: error,
|
|
54
61
|
});
|
|
@@ -66,14 +73,24 @@ export const ProfileView = ({
|
|
|
66
73
|
const router = parseParams(window.location.search);
|
|
67
74
|
const currentViewFromURL = router.currentProfileView as string;
|
|
68
75
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
69
|
-
const
|
|
76
|
+
const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
|
|
77
|
+
const {isLoading, response, error} = useQuery(queryClient, profileSource);
|
|
70
78
|
const [currentView, setCurrentView] = useState<string | undefined>(currentViewFromURL);
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
let showLoaderTimeout;
|
|
82
|
+
if (isLoading && !isLoaderVisible) {
|
|
83
|
+
// if the request takes longer than half a second, show the loading icon
|
|
84
|
+
showLoaderTimeout = setTimeout(() => {
|
|
85
|
+
setIsLoaderVisible(true);
|
|
86
|
+
}, 500);
|
|
87
|
+
} else {
|
|
88
|
+
setIsLoaderVisible(false);
|
|
89
|
+
}
|
|
90
|
+
return () => clearTimeout(showLoaderTimeout);
|
|
91
|
+
}, [isLoading]);
|
|
75
92
|
|
|
76
|
-
if (
|
|
93
|
+
if (isLoaderVisible) {
|
|
77
94
|
return (
|
|
78
95
|
<div
|
|
79
96
|
style={{
|
|
@@ -109,6 +126,10 @@ export const ProfileView = ({
|
|
|
109
126
|
);
|
|
110
127
|
}
|
|
111
128
|
|
|
129
|
+
if (error) {
|
|
130
|
+
return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
const downloadPProf = (e: React.MouseEvent<HTMLElement>) => {
|
|
113
134
|
e.preventDefault();
|
|
114
135
|
|
|
@@ -215,7 +236,7 @@ export const ProfileView = ({
|
|
|
215
236
|
<ProfileIcicleGraph
|
|
216
237
|
curPath={curPath}
|
|
217
238
|
setNewCurPath={setNewCurPath}
|
|
218
|
-
graph={response
|
|
239
|
+
graph={response?.getFlamegraph()?.toObject()}
|
|
219
240
|
/>
|
|
220
241
|
</CalcWidth>
|
|
221
242
|
</div>
|
|
@@ -238,7 +259,7 @@ export const ProfileView = ({
|
|
|
238
259
|
<ProfileIcicleGraph
|
|
239
260
|
curPath={curPath}
|
|
240
261
|
setNewCurPath={setNewCurPath}
|
|
241
|
-
graph={response
|
|
262
|
+
graph={response?.getFlamegraph()?.toObject()}
|
|
242
263
|
/>
|
|
243
264
|
</CalcWidth>
|
|
244
265
|
</div>
|
package/src/TopTable.tsx
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React, {useEffect, useState} from 'react';
|
|
2
2
|
|
|
3
3
|
import {ProfileSource} from './ProfileSource';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
QueryRequest,
|
|
6
|
+
QueryResponse,
|
|
7
|
+
QueryServiceClient,
|
|
8
|
+
ServiceError,
|
|
9
|
+
TopNodeMeta,
|
|
10
|
+
} from '@parca/client';
|
|
5
11
|
import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
|
|
6
12
|
import {getLastItem, valueFormatter} from '@parca/functions';
|
|
7
13
|
|
|
@@ -106,6 +112,26 @@ export const useQuery = (
|
|
|
106
112
|
return result;
|
|
107
113
|
};
|
|
108
114
|
|
|
115
|
+
export const RowLabel = (meta: TopNodeMeta.AsObject | undefined): string => {
|
|
116
|
+
if (meta === undefined) return '<unknown>';
|
|
117
|
+
const mapping = `${
|
|
118
|
+
meta?.mapping?.file !== undefined && meta?.mapping?.file !== ''
|
|
119
|
+
? `[${getLastItem(meta.mapping.file)}]`
|
|
120
|
+
: ''
|
|
121
|
+
}`;
|
|
122
|
+
if (meta.pb_function?.name !== undefined && meta.pb_function?.name !== '')
|
|
123
|
+
return `${mapping} ${meta.pb_function.name}`;
|
|
124
|
+
|
|
125
|
+
const address = `${
|
|
126
|
+
meta.location?.address !== undefined && meta.location?.address !== 0
|
|
127
|
+
? `0x${meta.location.address.toString(16)}`
|
|
128
|
+
: ''
|
|
129
|
+
}`;
|
|
130
|
+
const fallback = `${mapping} ${address}`;
|
|
131
|
+
|
|
132
|
+
return fallback === '' ? '<unknown>' : fallback;
|
|
133
|
+
};
|
|
134
|
+
|
|
109
135
|
export const TopTable = ({queryClient, profileSource}: ProfileViewProps): JSX.Element => {
|
|
110
136
|
const {response, error} = useQuery(queryClient, profileSource);
|
|
111
137
|
const {items, requestSort, sortConfig} = useSortableData(response);
|
|
@@ -167,8 +193,7 @@ export const TopTable = ({queryClient, profileSource}: ProfileViewProps): JSX.El
|
|
|
167
193
|
{items?.map((report, index) => (
|
|
168
194
|
<tr key={index} className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12]">
|
|
169
195
|
<td className="text-xs py-1.5 pl-2 min-w-[150px] max-w-[450px]">
|
|
170
|
-
{
|
|
171
|
-
{report.meta?.pb_function?.name}
|
|
196
|
+
{RowLabel(report.meta)}
|
|
172
197
|
</td>
|
|
173
198
|
<td className="text-xs min-w-[150px] max-w-[150px] py-1.5text-right">
|
|
174
199
|
{valueFormatter(report.flat, unit, 2)}
|