@parca/profile 0.13.1 → 0.13.3
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/package.json +2 -2
- package/src/IcicleGraph.tsx +6 -202
- package/src/ProfileView.tsx +6 -4
- package/src/TopTable.tsx +32 -18
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.13.3](https://github.com/parca-dev/parca/compare/ui-v0.13.2...ui-v0.13.3) (2022-06-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## [0.13.1](https://github.com/parca-dev/parca/compare/ui-v0.13.0...ui-v0.13.1) (2022-05-31)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @parca/profile
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@parca/client": "^0.13.0",
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"access": "public",
|
|
20
20
|
"registry": "https://registry.npmjs.org/"
|
|
21
21
|
},
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "f5e7eadd0ffae7a9de6bdd7a57534d26f072c1d3"
|
|
23
23
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -3,9 +3,9 @@ import {throttle} from 'lodash';
|
|
|
3
3
|
import {pointer} from 'd3-selection';
|
|
4
4
|
import {scaleLinear} from 'd3-scale';
|
|
5
5
|
import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
6
|
-
import {
|
|
7
|
-
import {getLastItem,
|
|
8
|
-
import {useAppSelector, selectDarkMode} from '@parca/store';
|
|
6
|
+
import {FlamegraphTooltip} from '@parca/components';
|
|
7
|
+
import {getLastItem, diffColor, isSearchMatch, SEARCH_STRING_COLOR} from '@parca/functions';
|
|
8
|
+
import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
|
|
9
9
|
|
|
10
10
|
interface IcicleGraphProps {
|
|
11
11
|
graph: Flamegraph;
|
|
@@ -29,15 +29,6 @@ interface IcicleGraphNodesProps {
|
|
|
29
29
|
xScale: (value: number) => number;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
interface FlamegraphTooltipProps {
|
|
33
|
-
x: number;
|
|
34
|
-
y: number;
|
|
35
|
-
unit: string;
|
|
36
|
-
total: number;
|
|
37
|
-
hoveringNode: FlamegraphNode | FlamegraphRootNode | undefined;
|
|
38
|
-
contextElement: Element | null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
32
|
interface IcicleGraphRootNodeProps {
|
|
42
33
|
node: FlamegraphRootNode;
|
|
43
34
|
xScale: (value: number) => number;
|
|
@@ -85,6 +76,7 @@ function IcicleRect({
|
|
|
85
76
|
onClick,
|
|
86
77
|
curPath,
|
|
87
78
|
}: IcicleRectProps) {
|
|
79
|
+
const currentSearchString = useAppSelector(selectSearchNodeString);
|
|
88
80
|
const isFaded = curPath.length > 0 && name !== curPath[curPath.length - 1];
|
|
89
81
|
const styles = isFaded ? fadedIcicleRectStyles : icicleRectStyles;
|
|
90
82
|
|
|
@@ -102,6 +94,8 @@ function IcicleRect({
|
|
|
102
94
|
width={width - 1}
|
|
103
95
|
height={height - 1}
|
|
104
96
|
style={{
|
|
97
|
+
opacity:
|
|
98
|
+
Boolean(currentSearchString) && !isSearchMatch(currentSearchString, name) ? 0.5 : 1,
|
|
105
99
|
fill: color,
|
|
106
100
|
}}
|
|
107
101
|
/>
|
|
@@ -232,196 +226,6 @@ export function IcicleGraphNodes({
|
|
|
232
226
|
|
|
233
227
|
const MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
|
|
234
228
|
|
|
235
|
-
const FlamegraphNodeTooltipTableRows = ({
|
|
236
|
-
hoveringNode,
|
|
237
|
-
}: {
|
|
238
|
-
hoveringNode: FlamegraphNode;
|
|
239
|
-
}): JSX.Element => {
|
|
240
|
-
if (hoveringNode.meta === undefined) return <></>;
|
|
241
|
-
|
|
242
|
-
return (
|
|
243
|
-
<>
|
|
244
|
-
{hoveringNode.meta.pb_function?.filename !== undefined &&
|
|
245
|
-
hoveringNode.meta.pb_function?.filename !== '' && (
|
|
246
|
-
<tr>
|
|
247
|
-
<td className="w-1/5">File</td>
|
|
248
|
-
<td className="w-4/5">
|
|
249
|
-
{hoveringNode.meta.pb_function.filename}
|
|
250
|
-
{hoveringNode.meta.line?.line !== undefined && hoveringNode.meta.line?.line !== 0
|
|
251
|
-
? ` +${hoveringNode.meta.line.line.toString()}`
|
|
252
|
-
: `${
|
|
253
|
-
hoveringNode.meta.pb_function?.startLine !== undefined &&
|
|
254
|
-
hoveringNode.meta.pb_function?.startLine !== 0
|
|
255
|
-
? ` +${hoveringNode.meta.pb_function.startLine.toString()}`
|
|
256
|
-
: ''
|
|
257
|
-
}`}
|
|
258
|
-
</td>
|
|
259
|
-
</tr>
|
|
260
|
-
)}
|
|
261
|
-
{hoveringNode.meta.location?.address !== undefined &&
|
|
262
|
-
hoveringNode.meta.location?.address !== 0 && (
|
|
263
|
-
<tr>
|
|
264
|
-
<td className="w-1/5">Address</td>
|
|
265
|
-
<td className="w-4/5">{' 0x' + hoveringNode.meta.location.address.toString(16)}</td>
|
|
266
|
-
</tr>
|
|
267
|
-
)}
|
|
268
|
-
{hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.file !== '' && (
|
|
269
|
-
<tr>
|
|
270
|
-
<td className="w-1/5">Binary</td>
|
|
271
|
-
<td className="w-4/5">{getLastItem(hoveringNode.meta.mapping.file)}</td>
|
|
272
|
-
</tr>
|
|
273
|
-
)}
|
|
274
|
-
</>
|
|
275
|
-
);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
function generateGetBoundingClientRect(contextElement: Element, x = 0, y = 0) {
|
|
279
|
-
const domRect = contextElement.getBoundingClientRect();
|
|
280
|
-
return () =>
|
|
281
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
282
|
-
({
|
|
283
|
-
width: 0,
|
|
284
|
-
height: 0,
|
|
285
|
-
top: domRect.y + y,
|
|
286
|
-
left: domRect.x + x,
|
|
287
|
-
right: domRect.x + x,
|
|
288
|
-
bottom: domRect.y + y,
|
|
289
|
-
} as ClientRect);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const virtualElement = {
|
|
293
|
-
getBoundingClientRect: () =>
|
|
294
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
295
|
-
({
|
|
296
|
-
width: 0,
|
|
297
|
-
height: 0,
|
|
298
|
-
top: 0,
|
|
299
|
-
left: 0,
|
|
300
|
-
right: 0,
|
|
301
|
-
bottom: 0,
|
|
302
|
-
} as ClientRect),
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
export const FlamegraphTooltip = ({
|
|
306
|
-
x,
|
|
307
|
-
y,
|
|
308
|
-
unit,
|
|
309
|
-
total,
|
|
310
|
-
hoveringNode,
|
|
311
|
-
contextElement,
|
|
312
|
-
}: FlamegraphTooltipProps): JSX.Element => {
|
|
313
|
-
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
314
|
-
|
|
315
|
-
const {styles, attributes, ...popperProps} = usePopper(virtualElement, popperElement, {
|
|
316
|
-
placement: 'auto-start',
|
|
317
|
-
strategy: 'absolute',
|
|
318
|
-
modifiers: [
|
|
319
|
-
{
|
|
320
|
-
name: 'preventOverflow',
|
|
321
|
-
options: {
|
|
322
|
-
tether: false,
|
|
323
|
-
altAxis: true,
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: 'offset',
|
|
328
|
-
options: {
|
|
329
|
-
offset: [30, 30],
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const update = popperProps.update;
|
|
336
|
-
|
|
337
|
-
useEffect(() => {
|
|
338
|
-
if (contextElement != null) {
|
|
339
|
-
virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
|
|
340
|
-
update?.();
|
|
341
|
-
}
|
|
342
|
-
}, [x, y, contextElement, update]);
|
|
343
|
-
|
|
344
|
-
if (hoveringNode === undefined || hoveringNode == null) return <></>;
|
|
345
|
-
|
|
346
|
-
const hoveringNodeCumulative = parseFloat(hoveringNode.cumulative);
|
|
347
|
-
const diff = hoveringNode.diff === undefined ? 0 : parseFloat(hoveringNode.diff);
|
|
348
|
-
const prevValue = hoveringNodeCumulative - diff;
|
|
349
|
-
const diffRatio = Math.abs(diff) > 0 ? diff / prevValue : 0;
|
|
350
|
-
const diffSign = diff > 0 ? '+' : '';
|
|
351
|
-
const diffValueText = diffSign + valueFormatter(diff, unit, 1);
|
|
352
|
-
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
353
|
-
const diffText = `${diffValueText} (${diffPercentageText})`;
|
|
354
|
-
|
|
355
|
-
const hoveringFlamegraphNode = hoveringNode as FlamegraphNode;
|
|
356
|
-
const metaRows =
|
|
357
|
-
hoveringFlamegraphNode.meta === undefined ? (
|
|
358
|
-
<></>
|
|
359
|
-
) : (
|
|
360
|
-
<FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode} />
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
return (
|
|
364
|
-
<div ref={setPopperElement} style={styles.popper} {...attributes.popper}>
|
|
365
|
-
<div className="flex">
|
|
366
|
-
<div className="m-auto">
|
|
367
|
-
<div
|
|
368
|
-
className="border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg opacity-90"
|
|
369
|
-
style={{borderWidth: 1}}
|
|
370
|
-
>
|
|
371
|
-
<div className="flex flex-row">
|
|
372
|
-
<div className="ml-2 mr-6">
|
|
373
|
-
<span className="font-semibold">
|
|
374
|
-
{hoveringFlamegraphNode.meta === undefined ? (
|
|
375
|
-
<p>root</p>
|
|
376
|
-
) : (
|
|
377
|
-
<>
|
|
378
|
-
{hoveringFlamegraphNode.meta.function !== undefined &&
|
|
379
|
-
hoveringFlamegraphNode.meta.function.name !== '' ? (
|
|
380
|
-
<p>{hoveringFlamegraphNode.meta.function.name}</p>
|
|
381
|
-
) : (
|
|
382
|
-
<>
|
|
383
|
-
{hoveringFlamegraphNode.meta.location !== undefined &&
|
|
384
|
-
parseInt(hoveringFlamegraphNode.meta.location.address, 10) !== 0 ? (
|
|
385
|
-
<p>
|
|
386
|
-
{'0x' + hoveringFlamegraphNode.meta.location.address.toString(16)}
|
|
387
|
-
</p>
|
|
388
|
-
) : (
|
|
389
|
-
<p>unknown</p>
|
|
390
|
-
)}
|
|
391
|
-
</>
|
|
392
|
-
)}
|
|
393
|
-
</>
|
|
394
|
-
)}
|
|
395
|
-
</span>
|
|
396
|
-
<span className="text-gray-700 dark:text-gray-300 my-2">
|
|
397
|
-
<table className="table-fixed">
|
|
398
|
-
<tbody>
|
|
399
|
-
<tr>
|
|
400
|
-
<td className="w-1/5">Cumulative</td>
|
|
401
|
-
<td className="w-4/5">
|
|
402
|
-
{valueFormatter(hoveringNodeCumulative, unit, 2)} (
|
|
403
|
-
{((hoveringNodeCumulative * 100) / total).toFixed(2)}%)
|
|
404
|
-
</td>
|
|
405
|
-
</tr>
|
|
406
|
-
{hoveringNode.diff !== undefined && diff !== 0 && (
|
|
407
|
-
<tr>
|
|
408
|
-
<td className="w-1/5">Diff</td>
|
|
409
|
-
<td className="w-4/5">{diffText}</td>
|
|
410
|
-
</tr>
|
|
411
|
-
)}
|
|
412
|
-
{metaRows}
|
|
413
|
-
</tbody>
|
|
414
|
-
</table>
|
|
415
|
-
</span>
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
</div>
|
|
420
|
-
</div>
|
|
421
|
-
</div>
|
|
422
|
-
);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
229
|
export function IcicleGraphRootNode({
|
|
426
230
|
node,
|
|
427
231
|
xScale,
|
package/src/ProfileView.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, {useEffect, useState} from 'react';
|
|
2
2
|
import {parseParams} from '@parca/functions';
|
|
3
3
|
import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
|
|
4
|
-
import {Button, Card, useGrpcMetadata, useParcaTheme} from '@parca/components';
|
|
4
|
+
import {Button, Card, Input, SearchNodes, useGrpcMetadata, useParcaTheme} from '@parca/components';
|
|
5
5
|
|
|
6
6
|
import ProfileIcicleGraph from './ProfileIcicleGraph';
|
|
7
7
|
import {ProfileSource} from './ProfileSource';
|
|
@@ -124,6 +124,8 @@ export const ProfileView = ({
|
|
|
124
124
|
Download pprof
|
|
125
125
|
</Button>
|
|
126
126
|
</div>
|
|
127
|
+
|
|
128
|
+
<SearchNodes />
|
|
127
129
|
</div>
|
|
128
130
|
|
|
129
131
|
<div className="flex ml-auto">
|
|
@@ -140,7 +142,7 @@ export const ProfileView = ({
|
|
|
140
142
|
|
|
141
143
|
<Button
|
|
142
144
|
variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
|
|
143
|
-
className="rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
145
|
+
className="items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
144
146
|
onClick={() => switchProfileView('table')}
|
|
145
147
|
>
|
|
146
148
|
Table
|
|
@@ -148,7 +150,7 @@ export const ProfileView = ({
|
|
|
148
150
|
|
|
149
151
|
<Button
|
|
150
152
|
variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
|
|
151
|
-
className="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 no-outline-on-buttons text-ellipsis"
|
|
153
|
+
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 no-outline-on-buttons text-ellipsis"
|
|
152
154
|
onClick={() => switchProfileView('both')}
|
|
153
155
|
>
|
|
154
156
|
Both
|
|
@@ -156,7 +158,7 @@ export const ProfileView = ({
|
|
|
156
158
|
|
|
157
159
|
<Button
|
|
158
160
|
variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
|
|
159
|
-
className="rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
161
|
+
className="items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
160
162
|
onClick={() => switchProfileView('icicle')}
|
|
161
163
|
>
|
|
162
164
|
Icicle Graph
|
package/src/TopTable.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {useAppSelector, selectCompareMode} from '@parca/store';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {getLastItem, valueFormatter, isSearchMatch, SEARCH_STRING_COLOR} from '@parca/functions';
|
|
3
|
+
import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
|
|
5
4
|
import {
|
|
6
5
|
QueryResponse,
|
|
7
6
|
QueryServiceClient,
|
|
@@ -107,6 +106,7 @@ export const TopTable = ({
|
|
|
107
106
|
}: ProfileViewProps): JSX.Element => {
|
|
108
107
|
const {response, error} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP);
|
|
109
108
|
const {items, requestSort, sortConfig} = useSortableData(response);
|
|
109
|
+
const currentSearchString = useAppSelector(selectSearchNodeString);
|
|
110
110
|
|
|
111
111
|
const compareMode = useAppSelector(selectCompareMode);
|
|
112
112
|
|
|
@@ -184,22 +184,36 @@ export const TopTable = ({
|
|
|
184
184
|
</tr>
|
|
185
185
|
</thead>
|
|
186
186
|
<tbody className="bg-white divide-y divide-gray-200 dark:bg-gray-900 dark:divide-gray-700">
|
|
187
|
-
{items?.map((report, index) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
187
|
+
{items?.map((report, index) => {
|
|
188
|
+
const name = RowLabel(report.meta);
|
|
189
|
+
return (
|
|
190
|
+
<tr
|
|
191
|
+
key={index}
|
|
192
|
+
className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12]"
|
|
193
|
+
style={{
|
|
194
|
+
opacity:
|
|
195
|
+
currentSearchString !== undefined &&
|
|
196
|
+
currentSearchString !== '' &&
|
|
197
|
+
!isSearchMatch(currentSearchString, name)
|
|
198
|
+
? 0.5
|
|
199
|
+
: 1,
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
<td className="text-xs py-1.5 pl-2">{name}</td>
|
|
203
|
+
<td className="text-xs py-1.5 text-right">
|
|
204
|
+
{valueFormatter(report.flat, unit, 2)}
|
|
205
|
+
</td>
|
|
197
206
|
<td className="text-xs py-1.5 text-right pr-2">
|
|
198
|
-
{
|
|
207
|
+
{valueFormatter(report.cumulative, unit, 2)}
|
|
199
208
|
</td>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
{compareMode && (
|
|
210
|
+
<td className="text-xs py-1.5 text-right pr-2">
|
|
211
|
+
{addPlusSign(valueFormatter(report.diff, unit, 2))}
|
|
212
|
+
</td>
|
|
213
|
+
)}
|
|
214
|
+
</tr>
|
|
215
|
+
);
|
|
216
|
+
})}
|
|
203
217
|
</tbody>
|
|
204
218
|
</table>
|
|
205
219
|
</div>
|