@parca/profile 0.16.58 → 0.16.60

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 (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Callgraph/constants.d.ts +2 -0
  3. package/dist/Callgraph/constants.js +14 -0
  4. package/dist/Callgraph/index.d.ts +2 -2
  5. package/dist/Callgraph/index.js +106 -35
  6. package/dist/Callgraph/utils.d.ts +6 -6
  7. package/dist/Callgraph/utils.js +55 -29
  8. package/dist/GraphTooltip/index.d.ts +0 -1
  9. package/dist/IcicleGraph.d.ts +0 -1
  10. package/dist/MatchersInput/index.d.ts +0 -1
  11. package/dist/MetricsCircle/index.d.ts +0 -1
  12. package/dist/MetricsGraph/index.d.ts +0 -1
  13. package/dist/MetricsSeries/index.d.ts +0 -1
  14. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +0 -1
  15. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +0 -1
  16. package/dist/ProfileExplorer/index.d.ts +0 -1
  17. package/dist/ProfileIcicleGraph.d.ts +0 -1
  18. package/dist/ProfileMetricsGraph/index.d.ts +0 -1
  19. package/dist/ProfileSelector/CompareButton.d.ts +0 -1
  20. package/dist/ProfileSelector/MergeButton.d.ts +0 -1
  21. package/dist/ProfileSelector/index.d.ts +0 -1
  22. package/dist/ProfileSource.d.ts +0 -1
  23. package/dist/ProfileTypeSelector/index.d.ts +0 -1
  24. package/dist/ProfileView.d.ts +2 -3
  25. package/dist/ProfileView.js +15 -7
  26. package/dist/ProfileViewWithData.d.ts +0 -1
  27. package/dist/TopTable.d.ts +0 -1
  28. package/dist/components/DiffLegend.d.ts +0 -1
  29. package/dist/components/ProfileShareButton/ResultBox.d.ts +0 -1
  30. package/dist/components/ProfileShareButton/index.d.ts +0 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/styles.css +1 -1
  33. package/package.json +7 -6
  34. package/src/Callgraph/constants.ts +15 -0
  35. package/src/Callgraph/index.tsx +232 -66
  36. package/src/Callgraph/utils.ts +68 -39
  37. package/src/ProfileView.tsx +128 -114
  38. package/dist/Callgraph/Edge/index.d.ts +0 -23
  39. package/dist/Callgraph/Edge/index.js +0 -30
  40. package/dist/Callgraph/Node/index.d.ts +0 -20
  41. package/dist/Callgraph/Node/index.js +0 -37
  42. package/src/Callgraph/Edge/index.tsx +0 -59
  43. package/src/Callgraph/Node/index.tsx +0 -66
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.60](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.59...@parca/profile@0.16.60) (2022-11-03)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.59](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.58...@parca/profile@0.16.59) (2022-11-03)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.58](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.57...@parca/profile@0.16.58) (2022-11-02)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_NODE_HEIGHT = 20;
2
+ export declare const GRAPH_MARGIN = 15;
@@ -0,0 +1,14 @@
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
+ export var DEFAULT_NODE_HEIGHT = 20;
14
+ export var GRAPH_MARGIN = 15;
@@ -1,9 +1,9 @@
1
- /// <reference types="react" />
2
1
  import { Callgraph as CallgraphType } from '@parca/client';
3
2
  export interface Props {
4
3
  graph: CallgraphType;
5
4
  sampleUnit: string;
6
5
  width: number;
6
+ colorRange: [string, string];
7
7
  }
8
- declare const Callgraph: ({ graph, sampleUnit, width }: Props) => JSX.Element;
8
+ declare const Callgraph: ({ graph, sampleUnit, width, colorRange }: Props) => JSX.Element;
9
9
  export default Callgraph;
@@ -45,7 +45,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
45
45
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
46
  }
47
47
  };
48
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
48
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
49
49
  // Copyright 2022 The Parca Authors
50
50
  // Licensed under the Apache License, Version 2.0 (the "License");
51
51
  // you may not use this file except in compliance with the License.
@@ -61,26 +61,65 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
61
61
  import { useState, useEffect, useRef } from 'react';
62
62
  import graphviz from 'graphviz-wasm';
63
63
  import * as d3 from 'd3';
64
- import { Stage, Layer } from 'react-konva';
64
+ import { Stage, Layer, Rect, Arrow, Text, Label } from 'react-konva';
65
+ import { jsonToDot, getCurvePoints } from './utils';
66
+ import { useAppSelector, selectSearchNodeString } from '@parca/store';
67
+ import { isSearchMatch } from '@parca/functions';
65
68
  import Tooltip from '../GraphTooltip';
66
- import { jsonToDot } from './utils';
67
- import Node from './Node';
68
- import Edge from './Edge';
69
+ import { DEFAULT_NODE_HEIGHT, GRAPH_MARGIN } from './constants';
70
+ var Node = function (_a) {
71
+ var node = _a.node, hoveredNode = _a.hoveredNode, setHoveredNode = _a.setHoveredNode, isCurrentSearchMatch = _a.isCurrentSearchMatch;
72
+ var id = node.data.id, x = node.x, y = node.y, color = node.color, functionName = node.functionName, widthString = node.width, heightString = node.height;
73
+ var isHovered = Boolean(hoveredNode) && (hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.data.id) === id;
74
+ var width = Number(widthString);
75
+ var height = Number(heightString);
76
+ var textPadding = 6;
77
+ var opacity = isCurrentSearchMatch ? 1 : 0.1;
78
+ 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) {
79
+ setHoveredNode(__assign(__assign({}, node), { mouseX: e.evt.clientX, mouseY: e.evt.clientY }));
80
+ }, onMouseOut: function () {
81
+ setHoveredNode(null);
82
+ } }), 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 }))] })));
83
+ };
84
+ var Edge = function (_a) {
85
+ var edge = _a.edge, sourceNode = _a.sourceNode, targetNode = _a.targetNode, xScale = _a.xScale, yScale = _a.yScale, isCurrentSearchMatch = _a.isCurrentSearchMatch;
86
+ var pos = edge.pos, color = edge.color, head = edge.head, tail = edge.tail, opacity = edge.opacity, boxHeight = edge.boxHeight;
87
+ var points = getCurvePoints({
88
+ pos: pos,
89
+ xScale: xScale,
90
+ yScale: yScale,
91
+ source: [sourceNode.x, sourceNode.y],
92
+ target: [targetNode.x, targetNode.y],
93
+ offset: boxHeight / 2,
94
+ isSelfLoop: head === tail,
95
+ });
96
+ return (_jsx(Arrow, { points: points, bezier: true, stroke: color, strokeWidth: 3, pointerLength: 10, pointerWidth: 10, fill: color, opacity: isCurrentSearchMatch ? Number(opacity) : 0 }));
97
+ };
69
98
  var Callgraph = function (_a) {
70
99
  var _b, _c;
71
- var graph = _a.graph, sampleUnit = _a.sampleUnit, width = _a.width;
100
+ var graph = _a.graph, sampleUnit = _a.sampleUnit, width = _a.width, colorRange = _a.colorRange;
72
101
  var containerRef = useRef(null);
73
102
  var _d = useState(null), graphData = _d[0], setGraphData = _d[1];
74
103
  var _e = useState(null), hoveredNode = _e[0], setHoveredNode = _e[1];
104
+ var _f = useState({
105
+ scale: { x: 1, y: 1 },
106
+ x: 0,
107
+ y: 0,
108
+ }), stage = _f[0], setStage = _f[1];
75
109
  var rawNodes = graph.nodes, total = graph.cumulative;
76
- var nodeRadius = 12;
110
+ var currentSearchString = useAppSelector(selectSearchNodeString);
111
+ var isSearchEmpty = currentSearchString === undefined;
77
112
  useEffect(function () {
78
113
  var getDataWithPositions = function () { return __awaiter(void 0, void 0, void 0, function () {
79
114
  var dataAsDot, jsonGraph;
80
115
  return __generator(this, function (_a) {
81
116
  switch (_a.label) {
82
117
  case 0:
83
- dataAsDot = jsonToDot({ graph: graph, width: width, nodeRadius: nodeRadius });
118
+ dataAsDot = jsonToDot({
119
+ graph: graph,
120
+ width: width,
121
+ colorRange: colorRange,
122
+ });
84
123
  // 2. Use Graphviz-WASM to translate the 'dot' graph to a 'JSON' graph
85
124
  return [4 /*yield*/, graphviz.loadWASM()];
86
125
  case 1:
@@ -95,43 +134,75 @@ var Callgraph = function (_a) {
95
134
  if (width !== null) {
96
135
  void getDataWithPositions();
97
136
  }
98
- }, [graph, width]);
137
+ }, [graph, width, colorRange]);
99
138
  // 3. Render the graph with calculated layout in Canvas container
100
139
  if (width == null || graphData == null)
101
140
  return _jsx(_Fragment, {});
102
- var height = width;
103
- var _f = JSON.parse(graphData), objects = _f.objects, gvizEdges = _f.edges, boundingBox = _f.bb;
104
- var cumulatives = objects
105
- .filter(function (node) { return node !== undefined; })
106
- .map(function (node) { return node.cumulative; });
107
- if (cumulatives.length === 0) {
108
- cumulatives.push('0');
109
- }
110
- var valueRange = d3.extent(cumulatives).map(function (value) { return parseInt(value); });
111
- var colorScale = d3
112
- .scaleSequentialLog(d3.interpolateRdGy)
113
- .domain(valueRange)
114
- .range(['lightgrey', 'red']);
141
+ var _g = JSON.parse(graphData), gvizNodes = _g.objects, edges = _g.edges, boundingBox = _g.bb;
115
142
  var graphBB = boundingBox.split(',');
143
+ var bbWidth = Number(graphBB[2]);
144
+ var bbHeight = Number(graphBB[3]);
145
+ var height = (width * bbHeight) / bbWidth;
116
146
  var xScale = d3
117
147
  .scaleLinear()
118
- .domain([0, Number(graphBB[2])])
119
- .range([0, width]);
148
+ .domain([0, bbWidth])
149
+ .range([0, width - 2 * GRAPH_MARGIN]);
120
150
  var yScale = d3
121
151
  .scaleLinear()
122
- .domain([0, Number(graphBB[3])])
123
- .range([0, height]);
124
- var nodes = objects.map(function (object) {
152
+ .domain([0, bbHeight])
153
+ .range([0, height - 2 * GRAPH_MARGIN]);
154
+ var nodes = gvizNodes.map(function (node) {
125
155
  var _a;
126
- var pos = object.pos.split(',');
127
- return __assign(__assign({}, object), { id: object._gvid, x: xScale(parseInt(pos[0])), y: yScale(parseInt(pos[1])), color: colorScale(Number(object.cumulative)), data: (_a = rawNodes.find(function (n) { return n.id === object.name; })) !== null && _a !== void 0 ? _a : { id: 'n0' } });
156
+ var _b = node.pos.split(','), x = _b[0], y = _b[1];
157
+ 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' } });
128
158
  });
129
- var edges = gvizEdges.map(function (edge) { return (__assign(__assign({}, edge), { source: edge.head, target: edge.tail, points: edge.pos, color: colorScale(+edge.cumulative) })); });
130
- 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 }, { children: _jsxs(Layer, { children: [edges.map(function (edge) {
159
+ // 4. Add zooming
160
+ var handleWheel = function (e) {
161
+ var _a;
162
+ e.evt.preventDefault();
163
+ var scaleXBy = 0.95;
164
+ var scaleYBy = 1.05;
165
+ var stage = e.target.getStage();
166
+ if (stage !== null) {
167
+ var oldScale = stage.scaleX();
168
+ var _b = (_a = stage.getPointerPosition()) !== null && _a !== void 0 ? _a : { x: 0, y: 0 }, x = _b.x, y = _b.y;
169
+ var mousePointTo = {
170
+ x: x / oldScale - stage.x() / oldScale,
171
+ y: y / oldScale - stage.y() / oldScale,
172
+ };
173
+ var newXScale = e.evt.deltaX > 0 ? oldScale * scaleXBy : oldScale / scaleXBy;
174
+ var newYScale = e.evt.deltaY > 0 ? oldScale * scaleYBy : oldScale / scaleYBy;
175
+ stage.scale({ x: newXScale, y: newYScale });
176
+ setStage({
177
+ scale: { x: newXScale, y: newYScale },
178
+ x: -(mousePointTo.x - x / newXScale) * newXScale,
179
+ y: -(mousePointTo.y - y / newYScale) * newYScale,
180
+ });
181
+ }
182
+ };
183
+ 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) {
131
184
  var _a, _b;
132
- var sourceNode = (_a = nodes.find(function (n) { return n.id === edge.source; })) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
133
- var targetNode = (_b = nodes.find(function (n) { return n.id === edge.target; })) !== null && _b !== void 0 ? _b : { x: 0, y: 0 };
134
- return (_jsx(Edge, { edge: edge, xScale: xScale, yScale: yScale, sourceNode: sourceNode, targetNode: targetNode, nodeRadius: nodeRadius }, "edge-".concat(edge.source, "-").concat(edge.target)));
135
- }), nodes.map(function (node) { return (_jsx(Node, { node: node, hoveredNode: hoveredNode, setHoveredNode: setHoveredNode, nodeRadius: nodeRadius }, "node-".concat(node.data.id))); })] }) })), _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: (_b = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseX) !== null && _b !== void 0 ? _b : 0, y: (_c = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseY) !== null && _c !== void 0 ? _c : 0, contextElement: containerRef.current })] })) })));
185
+ // 'tail' in graphviz-wasm means 'source' and 'head' means 'target'
186
+ var sourceNode = (_a = nodes.find(function (n) { return n._gvid === edge.tail; })) !== null && _a !== void 0 ? _a : {
187
+ x: 0,
188
+ y: 0,
189
+ functionName: '',
190
+ };
191
+ var targetNode = (_b = nodes.find(function (n) { return n._gvid === edge.head; })) !== null && _b !== void 0 ? _b : {
192
+ x: 0,
193
+ y: 0,
194
+ functionName: '',
195
+ };
196
+ var isCurrentSearchMatch = isSearchEmpty
197
+ ? true
198
+ : isSearchMatch(currentSearchString, sourceNode.functionName) &&
199
+ isSearchMatch(currentSearchString, targetNode.functionName);
200
+ return (_jsx(Edge, { edge: edge, xScale: xScale, yScale: yScale, sourceNode: sourceNode, targetNode: targetNode, isCurrentSearchMatch: isCurrentSearchMatch }, "edge-".concat(edge.tail, "-").concat(edge.head)));
201
+ }), nodes.map(function (node) {
202
+ var isCurrentSearchMatch = isSearchEmpty
203
+ ? true
204
+ : isSearchMatch(currentSearchString, node.functionName);
205
+ return (_jsx(Node, { node: node, hoveredNode: hoveredNode, setHoveredNode: setHoveredNode, isCurrentSearchMatch: isCurrentSearchMatch }, "node-".concat(node._gvid)));
206
+ })] })) })), _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: (_b = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseX) !== null && _b !== void 0 ? _b : 0, y: (_c = hoveredNode === null || hoveredNode === void 0 ? void 0 : hoveredNode.mouseY) !== null && _c !== void 0 ? _c : 0, contextElement: containerRef.current })] })) })));
136
207
  };
137
208
  export default Callgraph;
@@ -1,19 +1,19 @@
1
1
  import { CallgraphNode, CallgraphEdge } from '@parca/client';
2
2
  export declare const pixelsToInches: (pixels: number) => number;
3
- export declare const parseEdgePos: ({ pos, xScale, yScale, source, target, nodeRadius, isSelfLoop, }: {
3
+ export declare const getCurvePoints: ({ pos, xScale, yScale, source, target, offset, isSelfLoop, }: {
4
4
  pos: string;
5
- xScale?: ((pos: number) => void) | undefined;
6
- yScale?: ((pos: number) => void) | undefined;
5
+ xScale?: ((pos: number) => number) | undefined;
6
+ yScale?: ((pos: number) => number) | undefined;
7
7
  source?: number[] | undefined;
8
8
  target?: number[] | undefined;
9
- nodeRadius: number;
10
9
  isSelfLoop?: boolean | undefined;
10
+ offset?: number | undefined;
11
11
  }) => number[];
12
- export declare const jsonToDot: ({ graph, width, nodeRadius, }: {
12
+ export declare const jsonToDot: ({ graph, colorRange, }: {
13
13
  graph: {
14
14
  nodes: CallgraphNode[];
15
15
  edges: CallgraphEdge[];
16
16
  };
17
17
  width: number;
18
- nodeRadius: number;
18
+ colorRange: [string, string];
19
19
  }) => string;
@@ -19,48 +19,68 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
19
19
  }
20
20
  return to.concat(ar || Array.prototype.slice.call(from));
21
21
  };
22
+ import * as d3 from 'd3';
23
+ import { DEFAULT_NODE_HEIGHT } from './constants';
22
24
  export var pixelsToInches = function (pixels) { return pixels / 96; };
23
- export var parseEdgePos = function (_a) {
24
- var _b;
25
- var pos = _a.pos, _c = _a.xScale, xScale = _c === void 0 ? function (n) { return n; } : _c, _d = _a.yScale, yScale = _d === void 0 ? function (n) { return n; } : _d, _e = _a.source, source = _e === void 0 ? [] : _e, _f = _a.target, target = _f === void 0 ? [] : _f, nodeRadius = _a.nodeRadius, _g = _a.isSelfLoop, isSelfLoop = _g === void 0 ? false : _g;
26
- var parts = pos.split(' ');
27
- var arrow = (_b = parts.shift()) !== null && _b !== void 0 ? _b : '';
28
- var partsAsArrays = parts.map(function (part) { return part.split(','); });
29
- var scalePosArray = function (posArr) { return [+xScale(+posArr[0]), +yScale(+posArr[1])]; };
30
- var _h = partsAsArrays.map(function (posArr) { return scalePosArray(posArr); }), _start = _h[0], cp1 = _h[1], cp2 = _h[2], _end = _h[3];
31
- var arrowEnd = scalePosArray(arrow.replace('e,', '').split(','));
32
- var getTargetWithOffset = function (target, lastEdgePoint) {
33
- var diffX = target[0] - lastEdgePoint[0];
34
- var diffY = target[1] - lastEdgePoint[1];
35
- var diffZ = Math.hypot(diffX, diffY);
36
- var offsetX = (diffX * nodeRadius) / diffZ;
37
- var offsetY = (diffY * nodeRadius) / diffZ;
38
- return [target[0] - offsetX, target[1] - offsetY];
39
- };
25
+ export var getCurvePoints = function (_a) {
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;
40
27
  if (isSelfLoop) {
41
28
  var sourceX = source[0], sourceY = source[1];
42
29
  var targetX = target[0], targetY = target[1];
43
30
  return [
44
31
  sourceX,
45
- sourceY + nodeRadius,
32
+ sourceY + offset,
46
33
  sourceX,
47
- sourceY + 3 * nodeRadius,
48
- targetX + 5 * nodeRadius,
34
+ sourceY + 3 * offset,
35
+ targetX + 5 * offset,
49
36
  targetY,
50
- targetX + nodeRadius,
37
+ targetX + offset,
51
38
  targetY,
52
39
  ];
53
40
  }
54
- return __spreadArray(__spreadArray(__spreadArray(__spreadArray([], source, true), cp1, true), cp2, true), getTargetWithOffset(target, arrowEnd), true);
41
+ // graphviz pos format is in format 'endpoint,startpoint,triple(cp1,cp2,end),...triple...'
42
+ var scalePoint = function (point) { return [xScale(point[0]), yScale(point[1])]; };
43
+ var strAsNumArray = function (string) {
44
+ return string
45
+ .replace('e,', '')
46
+ .split(',')
47
+ .map(function (str) { return Number(str); });
48
+ };
49
+ var getLastPointWithOffset = function (target, last, offset) {
50
+ var targetX = target[0], targetY = target[1];
51
+ var lastX = last[0], lastY = last[1];
52
+ var diffX = targetX - lastX;
53
+ var diffY = targetY - lastY;
54
+ var diffZ = Math.hypot(diffX, diffY);
55
+ var offsetX = (diffX * offset) / diffZ;
56
+ var offsetY = (diffY * offset) / diffZ;
57
+ return [targetX - offsetX, targetY - offsetY];
58
+ };
59
+ var points = pos.split(' ').map(function (str) { return strAsNumArray(str); });
60
+ var scaledPoints = points.map(function (point) { return scalePoint(point); });
61
+ var lastPointIndex = scaledPoints.length - 1;
62
+ var lastPointWithOffset = getLastPointWithOffset(target, scaledPoints[lastPointIndex], offset);
63
+ return __spreadArray(__spreadArray([source], scaledPoints.slice(2, points.length - 1), true), [lastPointWithOffset], false).flat();
64
+ };
65
+ var objectAsDotAttributes = function (obj) {
66
+ return Object.entries(obj)
67
+ .map(function (entry) { return "".concat(entry[0], "=\"").concat(entry[1], "\""); })
68
+ .join(' ');
55
69
  };
56
70
  export var jsonToDot = function (_a) {
57
- var graph = _a.graph, width = _a.width, nodeRadius = _a.nodeRadius;
71
+ var graph = _a.graph, colorRange = _a.colorRange;
58
72
  var nodes = graph.nodes, edges = graph.edges;
59
- var objectAsDotAttributes = function (obj) {
60
- return Object.entries(obj)
61
- .map(function (entry) { return "".concat(entry[0], "=\"").concat(entry[1], "\""); })
62
- .join(' ');
63
- };
73
+ var cumulatives = nodes.map(function (node) { return node.cumulative; });
74
+ var cumulativesRange = d3.extent(cumulatives).map(function (value) { return Number(value); });
75
+ var colorScale = d3
76
+ .scaleSequentialLog(d3.interpolateBlues)
77
+ .domain(cumulativesRange)
78
+ .range(colorRange);
79
+ var colorOpacityScale = d3.scaleSequentialLog().domain(cumulativesRange).range([0.2, 1]);
80
+ var boxWidthScale = d3
81
+ .scaleLog()
82
+ .domain(cumulativesRange)
83
+ .range([DEFAULT_NODE_HEIGHT, DEFAULT_NODE_HEIGHT + 40]);
64
84
  var nodesAsStrings = nodes.map(function (node) {
65
85
  var _a, _b, _c, _d, _e, _f, _g;
66
86
  var dataAttributes = {
@@ -68,15 +88,21 @@ export var jsonToDot = function (_a) {
68
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 : '',
69
89
  cumulative: (_g = node.cumulative) !== null && _g !== void 0 ? _g : '',
70
90
  root: (node.id === 'root').toString(),
91
+ // TODO: set box width scale to be based on flat value once we have that value available
92
+ width: boxWidthScale(Number(node.cumulative)),
93
+ color: colorScale(Number(node.cumulative)),
71
94
  };
72
95
  return "\"".concat(node.id, "\" [").concat(objectAsDotAttributes(dataAttributes), "]");
73
96
  });
74
97
  var edgesAsStrings = edges.map(function (edge) {
75
98
  var dataAttributes = {
76
99
  cumulative: edge.cumulative,
100
+ color: colorRange[1],
101
+ opacity: colorOpacityScale(Number(edge.cumulative)),
102
+ boxHeight: DEFAULT_NODE_HEIGHT,
77
103
  };
78
104
  return "\"".concat(edge.source, "\" -> \"").concat(edge.target, "\" [").concat(objectAsDotAttributes(dataAttributes), "]");
79
105
  });
80
- var graphAsDot = "digraph \"callgraph\" {\n rankdir=\"TB\"\n ratio=\"1,3\"\n size=\"".concat(pixelsToInches(width), ", ").concat(pixelsToInches(width), "!\"\n margin=10\n edge [margin=0]\n node [margin=0 width=").concat(nodeRadius, "]\n ").concat(nodesAsStrings.join(' '), "\n ").concat(edgesAsStrings.join(' '), "\n }");
106
+ var graphAsDot = "digraph \"callgraph\" {\n rankdir=\"BT\"\n overlap=\"prism\"\n ratio=\"1,3\"\n margin=15\n edge [margin=0]\n node [shape=box style=rounded height=".concat(DEFAULT_NODE_HEIGHT, "]\n ").concat(nodesAsStrings.join(' '), "\n ").concat(edgesAsStrings.join(' '), "\n }");
81
107
  return graphAsDot;
82
108
  };
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { CallgraphNode, FlamegraphNode, FlamegraphNodeMeta, FlamegraphRootNode } from '@parca/client';
3
2
  import { Function, Location, Mapping } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
4
3
  interface GraphTooltipProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Flamegraph, FlamegraphNode, FlamegraphRootNode } from '@parca/client';
3
2
  import { Mapping, Function, Location } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
4
3
  interface IcicleGraphProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Query } from '@parca/parser';
3
2
  import { LabelsResponse, QueryServiceClient, ValuesResponse } from '@parca/client';
4
3
  interface MatchersInputProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  interface MetricsCircleProps {
3
2
  cx: number;
4
3
  cy: number;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { SingleProfileSelection } from '..';
3
2
  import { MetricsSeries as MetricsSeriesPb, Label } from '@parca/client';
4
3
  import { DateTimeRange } from '@parca/components';
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import * as d3 from 'd3';
3
2
  interface MetricsSeriesProps {
4
3
  data: any;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { ProfileSelection } from '..';
3
2
  import { QueryServiceClient } from '@parca/client';
4
3
  import { NavigateFunction } from '.';
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { QueryServiceClient } from '@parca/client';
3
2
  import { ProfileSelection } from '..';
4
3
  import { NavigateFunction } from '../ProfileExplorer';
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { QueryServiceClient } from '@parca/client';
3
2
  export declare type NavigateFunction = (path: string, queryParams: any) => void;
4
3
  interface ProfileExplorerProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Flamegraph } from '@parca/client';
3
2
  interface ProfileIcicleGraphProps {
4
3
  width?: number;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { ProfileSelection } from '..';
3
2
  import { QueryServiceClient, QueryRangeResponse } from '@parca/client';
4
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const CompareButton: ({ disabled, onClick, }: {
3
2
  disabled: boolean;
4
3
  onClick: () => void;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const MergeButton: ({ disabled, onClick, }: {
3
2
  disabled: boolean;
4
3
  onClick: () => void;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { QueryServiceClient, ProfileTypesResponse } from '@parca/client';
3
2
  import { RpcError } from '@protobuf-ts/runtime-rpc';
4
3
  import { ProfileSelection } from '..';
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { ProfileType } from '@parca/parser';
3
2
  import { Label, QueryRequest, ProfileDiffSelection } from '@parca/client';
4
3
  export interface ProfileSource {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { ProfileType, ProfileTypesResponse } from '@parca/client';
3
2
  import { RpcError } from '@protobuf-ts/runtime-rpc';
4
3
  interface WellKnownProfile {
@@ -1,5 +1,4 @@
1
- /// <reference types="react" />
2
- import { QueryServiceClient, Flamegraph, Top, Callgraph } from '@parca/client';
1
+ import { QueryServiceClient, Flamegraph, Top, Callgraph as CallgraphType } from '@parca/client';
3
2
  import { ProfileSource } from './ProfileSource';
4
3
  import './ProfileView.styles.css';
5
4
  declare type NavigateFunction = (path: string, queryParams: any) => void;
@@ -15,7 +14,7 @@ export interface TopTableData {
15
14
  }
16
15
  interface CallgraphData {
17
16
  loading: boolean;
18
- data?: Callgraph;
17
+ data?: CallgraphType;
19
18
  error?: any;
20
19
  }
21
20
  export declare type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
@@ -23,15 +23,17 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
23
23
  // See the License for the specific language governing permissions and
24
24
  // limitations under the License.
25
25
  import { useEffect, useMemo, useState } from 'react';
26
- import { parseParams } from '@parca/functions';
26
+ import { getNewSpanColor, parseParams } from '@parca/functions';
27
27
  import useUIFeatureFlag from '@parca/functions/useUIFeatureFlag';
28
28
  import { Button, Card, SearchNodes, useParcaTheme } from '@parca/components';
29
- import { Callgraph as CallgraphComponent } from './';
29
+ import { Callgraph } from './';
30
30
  import { useContainerDimensions } from '@parca/dynamicsize';
31
+ import { useAppSelector, selectDarkMode, selectSearchNodeString } from '@parca/store';
31
32
  import ProfileShareButton from './components/ProfileShareButton';
32
33
  import ProfileIcicleGraph from './ProfileIcicleGraph';
33
34
  import TopTable from './TopTable';
34
35
  import useDelayedLoader from './useDelayedLoader';
36
+ import { scaleLinear } from 'd3';
35
37
  import './ProfileView.styles.css';
36
38
  function arrayEquals(a, b) {
37
39
  return (Array.isArray(a) &&
@@ -58,6 +60,8 @@ export var ProfileView = function (_a) {
58
60
  var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
59
61
  var _c = useState([]), curPath = _c[0], setCurPath = _c[1];
60
62
  var currentView = profileVisState.currentView, setCurrentView = profileVisState.setCurrentView;
63
+ var isDarkMode = useAppSelector(selectDarkMode);
64
+ var currentSearchString = useAppSelector(selectSearchNodeString);
61
65
  var callgraphEnabled = useUIFeatureFlag('callgraph')[0];
62
66
  var loader = useParcaTheme().loader;
63
67
  useEffect(function () {
@@ -102,10 +106,14 @@ export var ProfileView = function (_a) {
102
106
  return;
103
107
  }
104
108
  var router = parseParams(window.location.search);
105
- navigateTo('/', __assign(__assign({}, router), { currentProfileView: view }));
109
+ navigateTo('/', __assign(__assign(__assign({}, router), { currentProfileView: view }), { searchString: currentSearchString }));
106
110
  };
107
- return (_jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
108
- e.preventDefault();
109
- onDownloadPProf();
110
- } }, { children: "Download pprof" }))] })), _jsx(SearchNodes, {})] })), _jsxs("div", __assign({ className: "flex ml-auto" }, { children: [_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })) })), callgraphEnabled ? (_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Call Graph" })) }))) : null, _jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] })), _jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(CallgraphComponent, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] }))] }) }) })));
111
+ var maxColor = getNewSpanColor(isDarkMode);
112
+ // TODO: fix colors for dark mode
113
+ var minColor = scaleLinear([isDarkMode ? 'black' : 'white', maxColor])(0.3);
114
+ var colorRange = [minColor, maxColor];
115
+ return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
116
+ e.preventDefault();
117
+ onDownloadPProf();
118
+ } }, { children: "Download pprof" }))] })), _jsx(SearchNodes, {})] })), _jsxs("div", __assign({ className: "flex ml-auto" }, { children: [_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })) })), callgraphEnabled ? (_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Callgraph" })) }))) : null, _jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] })), _jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] }))] }) }) })) }));
111
119
  };
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { QueryServiceClient } from '@parca/client';
3
2
  import { ProfileSource } from './ProfileSource';
4
3
  declare type NavigateFunction = (path: string, queryParams: any) => void;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { TopNodeMeta, Top } from '@parca/client';
3
2
  import './TopTable.styles.css';
4
3
  interface TopTableProps {
@@ -1,3 +1,2 @@
1
- /// <reference types="react" />
2
1
  declare const DiffLegend: () => JSX.Element;
3
2
  export default DiffLegend;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  interface Props {
3
2
  value: string;
4
3
  className?: string;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { QueryRequest, QueryServiceClient } from '@parca/client';
3
2
  interface Props {
4
3
  queryRequest: QueryRequest;
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export * from './ProfileViewWithData';
12
12
  export * from './utils';
13
13
  export * from './ProfileTypeSelector';
14
14
  export type { CallgraphProps };
15
- declare const Callgraph: React.LazyExoticComponent<({ graph, sampleUnit, width }: CallgraphProps) => JSX.Element>;
15
+ declare const Callgraph: React.LazyExoticComponent<({ graph, sampleUnit, width, colorRange }: CallgraphProps) => JSX.Element>;
16
16
  export { Callgraph, ProfileExplorer, ProfileTypeSelector };
17
17
  interface GrafanaParcaDataPayload {
18
18
  flamegraphData: FlamegraphData;