@parca/profile 0.16.136 → 0.16.138
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 +8 -0
- package/dist/Callgraph/index.d.ts +3 -3
- package/dist/Callgraph/index.js +79 -191
- package/dist/Callgraph/utils.js +15 -19
- package/dist/GraphTooltip/index.d.ts +22 -8
- package/dist/GraphTooltip/index.js +14 -14
- package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.js +2 -1
- package/dist/ProfileView/index.js +113 -11
- package/dist/index.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -8
- package/src/Callgraph/index.tsx +127 -323
- package/src/Callgraph/utils.ts +15 -21
- package/src/GraphTooltip/index.tsx +38 -13
- package/src/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.tsx +2 -1
- package/src/ProfileView/index.tsx +72 -19
- package/typings.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.16.138 (2023-03-16)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.137 (2023-03-15)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.136](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.135...@parca/profile@0.16.136) (2023-03-13)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Callgraph as CallgraphType } from '@parca/client';
|
|
2
2
|
export interface Props {
|
|
3
|
-
|
|
3
|
+
data: CallgraphType;
|
|
4
|
+
svgString: string;
|
|
4
5
|
sampleUnit: string;
|
|
5
6
|
width: number;
|
|
6
|
-
colorRange: [string, string];
|
|
7
7
|
}
|
|
8
|
-
declare const Callgraph: ({
|
|
8
|
+
declare const Callgraph: ({ data, svgString, sampleUnit, width }: Props) => JSX.Element;
|
|
9
9
|
export default Callgraph;
|
package/dist/Callgraph/index.js
CHANGED
|
@@ -9,43 +9,7 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
23
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
-
function step(op) {
|
|
26
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
-
switch (op[0]) {
|
|
31
|
-
case 0: case 1: t = op; break;
|
|
32
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
-
default:
|
|
36
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
-
if (t[2]) _.ops.pop();
|
|
41
|
-
_.trys.pop(); continue;
|
|
42
|
-
}
|
|
43
|
-
op = body.call(thisArg, _);
|
|
44
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
49
13
|
// Copyright 2022 The Parca Authors
|
|
50
14
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
51
15
|
// you may not use this file except in compliance with the License.
|
|
@@ -61,166 +25,90 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
61
25
|
import { useEffect, useRef, useState } from 'react';
|
|
62
26
|
import cx from 'classnames';
|
|
63
27
|
import * as d3 from 'd3';
|
|
64
|
-
import
|
|
65
|
-
import {
|
|
66
|
-
import { Button, useURLState } from '@parca/components';
|
|
67
|
-
import {
|
|
68
|
-
import
|
|
69
|
-
import
|
|
70
|
-
import { getCurvePoints, jsonToDot } from './utils';
|
|
71
|
-
var Node = function (_a) {
|
|
72
|
-
var node = _a.node, hoveredNode = _a.hoveredNode, setHoveredNode = _a.setHoveredNode, isCurrentSearchMatch = _a.isCurrentSearchMatch;
|
|
73
|
-
var id = node.data.id, x = node.x, y = node.y, color = node.color, functionName = node.functionName, widthString = node.width, heightString = node.height;
|
|
74
|
-
var isHovered = Boolean(hoveredNode) && (hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.data.id) === id;
|
|
75
|
-
var width = Number(widthString);
|
|
76
|
-
var height = Number(heightString);
|
|
77
|
-
var textPadding = 6;
|
|
78
|
-
var opacity = isCurrentSearchMatch ? 1 : 0.1;
|
|
79
|
-
return (_jsxs(Label, __assign({ x: x - width / 2, y: y - height / 2 }, { children: [_jsx(Rect, { width: width, height: height, fill: color, opacity: opacity, cornerRadius: 3, stroke: isHovered ? 'black' : color, strokeWidth: 2, onMouseOver: function (e) {
|
|
80
|
-
setHoveredNode(__assign(__assign({}, node), { mouseX: e.evt.clientX, mouseY: e.evt.clientY }));
|
|
81
|
-
}, onMouseOut: function () {
|
|
82
|
-
setHoveredNode(null);
|
|
83
|
-
} }), width > DEFAULT_NODE_HEIGHT + 10 && (_jsx(Text, { text: functionName, fontSize: 10, fill: "white", width: width - textPadding, height: height - textPadding, x: textPadding / 2, y: textPadding / 2, align: "center", verticalAlign: "middle", listening: false }))] })));
|
|
84
|
-
};
|
|
85
|
-
var Edge = function (_a) {
|
|
86
|
-
var edge = _a.edge, sourceNode = _a.sourceNode, targetNode = _a.targetNode, xScale = _a.xScale, yScale = _a.yScale, isCurrentSearchMatch = _a.isCurrentSearchMatch;
|
|
87
|
-
var pos = edge.pos, color = edge.color, head = edge.head, tail = edge.tail, opacity = edge.opacity, boxHeight = edge.boxHeight;
|
|
88
|
-
var points = getCurvePoints({
|
|
89
|
-
pos: pos,
|
|
90
|
-
xScale: xScale,
|
|
91
|
-
yScale: yScale,
|
|
92
|
-
source: [sourceNode.x, sourceNode.y],
|
|
93
|
-
target: [targetNode.x, targetNode.y],
|
|
94
|
-
offset: boxHeight / 2,
|
|
95
|
-
isSelfLoop: head === tail,
|
|
96
|
-
});
|
|
97
|
-
return (_jsx(Arrow, { points: points, bezier: true, stroke: color, strokeWidth: 3, pointerLength: 10, pointerWidth: 10, fill: color, opacity: isCurrentSearchMatch ? Number(opacity) : 0 }));
|
|
98
|
-
};
|
|
28
|
+
import SVG from 'react-inlinesvg';
|
|
29
|
+
import { MapInteractionCSS } from 'react-map-interaction';
|
|
30
|
+
import { Button, useKeyDown, useURLState } from '@parca/components';
|
|
31
|
+
import { getNewSpanColor } from '@parca/functions';
|
|
32
|
+
import { selectDarkMode, setHoveringNode, useAppDispatch, useAppSelector } from '@parca/store';
|
|
33
|
+
import GraphTooltip from '../GraphTooltip';
|
|
99
34
|
var Callgraph = function (_a) {
|
|
100
|
-
var
|
|
101
|
-
var
|
|
35
|
+
var data = _a.data, svgString = _a.svgString, sampleUnit = _a.sampleUnit, width = _a.width;
|
|
36
|
+
var originalView = {
|
|
37
|
+
scale: 1,
|
|
38
|
+
translation: { x: 0, y: 0 },
|
|
39
|
+
};
|
|
40
|
+
var _b = useState(originalView), view = _b[0], setView = _b[1];
|
|
102
41
|
var containerRef = useRef(null);
|
|
103
|
-
var
|
|
104
|
-
var
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
42
|
+
var svgRef = useRef(null);
|
|
43
|
+
var svgWrapper = useRef(null);
|
|
44
|
+
var _c = useState(false), svgWrapperLoaded = _c[0], setSvgWrapperLoaded = _c[1];
|
|
45
|
+
var dispatch = useAppDispatch();
|
|
46
|
+
var isShiftDown = useKeyDown().isShiftDown;
|
|
47
|
+
// TODO: implement highlighting nodes on user search
|
|
48
|
+
// const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
49
|
+
// const isSearchEmpty = currentSearchString === undefined || currentSearchString === '';
|
|
50
|
+
// const isCurrentSearchMatch = isSearchEmpty
|
|
51
|
+
// ? true
|
|
52
|
+
// : isSearchMatch(currentSearchString, sourceNode.functionName) &&
|
|
53
|
+
// isSearchMatch(currentSearchString, targetNode.functionName);
|
|
113
54
|
var rawDashboardItems = useURLState({ param: 'dashboard_items' })[0];
|
|
114
55
|
var dashboardItems = rawDashboardItems !== undefined ? rawDashboardItems : ['icicle'];
|
|
56
|
+
var isDarkMode = useAppSelector(selectDarkMode);
|
|
57
|
+
var maxColor = getNewSpanColor(isDarkMode);
|
|
58
|
+
var minColor = d3.scaleLinear([isDarkMode ? 'black' : 'white', maxColor])(0.3);
|
|
59
|
+
var colorRange = [minColor, maxColor];
|
|
60
|
+
var cumulatives = data.edges.map(function (edge) { return parseInt(edge.cumulative); });
|
|
61
|
+
var cumulativesRange = d3.extent(cumulatives);
|
|
62
|
+
var colorScale = d3
|
|
63
|
+
.scaleSequentialLog(d3.interpolateBlues)
|
|
64
|
+
.domain([Number(cumulativesRange[0]), Number(cumulativesRange[1])])
|
|
65
|
+
.range(colorRange);
|
|
115
66
|
useEffect(function () {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
var height = (width * bbHeight) / bbWidth;
|
|
151
|
-
var xScale = d3
|
|
152
|
-
.scaleLinear()
|
|
153
|
-
.domain([0, bbWidth])
|
|
154
|
-
.range([0, width - 2 * GRAPH_MARGIN]);
|
|
155
|
-
var yScale = d3
|
|
156
|
-
.scaleLinear()
|
|
157
|
-
.domain([0, bbHeight])
|
|
158
|
-
.range([0, height - 2 * GRAPH_MARGIN]);
|
|
159
|
-
var nodes = gvizNodes.map(function (node) {
|
|
160
|
-
var _a;
|
|
161
|
-
var _b = node.pos.split(','), x = _b[0], y = _b[1];
|
|
162
|
-
return __assign(__assign({}, node), { x: xScale(Number(x)), y: yScale(Number(y)), data: (_a = rawNodes.find(function (n) { return n.id === node.name; })) !== null && _a !== void 0 ? _a : { id: 'n0' } });
|
|
163
|
-
});
|
|
164
|
-
// 4. Add zooming
|
|
165
|
-
var handleWheel = function (e) {
|
|
166
|
-
var _a;
|
|
167
|
-
// stop default scrolling
|
|
168
|
-
e.evt.preventDefault();
|
|
169
|
-
var scaleBy = 1.01;
|
|
170
|
-
var stage = e.target.getStage();
|
|
171
|
-
if (stage !== null) {
|
|
172
|
-
var oldScale = stage.scaleX();
|
|
173
|
-
var pointer = (_a = stage.getPointerPosition()) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
|
|
174
|
-
var mousePointTo = {
|
|
175
|
-
x: pointer.x / oldScale - stage.x() / oldScale,
|
|
176
|
-
y: pointer.y / oldScale - stage.y() / oldScale,
|
|
67
|
+
setSvgWrapperLoaded(true);
|
|
68
|
+
}, []);
|
|
69
|
+
useEffect(function () {
|
|
70
|
+
if (svgWrapperLoaded && svgRef.current !== null) {
|
|
71
|
+
var addInteraction = function () {
|
|
72
|
+
var svg = d3.select(svgRef.current);
|
|
73
|
+
var nodes = svg.selectAll('.node');
|
|
74
|
+
nodes.each(function () {
|
|
75
|
+
var _this = this;
|
|
76
|
+
var nodeData = data.nodes.find(function (n) {
|
|
77
|
+
// @ts-expect-error
|
|
78
|
+
return n.id === _this.id;
|
|
79
|
+
});
|
|
80
|
+
var defaultColor = colorScale(Number(nodeData === null || nodeData === void 0 ? void 0 : nodeData.cumulative));
|
|
81
|
+
var node = d3.select(this);
|
|
82
|
+
var path = node.select('path');
|
|
83
|
+
node
|
|
84
|
+
.style('cursor', 'pointer')
|
|
85
|
+
.on('mouseenter', function () {
|
|
86
|
+
if (isShiftDown)
|
|
87
|
+
return;
|
|
88
|
+
d3.select(this).select('path').style('fill', 'white');
|
|
89
|
+
var hoveringNode = __assign(__assign({}, nodeData), { meta: __assign(__assign({}, nodeData === null || nodeData === void 0 ? void 0 : nodeData.meta), { lineIndex: 0, locationIndex: 0 }) });
|
|
90
|
+
// @ts-expect-error
|
|
91
|
+
dispatch(setHoveringNode(hoveringNode));
|
|
92
|
+
})
|
|
93
|
+
.on('mouseleave', function () {
|
|
94
|
+
if (isShiftDown)
|
|
95
|
+
return;
|
|
96
|
+
d3.select(this).select('path').style('fill', defaultColor);
|
|
97
|
+
dispatch(setHoveringNode(undefined));
|
|
98
|
+
});
|
|
99
|
+
path.style('fill', defaultColor);
|
|
100
|
+
});
|
|
177
101
|
};
|
|
178
|
-
|
|
179
|
-
var direction = e.evt.deltaY > 0 ? 1 : -1;
|
|
180
|
-
// for trackpad, e.evt.ctrlKey is true => in that case, revert direction
|
|
181
|
-
if (e.evt.ctrlKey) {
|
|
182
|
-
direction = -direction;
|
|
183
|
-
}
|
|
184
|
-
var newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
|
|
185
|
-
stage.scale({ x: newScale, y: newScale });
|
|
186
|
-
setStage({
|
|
187
|
-
scale: { x: newScale, y: newScale },
|
|
188
|
-
x: -(mousePointTo.x - pointer.x / newScale) * newScale,
|
|
189
|
-
y: -(mousePointTo.y - pointer.y / newScale) * newScale,
|
|
190
|
-
});
|
|
102
|
+
setTimeout(addInteraction, 1000);
|
|
191
103
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
201
|
-
return (_jsx("div", __assign({ className: "relative" }, { children: _jsxs("div", __assign({ className: "w-[".concat(width, "px] h-[").concat(height, "px]"), ref: containerRef }, { children: [_jsx(Stage, __assign({ width: width, height: height, onWheel: handleWheel, scaleX: stage.scale.x, scaleY: stage.scale.y, x: stage.x, y: stage.y, draggable: true }, { children: _jsxs(Layer, __assign({ offsetX: -GRAPH_MARGIN, offsetY: -GRAPH_MARGIN }, { children: [edges.map(function (edge) {
|
|
202
|
-
var _a, _b;
|
|
203
|
-
// 'tail' in graphviz-wasm means 'source' and 'head' means 'target'
|
|
204
|
-
var sourceNode = (_a = nodes.find(function (n) { return n._gvid === edge.tail; })) !== null && _a !== void 0 ? _a : {
|
|
205
|
-
x: 0,
|
|
206
|
-
y: 0,
|
|
207
|
-
functionName: '',
|
|
208
|
-
};
|
|
209
|
-
var targetNode = (_b = nodes.find(function (n) { return n._gvid === edge.head; })) !== null && _b !== void 0 ? _b : {
|
|
210
|
-
x: 0,
|
|
211
|
-
y: 0,
|
|
212
|
-
functionName: '',
|
|
213
|
-
};
|
|
214
|
-
var isCurrentSearchMatch = isSearchEmpty
|
|
215
|
-
? true
|
|
216
|
-
: isSearchMatch(currentSearchString, sourceNode.functionName) &&
|
|
217
|
-
isSearchMatch(currentSearchString, targetNode.functionName);
|
|
218
|
-
return (_jsx(Edge, { edge: edge, xScale: xScale, yScale: yScale, sourceNode: sourceNode, targetNode: targetNode, isCurrentSearchMatch: isCurrentSearchMatch }, "edge-".concat(edge.tail, "-").concat(edge.head)));
|
|
219
|
-
}), nodes.map(function (node) {
|
|
220
|
-
var isCurrentSearchMatch = isSearchEmpty
|
|
221
|
-
? true
|
|
222
|
-
: isSearchMatch(currentSearchString, node.functionName);
|
|
223
|
-
return (_jsx(Node, { node: node, hoveredNode: hoveredNode, setHoveredNode: setHoveredNode, isCurrentSearchMatch: isCurrentSearchMatch }, "node-".concat(node._gvid)));
|
|
224
|
-
})] })) })), _jsx(Tooltip, { hoveringNode: rawNodes.find(function (n) { return n.id === (hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.data.id); }), unit: sampleUnit, total: +total, isFixed: false, x: (_c = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseX) !== null && _c !== void 0 ? _c : 0, y: (_d = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseY) !== null && _d !== void 0 ? _d : 0, contextElement: containerRef.current }), stage.scale.x !== 1 && (_jsx("div", __assign({ className: cx(dashboardItems.length > 1 ? 'left-[25px]' : 'left-0', 'w-auto absolute top-[-46px]') }, { children: _jsx(Button, __assign({ variant: "neutral", onClick: resetZoom }, { children: "Reset Zoom" })) })))] })) })));
|
|
104
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, [svgWrapper.current, svgWrapperLoaded]);
|
|
106
|
+
if (data.nodes.length < 1)
|
|
107
|
+
return _jsx(_Fragment, { children: "Profile has no samples" });
|
|
108
|
+
var resetView = function () { return setView(originalView); };
|
|
109
|
+
var isResetViewButtonEnabled = view.scale !== originalView.scale ||
|
|
110
|
+
view.translation.x !== originalView.translation.x ||
|
|
111
|
+
view.translation.y !== originalView.translation.y;
|
|
112
|
+
return (_jsxs("div", __assign({ className: "w-full relative" }, { children: [_jsxs("div", __assign({ ref: containerRef, className: "w-full overflow-hidden" }, { children: [_jsx(MapInteractionCSS, __assign({ showControls: true, minScale: 1, maxScale: 5, value: view, onChange: function (value) { return setView(value); } }, { children: _jsx(SVG, { ref: svgWrapper, src: svgString, width: width, height: "auto", title: "Callgraph", innerRef: svgRef }) })), svgRef.current !== null && (_jsx(GraphTooltip, { type: "callgraph", unit: sampleUnit, total: parseInt(data.cumulative), contextElement: containerRef.current }))] })), _jsx("div", __assign({ className: cx(dashboardItems.length > 1 ? 'left-[25px]' : 'left-0', 'w-auto absolute top-[-46px]') }, { children: _jsx(Button, __assign({ variant: "neutral", onClick: resetView, className: "z-50", disabled: !isResetViewButtonEnabled }, { children: "Reset View" })) }))] })));
|
|
225
113
|
};
|
|
226
114
|
export default Callgraph;
|
package/dist/Callgraph/utils.js
CHANGED
|
@@ -20,7 +20,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
20
20
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
21
21
|
};
|
|
22
22
|
import * as d3 from 'd3';
|
|
23
|
-
import {
|
|
23
|
+
import { withAlphaHex } from 'with-alpha-hex';
|
|
24
24
|
export var pixelsToInches = function (pixels) { return pixels / 96; };
|
|
25
25
|
export var getCurvePoints = function (_a) {
|
|
26
26
|
var pos = _a.pos, _b = _a.xScale, xScale = _b === void 0 ? function (n) { return n; } : _b, _c = _a.yScale, yScale = _c === void 0 ? function (n) { return n; } : _c, _d = _a.source, source = _d === void 0 ? [] : _d, _e = _a.target, target = _e === void 0 ? [] : _e, _f = _a.offset, offset = _f === void 0 ? 0 : _f, _g = _a.isSelfLoop, isSelfLoop = _g === void 0 ? false : _g;
|
|
@@ -70,39 +70,35 @@ var objectAsDotAttributes = function (obj) {
|
|
|
70
70
|
export var jsonToDot = function (_a) {
|
|
71
71
|
var graph = _a.graph, colorRange = _a.colorRange;
|
|
72
72
|
var nodes = graph.nodes, edges = graph.edges;
|
|
73
|
-
var cumulatives =
|
|
74
|
-
var cumulativesRange = d3.extent(cumulatives)
|
|
73
|
+
var cumulatives = edges.map(function (edge) { return Number(edge.cumulative); });
|
|
74
|
+
var cumulativesRange = d3.extent(cumulatives);
|
|
75
75
|
var colorScale = d3
|
|
76
76
|
.scaleSequentialLog(d3.interpolateBlues)
|
|
77
77
|
.domain(cumulativesRange)
|
|
78
78
|
.range(colorRange);
|
|
79
|
-
var colorOpacityScale = d3.
|
|
80
|
-
var boxWidthScale = d3
|
|
81
|
-
.scaleLog()
|
|
82
|
-
.domain(cumulativesRange)
|
|
83
|
-
.range([DEFAULT_NODE_HEIGHT, DEFAULT_NODE_HEIGHT + 40]);
|
|
79
|
+
var colorOpacityScale = d3.scaleLinear().domain(cumulativesRange).range([0.5, 1]);
|
|
84
80
|
var nodesAsStrings = nodes.map(function (node) {
|
|
85
|
-
var _a, _b, _c, _d, _e
|
|
81
|
+
var _a, _b, _c, _d, _e;
|
|
82
|
+
var rgbColor = colorScale(Number(node.cumulative));
|
|
83
|
+
var hexColor = (_b = (_a = d3.color(rgbColor)) === null || _a === void 0 ? void 0 : _a.formatHex()) !== null && _b !== void 0 ? _b : 'red';
|
|
86
84
|
var dataAttributes = {
|
|
87
|
-
|
|
88
|
-
functionName: (_f = (_e = (_d = node.meta) === null || _d === void 0 ? void 0 : _d.function) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : '',
|
|
89
|
-
cumulative: (_g = node.cumulative) !== null && _g !== void 0 ? _g : '',
|
|
85
|
+
label: (_e = (_d = (_c = node.meta) === null || _c === void 0 ? void 0 : _c.function) === null || _d === void 0 ? void 0 : _d.name.substring(0, 12)) !== null && _e !== void 0 ? _e : '',
|
|
90
86
|
root: (node.id === 'root').toString(),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
fillcolor: hexColor,
|
|
88
|
+
className: 'node',
|
|
89
|
+
id: node.id,
|
|
94
90
|
};
|
|
95
91
|
return "\"".concat(node.id, "\" [").concat(objectAsDotAttributes(dataAttributes), "]");
|
|
96
92
|
});
|
|
97
93
|
var edgesAsStrings = edges.map(function (edge) {
|
|
98
94
|
var dataAttributes = {
|
|
99
95
|
cumulative: edge.cumulative,
|
|
100
|
-
color: colorRange[1],
|
|
101
|
-
|
|
102
|
-
boxHeight: DEFAULT_NODE_HEIGHT,
|
|
96
|
+
color: withAlphaHex(colorRange[1], colorOpacityScale(Number(edge.cumulative))),
|
|
97
|
+
className: 'edge',
|
|
98
|
+
// boxHeight: DEFAULT_NODE_HEIGHT,
|
|
103
99
|
};
|
|
104
100
|
return "\"".concat(edge.source, "\" -> \"").concat(edge.target, "\" [").concat(objectAsDotAttributes(dataAttributes), "]");
|
|
105
101
|
});
|
|
106
|
-
var graphAsDot = "digraph \"callgraph\" {\n rankdir=\"
|
|
102
|
+
var graphAsDot = "digraph \"callgraph\" {\n rankdir=\"TB\"\n overlap=\"prism\"\n ratio=\"1,3\"\n margin=15\n edge [margin=0]\n node [shape=box style=\"rounded,filled\"]\n ".concat(nodesAsStrings.join(' '), "\n ").concat(edgesAsStrings.join(' '), "\n }");
|
|
107
103
|
return graphAsDot;
|
|
108
104
|
};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { CallgraphNode, FlamegraphNode, FlamegraphNodeMeta, FlamegraphRootNode } from '@parca/client';
|
|
1
|
+
import { CallgraphNode, CallgraphNodeMeta, FlamegraphNode, FlamegraphNodeMeta, FlamegraphRootNode } from '@parca/client';
|
|
2
2
|
import { Location, Mapping, Function as ParcaFunction } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
|
|
3
|
+
interface ExtendedCallgraphNodeMeta extends CallgraphNodeMeta {
|
|
4
|
+
lineIndex: number;
|
|
5
|
+
locationIndex: number;
|
|
6
|
+
}
|
|
7
|
+
interface HoveringNode extends FlamegraphRootNode, FlamegraphNode, CallgraphNode {
|
|
8
|
+
diff: string;
|
|
9
|
+
meta?: FlamegraphNodeMeta | ExtendedCallgraphNodeMeta;
|
|
10
|
+
}
|
|
3
11
|
interface GraphTooltipProps {
|
|
4
12
|
x?: number;
|
|
5
13
|
y?: number;
|
|
@@ -13,12 +21,18 @@ interface GraphTooltipProps {
|
|
|
13
21
|
mappings?: Mapping[];
|
|
14
22
|
locations?: Location[];
|
|
15
23
|
functions?: ParcaFunction[];
|
|
24
|
+
type?: string;
|
|
16
25
|
}
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
export declare const GraphTooltipContent: ({ hoveringNode, unit, total, isFixed, strings, mappings, locations, functions, type, }: {
|
|
27
|
+
hoveringNode: HoveringNode;
|
|
28
|
+
unit: string;
|
|
29
|
+
total: number;
|
|
30
|
+
isFixed: boolean;
|
|
31
|
+
strings?: string[] | undefined;
|
|
32
|
+
mappings?: Mapping[] | undefined;
|
|
33
|
+
locations?: Location[] | undefined;
|
|
34
|
+
functions?: ParcaFunction[] | undefined;
|
|
35
|
+
type?: string | undefined;
|
|
36
|
+
}) => JSX.Element;
|
|
37
|
+
declare const GraphTooltip: ({ x, y, unit, total, hoveringNode: hoveringNodeProp, contextElement, isFixed, virtualContextElement, strings, mappings, locations, functions, type, }: GraphTooltipProps) => JSX.Element;
|
|
24
38
|
export default GraphTooltip;
|
|
@@ -76,9 +76,10 @@ function generateGetBoundingClientRect(contextElement, x, y) {
|
|
|
76
76
|
}
|
|
77
77
|
var TooltipMetaInfo = function (_a) {
|
|
78
78
|
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
79
|
-
var hoveringNode = _a.hoveringNode, onCopy = _a.onCopy, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions;
|
|
79
|
+
var hoveringNode = _a.hoveringNode, onCopy = _a.onCopy, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions, _m = _a.type, type = _m === void 0 ? 'flamegraph' : _m;
|
|
80
80
|
// populate meta from the flamegraph metadata tables
|
|
81
|
-
if (
|
|
81
|
+
if (type === 'flamegraph' &&
|
|
82
|
+
locations !== undefined &&
|
|
82
83
|
((_b = hoveringNode.meta) === null || _b === void 0 ? void 0 : _b.locationIndex) !== undefined &&
|
|
83
84
|
hoveringNode.meta.locationIndex !== 0) {
|
|
84
85
|
var location_1 = locations[hoveringNode.meta.locationIndex - 1];
|
|
@@ -127,9 +128,9 @@ var TooltipMetaInfo = function (_a) {
|
|
|
127
128
|
((_h = hoveringNode.meta) === null || _h === void 0 ? void 0 : _h.location.address) === '0' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hexifyAddress(hoveringNode.meta.location.address) }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: hexifyAddress(hoveringNode.meta.location.address) })) }))) }))] }), _jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Binary" })), _jsx("td", __assign({ className: "w-3/4 break-all" }, { children: ((_j = hoveringNode.meta) === null || _j === void 0 ? void 0 : _j.mapping) == null || hoveringNode.meta.mapping.file === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.mapping.file }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getLastItem(hoveringNode.meta.mapping.file) })) }))) }))] }), _jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Build Id" })), _jsx("td", __assign({ className: "w-3/4 break-all" }, { children: ((_k = hoveringNode.meta) === null || _k === void 0 ? void 0 : _k.mapping) == null || ((_l = hoveringNode.meta) === null || _l === void 0 ? void 0 : _l.mapping.buildId) === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.mapping.buildId }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: truncateString(getLastItem(hoveringNode.meta.mapping.buildId), 28) })) }))) }))] })] }));
|
|
128
129
|
};
|
|
129
130
|
var timeoutHandle = null;
|
|
130
|
-
var GraphTooltipContent = function (_a) {
|
|
131
|
-
var hoveringNode = _a.hoveringNode, unit = _a.unit, total = _a.total, isFixed = _a.isFixed, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions;
|
|
132
|
-
var
|
|
131
|
+
export var GraphTooltipContent = function (_a) {
|
|
132
|
+
var hoveringNode = _a.hoveringNode, unit = _a.unit, total = _a.total, isFixed = _a.isFixed, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions, _b = _a.type, type = _b === void 0 ? 'flamegraph' : _b;
|
|
133
|
+
var _c = useState(false), isCopied = _c[0], setIsCopied = _c[1];
|
|
133
134
|
var onCopy = function () {
|
|
134
135
|
setIsCopied(true);
|
|
135
136
|
if (timeoutHandle !== null) {
|
|
@@ -150,23 +151,22 @@ var GraphTooltipContent = function (_a) {
|
|
|
150
151
|
};
|
|
151
152
|
return (_jsx("div", __assign({ className: "text-sm flex ".concat(isFixed ? 'w-full' : '') }, { children: _jsx("div", __assign({ className: "m-auto w-full ".concat(isFixed ? 'w-full' : '') }, { children: _jsxs("div", __assign({ className: "border border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg min-h-52 w-[500px] flex justify-between flex-col" }, { children: [_jsx("div", __assign({ className: "flex flex-row" }, { children: _jsxs("div", __assign({ className: "mx-2" }, { children: [_jsx("div", __assign({ className: "font-semibold break-all h-10 flex items-center" }, { children: hoveringNode.meta === undefined ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: hoveringNode.meta.function !== undefined &&
|
|
152
153
|
hoveringNode.meta.function.name !== '' ? (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.function.name }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: hoveringNode.meta.function.name })) }))) : (_jsx(_Fragment, { children: hoveringNode.meta.location !== undefined &&
|
|
153
|
-
parseInt(hoveringNode.meta.location.address, 10) !== 0 ? (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hexifyAddress(hoveringNode.meta.location.address) }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: hexifyAddress(hoveringNode.meta.location.address) })) }))) : (_jsx("p", { children: "unknown" })) })) })) })), _jsx("table", __assign({ className: "table-fixed pr-0 text-gray-700 dark:text-gray-300 my-2 w-full" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Cumulative" })), _jsx("td", __assign({ className: "w-3/4" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: getTextForCumulative(hoveringNodeCumulative) }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getTextForCumulative(hoveringNodeCumulative) })) })) }))] }), hoveringNode.diff !== undefined && diff !== 0 && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Diff" })), _jsx("td", __assign({ className: "w-3/4" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: diffText }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: diffText })) })) }))] })), _jsx(TooltipMetaInfo, { onCopy: onCopy,
|
|
154
|
-
// @ts-expect-error
|
|
155
|
-
hoveringNode: hoveringNode, strings: strings, mappings: mappings, locations: locations, functions: functions })] }) }))] })) })), _jsx("span", __assign({ className: "block text-gray-500 text-xs mx-2" }, { children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' }))] })) })) })));
|
|
154
|
+
parseInt(hoveringNode.meta.location.address, 10) !== 0 ? (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hexifyAddress(hoveringNode.meta.location.address) }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: hexifyAddress(hoveringNode.meta.location.address) })) }))) : (_jsx("p", { children: "unknown" })) })) })) })), _jsx("table", __assign({ className: "table-fixed pr-0 text-gray-700 dark:text-gray-300 my-2 w-full" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Cumulative" })), _jsx("td", __assign({ className: "w-3/4" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: getTextForCumulative(hoveringNodeCumulative) }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getTextForCumulative(hoveringNodeCumulative) })) })) }))] }), hoveringNode.diff !== undefined && diff !== 0 && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Diff" })), _jsx("td", __assign({ className: "w-3/4" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: diffText }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: diffText })) })) }))] })), _jsx(TooltipMetaInfo, { onCopy: onCopy, hoveringNode: hoveringNode, strings: strings, mappings: mappings, locations: locations, functions: functions, type: type })] }) }))] })) })), _jsx("span", __assign({ className: "block text-gray-500 text-xs mx-2" }, { children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' }))] })) })) })));
|
|
156
155
|
};
|
|
157
156
|
var GraphTooltip = function (_a) {
|
|
158
|
-
var x = _a.x, y = _a.y, unit = _a.unit, total = _a.total, hoveringNodeProp = _a.hoveringNode, contextElement = _a.contextElement, _b = _a.isFixed, isFixed = _b === void 0 ? false : _b, _c = _a.virtualContextElement, virtualContextElement = _c === void 0 ? true : _c, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions;
|
|
157
|
+
var x = _a.x, y = _a.y, unit = _a.unit, total = _a.total, hoveringNodeProp = _a.hoveringNode, contextElement = _a.contextElement, _b = _a.isFixed, isFixed = _b === void 0 ? false : _b, _c = _a.virtualContextElement, virtualContextElement = _c === void 0 ? true : _c, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions, _d = _a.type, type = _d === void 0 ? 'flamegraph' : _d;
|
|
159
158
|
var hoveringNodeState = useAppSelector(selectHoveringNode);
|
|
159
|
+
// @ts-expect-error
|
|
160
160
|
var hoveringNode = useMemo(function () {
|
|
161
|
-
var h =
|
|
161
|
+
var h = hoveringNodeProp !== null && hoveringNodeProp !== void 0 ? hoveringNodeProp : hoveringNodeState;
|
|
162
162
|
if (h == null) {
|
|
163
163
|
return h;
|
|
164
164
|
}
|
|
165
165
|
// Cloning the object to avoid the mutating error as this is Redux store object and we are modifying the meta object in GraphTooltipContent component.
|
|
166
166
|
return __assign(__assign({}, h), { meta: __assign({}, h.meta) });
|
|
167
167
|
}, [hoveringNodeProp, hoveringNodeState]);
|
|
168
|
-
var
|
|
169
|
-
var
|
|
168
|
+
var _e = useState(null), popperElement = _e[0], setPopperElement = _e[1];
|
|
169
|
+
var _f = usePopper(virtualContextElement ? virtualElement : contextElement, popperElement, {
|
|
170
170
|
placement: 'bottom-start',
|
|
171
171
|
strategy: 'absolute',
|
|
172
172
|
modifiers: [
|
|
@@ -184,7 +184,7 @@ var GraphTooltip = function (_a) {
|
|
|
184
184
|
},
|
|
185
185
|
},
|
|
186
186
|
],
|
|
187
|
-
}), styles =
|
|
187
|
+
}), styles = _f.styles, attributes = _f.attributes, popperProps = __rest(_f, ["styles", "attributes"]);
|
|
188
188
|
var isShiftDown = useKeyDown().isShiftDown;
|
|
189
189
|
useEffect(function () {
|
|
190
190
|
if (contextElement === null)
|
|
@@ -211,6 +211,6 @@ var GraphTooltip = function (_a) {
|
|
|
211
211
|
}, [contextElement, popperProps, isShiftDown, x, y]);
|
|
212
212
|
if (hoveringNode === undefined || hoveringNode == null)
|
|
213
213
|
return _jsx(_Fragment, {});
|
|
214
|
-
return isFixed ? (_jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed })) : (_jsx("div", __assign({ ref: setPopperElement, style: styles.popper }, attributes.popper, { children: _jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed, strings: strings, mappings: mappings, locations: locations, functions: functions }) })));
|
|
214
|
+
return isFixed ? (_jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed, type: type })) : (_jsx("div", __assign({ ref: setPopperElement, style: styles.popper }, attributes.popper, { children: _jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed, strings: strings, mappings: mappings, locations: locations, functions: functions, type: type }) })));
|
|
215
215
|
};
|
|
216
216
|
export default GraphTooltip;
|
|
@@ -91,7 +91,8 @@ export var IcicleNode = React.memo(function IcicleNodeNoMemo(_a) {
|
|
|
91
91
|
var onMouseEnter = function () {
|
|
92
92
|
if (isShiftDown)
|
|
93
93
|
return;
|
|
94
|
-
|
|
94
|
+
// need to add id and flat for tooltip purposes
|
|
95
|
+
dispatch(setHoveringNode(__assign(__assign({}, data), { id: '', flat: '' })));
|
|
95
96
|
};
|
|
96
97
|
var onMouseLeave = function () {
|
|
97
98
|
if (isShiftDown)
|