@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/GraphTooltip/index.js +2 -2
  3. package/dist/MetricsGraph/index.js +2 -3
  4. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -1
  5. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -1
  6. package/dist/ProfileExplorer/ProfileExplorerSingle.js +2 -1
  7. package/dist/ProfileExplorer/index.d.ts +1 -1
  8. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.d.ts +8 -0
  9. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.js +75 -0
  10. package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.d.ts +47 -0
  11. package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.js +98 -0
  12. package/dist/ProfileIcicleGraph/IcicleGraph/index.d.ts +13 -0
  13. package/dist/ProfileIcicleGraph/IcicleGraph/index.js +74 -0
  14. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.d.ts +14 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +59 -0
  16. package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.d.ts +7 -0
  17. package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.js +32 -0
  18. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +5 -0
  19. package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +59 -0
  20. package/dist/ProfileIcicleGraph/index.d.ts +3 -1
  21. package/dist/ProfileIcicleGraph/index.js +3 -3
  22. package/dist/ProfileView/FilterByFunctionButton.d.ts +1 -1
  23. package/dist/ProfileView/FilterByFunctionButton.js +1 -2
  24. package/dist/ProfileView/ViewSelector.d.ts +1 -1
  25. package/dist/ProfileView/ViewSelector.js +1 -14
  26. package/dist/ProfileView/index.js +4 -4
  27. package/dist/ProfileViewWithData.d.ts +1 -1
  28. package/dist/ProfileViewWithData.js +2 -2
  29. package/dist/TopTable/index.d.ts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/styles.css +1 -1
  33. package/package.json +6 -5
  34. package/src/GraphTooltip/index.tsx +2 -2
  35. package/src/MetricsGraph/index.tsx +2 -3
  36. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +1 -1
  37. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +4 -3
  38. package/src/ProfileExplorer/index.tsx +1 -1
  39. package/src/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.tsx +93 -0
  40. package/src/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.tsx +262 -0
  41. package/src/ProfileIcicleGraph/IcicleGraph/index.tsx +160 -0
  42. package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +106 -0
  43. package/src/ProfileIcicleGraph/IcicleGraph/useNodeColor.ts +42 -0
  44. package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +94 -0
  45. package/src/ProfileIcicleGraph/index.tsx +5 -1
  46. package/src/ProfileView/FilterByFunctionButton.tsx +2 -2
  47. package/src/ProfileView/ViewSelector.tsx +2 -2
  48. package/src/ProfileView/index.tsx +6 -5
  49. package/src/ProfileViewWithData.tsx +3 -2
  50. package/src/TopTable/index.tsx +1 -1
  51. package/src/index.tsx +1 -1
  52. package/dist/IcicleGraph.d.ts +0 -45
  53. package/dist/IcicleGraph.js +0 -180
  54. 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 '../IcicleGraph';
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
  </>