@parca/profile 0.16.56 → 0.16.58

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 (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Callgraph/Edge/index.d.ts +1 -0
  3. package/dist/Callgraph/Node/index.d.ts +1 -0
  4. package/dist/Callgraph/index.d.ts +1 -0
  5. package/dist/GraphTooltip/index.d.ts +10 -4
  6. package/dist/GraphTooltip/index.js +31 -5
  7. package/dist/IcicleGraph.d.ts +13 -3
  8. package/dist/IcicleGraph.js +23 -19
  9. package/dist/MatchersInput/index.d.ts +1 -0
  10. package/dist/MetricsCircle/index.d.ts +1 -0
  11. package/dist/MetricsGraph/index.d.ts +1 -0
  12. package/dist/MetricsSeries/index.d.ts +1 -0
  13. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -0
  14. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -0
  15. package/dist/ProfileExplorer/index.d.ts +1 -0
  16. package/dist/ProfileIcicleGraph.d.ts +1 -0
  17. package/dist/ProfileMetricsGraph/index.d.ts +1 -0
  18. package/dist/ProfileSelector/CompareButton.d.ts +1 -0
  19. package/dist/ProfileSelector/MergeButton.d.ts +1 -0
  20. package/dist/ProfileSelector/index.d.ts +1 -0
  21. package/dist/ProfileSource.d.ts +1 -0
  22. package/dist/ProfileTypeSelector/index.d.ts +1 -0
  23. package/dist/ProfileView.d.ts +1 -0
  24. package/dist/ProfileViewWithData.d.ts +1 -0
  25. package/dist/ProfileViewWithData.js +1 -1
  26. package/dist/TopTable.d.ts +1 -0
  27. package/dist/TopTable.js +8 -4
  28. package/dist/components/DiffLegend.d.ts +1 -0
  29. package/dist/components/ProfileShareButton/ResultBox.d.ts +1 -0
  30. package/dist/components/ProfileShareButton/index.d.ts +1 -0
  31. package/package.json +4 -4
  32. package/src/GraphTooltip/index.tsx +74 -4
  33. package/src/IcicleGraph.tsx +63 -15
  34. package/src/ProfileViewWithData.tsx +1 -1
  35. package/src/TopTable.tsx +18 -4
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.58](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.57...@parca/profile@0.16.58) (2022-11-02)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.57](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.56...@parca/profile@0.16.57) (2022-11-02)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## 0.16.56 (2022-10-31)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export interface IEdge {
2
3
  source: number;
3
4
  target: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export interface INode {
2
3
  id: number;
3
4
  x: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Callgraph as CallgraphType } from '@parca/client';
2
3
  export interface Props {
3
4
  graph: CallgraphType;
@@ -1,4 +1,6 @@
1
- import { CallgraphNode, FlamegraphRootNode } from '@parca/client';
1
+ /// <reference types="react" />
2
+ import { CallgraphNode, FlamegraphNode, FlamegraphNodeMeta, FlamegraphRootNode } from '@parca/client';
3
+ import { Function, Location, Mapping } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
2
4
  interface GraphTooltipProps {
3
5
  x: number;
4
6
  y: number;
@@ -8,12 +10,16 @@ interface GraphTooltipProps {
8
10
  contextElement: Element | null;
9
11
  isFixed?: boolean;
10
12
  virtualContextElement?: boolean;
13
+ strings?: string[];
14
+ mappings?: Mapping[];
15
+ locations?: Location[];
16
+ functions?: Function[];
11
17
  }
12
- export interface HoveringNode extends CallgraphNode, FlamegraphRootNode {
18
+ export interface HoveringNode extends CallgraphNode, FlamegraphRootNode, FlamegraphNode {
13
19
  diff: string;
14
- meta?: {
20
+ meta?: FlamegraphNodeMeta | {
15
21
  [key: string]: any;
16
22
  };
17
23
  }
18
- declare const GraphTooltip: ({ x, y, unit, total, hoveringNode, contextElement, isFixed, virtualContextElement, }: GraphTooltipProps) => JSX.Element;
24
+ declare const GraphTooltip: ({ x, y, unit, total, hoveringNode, contextElement, isFixed, virtualContextElement, strings, mappings, locations, functions, }: GraphTooltipProps) => JSX.Element;
19
25
  export default GraphTooltip;
@@ -70,9 +70,33 @@ function generateGetBoundingClientRect(contextElement, x, y) {
70
70
  }
71
71
  var TooltipMetaInfo = function (_a) {
72
72
  var _b, _c, _d, _e;
73
- var hoveringNode = _a.hoveringNode, onCopy = _a.onCopy;
73
+ var hoveringNode = _a.hoveringNode, onCopy = _a.onCopy, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions;
74
74
  if (hoveringNode.meta === undefined)
75
75
  return _jsx(_Fragment, {});
76
+ // populate meta from the flamegraph metadata tables
77
+ if (locations !== undefined) {
78
+ var location_1 = locations[hoveringNode.meta.locationIndex];
79
+ hoveringNode.meta.location = location_1;
80
+ if (mappings !== undefined) {
81
+ var mapping = mappings[location_1.mappingIndex];
82
+ if (strings !== undefined) {
83
+ mapping.file = strings[mapping.fileStringIndex];
84
+ mapping.buildId = strings[mapping.buildIdStringIndex];
85
+ }
86
+ hoveringNode.meta.mapping = mapping;
87
+ }
88
+ location_1.lines.forEach(function (line) {
89
+ if (functions !== undefined && hoveringNode.meta !== undefined) {
90
+ var func = functions[line.functionIndex];
91
+ if (strings !== undefined) {
92
+ func.name = strings[func.nameStringIndex];
93
+ func.systemName = strings[func.systemNameStringIndex];
94
+ func.filename = strings[func.filenameStringIndex];
95
+ }
96
+ hoveringNode.meta.function = func;
97
+ }
98
+ });
99
+ }
76
100
  var getTextForFile = function (hoveringNode) {
77
101
  var _a, _b, _c, _d;
78
102
  if (hoveringNode.meta === undefined)
@@ -91,7 +115,7 @@ var TooltipMetaInfo = function (_a) {
91
115
  };
92
116
  var timeoutHandle = null;
93
117
  var GraphTooltipContent = function (_a) {
94
- var hoveringNode = _a.hoveringNode, unit = _a.unit, total = _a.total, isFixed = _a.isFixed;
118
+ 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;
95
119
  var _b = useState(false), isCopied = _b[0], setIsCopied = _b[1];
96
120
  var onCopy = function () {
97
121
  setIsCopied(true);
@@ -108,7 +132,9 @@ var GraphTooltipContent = function (_a) {
108
132
  var diffValueText = diffSign + valueFormatter(diff, unit, 1);
109
133
  var diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
110
134
  var diffText = "".concat(diffValueText, " (").concat(diffPercentageText, ")");
111
- var metaRows = hoveringNode.meta === undefined ? (_jsx(_Fragment, {})) : (_jsx(TooltipMetaInfo, { onCopy: onCopy, hoveringNode: hoveringNode }));
135
+ var metaRows = hoveringNode.meta === undefined ? (_jsx(_Fragment, {})) : (_jsx(TooltipMetaInfo, { onCopy: onCopy,
136
+ // @ts-expect-error
137
+ hoveringNode: hoveringNode, strings: strings, mappings: mappings, locations: locations, functions: functions }));
112
138
  var getTextForCumulative = function (hoveringNodeCumulative) {
113
139
  return "".concat(valueFormatter(hoveringNodeCumulative, unit, 2), " (\n ").concat(((hoveringNodeCumulative * 100) / total).toFixed(2), "%)");
114
140
  };
@@ -117,7 +143,7 @@ var GraphTooltipContent = function (_a) {
117
143
  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("span", __assign({ className: "text-gray-700 dark:text-gray-300 my-2" }, { children: _jsx("table", __assign({ className: "table-fixed" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Cumulative" })), _jsx("td", __assign({ className: "w-4/5" }, { 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/5" }, { children: "Diff" })), _jsx("td", __assign({ className: "w-4/5" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: diffText }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: diffText })) })) }))] })), metaRows] }) })) })), _jsx("span", __assign({ className: "block text-gray-500 text-xs mt-2" }, { children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' }))] })) })) })) })) })));
118
144
  };
119
145
  var GraphTooltip = function (_a) {
120
- var x = _a.x, y = _a.y, unit = _a.unit, total = _a.total, hoveringNode = _a.hoveringNode, contextElement = _a.contextElement, _b = _a.isFixed, isFixed = _b === void 0 ? false : _b, _c = _a.virtualContextElement, virtualContextElement = _c === void 0 ? true : _c;
146
+ var x = _a.x, y = _a.y, unit = _a.unit, total = _a.total, hoveringNode = _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;
121
147
  var _d = useState(null), popperElement = _d[0], setPopperElement = _d[1];
122
148
  var _e = usePopper(virtualContextElement ? virtualElement : contextElement, popperElement, {
123
149
  placement: 'auto-start',
@@ -150,6 +176,6 @@ var GraphTooltip = function (_a) {
150
176
  }, [x, y, contextElement, update, isShiftDown]);
151
177
  if (hoveringNode === undefined || hoveringNode == null)
152
178
  return _jsx(_Fragment, {});
153
- 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 }) })));
179
+ 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 }) })));
154
180
  };
155
181
  export default GraphTooltip;
@@ -1,4 +1,6 @@
1
+ /// <reference types="react" />
1
2
  import { Flamegraph, FlamegraphNode, FlamegraphRootNode } from '@parca/client';
3
+ import { Mapping, Function, Location } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
2
4
  interface IcicleGraphProps {
3
5
  graph: Flamegraph;
4
6
  sampleUnit: string;
@@ -8,6 +10,10 @@ interface IcicleGraphProps {
8
10
  }
9
11
  interface IcicleGraphNodesProps {
10
12
  data: FlamegraphNode[];
13
+ strings: string[];
14
+ mappings: Mapping[];
15
+ locations: Location[];
16
+ functions: Function[];
11
17
  x: number;
12
18
  y: number;
13
19
  total: number;
@@ -21,6 +27,10 @@ interface IcicleGraphNodesProps {
21
27
  }
22
28
  interface IcicleGraphRootNodeProps {
23
29
  node: FlamegraphRootNode;
30
+ strings: string[];
31
+ mappings: Mapping[];
32
+ locations: Location[];
33
+ functions: Function[];
24
34
  xScale: (value: number) => number;
25
35
  total: number;
26
36
  totalWidth: number;
@@ -28,8 +38,8 @@ interface IcicleGraphRootNodeProps {
28
38
  setCurPath: (path: string[]) => void;
29
39
  setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
30
40
  }
31
- export declare function nodeLabel(node: FlamegraphNode): string;
32
- export declare function IcicleGraphNodes({ data, x, y, xScale, total, totalWidth, level, setHoveringNode, path, setCurPath, curPath, }: IcicleGraphNodesProps): JSX.Element;
33
- export declare function IcicleGraphRootNode({ node, xScale, total, totalWidth, setHoveringNode, setCurPath, curPath, }: IcicleGraphRootNodeProps): JSX.Element;
41
+ export declare function nodeLabel(node: FlamegraphNode, strings: string[], mappings: Mapping[], locations: Location[], functions: Function[]): string;
42
+ export declare function IcicleGraphNodes({ data, strings, mappings, locations, functions, x, y, xScale, total, totalWidth, level, setHoveringNode, path, setCurPath, curPath, }: IcicleGraphNodesProps): JSX.Element;
43
+ export declare function IcicleGraphRootNode({ node, strings, mappings, locations, functions, xScale, total, totalWidth, setHoveringNode, setCurPath, curPath, }: IcicleGraphRootNodeProps): JSX.Element;
34
44
  export default function IcicleGraph({ graph, width, setCurPath, curPath, sampleUnit, }: IcicleGraphProps): JSX.Element;
35
45
  export {};
@@ -27,8 +27,8 @@ import { throttle } from 'lodash';
27
27
  import { pointer } from 'd3-selection';
28
28
  import { scaleLinear } from 'd3-scale';
29
29
  import GraphTooltip from './GraphTooltip';
30
- import { getLastItem, diffColor, isSearchMatch } from '@parca/functions';
31
- import { useAppSelector, selectDarkMode, selectSearchNodeString } from '@parca/store';
30
+ import { diffColor, getLastItem, isSearchMatch } from '@parca/functions';
31
+ import { selectDarkMode, selectSearchNodeString, useAppSelector } from '@parca/store';
32
32
  import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
33
33
  import { hexifyAddress } from './utils';
34
34
  var RowHeight = 26;
@@ -55,24 +55,28 @@ function IcicleRect(_a) {
55
55
  fill: color,
56
56
  } }), width > 5 && (_jsx("svg", __assign({ width: width - 5, height: height }, { children: _jsx("text", __assign({ x: 5, y: 15, style: { fontSize: '12px' } }, { children: name })) })))] })));
57
57
  }
58
- export function nodeLabel(node) {
59
- var _a, _b, _c, _d, _e, _f, _g, _h;
60
- if (node.meta === undefined)
58
+ export function nodeLabel(node, strings, mappings, locations, functions) {
59
+ var _a, _b;
60
+ if (((_a = node.meta) === null || _a === void 0 ? void 0 : _a.locationIndex) === undefined)
61
61
  return '<unknown>';
62
- var mapping = "".concat(((_b = (_a = node.meta) === null || _a === void 0 ? void 0 : _a.mapping) === null || _b === void 0 ? void 0 : _b.file) !== undefined && ((_d = (_c = node.meta) === null || _c === void 0 ? void 0 : _c.mapping) === null || _d === void 0 ? void 0 : _d.file) !== ''
63
- ? '[' + ((_e = getLastItem(node.meta.mapping.file)) !== null && _e !== void 0 ? _e : '') + '] '
64
- : '');
65
- if (((_f = node.meta.function) === null || _f === void 0 ? void 0 : _f.name) !== undefined && ((_g = node.meta.function) === null || _g === void 0 ? void 0 : _g.name) !== '')
66
- return mapping + node.meta.function.name;
67
- var address = hexifyAddress((_h = node.meta.location) === null || _h === void 0 ? void 0 : _h.address);
68
- var fallback = "".concat(mapping).concat(address);
62
+ var location = locations[node.meta.locationIndex];
63
+ var mappingFile = strings[mappings[location.mappingIndex].fileStringIndex];
64
+ var mappingString = "".concat(mappingFile !== '' ? '[' + ((_b = getLastItem(mappingFile)) !== null && _b !== void 0 ? _b : '') + '] ' : '');
65
+ if (location.lines.length > 0) {
66
+ var funcName = strings[functions[location.lines[0].functionIndex].nameStringIndex];
67
+ return "".concat(mappingString, " ").concat(funcName);
68
+ }
69
+ var address = hexifyAddress(location.address);
70
+ var fallback = "".concat(mappingString).concat(address);
69
71
  return fallback === '' ? '<unknown>' : fallback;
70
72
  }
71
73
  export function IcicleGraphNodes(_a) {
72
- var data = _a.data, x = _a.x, y = _a.y, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, level = _a.level, setHoveringNode = _a.setHoveringNode, path = _a.path, setCurPath = _a.setCurPath, curPath = _a.curPath;
74
+ var data = _a.data, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions, x = _a.x, y = _a.y, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, level = _a.level, setHoveringNode = _a.setHoveringNode, path = _a.path, setCurPath = _a.setCurPath, curPath = _a.curPath;
73
75
  var isDarkMode = useAppSelector(selectDarkMode);
74
76
  var isShiftDown = useIsShiftDown();
75
- var nodes = curPath.length === 0 ? data : data.filter(function (d) { return d != null && curPath[0] === nodeLabel(d); });
77
+ var nodes = curPath.length === 0
78
+ ? data
79
+ : data.filter(function (d) { return d != null && curPath[0] === nodeLabel(d, strings, mappings, locations, functions); });
76
80
  var nextLevel = level + 1;
77
81
  return (_jsx("g", __assign({ transform: "translate(".concat(x, ", ").concat(y, ")") }, { children: nodes.map(function (d, i) {
78
82
  var cumulative = parseFloat(d.cumulative);
@@ -85,7 +89,7 @@ export function IcicleGraphNodes(_a) {
85
89
  if (width <= 1) {
86
90
  return null;
87
91
  }
88
- var name = nodeLabel(d);
92
+ var name = nodeLabel(d, strings, mappings, locations, functions);
89
93
  var key = "".concat(level, "-").concat(i);
90
94
  var nextPath = path.concat([name]);
91
95
  var color = diffColor(diff, cumulative, isDarkMode);
@@ -106,12 +110,12 @@ export function IcicleGraphNodes(_a) {
106
110
  return;
107
111
  setHoveringNode(undefined);
108
112
  };
109
- return (_jsxs(React.Fragment, { children: [_jsx(IcicleRect, { x: xStart, y: 0, width: width, height: RowHeight, name: name, color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }, "rect-".concat(key)), data !== undefined && data.length > 0 && (_jsx(IcicleGraphNodes, { data: d.children, x: xStart, y: RowHeight, xScale: newXScale, total: total, totalWidth: totalWidth, level: nextLevel, setHoveringNode: setHoveringNode, path: nextPath, curPath: nextCurPath, setCurPath: setCurPath }, "node-".concat(key)))] }, "node-".concat(key)));
113
+ return (_jsxs(React.Fragment, { children: [_jsx(IcicleRect, { x: xStart, y: 0, width: width, height: RowHeight, name: name, color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }, "rect-".concat(key)), data !== undefined && data.length > 0 && (_jsx(IcicleGraphNodes, { data: d.children, strings: strings, mappings: mappings, locations: locations, functions: functions, x: xStart, y: RowHeight, xScale: newXScale, total: total, totalWidth: totalWidth, level: nextLevel, setHoveringNode: setHoveringNode, path: nextPath, curPath: nextCurPath, setCurPath: setCurPath }, "node-".concat(key)))] }, "node-".concat(key)));
110
114
  }) })));
111
115
  }
112
116
  var MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
113
117
  export function IcicleGraphRootNode(_a) {
114
- var node = _a.node, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, setHoveringNode = _a.setHoveringNode, setCurPath = _a.setCurPath, curPath = _a.curPath;
118
+ var node = _a.node, strings = _a.strings, mappings = _a.mappings, locations = _a.locations, functions = _a.functions, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, setHoveringNode = _a.setHoveringNode, setCurPath = _a.setCurPath, curPath = _a.curPath;
115
119
  var isDarkMode = useAppSelector(selectDarkMode);
116
120
  var isShiftDown = useIsShiftDown();
117
121
  var cumulative = parseFloat(node.cumulative);
@@ -129,7 +133,7 @@ export function IcicleGraphRootNode(_a) {
129
133
  setHoveringNode(undefined);
130
134
  };
131
135
  var path = [];
132
- return (_jsxs("g", __assign({ transform: 'translate(0, 0)' }, { children: [_jsx(IcicleRect, { x: 0, y: 0, width: totalWidth, height: RowHeight, name: 'root', color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }), _jsx(MemoizedIcicleGraphNodes, { data: node.children, x: 0, y: RowHeight, xScale: xScale, total: total, totalWidth: totalWidth, level: 0, setHoveringNode: setHoveringNode, path: path, curPath: curPath, setCurPath: setCurPath })] })));
136
+ return (_jsxs("g", __assign({ transform: 'translate(0, 0)' }, { children: [_jsx(IcicleRect, { x: 0, y: 0, width: totalWidth, height: RowHeight, name: 'root', color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }), _jsx(MemoizedIcicleGraphNodes, { data: node.children, strings: strings, mappings: mappings, locations: locations, functions: functions, x: 0, y: RowHeight, xScale: xScale, total: total, totalWidth: totalWidth, level: 0, setHoveringNode: setHoveringNode, path: path, curPath: curPath, setCurPath: setCurPath })] })));
133
137
  }
134
138
  var MemoizedIcicleGraphRootNode = React.memo(IcicleGraphRootNode);
135
139
  export default function IcicleGraph(_a) {
@@ -154,5 +158,5 @@ export default function IcicleGraph(_a) {
154
158
  };
155
159
  var total = parseFloat(graph.total);
156
160
  var xScale = scaleLinear().domain([0, total]).range([0, width]);
157
- return (_jsxs("div", __assign({ onMouseLeave: function () { return setHoveringNode(undefined); } }, { children: [_jsx(GraphTooltip, { unit: sampleUnit, total: total, x: pos[0], y: pos[1], hoveringNode: hoveringNode, contextElement: svg.current }), _jsx("svg", __assign({ className: "font-robotoMono", width: width, height: height, onMouseMove: onMouseMove, preserveAspectRatio: "xMinYMid", ref: svg }, { children: _jsx("g", __assign({ ref: ref }, { children: _jsx(MemoizedIcicleGraphRootNode, { node: graph.root, setHoveringNode: setHoveringNode, curPath: curPath, setCurPath: setCurPath, xScale: xScale, total: total, totalWidth: width }) })) }))] })));
161
+ return (_jsxs("div", __assign({ onMouseLeave: function () { return setHoveringNode(undefined); } }, { children: [_jsx(GraphTooltip, { unit: sampleUnit, total: total, x: pos[0], y: pos[1], hoveringNode: hoveringNode, contextElement: svg.current, strings: graph.stringTable, mappings: graph.mapping, locations: graph.locations, functions: graph.function }), _jsx("svg", __assign({ className: "font-robotoMono", width: width, height: height, onMouseMove: onMouseMove, preserveAspectRatio: "xMinYMid", ref: svg }, { children: _jsx("g", __assign({ ref: ref }, { children: _jsx(MemoizedIcicleGraphRootNode, { node: graph.root, strings: graph.stringTable, mappings: graph.mapping, locations: graph.locations, functions: graph.function, setHoveringNode: setHoveringNode, curPath: curPath, setCurPath: setCurPath, xScale: xScale, total: total, totalWidth: width }) })) }))] })));
158
162
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Query } from '@parca/parser';
2
3
  import { LabelsResponse, QueryServiceClient, ValuesResponse } from '@parca/client';
3
4
  interface MatchersInputProps {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  interface MetricsCircleProps {
2
3
  cx: number;
3
4
  cy: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { SingleProfileSelection } from '..';
2
3
  import { MetricsSeries as MetricsSeriesPb, Label } from '@parca/client';
3
4
  import { DateTimeRange } from '@parca/components';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import * as d3 from 'd3';
2
3
  interface MetricsSeriesProps {
3
4
  data: any;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileSelection } from '..';
2
3
  import { QueryServiceClient } from '@parca/client';
3
4
  import { NavigateFunction } from '.';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  import { ProfileSelection } from '..';
3
4
  import { NavigateFunction } from '../ProfileExplorer';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  export declare type NavigateFunction = (path: string, queryParams: any) => void;
3
4
  interface ProfileExplorerProps {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Flamegraph } from '@parca/client';
2
3
  interface ProfileIcicleGraphProps {
3
4
  width?: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileSelection } from '..';
2
3
  import { QueryServiceClient, QueryRangeResponse } from '@parca/client';
3
4
  import { RpcError } from '@protobuf-ts/runtime-rpc';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  declare const CompareButton: ({ disabled, onClick, }: {
2
3
  disabled: boolean;
3
4
  onClick: () => void;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  declare const MergeButton: ({ disabled, onClick, }: {
2
3
  disabled: boolean;
3
4
  onClick: () => void;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient, ProfileTypesResponse } from '@parca/client';
2
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
4
  import { ProfileSelection } from '..';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileType } from '@parca/parser';
2
3
  import { Label, QueryRequest, ProfileDiffSelection } from '@parca/client';
3
4
  export interface ProfileSource {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileType, ProfileTypesResponse } from '@parca/client';
2
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
4
  interface WellKnownProfile {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient, Flamegraph, Top, Callgraph } from '@parca/client';
2
3
  import { ProfileSource } from './ProfileSource';
3
4
  import './ProfileView.styles.css';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  import { ProfileSource } from './ProfileSource';
3
4
  declare type NavigateFunction = (path: string, queryParams: any) => void;
@@ -59,7 +59,7 @@ export var ProfileViewWithData = function (_a) {
59
59
  var profileVisState = useProfileVisState();
60
60
  var metadata = useGrpcMetadata();
61
61
  var currentView = profileVisState.currentView;
62
- var _d = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED, {
62
+ var _d = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
63
63
  skip: currentView !== 'icicle' && currentView !== 'both',
64
64
  }), flamegraphLoading = _d.isLoading, flamegraphResponse = _d.response, flamegraphError = _d.error;
65
65
  var _e = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { TopNodeMeta, Top } from '@parca/client';
2
3
  import './TopTable.styles.css';
3
4
  interface TopTableProps {
package/dist/TopTable.js CHANGED
@@ -33,7 +33,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
33
33
  // limitations under the License.
34
34
  import React from 'react';
35
35
  import { getLastItem, valueFormatter, isSearchMatch } from '@parca/functions';
36
- import { useAppSelector, selectCompareMode, selectSearchNodeString } from '@parca/store';
36
+ import { useAppSelector, selectCompareMode, selectSearchNodeString, setSearchNodeString, useAppDispatch, } from '@parca/store';
37
37
  import { hexifyAddress } from './utils';
38
38
  import './TopTable.styles.css';
39
39
  var Arrow = function (_a) {
@@ -110,6 +110,7 @@ export var TopTable = function (_a) {
110
110
  var _f = useSortableData(top), items = _f.items, requestSort = _f.requestSort, sortConfig = _f.sortConfig;
111
111
  var currentSearchString = useAppSelector(selectSearchNodeString);
112
112
  var compareMode = useAppSelector(selectCompareMode);
113
+ var dispatch = useAppDispatch();
113
114
  var unit = sampleUnit;
114
115
  var total = top != null ? top.list.length : 0;
115
116
  if (total === 0)
@@ -126,15 +127,18 @@ export var TopTable = function (_a) {
126
127
  }
127
128
  return "+".concat(num);
128
129
  };
129
- return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "w-full font-robotoMono" }, { children: _jsxs("table", __assign({ className: "iciclegraph-table table-fixed text-left w-full divide-y divide-gray-200 dark:divide-gray-700" }, { children: [_jsx("thead", __assign({ className: "bg-gray-50 dark:bg-gray-800" }, { children: _jsxs("tr", { children: [_jsxs("th", __assign({ className: "text-sm cursor-pointer pt-2 pb-2 pl-2", onClick: function () { return requestSort('name'); } }, { children: ["Name", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_b = getClassNamesFor('name')) !== null && _b !== void 0 ? _b : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('name') }) }))] })), _jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 w-[150px]", onClick: function () { return requestSort('flat'); } }, { children: ["Flat", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_c = getClassNamesFor('flat')) !== null && _c !== void 0 ? _c : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('flat') }) }))] })), _jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 pr-2 w-[150px]", onClick: function () { return requestSort('cumulative'); } }, { children: ["Cumulative", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_d = getClassNamesFor('cumulative')) !== null && _d !== void 0 ? _d : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('cumulative') }) }))] })), compareMode && (_jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 pr-2 w-[150px]", onClick: function () { return requestSort('diff'); } }, { children: ["Diff", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_e = getClassNamesFor('diff')) !== null && _e !== void 0 ? _e : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('diff') }) }))] })))] }) })), _jsx("tbody", __assign({ className: "bg-white divide-y divide-gray-200 dark:bg-gray-900 dark:divide-gray-700" }, { children: items === null || items === void 0 ? void 0 : items.map(function (report, index) {
130
+ var selectSpan = function (span) {
131
+ dispatch(setSearchNodeString(span.trim()));
132
+ };
133
+ return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "w-full font-robotoMono" }, { children: _jsxs("table", __assign({ className: "iciclegraph-table table-fixed text-left w-full divide-y divide-gray-200 dark:divide-gray-700", tabIndex: 1 }, { children: [_jsx("thead", __assign({ className: "bg-gray-50 dark:bg-gray-800" }, { children: _jsxs("tr", { children: [_jsxs("th", __assign({ className: "text-sm cursor-pointer pt-2 pb-2 pl-2", onClick: function () { return requestSort('name'); } }, { children: ["Name", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_b = getClassNamesFor('name')) !== null && _b !== void 0 ? _b : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('name') }) }))] })), _jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 w-[150px]", onClick: function () { return requestSort('flat'); } }, { children: ["Flat", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_c = getClassNamesFor('flat')) !== null && _c !== void 0 ? _c : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('flat') }) }))] })), _jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 pr-2 w-[150px]", onClick: function () { return requestSort('cumulative'); } }, { children: ["Cumulative", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_d = getClassNamesFor('cumulative')) !== null && _d !== void 0 ? _d : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('cumulative') }) }))] })), compareMode && (_jsxs("th", __assign({ className: "text-right text-sm cursor-pointer pt-2 pb-2 pr-2 w-[150px]", onClick: function () { return requestSort('diff'); } }, { children: ["Diff", _jsx("span", __assign({ className: "inline-block align-middle ml-2 ".concat((_e = getClassNamesFor('diff')) !== null && _e !== void 0 ? _e : '') }, { children: _jsx(Arrow, { direction: getClassNamesFor('diff') }) }))] })))] }) })), _jsx("tbody", __assign({ className: "bg-white divide-y divide-gray-200 dark:bg-gray-900 dark:divide-gray-700" }, { children: items === null || items === void 0 ? void 0 : items.map(function (report, index) {
130
134
  var name = RowLabel(report.meta);
131
- return (_jsxs("tr", __assign({ className: "hover:bg-[#62626212] dark:hover:bg-[#ffffff12]", style: {
135
+ return (_jsxs("tr", __assign({ className: "hover:bg-[#62626212] dark:hover:bg-[#ffffff12] cursor-pointer", style: {
132
136
  opacity: currentSearchString !== undefined &&
133
137
  currentSearchString !== '' &&
134
138
  !isSearchMatch(currentSearchString, name)
135
139
  ? 0.5
136
140
  : 1,
137
- } }, { children: [_jsx("td", __assign({ className: "text-xs py-1.5 pl-2" }, { children: name })), _jsx("td", __assign({ className: "text-xs py-1.5 text-right" }, { children: valueFormatter(report.flat, unit, 2) })), _jsx("td", __assign({ className: "text-xs py-1.5 text-right pr-2" }, { children: valueFormatter(report.cumulative, unit, 2) })), compareMode && (_jsx("td", __assign({ className: "text-xs py-1.5 text-right pr-2" }, { children: addPlusSign(valueFormatter(report.diff, unit, 2)) })))] }), index));
141
+ }, onClick: function () { return selectSpan(name); } }, { children: [_jsx("td", __assign({ className: "text-xs py-1.5 pl-2" }, { children: name })), _jsx("td", __assign({ className: "text-xs py-1.5 text-right" }, { children: valueFormatter(report.flat, unit, 2) })), _jsx("td", __assign({ className: "text-xs py-1.5 text-right pr-2" }, { children: valueFormatter(report.cumulative, unit, 2) })), compareMode && (_jsx("td", __assign({ className: "text-xs py-1.5 text-right pr-2" }, { children: addPlusSign(valueFormatter(report.diff, unit, 2)) })))] }), index));
138
142
  }) }))] })) })) }));
139
143
  };
140
144
  export default TopTable;
@@ -1,2 +1,3 @@
1
+ /// <reference types="react" />
1
2
  declare const DiffLegend: () => JSX.Element;
2
3
  export default DiffLegend;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  interface Props {
2
3
  value: string;
3
4
  className?: string;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryRequest, QueryServiceClient } from '@parca/client';
2
3
  interface Props {
3
4
  queryRequest: QueryRequest;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.56",
3
+ "version": "0.16.58",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
- "@parca/client": "^0.16.52",
8
- "@parca/components": "^0.16.53",
7
+ "@parca/client": "^0.16.53",
8
+ "@parca/components": "^0.16.54",
9
9
  "@parca/dynamicsize": "^0.16.51",
10
10
  "@parca/functions": "^0.16.51",
11
11
  "@parca/parser": "^0.16.49",
@@ -39,5 +39,5 @@
39
39
  "access": "public",
40
40
  "registry": "https://registry.npmjs.org/"
41
41
  },
42
- "gitHead": "931dedcfe76e4fddaa68a693874d94ecb7e36fdb"
42
+ "gitHead": "eee07852940670c11af89dffde8136d5ab177e6c"
43
43
  }
@@ -15,10 +15,16 @@ import {CopyToClipboard} from 'react-copy-to-clipboard';
15
15
  import {useState, useEffect} from 'react';
16
16
  import {usePopper} from 'react-popper';
17
17
 
18
- import {CallgraphNode, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
18
+ import {CallgraphNode, FlamegraphNode, FlamegraphNodeMeta, FlamegraphRootNode} from '@parca/client';
19
19
  import {getLastItem, valueFormatter} from '@parca/functions';
20
20
  import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
21
21
  import {hexifyAddress, truncateString} from '../';
22
+ import {
23
+ Function,
24
+ Location,
25
+ Mapping,
26
+ Line,
27
+ } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
22
28
 
23
29
  interface GraphTooltipProps {
24
30
  x: number;
@@ -29,6 +35,10 @@ interface GraphTooltipProps {
29
35
  contextElement: Element | null;
30
36
  isFixed?: boolean;
31
37
  virtualContextElement?: boolean;
38
+ strings?: string[];
39
+ mappings?: Mapping[];
40
+ locations?: Location[];
41
+ functions?: Function[];
32
42
  }
33
43
 
34
44
  const virtualElement = {
@@ -61,12 +71,47 @@ function generateGetBoundingClientRect(contextElement: Element, x = 0, y = 0): (
61
71
  const TooltipMetaInfo = ({
62
72
  hoveringNode,
63
73
  onCopy,
74
+ strings,
75
+ mappings,
76
+ locations,
77
+ functions,
64
78
  }: {
65
79
  hoveringNode: FlamegraphNode;
66
80
  onCopy: () => void;
81
+ strings?: string[];
82
+ mappings?: Mapping[];
83
+ locations?: Location[];
84
+ functions?: Function[];
67
85
  }): JSX.Element => {
68
86
  if (hoveringNode.meta === undefined) return <></>;
69
87
 
88
+ // populate meta from the flamegraph metadata tables
89
+ if (locations !== undefined) {
90
+ const location = locations[hoveringNode.meta.locationIndex];
91
+ hoveringNode.meta.location = location;
92
+
93
+ if (mappings !== undefined) {
94
+ const mapping = mappings[location.mappingIndex];
95
+ if (strings !== undefined) {
96
+ mapping.file = strings[mapping.fileStringIndex];
97
+ mapping.buildId = strings[mapping.buildIdStringIndex];
98
+ }
99
+ hoveringNode.meta.mapping = mapping;
100
+ }
101
+
102
+ location.lines.forEach((line: Line) => {
103
+ if (functions !== undefined && hoveringNode.meta !== undefined) {
104
+ const func = functions[line.functionIndex];
105
+ if (strings !== undefined) {
106
+ func.name = strings[func.nameStringIndex];
107
+ func.systemName = strings[func.systemNameStringIndex];
108
+ func.filename = strings[func.filenameStringIndex];
109
+ }
110
+ hoveringNode.meta.function = func;
111
+ }
112
+ });
113
+ }
114
+
70
115
  const getTextForFile = (hoveringNode: FlamegraphNode): string => {
71
116
  if (hoveringNode.meta === undefined) return '<unknown>';
72
117
 
@@ -140,9 +185,10 @@ const TooltipMetaInfo = ({
140
185
  );
141
186
  };
142
187
 
143
- export interface HoveringNode extends CallgraphNode, FlamegraphRootNode {
188
+ // @ts-expect-error
189
+ export interface HoveringNode extends CallgraphNode, FlamegraphRootNode, FlamegraphNode {
144
190
  diff: string;
145
- meta?: {[key: string]: any};
191
+ meta?: FlamegraphNodeMeta | {[key: string]: any};
146
192
  }
147
193
 
148
194
  let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
@@ -152,11 +198,19 @@ const GraphTooltipContent = ({
152
198
  unit,
153
199
  total,
154
200
  isFixed,
201
+ strings,
202
+ mappings,
203
+ locations,
204
+ functions,
155
205
  }: {
156
206
  hoveringNode: HoveringNode;
157
207
  unit: string;
158
208
  total: number;
159
209
  isFixed: boolean;
210
+ strings?: string[];
211
+ mappings?: Mapping[];
212
+ locations?: Location[];
213
+ functions?: Function[];
160
214
  }): JSX.Element => {
161
215
  const [isCopied, setIsCopied] = useState<boolean>(false);
162
216
 
@@ -181,7 +235,15 @@ const GraphTooltipContent = ({
181
235
  hoveringNode.meta === undefined ? (
182
236
  <></>
183
237
  ) : (
184
- <TooltipMetaInfo onCopy={onCopy} hoveringNode={hoveringNode} />
238
+ <TooltipMetaInfo
239
+ onCopy={onCopy}
240
+ // @ts-expect-error
241
+ hoveringNode={hoveringNode}
242
+ strings={strings}
243
+ mappings={mappings}
244
+ locations={locations}
245
+ functions={functions}
246
+ />
185
247
  );
186
248
 
187
249
  const getTextForCumulative = (hoveringNodeCumulative: number): string => {
@@ -282,6 +344,10 @@ const GraphTooltip = ({
282
344
  contextElement,
283
345
  isFixed = false,
284
346
  virtualContextElement = true,
347
+ strings,
348
+ mappings,
349
+ locations,
350
+ functions,
285
351
  }: GraphTooltipProps): JSX.Element => {
286
352
  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
287
353
 
@@ -332,6 +398,10 @@ const GraphTooltip = ({
332
398
  unit={unit}
333
399
  total={total}
334
400
  isFixed={isFixed}
401
+ strings={strings}
402
+ mappings={mappings}
403
+ locations={locations}
404
+ functions={functions}
335
405
  />
336
406
  </div>
337
407
  );
@@ -18,12 +18,13 @@ import {pointer} from 'd3-selection';
18
18
  import {scaleLinear} from 'd3-scale';
19
19
 
20
20
  import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
21
+ import {Mapping, Function, Location} from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
22
+ import type {HoveringNode} from './GraphTooltip';
21
23
  import GraphTooltip from './GraphTooltip';
22
- import {getLastItem, diffColor, isSearchMatch} from '@parca/functions';
23
- import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
24
+ import {diffColor, getLastItem, isSearchMatch} from '@parca/functions';
25
+ import {selectDarkMode, selectSearchNodeString, useAppSelector} from '@parca/store';
24
26
  import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
25
27
  import {hexifyAddress} from './utils';
26
- import type {HoveringNode} from './GraphTooltip';
27
28
 
28
29
  interface IcicleGraphProps {
29
30
  graph: Flamegraph;
@@ -35,6 +36,10 @@ interface IcicleGraphProps {
35
36
 
36
37
  interface IcicleGraphNodesProps {
37
38
  data: FlamegraphNode[];
39
+ strings: string[];
40
+ mappings: Mapping[];
41
+ locations: Location[];
42
+ functions: Function[];
38
43
  x: number;
39
44
  y: number;
40
45
  total: number;
@@ -49,6 +54,10 @@ interface IcicleGraphNodesProps {
49
54
 
50
55
  interface IcicleGraphRootNodeProps {
51
56
  node: FlamegraphRootNode;
57
+ strings: string[];
58
+ mappings: Mapping[];
59
+ locations: Location[];
60
+ functions: Function[];
52
61
  xScale: (value: number) => number;
53
62
  total: number;
54
63
  totalWidth: number;
@@ -132,24 +141,39 @@ function IcicleRect({
132
141
  );
133
142
  }
134
143
 
135
- export function nodeLabel(node: FlamegraphNode): string {
136
- if (node.meta === undefined) return '<unknown>';
137
- const mapping = `${
138
- node.meta?.mapping?.file !== undefined && node.meta?.mapping?.file !== ''
139
- ? '[' + (getLastItem(node.meta.mapping.file) ?? '') + '] '
140
- : ''
144
+ export function nodeLabel(
145
+ node: FlamegraphNode,
146
+ strings: string[],
147
+ mappings: Mapping[],
148
+ locations: Location[],
149
+ functions: Function[]
150
+ ): string {
151
+ if (node.meta?.locationIndex === undefined) return '<unknown>';
152
+
153
+ const location = locations[node.meta.locationIndex];
154
+ const mappingFile = strings[mappings[location.mappingIndex].fileStringIndex];
155
+
156
+ const mappingString: string = `${
157
+ mappingFile !== '' ? '[' + (getLastItem(mappingFile) ?? '') + '] ' : ''
141
158
  }`;
142
- if (node.meta.function?.name !== undefined && node.meta.function?.name !== '')
143
- return mapping + node.meta.function.name;
144
159
 
145
- const address = hexifyAddress(node.meta.location?.address);
146
- const fallback = `${mapping}${address}`;
160
+ if (location.lines.length > 0) {
161
+ const funcName = strings[functions[location.lines[0].functionIndex].nameStringIndex];
162
+ return `${mappingString} ${funcName}`;
163
+ }
164
+
165
+ const address = hexifyAddress(location.address);
166
+ const fallback = `${mappingString}${address}`;
147
167
 
148
168
  return fallback === '' ? '<unknown>' : fallback;
149
169
  }
150
170
 
151
171
  export function IcicleGraphNodes({
152
172
  data,
173
+ strings,
174
+ mappings,
175
+ locations,
176
+ functions,
153
177
  x,
154
178
  y,
155
179
  xScale,
@@ -165,7 +189,11 @@ export function IcicleGraphNodes({
165
189
  const isShiftDown = useIsShiftDown();
166
190
 
167
191
  const nodes =
168
- curPath.length === 0 ? data : data.filter(d => d != null && curPath[0] === nodeLabel(d));
192
+ curPath.length === 0
193
+ ? data
194
+ : data.filter(
195
+ d => d != null && curPath[0] === nodeLabel(d, strings, mappings, locations, functions)
196
+ );
169
197
 
170
198
  const nextLevel = level + 1;
171
199
 
@@ -186,7 +214,7 @@ export function IcicleGraphNodes({
186
214
  return null;
187
215
  }
188
216
 
189
- const name = nodeLabel(d);
217
+ const name = nodeLabel(d, strings, mappings, locations, functions);
190
218
  const key = `${level}-${i}`;
191
219
  const nextPath = path.concat([name]);
192
220
 
@@ -232,6 +260,10 @@ export function IcicleGraphNodes({
232
260
  <IcicleGraphNodes
233
261
  key={`node-${key}`}
234
262
  data={d.children}
263
+ strings={strings}
264
+ mappings={mappings}
265
+ locations={locations}
266
+ functions={functions}
235
267
  x={xStart}
236
268
  y={RowHeight}
237
269
  xScale={newXScale}
@@ -255,6 +287,10 @@ const MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
255
287
 
256
288
  export function IcicleGraphRootNode({
257
289
  node,
290
+ strings,
291
+ mappings,
292
+ locations,
293
+ functions,
258
294
  xScale,
259
295
  total,
260
296
  totalWidth,
@@ -299,6 +335,10 @@ export function IcicleGraphRootNode({
299
335
  />
300
336
  <MemoizedIcicleGraphNodes
301
337
  data={node.children}
338
+ strings={strings}
339
+ mappings={mappings}
340
+ locations={locations}
341
+ functions={functions}
302
342
  x={0}
303
343
  y={RowHeight}
304
344
  xScale={xScale}
@@ -359,6 +399,10 @@ export default function IcicleGraph({
359
399
  y={pos[1]}
360
400
  hoveringNode={hoveringNode as HoveringNode}
361
401
  contextElement={svg.current}
402
+ strings={graph.stringTable}
403
+ mappings={graph.mapping}
404
+ locations={graph.locations}
405
+ functions={graph.function}
362
406
  />
363
407
  <svg
364
408
  className="font-robotoMono"
@@ -371,6 +415,10 @@ export default function IcicleGraph({
371
415
  <g ref={ref}>
372
416
  <MemoizedIcicleGraphRootNode
373
417
  node={graph.root}
418
+ strings={graph.stringTable}
419
+ mappings={graph.mapping}
420
+ locations={graph.locations}
421
+ functions={graph.function}
374
422
  setHoveringNode={setHoveringNode}
375
423
  curPath={curPath}
376
424
  setCurPath={setCurPath}
@@ -41,7 +41,7 @@ export const ProfileViewWithData = ({
41
41
  isLoading: flamegraphLoading,
42
42
  response: flamegraphResponse,
43
43
  error: flamegraphError,
44
- } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED, {
44
+ } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
45
45
  skip: currentView !== 'icicle' && currentView !== 'both',
46
46
  });
47
47
 
package/src/TopTable.tsx CHANGED
@@ -14,7 +14,13 @@
14
14
  import React from 'react';
15
15
 
16
16
  import {getLastItem, valueFormatter, isSearchMatch} from '@parca/functions';
17
- import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
17
+ import {
18
+ useAppSelector,
19
+ selectCompareMode,
20
+ selectSearchNodeString,
21
+ setSearchNodeString,
22
+ useAppDispatch,
23
+ } from '@parca/store';
18
24
  import {TopNode, TopNodeMeta, Top} from '@parca/client';
19
25
 
20
26
  import {hexifyAddress} from './utils';
@@ -136,8 +142,8 @@ export const RowLabel = (meta: TopNodeMeta | undefined): string => {
136
142
  export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element => {
137
143
  const {items, requestSort, sortConfig} = useSortableData(top);
138
144
  const currentSearchString = useAppSelector(selectSearchNodeString);
139
-
140
145
  const compareMode = useAppSelector(selectCompareMode);
146
+ const dispatch = useAppDispatch();
141
147
 
142
148
  const unit = sampleUnit;
143
149
 
@@ -159,10 +165,17 @@ export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element =>
159
165
  return `+${num}`;
160
166
  };
161
167
 
168
+ const selectSpan = (span: string): void => {
169
+ dispatch(setSearchNodeString(span.trim()));
170
+ };
171
+
162
172
  return (
163
173
  <>
164
174
  <div className="w-full font-robotoMono">
165
- <table className="iciclegraph-table table-fixed text-left w-full divide-y divide-gray-200 dark:divide-gray-700">
175
+ <table
176
+ className="iciclegraph-table table-fixed text-left w-full divide-y divide-gray-200 dark:divide-gray-700"
177
+ tabIndex={1}
178
+ >
166
179
  <thead className="bg-gray-50 dark:bg-gray-800">
167
180
  <tr>
168
181
  <th
@@ -221,7 +234,7 @@ export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element =>
221
234
  return (
222
235
  <tr
223
236
  key={index}
224
- className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12]"
237
+ className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12] cursor-pointer"
225
238
  style={{
226
239
  opacity:
227
240
  currentSearchString !== undefined &&
@@ -230,6 +243,7 @@ export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element =>
230
243
  ? 0.5
231
244
  : 1,
232
245
  }}
246
+ onClick={() => selectSpan(name)}
233
247
  >
234
248
  <td className="text-xs py-1.5 pl-2">{name}</td>
235
249
  <td className="text-xs py-1.5 text-right">