@parca/profile 0.12.30 → 0.12.33
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 +18 -0
- package/package.json +2 -2
- package/src/IcicleGraph.tsx +52 -72
- package/src/ProfileIcicleGraph.tsx +16 -8
- package/src/ProfileView.tsx +5 -5
- package/src/TopTable.tsx +3 -0
- package/src/components/DiffLegend.tsx +99 -0
- package/src/useQuery.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.33](https://github.com/parca-dev/parca/compare/ui-v0.12.32...ui-v0.12.33) (2022-05-02)
|
|
7
|
+
|
|
8
|
+
## [0.12.27](https://github.com/parca-dev/parca/compare/ui-v0.12.26...ui-v0.12.27) (2022-04-29)
|
|
9
|
+
|
|
10
|
+
## [0.12.26](https://github.com/parca-dev/parca/compare/ui-v0.12.23...ui-v0.12.26) (2022-04-28)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
14
|
+
## [0.12.32](https://github.com/parca-dev/parca/compare/ui-v0.12.31...ui-v0.12.32) (2022-05-02)
|
|
15
|
+
|
|
16
|
+
## [0.12.29](https://github.com/parca-dev/parca/compare/ui-v0.12.26...ui-v0.12.29) (2022-04-29)
|
|
17
|
+
|
|
18
|
+
**Note:** Version bump only for package @parca/profile
|
|
19
|
+
|
|
20
|
+
## [0.12.31](https://github.com/parca-dev/parca/compare/ui-v0.12.30...ui-v0.12.31) (2022-05-02)
|
|
21
|
+
|
|
22
|
+
**Note:** Version bump only for package @parca/profile
|
|
23
|
+
|
|
6
24
|
## [0.12.30](https://github.com/parca-dev/parca/compare/ui-v0.12.29...ui-v0.12.30) (2022-04-29)
|
|
7
25
|
|
|
8
26
|
**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.12.
|
|
3
|
+
"version": "0.12.33",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@parca/client": "^0.12.30",
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"access": "public",
|
|
20
20
|
"registry": "https://registry.npmjs.org/"
|
|
21
21
|
},
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "a7d406cff5634de7835a0e69c42c7acec967828b"
|
|
23
23
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -4,20 +4,49 @@ import {pointer} from 'd3-selection';
|
|
|
4
4
|
import {scaleLinear} from 'd3-scale';
|
|
5
5
|
import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
6
6
|
import {usePopper} from 'react-popper';
|
|
7
|
-
import {getLastItem, valueFormatter} from '@parca/functions';
|
|
7
|
+
import {getLastItem, valueFormatter, diffColor} from '@parca/functions';
|
|
8
8
|
import {useAppSelector, selectDarkMode} from '@parca/store';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface IcicleGraphProps {
|
|
11
|
+
graph: Flamegraph;
|
|
12
|
+
sampleUnit: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
curPath: string[];
|
|
15
|
+
setCurPath: (path: string[]) => void;
|
|
16
|
+
}
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
interface IcicleGraphNodesProps {
|
|
19
|
+
data: FlamegraphNode[];
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
total: number;
|
|
23
|
+
totalWidth: number;
|
|
24
|
+
level: number;
|
|
25
|
+
curPath: string[];
|
|
26
|
+
setCurPath: (path: string[]) => void;
|
|
27
|
+
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
28
|
+
path: string[];
|
|
29
|
+
xScale: (value: number) => number;
|
|
30
|
+
}
|
|
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
|
+
interface IcicleGraphRootNodeProps {
|
|
42
|
+
node: FlamegraphRootNode;
|
|
43
|
+
xScale: (value: number) => number;
|
|
44
|
+
total: number;
|
|
45
|
+
totalWidth: number;
|
|
46
|
+
curPath: string[];
|
|
47
|
+
setCurPath: (path: string[]) => void;
|
|
48
|
+
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
49
|
+
}
|
|
21
50
|
|
|
22
51
|
interface IcicleRectProps {
|
|
23
52
|
x: number;
|
|
@@ -32,6 +61,18 @@ interface IcicleRectProps {
|
|
|
32
61
|
curPath: string[];
|
|
33
62
|
}
|
|
34
63
|
|
|
64
|
+
const RowHeight = 26;
|
|
65
|
+
|
|
66
|
+
const icicleRectStyles = {
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
transition: 'opacity .15s linear',
|
|
69
|
+
};
|
|
70
|
+
const fadedIcicleRectStyles = {
|
|
71
|
+
cursor: 'pointer',
|
|
72
|
+
transition: 'opacity .15s linear',
|
|
73
|
+
opacity: '0.5',
|
|
74
|
+
};
|
|
75
|
+
|
|
35
76
|
function IcicleRect({
|
|
36
77
|
x,
|
|
37
78
|
y,
|
|
@@ -75,20 +116,6 @@ function IcicleRect({
|
|
|
75
116
|
);
|
|
76
117
|
}
|
|
77
118
|
|
|
78
|
-
interface IcicleGraphNodesProps {
|
|
79
|
-
data: FlamegraphNode[];
|
|
80
|
-
x: number;
|
|
81
|
-
y: number;
|
|
82
|
-
total: number;
|
|
83
|
-
totalWidth: number;
|
|
84
|
-
level: number;
|
|
85
|
-
curPath: string[];
|
|
86
|
-
setCurPath: (path: string[]) => void;
|
|
87
|
-
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
88
|
-
path: string[];
|
|
89
|
-
xScale: (value: number) => number;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
119
|
export function nodeLabel(node: FlamegraphNode): string {
|
|
93
120
|
if (node.meta === undefined) return '<unknown>';
|
|
94
121
|
const mapping = `${
|
|
@@ -109,26 +136,6 @@ export function nodeLabel(node: FlamegraphNode): string {
|
|
|
109
136
|
return fallback === '' ? '<unknown>' : fallback;
|
|
110
137
|
}
|
|
111
138
|
|
|
112
|
-
function diffColor(diff: number, cumulative: number, isDarkMode: boolean): string {
|
|
113
|
-
const prevValue = cumulative - diff;
|
|
114
|
-
const diffRatio = prevValue > 0 ? (Math.abs(diff) > 0 ? diff / prevValue : 0) : 1.0;
|
|
115
|
-
|
|
116
|
-
const diffTransparency =
|
|
117
|
-
Math.abs(diff) > 0 ? Math.min((Math.abs(diffRatio) / 2 + 0.5) * 0.8, 0.8) : 0;
|
|
118
|
-
|
|
119
|
-
const newSpanColor = isDarkMode ? '#B3BAE1' : '#929FEB';
|
|
120
|
-
const increasedSpanColor = isDarkMode
|
|
121
|
-
? `rgba(255, 177, 204, ${diffTransparency})`
|
|
122
|
-
: `rgba(254, 153, 187, ${diffTransparency})`;
|
|
123
|
-
const reducedSpanColor = isDarkMode
|
|
124
|
-
? `rgba(103, 158, 92, ${diffTransparency})`
|
|
125
|
-
: `rgba(164, 214, 153, ${diffTransparency})`;
|
|
126
|
-
|
|
127
|
-
const color = diff === 0 ? newSpanColor : diff > 0 ? increasedSpanColor : reducedSpanColor;
|
|
128
|
-
|
|
129
|
-
return color;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
139
|
export function IcicleGraphNodes({
|
|
133
140
|
data,
|
|
134
141
|
x,
|
|
@@ -225,15 +232,6 @@ export function IcicleGraphNodes({
|
|
|
225
232
|
|
|
226
233
|
const MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
|
|
227
234
|
|
|
228
|
-
interface FlamegraphTooltipProps {
|
|
229
|
-
x: number;
|
|
230
|
-
y: number;
|
|
231
|
-
unit: string;
|
|
232
|
-
total: number;
|
|
233
|
-
hoveringNode: FlamegraphNode | FlamegraphRootNode | undefined;
|
|
234
|
-
contextElement: Element | null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
235
|
const FlamegraphNodeTooltipTableRows = ({
|
|
238
236
|
hoveringNode,
|
|
239
237
|
}: {
|
|
@@ -424,16 +422,6 @@ export const FlamegraphTooltip = ({
|
|
|
424
422
|
);
|
|
425
423
|
};
|
|
426
424
|
|
|
427
|
-
interface IcicleGraphRootNodeProps {
|
|
428
|
-
node: FlamegraphRootNode;
|
|
429
|
-
xScale: (value: number) => number;
|
|
430
|
-
total: number;
|
|
431
|
-
totalWidth: number;
|
|
432
|
-
curPath: string[];
|
|
433
|
-
setCurPath: (path: string[]) => void;
|
|
434
|
-
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
425
|
export function IcicleGraphRootNode({
|
|
438
426
|
node,
|
|
439
427
|
xScale,
|
|
@@ -487,14 +475,6 @@ export function IcicleGraphRootNode({
|
|
|
487
475
|
|
|
488
476
|
const MemoizedIcicleGraphRootNode = React.memo(IcicleGraphRootNode);
|
|
489
477
|
|
|
490
|
-
interface IcicleGraphProps {
|
|
491
|
-
graph: Flamegraph;
|
|
492
|
-
sampleUnit: string;
|
|
493
|
-
width?: number;
|
|
494
|
-
curPath: string[];
|
|
495
|
-
setCurPath: (path: string[]) => void;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
478
|
export default function IcicleGraph({
|
|
499
479
|
graph,
|
|
500
480
|
width,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import IcicleGraph from './IcicleGraph';
|
|
2
1
|
import {Flamegraph} from '@parca/client';
|
|
2
|
+
import {useAppSelector, selectCompareMode} from '@parca/store';
|
|
3
|
+
|
|
4
|
+
import DiffLegend from './components/DiffLegend';
|
|
5
|
+
import IcicleGraph from './IcicleGraph';
|
|
3
6
|
|
|
4
7
|
interface ProfileIcicleGraphProps {
|
|
5
8
|
width?: number;
|
|
@@ -16,18 +19,23 @@ const ProfileIcicleGraph = ({
|
|
|
16
19
|
setNewCurPath,
|
|
17
20
|
sampleUnit,
|
|
18
21
|
}: ProfileIcicleGraphProps) => {
|
|
22
|
+
const compareMode = useAppSelector(selectCompareMode);
|
|
23
|
+
|
|
19
24
|
if (graph === undefined) return <div>no data...</div>;
|
|
20
25
|
const total = graph.total;
|
|
21
26
|
if (parseFloat(total) === 0) return <>Profile has no samples</>;
|
|
22
27
|
|
|
23
28
|
return (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
<>
|
|
30
|
+
{compareMode && <DiffLegend />}
|
|
31
|
+
<IcicleGraph
|
|
32
|
+
width={width}
|
|
33
|
+
graph={graph}
|
|
34
|
+
curPath={curPath}
|
|
35
|
+
setCurPath={setNewCurPath}
|
|
36
|
+
sampleUnit={sampleUnit}
|
|
37
|
+
/>
|
|
38
|
+
</>
|
|
31
39
|
);
|
|
32
40
|
};
|
|
33
41
|
|
package/src/ProfileView.tsx
CHANGED
|
@@ -46,7 +46,7 @@ export const ProfileView = ({
|
|
|
46
46
|
QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED
|
|
47
47
|
);
|
|
48
48
|
const [currentView, setCurrentView] = useState<string | undefined>(currentViewFromURL);
|
|
49
|
-
const
|
|
49
|
+
const metadata = useGrpcMetadata();
|
|
50
50
|
|
|
51
51
|
useEffect(() => {
|
|
52
52
|
let showLoaderTimeout;
|
|
@@ -110,7 +110,7 @@ export const ProfileView = ({
|
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
queryClient
|
|
113
|
-
.query(req,
|
|
113
|
+
.query(req, {meta: metadata})
|
|
114
114
|
.response.then(response => {
|
|
115
115
|
if (response.report.oneofKind !== 'pprof') {
|
|
116
116
|
console.log('Expected pprof report, got:', response.report.oneofKind);
|
|
@@ -173,7 +173,7 @@ export const ProfileView = ({
|
|
|
173
173
|
</div>
|
|
174
174
|
|
|
175
175
|
<Button
|
|
176
|
-
|
|
176
|
+
variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
|
|
177
177
|
className="rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
178
178
|
onClick={() => switchProfileView('table')}
|
|
179
179
|
>
|
|
@@ -181,7 +181,7 @@ export const ProfileView = ({
|
|
|
181
181
|
</Button>
|
|
182
182
|
|
|
183
183
|
<Button
|
|
184
|
-
|
|
184
|
+
variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
|
|
185
185
|
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"
|
|
186
186
|
onClick={() => switchProfileView('both')}
|
|
187
187
|
>
|
|
@@ -189,7 +189,7 @@ export const ProfileView = ({
|
|
|
189
189
|
</Button>
|
|
190
190
|
|
|
191
191
|
<Button
|
|
192
|
-
|
|
192
|
+
variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
|
|
193
193
|
className="rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
194
194
|
onClick={() => switchProfileView('icicle')}
|
|
195
195
|
>
|
package/src/TopTable.tsx
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {Fragment, useState} from 'react';
|
|
2
|
+
import {Popover, Transition} from '@headlessui/react';
|
|
3
|
+
import {useAppSelector, selectDarkMode} from '@parca/store';
|
|
4
|
+
import {getNewSpanColor, getIncreasedSpanColor, getReducedSpanColor} from '@parca/functions';
|
|
5
|
+
import {usePopper} from 'react-popper';
|
|
6
|
+
|
|
7
|
+
const transparencyValues = [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100];
|
|
8
|
+
|
|
9
|
+
const DiffLegendBar = ({
|
|
10
|
+
onMouseEnter,
|
|
11
|
+
onMouseLeave,
|
|
12
|
+
}: {
|
|
13
|
+
onMouseEnter: () => void;
|
|
14
|
+
onMouseLeave: () => void;
|
|
15
|
+
}) => {
|
|
16
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex items-center m-2">
|
|
20
|
+
{transparencyValues.map(value => {
|
|
21
|
+
const valueAsPercentage = value / 100;
|
|
22
|
+
const absoluteValue = Math.abs(valueAsPercentage);
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
onMouseEnter={onMouseEnter}
|
|
26
|
+
onMouseLeave={onMouseLeave}
|
|
27
|
+
className="w-8 h-4"
|
|
28
|
+
key={valueAsPercentage}
|
|
29
|
+
style={{
|
|
30
|
+
backgroundColor:
|
|
31
|
+
absoluteValue === 0
|
|
32
|
+
? getNewSpanColor(isDarkMode)
|
|
33
|
+
: valueAsPercentage > 0
|
|
34
|
+
? getIncreasedSpanColor(absoluteValue, isDarkMode)
|
|
35
|
+
: getReducedSpanColor(absoluteValue, isDarkMode),
|
|
36
|
+
}}
|
|
37
|
+
></div>
|
|
38
|
+
);
|
|
39
|
+
})}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DiffLegend = () => {
|
|
45
|
+
const [showLegendTooltip, setShowLegendTooltip] = useState(false);
|
|
46
|
+
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
47
|
+
let [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
|
|
48
|
+
|
|
49
|
+
const {styles, attributes, ...popperProps} = usePopper(referenceElement, popperElement, {
|
|
50
|
+
placement: 'auto-start',
|
|
51
|
+
strategy: 'absolute',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const handleMouseEnter = () => {
|
|
55
|
+
setShowLegendTooltip(true);
|
|
56
|
+
};
|
|
57
|
+
const handleMouseLeave = () => {
|
|
58
|
+
setShowLegendTooltip(false);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="mt-1 mb-2">
|
|
63
|
+
<div ref={setReferenceElement} className="flex items-center justify-center">
|
|
64
|
+
<span>Better</span>
|
|
65
|
+
<DiffLegendBar onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} />
|
|
66
|
+
<span>Worse</span>
|
|
67
|
+
</div>
|
|
68
|
+
<Popover className="relative">
|
|
69
|
+
{() => (
|
|
70
|
+
<Transition
|
|
71
|
+
show={showLegendTooltip}
|
|
72
|
+
as={Fragment}
|
|
73
|
+
enter="transition ease-out duration-200"
|
|
74
|
+
enterFrom="opacity-0 translate-y-1"
|
|
75
|
+
enterTo="opacity-100 translate-y-0"
|
|
76
|
+
leave="transition ease-in duration-150"
|
|
77
|
+
leaveFrom="opacity-100 translate-y-0"
|
|
78
|
+
leaveTo="opacity-0 translate-y-1"
|
|
79
|
+
>
|
|
80
|
+
<Popover.Panel ref={setPopperElement} style={styles.popper} {...attributes.popper}>
|
|
81
|
+
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
|
82
|
+
<div className="p-4 bg-gray-50 dark:bg-gray-800">
|
|
83
|
+
<div className="flex items-center justify-center"></div>
|
|
84
|
+
<span className="block text-sm text-gray-500 dark:text-gray-50">
|
|
85
|
+
This is a differential icicle graph, where a purple-colored node means
|
|
86
|
+
unchanged, and the darker the red, the worse the node got, and the darker the
|
|
87
|
+
green, the better the node got.
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</Popover.Panel>
|
|
92
|
+
</Transition>
|
|
93
|
+
)}
|
|
94
|
+
</Popover>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default DiffLegend;
|
package/src/useQuery.tsx
CHANGED
|
@@ -31,7 +31,7 @@ export const useQuery = (
|
|
|
31
31
|
const req = profileSource.QueryRequest();
|
|
32
32
|
req.reportType = reportType;
|
|
33
33
|
|
|
34
|
-
const call = client.query(req, metadata);
|
|
34
|
+
const call = client.query(req, {meta: metadata});
|
|
35
35
|
|
|
36
36
|
call.response
|
|
37
37
|
.then(response => setResult({response: response, error: null, isLoading: false}))
|