@parca/profile 0.16.107 → 0.16.108
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/dist/GraphTooltip/index.js +2 -2
- package/dist/MetricsGraph/index.js +2 -3
- package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -1
- package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -1
- package/dist/ProfileExplorer/ProfileExplorerSingle.js +2 -1
- package/dist/ProfileExplorer/index.d.ts +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.d.ts +8 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.js +75 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.d.ts +47 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.js +98 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/index.d.ts +13 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/index.js +74 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.d.ts +14 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +59 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.d.ts +7 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.js +32 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +5 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +59 -0
- package/dist/ProfileIcicleGraph/index.d.ts +3 -1
- package/dist/ProfileIcicleGraph/index.js +3 -3
- package/dist/ProfileView/FilterByFunctionButton.d.ts +1 -1
- package/dist/ProfileView/FilterByFunctionButton.js +1 -2
- package/dist/ProfileView/ViewSelector.d.ts +1 -1
- package/dist/ProfileView/ViewSelector.js +1 -14
- package/dist/ProfileView/index.js +4 -4
- package/dist/ProfileViewWithData.d.ts +1 -1
- package/dist/ProfileViewWithData.js +2 -2
- package/dist/TopTable/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -5
- package/src/GraphTooltip/index.tsx +2 -2
- package/src/MetricsGraph/index.tsx +2 -3
- package/src/ProfileExplorer/ProfileExplorerCompare.tsx +1 -1
- package/src/ProfileExplorer/ProfileExplorerSingle.tsx +4 -3
- package/src/ProfileExplorer/index.tsx +1 -1
- package/src/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.tsx +93 -0
- package/src/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.tsx +262 -0
- package/src/ProfileIcicleGraph/IcicleGraph/index.tsx +160 -0
- package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +106 -0
- package/src/ProfileIcicleGraph/IcicleGraph/useNodeColor.ts +42 -0
- package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +94 -0
- package/src/ProfileIcicleGraph/index.tsx +5 -1
- package/src/ProfileView/FilterByFunctionButton.tsx +2 -2
- package/src/ProfileView/ViewSelector.tsx +2 -2
- package/src/ProfileView/index.tsx +6 -5
- package/src/ProfileViewWithData.tsx +3 -2
- package/src/TopTable/index.tsx +1 -1
- package/src/index.tsx +1 -1
- package/dist/IcicleGraph.d.ts +0 -45
- package/dist/IcicleGraph.js +0 -180
- package/src/IcicleGraph.tsx +0 -481
|
@@ -0,0 +1,262 @@
|
|
|
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, {useMemo} from 'react';
|
|
15
|
+
|
|
16
|
+
import cx from 'classnames';
|
|
17
|
+
import {scaleLinear} from 'd3-scale';
|
|
18
|
+
import {
|
|
19
|
+
Mapping,
|
|
20
|
+
Function as ParcaFunction,
|
|
21
|
+
Location,
|
|
22
|
+
} from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
|
|
23
|
+
import {isSearchMatch} from '@parca/functions';
|
|
24
|
+
import {FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
25
|
+
|
|
26
|
+
import {nodeLabel} from './utils';
|
|
27
|
+
import useNodeColor from './useNodeColor';
|
|
28
|
+
import {useKeyDown} from '@parca/components';
|
|
29
|
+
|
|
30
|
+
export const RowHeight = 26;
|
|
31
|
+
|
|
32
|
+
interface IcicleGraphNodesProps {
|
|
33
|
+
data: FlamegraphNode[];
|
|
34
|
+
strings: string[];
|
|
35
|
+
mappings: Mapping[];
|
|
36
|
+
locations: Location[];
|
|
37
|
+
functions: ParcaFunction[];
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
total: number;
|
|
41
|
+
totalWidth: number;
|
|
42
|
+
level: number;
|
|
43
|
+
curPath: string[];
|
|
44
|
+
setCurPath: (path: string[]) => void;
|
|
45
|
+
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
46
|
+
path: string[];
|
|
47
|
+
xScale: (value: number) => number;
|
|
48
|
+
searchString?: string;
|
|
49
|
+
compareMode: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const IcicleGraphNodes = React.memo(function IcicleGraphNodes({
|
|
53
|
+
data,
|
|
54
|
+
strings,
|
|
55
|
+
mappings,
|
|
56
|
+
locations,
|
|
57
|
+
functions,
|
|
58
|
+
x,
|
|
59
|
+
y,
|
|
60
|
+
xScale,
|
|
61
|
+
total,
|
|
62
|
+
totalWidth,
|
|
63
|
+
level,
|
|
64
|
+
setHoveringNode,
|
|
65
|
+
path,
|
|
66
|
+
setCurPath,
|
|
67
|
+
curPath,
|
|
68
|
+
searchString,
|
|
69
|
+
compareMode,
|
|
70
|
+
}: IcicleGraphNodesProps): JSX.Element {
|
|
71
|
+
const nodes =
|
|
72
|
+
curPath.length === 0
|
|
73
|
+
? data
|
|
74
|
+
: data.filter(
|
|
75
|
+
d => d != null && curPath[0] === nodeLabel(d, strings, mappings, locations, functions)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<g transform={`translate(${x}, ${y})`}>
|
|
80
|
+
{nodes.map((d, i) => {
|
|
81
|
+
const start = nodes.slice(0, i).reduce((sum, d) => sum + parseFloat(d.cumulative), 0);
|
|
82
|
+
const xStart = xScale(start);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<IcicleNode
|
|
86
|
+
key={`node-${level}-${i}`}
|
|
87
|
+
x={xStart}
|
|
88
|
+
y={0}
|
|
89
|
+
totalWidth={totalWidth}
|
|
90
|
+
height={RowHeight}
|
|
91
|
+
path={path}
|
|
92
|
+
setCurPath={setCurPath}
|
|
93
|
+
setHoveringNode={setHoveringNode}
|
|
94
|
+
level={level}
|
|
95
|
+
curPath={curPath}
|
|
96
|
+
data={d}
|
|
97
|
+
strings={strings}
|
|
98
|
+
mappings={mappings}
|
|
99
|
+
locations={locations}
|
|
100
|
+
functions={functions}
|
|
101
|
+
total={total}
|
|
102
|
+
xScale={xScale}
|
|
103
|
+
searchString={searchString}
|
|
104
|
+
compareMode={compareMode}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</g>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
interface IcicleNodeProps {
|
|
113
|
+
x: number;
|
|
114
|
+
y: number;
|
|
115
|
+
height: number;
|
|
116
|
+
totalWidth: number;
|
|
117
|
+
curPath: string[];
|
|
118
|
+
level: number;
|
|
119
|
+
data: FlamegraphNode;
|
|
120
|
+
strings: string[];
|
|
121
|
+
mappings: Mapping[];
|
|
122
|
+
locations: Location[];
|
|
123
|
+
functions: ParcaFunction[];
|
|
124
|
+
path: string[];
|
|
125
|
+
total: number;
|
|
126
|
+
setCurPath: (path: string[]) => void;
|
|
127
|
+
setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
|
|
128
|
+
xScale: (value: number) => number;
|
|
129
|
+
isRoot?: boolean;
|
|
130
|
+
searchString?: string;
|
|
131
|
+
compareMode: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const icicleRectStyles = {
|
|
135
|
+
cursor: 'pointer',
|
|
136
|
+
transition: 'opacity .15s linear',
|
|
137
|
+
};
|
|
138
|
+
const fadedIcicleRectStyles = {
|
|
139
|
+
cursor: 'pointer',
|
|
140
|
+
transition: 'opacity .15s linear',
|
|
141
|
+
opacity: '0.5',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const IcicleNode = React.memo(function IcicleNode({
|
|
145
|
+
x,
|
|
146
|
+
y,
|
|
147
|
+
height,
|
|
148
|
+
setCurPath,
|
|
149
|
+
setHoveringNode,
|
|
150
|
+
curPath,
|
|
151
|
+
level,
|
|
152
|
+
path,
|
|
153
|
+
data,
|
|
154
|
+
strings,
|
|
155
|
+
mappings,
|
|
156
|
+
locations,
|
|
157
|
+
functions,
|
|
158
|
+
total,
|
|
159
|
+
totalWidth,
|
|
160
|
+
xScale,
|
|
161
|
+
isRoot = false,
|
|
162
|
+
searchString,
|
|
163
|
+
compareMode,
|
|
164
|
+
}: IcicleNodeProps): JSX.Element {
|
|
165
|
+
const {isShiftDown} = useKeyDown();
|
|
166
|
+
const colorResult = useNodeColor({data, compareMode});
|
|
167
|
+
const name = useMemo(() => {
|
|
168
|
+
return isRoot ? 'root' : nodeLabel(data, strings, mappings, locations, functions);
|
|
169
|
+
}, [data, strings, mappings, locations, functions, isRoot]);
|
|
170
|
+
const nextPath = path.concat([name]);
|
|
171
|
+
const isFaded = curPath.length > 0 && name !== curPath[curPath.length - 1];
|
|
172
|
+
const styles = isFaded ? fadedIcicleRectStyles : icicleRectStyles;
|
|
173
|
+
const nextLevel = level + 1;
|
|
174
|
+
const cumulative = parseFloat(data.cumulative);
|
|
175
|
+
const nextCurPath = curPath.length === 0 ? [] : curPath.slice(1);
|
|
176
|
+
const newXScale =
|
|
177
|
+
nextCurPath.length === 0 && curPath.length === 1
|
|
178
|
+
? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
|
|
179
|
+
: xScale;
|
|
180
|
+
|
|
181
|
+
const width =
|
|
182
|
+
nextCurPath.length > 0 || (nextCurPath.length === 0 && curPath.length === 1)
|
|
183
|
+
? totalWidth
|
|
184
|
+
: xScale(cumulative);
|
|
185
|
+
|
|
186
|
+
const {isHighlightEnabled = false, isHighlighted = false} = useMemo(() => {
|
|
187
|
+
if (searchString === undefined || searchString === '') {
|
|
188
|
+
return {isHighlightEnabled: false};
|
|
189
|
+
}
|
|
190
|
+
return {isHighlightEnabled: true, isHighlighted: isSearchMatch(searchString, name)};
|
|
191
|
+
}, [searchString, name]);
|
|
192
|
+
|
|
193
|
+
if (width <= 1) {
|
|
194
|
+
return <>{null}</>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const onMouseEnter = (): void => {
|
|
198
|
+
if (isShiftDown) return;
|
|
199
|
+
|
|
200
|
+
setHoveringNode(data);
|
|
201
|
+
};
|
|
202
|
+
const onMouseLeave = (): void => {
|
|
203
|
+
if (isShiftDown) return;
|
|
204
|
+
|
|
205
|
+
setHoveringNode(undefined);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<>
|
|
210
|
+
<g
|
|
211
|
+
transform={`translate(${x + 1}, ${y + 1})`}
|
|
212
|
+
style={styles}
|
|
213
|
+
onMouseEnter={onMouseEnter}
|
|
214
|
+
onMouseLeave={onMouseLeave}
|
|
215
|
+
onClick={() => {
|
|
216
|
+
setCurPath(nextPath);
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<rect
|
|
220
|
+
x={0}
|
|
221
|
+
y={0}
|
|
222
|
+
width={width - 1}
|
|
223
|
+
height={height - 1}
|
|
224
|
+
style={{
|
|
225
|
+
fill: colorResult,
|
|
226
|
+
}}
|
|
227
|
+
className={cx({
|
|
228
|
+
'opacity-50': isHighlightEnabled && !isHighlighted,
|
|
229
|
+
})}
|
|
230
|
+
/>
|
|
231
|
+
{width > 5 && (
|
|
232
|
+
<svg width={width - 5} height={height}>
|
|
233
|
+
<text x={5} y={15} style={{fontSize: '12px'}}>
|
|
234
|
+
{name}
|
|
235
|
+
</text>
|
|
236
|
+
</svg>
|
|
237
|
+
)}
|
|
238
|
+
</g>
|
|
239
|
+
{data.children !== undefined && data.children.length > 0 && (
|
|
240
|
+
<IcicleGraphNodes
|
|
241
|
+
data={data.children}
|
|
242
|
+
strings={strings}
|
|
243
|
+
mappings={mappings}
|
|
244
|
+
locations={locations}
|
|
245
|
+
functions={functions}
|
|
246
|
+
x={x}
|
|
247
|
+
y={RowHeight}
|
|
248
|
+
xScale={newXScale}
|
|
249
|
+
total={total}
|
|
250
|
+
totalWidth={totalWidth}
|
|
251
|
+
level={nextLevel}
|
|
252
|
+
setHoveringNode={setHoveringNode}
|
|
253
|
+
path={nextPath}
|
|
254
|
+
curPath={nextCurPath}
|
|
255
|
+
setCurPath={setCurPath}
|
|
256
|
+
searchString={searchString}
|
|
257
|
+
compareMode={compareMode}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
</>
|
|
261
|
+
);
|
|
262
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
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, {memo, useEffect, useMemo, useRef, useState} from 'react';
|
|
15
|
+
|
|
16
|
+
import cx from 'classnames';
|
|
17
|
+
import {throttle} from 'lodash';
|
|
18
|
+
import {pointer} from 'd3-selection';
|
|
19
|
+
import {scaleLinear} from 'd3-scale';
|
|
20
|
+
|
|
21
|
+
import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
22
|
+
import type {HoveringNode} from '../../GraphTooltip';
|
|
23
|
+
import GraphTooltip from '../../GraphTooltip';
|
|
24
|
+
import {Button, useURLState} from '@parca/components';
|
|
25
|
+
import {IcicleNode, RowHeight} from './IcicleGraphNodes';
|
|
26
|
+
import useColoredGraph from './useColoredGraph';
|
|
27
|
+
import {selectQueryParam} from '@parca/functions';
|
|
28
|
+
import type {NavigateFunction} from '@parca/functions';
|
|
29
|
+
import ColorStackLegend from './ColorStackLegend';
|
|
30
|
+
|
|
31
|
+
interface IcicleGraphProps {
|
|
32
|
+
graph: Flamegraph;
|
|
33
|
+
sampleUnit: string;
|
|
34
|
+
width?: number;
|
|
35
|
+
curPath: string[];
|
|
36
|
+
setCurPath: (path: string[]) => void;
|
|
37
|
+
navigateTo?: NavigateFunction;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const IcicleGraph = memo(function IcicleGraph({
|
|
41
|
+
graph,
|
|
42
|
+
width,
|
|
43
|
+
setCurPath,
|
|
44
|
+
curPath,
|
|
45
|
+
sampleUnit,
|
|
46
|
+
navigateTo,
|
|
47
|
+
}: IcicleGraphProps): JSX.Element {
|
|
48
|
+
const [hoveringNode, setHoveringNode] = useState<
|
|
49
|
+
FlamegraphNode | FlamegraphRootNode | undefined
|
|
50
|
+
>();
|
|
51
|
+
const [pos, setPos] = useState([0, 0]);
|
|
52
|
+
const [height, setHeight] = useState(0);
|
|
53
|
+
const svg = useRef(null);
|
|
54
|
+
const ref = useRef<SVGGElement>(null);
|
|
55
|
+
const [rawDashboardItems] = useURLState({
|
|
56
|
+
param: 'dashboard_items',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const dashboardItems = rawDashboardItems as string[];
|
|
60
|
+
const coloredGraph = useColoredGraph(graph);
|
|
61
|
+
const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
62
|
+
const compareMode: boolean =
|
|
63
|
+
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (ref.current != null) {
|
|
67
|
+
setHeight(ref?.current.getBoundingClientRect().height);
|
|
68
|
+
}
|
|
69
|
+
}, [width, coloredGraph]);
|
|
70
|
+
|
|
71
|
+
const total = useMemo(() => parseFloat(coloredGraph.total), [coloredGraph.total]);
|
|
72
|
+
const xScale = useMemo(() => {
|
|
73
|
+
if (width === undefined) {
|
|
74
|
+
return () => 0;
|
|
75
|
+
}
|
|
76
|
+
return scaleLinear().domain([0, total]).range([0, width]);
|
|
77
|
+
}, [total, width]);
|
|
78
|
+
|
|
79
|
+
if (coloredGraph.root === undefined || width === undefined) {
|
|
80
|
+
return <></>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const throttledSetPos = throttle(setPos, 20);
|
|
84
|
+
const onMouseMove = (e: React.MouseEvent<SVGSVGElement | HTMLDivElement>): void => {
|
|
85
|
+
// X/Y coordinate array relative to svg
|
|
86
|
+
const rel = pointer(e);
|
|
87
|
+
|
|
88
|
+
throttledSetPos([rel[0], rel[1]]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div onMouseLeave={() => setHoveringNode(undefined)} className="relative">
|
|
93
|
+
<ColorStackLegend navigateTo={navigateTo} compareMode={compareMode} />
|
|
94
|
+
<GraphTooltip
|
|
95
|
+
unit={sampleUnit}
|
|
96
|
+
total={total}
|
|
97
|
+
x={pos[0]}
|
|
98
|
+
y={pos[1]}
|
|
99
|
+
hoveringNode={hoveringNode as HoveringNode}
|
|
100
|
+
contextElement={svg.current}
|
|
101
|
+
strings={coloredGraph.stringTable}
|
|
102
|
+
mappings={coloredGraph.mapping}
|
|
103
|
+
locations={coloredGraph.locations}
|
|
104
|
+
functions={coloredGraph.function}
|
|
105
|
+
/>
|
|
106
|
+
<div
|
|
107
|
+
className={cx(
|
|
108
|
+
dashboardItems.length > 1 ? 'top-[-46px] left-[25px]' : 'top-[-45px]',
|
|
109
|
+
'flex justify-start absolute '
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<Button
|
|
113
|
+
color="neutral"
|
|
114
|
+
onClick={() => setCurPath([])}
|
|
115
|
+
disabled={curPath.length === 0}
|
|
116
|
+
className="w-auto"
|
|
117
|
+
variant="neutral"
|
|
118
|
+
>
|
|
119
|
+
Reset View
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
<svg
|
|
123
|
+
className="font-robotoMono"
|
|
124
|
+
width={width}
|
|
125
|
+
height={height}
|
|
126
|
+
onMouseMove={onMouseMove}
|
|
127
|
+
preserveAspectRatio="xMinYMid"
|
|
128
|
+
ref={svg}
|
|
129
|
+
>
|
|
130
|
+
<g ref={ref}>
|
|
131
|
+
<g transform={'translate(0, 0)'}>
|
|
132
|
+
<IcicleNode
|
|
133
|
+
x={0}
|
|
134
|
+
y={0}
|
|
135
|
+
totalWidth={width}
|
|
136
|
+
height={RowHeight}
|
|
137
|
+
setCurPath={setCurPath}
|
|
138
|
+
setHoveringNode={setHoveringNode}
|
|
139
|
+
curPath={curPath}
|
|
140
|
+
data={coloredGraph.root}
|
|
141
|
+
strings={coloredGraph.stringTable}
|
|
142
|
+
mappings={coloredGraph.mapping}
|
|
143
|
+
locations={coloredGraph.locations}
|
|
144
|
+
functions={coloredGraph.function}
|
|
145
|
+
total={total}
|
|
146
|
+
xScale={xScale}
|
|
147
|
+
path={[]}
|
|
148
|
+
level={0}
|
|
149
|
+
isRoot={true}
|
|
150
|
+
searchString={currentSearchString}
|
|
151
|
+
compareMode={compareMode}
|
|
152
|
+
/>
|
|
153
|
+
</g>
|
|
154
|
+
</g>
|
|
155
|
+
</svg>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
export default IcicleGraph;
|
|
@@ -0,0 +1,106 @@
|
|
|
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 {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
15
|
+
import {
|
|
16
|
+
Mapping,
|
|
17
|
+
Function as ParcaFunction,
|
|
18
|
+
Location,
|
|
19
|
+
} from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
|
|
20
|
+
import type {ColorProfileName} from '@parca/functions';
|
|
21
|
+
import useUserPreference, {USER_PREFERENCES} from '@parca/functions/useUserPreference';
|
|
22
|
+
import {setFeatures, useAppDispatch} from '@parca/store';
|
|
23
|
+
import {useEffect, useMemo} from 'react';
|
|
24
|
+
import {extractFeature} from './utils';
|
|
25
|
+
|
|
26
|
+
export interface ColoredFlamegraphNode extends FlamegraphNode {
|
|
27
|
+
feature?: string;
|
|
28
|
+
children: ColoredFlamegraphNode[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ColoredFlamgraphRootNode extends FlamegraphRootNode {
|
|
32
|
+
feature?: string;
|
|
33
|
+
children: ColoredFlamegraphNode[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ColoredFlamegraph extends Flamegraph {
|
|
37
|
+
root: ColoredFlamgraphRootNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const colorNodes = (
|
|
41
|
+
nodes: FlamegraphNode[] | undefined,
|
|
42
|
+
strings: string[],
|
|
43
|
+
mappings: Mapping[],
|
|
44
|
+
locations: Location[],
|
|
45
|
+
functions: ParcaFunction[],
|
|
46
|
+
features: {[key: string]: boolean}
|
|
47
|
+
): ColoredFlamegraphNode[] => {
|
|
48
|
+
if (nodes === undefined) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return nodes.map<ColoredFlamegraphNode>(node => {
|
|
52
|
+
const coloredNode: ColoredFlamegraphNode = {
|
|
53
|
+
...node,
|
|
54
|
+
};
|
|
55
|
+
if (node.children != null) {
|
|
56
|
+
coloredNode.children = colorNodes(
|
|
57
|
+
node.children,
|
|
58
|
+
strings,
|
|
59
|
+
mappings,
|
|
60
|
+
locations,
|
|
61
|
+
functions,
|
|
62
|
+
features
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
coloredNode.feature = extractFeature(node, mappings, locations, strings, functions);
|
|
66
|
+
features[coloredNode.feature] = true;
|
|
67
|
+
return coloredNode;
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const useColoredGraph = (graph: Flamegraph): ColoredFlamegraph => {
|
|
72
|
+
const dispatch = useAppDispatch();
|
|
73
|
+
const [colorProfile] = useUserPreference<ColorProfileName>(
|
|
74
|
+
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const [coloredGraph, features]: [ColoredFlamegraph, string[]] = useMemo(() => {
|
|
78
|
+
if (graph.root == null) {
|
|
79
|
+
return [graph as ColoredFlamegraph, []];
|
|
80
|
+
}
|
|
81
|
+
const features: {[key: string]: boolean} = {};
|
|
82
|
+
const coloredGraph = {
|
|
83
|
+
...graph,
|
|
84
|
+
root: {
|
|
85
|
+
...graph.root,
|
|
86
|
+
children: colorNodes(
|
|
87
|
+
graph.root.children ?? [],
|
|
88
|
+
graph.stringTable,
|
|
89
|
+
graph.mapping,
|
|
90
|
+
graph.locations,
|
|
91
|
+
graph.function,
|
|
92
|
+
features
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return [coloredGraph, Object.keys(features)];
|
|
97
|
+
}, [graph]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
dispatch(setFeatures({features, colorProfileName: colorProfile}));
|
|
101
|
+
}, [features, colorProfile, dispatch]);
|
|
102
|
+
|
|
103
|
+
return coloredGraph;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default useColoredGraph;
|
|
@@ -0,0 +1,42 @@
|
|
|
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 {diffColor} from '@parca/functions';
|
|
15
|
+
import {EVERYTHING_ELSE, selectDarkMode, selectStackColors, useAppSelector} from '@parca/store';
|
|
16
|
+
import {useMemo} from 'react';
|
|
17
|
+
import type {ColoredFlamegraphNode} from './useColoredGraph';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
data: ColoredFlamegraphNode;
|
|
21
|
+
compareMode: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const useNodeColor = ({data, compareMode}: Props): string => {
|
|
25
|
+
const colors = useAppSelector(selectStackColors);
|
|
26
|
+
const isDarkMode = useAppSelector(selectDarkMode);
|
|
27
|
+
|
|
28
|
+
const color: string = useMemo(() => {
|
|
29
|
+
if (compareMode) {
|
|
30
|
+
const diff = parseFloat(data.diff);
|
|
31
|
+
const cumulative = parseFloat(data.cumulative);
|
|
32
|
+
return diffColor(diff, cumulative, isDarkMode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const color = colors[data.feature ?? EVERYTHING_ELSE];
|
|
36
|
+
return color;
|
|
37
|
+
}, [data, colors, isDarkMode, compareMode]);
|
|
38
|
+
|
|
39
|
+
return color;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default useNodeColor;
|
|
@@ -0,0 +1,94 @@
|
|
|
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 {getLastItem} from '@parca/functions';
|
|
15
|
+
import {FlamegraphNode} from '@parca/client';
|
|
16
|
+
import {
|
|
17
|
+
Mapping,
|
|
18
|
+
Function as ParcaFunction,
|
|
19
|
+
Location,
|
|
20
|
+
} from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
|
|
21
|
+
import {hexifyAddress} from '../../utils';
|
|
22
|
+
import {EVERYTHING_ELSE} from '@parca/store';
|
|
23
|
+
|
|
24
|
+
export const getBinaryName = (
|
|
25
|
+
node: FlamegraphNode,
|
|
26
|
+
mappings: Mapping[],
|
|
27
|
+
locations: Location[],
|
|
28
|
+
strings: string[]
|
|
29
|
+
): string | undefined => {
|
|
30
|
+
if (node.meta?.locationIndex === undefined || node.meta?.locationIndex === 0) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const location = locations[node.meta.locationIndex - 1];
|
|
35
|
+
|
|
36
|
+
if (location.mappingIndex === undefined || location.mappingIndex === 0) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const mapping = mappings[location.mappingIndex - 1];
|
|
40
|
+
if (mapping == null || mapping.fileStringIndex == null) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mappingFile = strings[mapping.fileStringIndex];
|
|
45
|
+
return getLastItem(mappingFile);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function nodeLabel(
|
|
49
|
+
node: FlamegraphNode,
|
|
50
|
+
strings: string[],
|
|
51
|
+
mappings: Mapping[],
|
|
52
|
+
locations: Location[],
|
|
53
|
+
functions: ParcaFunction[]
|
|
54
|
+
): string {
|
|
55
|
+
if (node.meta?.locationIndex === undefined) return '<unknown>';
|
|
56
|
+
if (node.meta?.locationIndex === 0) return '<unknown>';
|
|
57
|
+
|
|
58
|
+
const location = locations[node.meta.locationIndex - 1];
|
|
59
|
+
|
|
60
|
+
const binary = getBinaryName(node, mappings, locations, strings);
|
|
61
|
+
|
|
62
|
+
const mappingString: string = binary != null ? `[${binary}]` : '';
|
|
63
|
+
|
|
64
|
+
if (location.lines.length > 0) {
|
|
65
|
+
const funcName =
|
|
66
|
+
strings[functions[location.lines[node.meta.lineIndex].functionIndex - 1].nameStringIndex];
|
|
67
|
+
return `${mappingString} ${funcName}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const address = hexifyAddress(location.address);
|
|
71
|
+
const fallback = `${mappingString}${address}`;
|
|
72
|
+
|
|
73
|
+
return fallback === '' ? '<unknown>' : fallback;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const extractFeature = (
|
|
77
|
+
data: FlamegraphNode,
|
|
78
|
+
mappings: Mapping[],
|
|
79
|
+
locations: Location[],
|
|
80
|
+
strings: string[],
|
|
81
|
+
functions: ParcaFunction[]
|
|
82
|
+
): string => {
|
|
83
|
+
const name = nodeLabel(data, strings, mappings, locations, functions).trim();
|
|
84
|
+
if (name.startsWith('runtime') || name === 'root') {
|
|
85
|
+
return 'runtime';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const binaryName = getBinaryName(data, mappings, locations, strings);
|
|
89
|
+
if (binaryName != null) {
|
|
90
|
+
return binaryName;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return EVERYTHING_ELSE;
|
|
94
|
+
};
|
|
@@ -15,8 +15,9 @@ import {Flamegraph} from '@parca/client';
|
|
|
15
15
|
import {useContainerDimensions} from '@parca/dynamicsize';
|
|
16
16
|
|
|
17
17
|
import DiffLegend from '../components/DiffLegend';
|
|
18
|
-
import IcicleGraph from '
|
|
18
|
+
import IcicleGraph from './IcicleGraph';
|
|
19
19
|
import {selectQueryParam} from '@parca/functions';
|
|
20
|
+
import type {NavigateFunction} from '@parca/functions';
|
|
20
21
|
import {useEffect, useMemo} from 'react';
|
|
21
22
|
|
|
22
23
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
@@ -30,6 +31,7 @@ interface ProfileIcicleGraphProps {
|
|
|
30
31
|
curPath: string[] | [];
|
|
31
32
|
setNewCurPath: (path: string[]) => void;
|
|
32
33
|
onContainerResize?: ResizeHandler;
|
|
34
|
+
navigateTo?: NavigateFunction;
|
|
33
35
|
loading: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -39,6 +41,7 @@ const ProfileIcicleGraph = ({
|
|
|
39
41
|
setNewCurPath,
|
|
40
42
|
sampleUnit,
|
|
41
43
|
onContainerResize,
|
|
44
|
+
navigateTo,
|
|
42
45
|
loading,
|
|
43
46
|
}: ProfileIcicleGraphProps): JSX.Element => {
|
|
44
47
|
const compareMode: boolean =
|
|
@@ -93,6 +96,7 @@ const ProfileIcicleGraph = ({
|
|
|
93
96
|
curPath={curPath}
|
|
94
97
|
setCurPath={setNewCurPath}
|
|
95
98
|
sampleUnit={sampleUnit}
|
|
99
|
+
navigateTo={navigateTo}
|
|
96
100
|
/>
|
|
97
101
|
</div>
|
|
98
102
|
</>
|