@parca/profile 0.17.2 → 0.17.4

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 (120) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.d.ts +1 -2
  3. package/dist/GraphTooltipArrow/Content.d.ts.map +1 -1
  4. package/dist/GraphTooltipArrow/Content.js +1 -2
  5. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.d.ts +1 -2
  6. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.d.ts.map +1 -1
  7. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +1 -2
  8. package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts +1 -2
  9. package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts.map +1 -1
  10. package/dist/GraphTooltipArrow/useGraphTooltip/index.js +2 -2
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -6
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +4 -5
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +20 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -0
  16. package/{src/Callgraph/constants.ts → dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js} +12 -3
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +8 -51
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +59 -136
  20. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts +8 -0
  21. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -0
  22. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +40 -0
  23. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts +32 -0
  24. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts.map +1 -0
  25. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.js +40 -0
  26. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +4 -3
  27. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  28. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +62 -76
  29. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +3 -3
  30. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts.map +1 -1
  31. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +9 -7
  32. package/dist/ProfileIcicleGraph/index.d.ts +3 -6
  33. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  34. package/dist/ProfileIcicleGraph/index.js +8 -16
  35. package/dist/ProfileView/components/DashboardItems/index.d.ts +3 -5
  36. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  37. package/dist/ProfileView/components/DashboardItems/index.js +4 -9
  38. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  39. package/dist/ProfileView/components/Toolbars/index.js +1 -2
  40. package/dist/ProfileView/index.d.ts +1 -1
  41. package/dist/ProfileView/index.d.ts.map +1 -1
  42. package/dist/ProfileView/index.js +1 -13
  43. package/dist/ProfileView/types/visualization.d.ts +1 -1
  44. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  45. package/dist/index.d.ts +0 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +0 -2
  48. package/dist/styles.css +1 -1
  49. package/package.json +5 -5
  50. package/src/GraphTooltipArrow/Content.tsx +0 -3
  51. package/src/GraphTooltipArrow/DockedGraphTooltip/index.tsx +0 -3
  52. package/src/GraphTooltipArrow/useGraphTooltip/index.ts +1 -3
  53. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +5 -13
  54. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +53 -0
  55. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +96 -310
  56. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +78 -0
  57. package/src/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.tsx +93 -0
  58. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +110 -213
  59. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +8 -15
  60. package/src/ProfileIcicleGraph/index.tsx +7 -36
  61. package/src/ProfileView/components/DashboardItems/index.tsx +2 -27
  62. package/src/ProfileView/components/Toolbars/index.tsx +0 -2
  63. package/src/ProfileView/index.tsx +0 -14
  64. package/src/ProfileView/types/visualization.ts +1 -1
  65. package/src/index.tsx +0 -5
  66. package/dist/Callgraph/constants.d.ts +0 -3
  67. package/dist/Callgraph/constants.d.ts.map +0 -1
  68. package/dist/Callgraph/constants.js +0 -14
  69. package/dist/Callgraph/index.d.ts +0 -11
  70. package/dist/Callgraph/index.d.ts.map +0 -1
  71. package/dist/Callgraph/index.js +0 -104
  72. package/dist/Callgraph/mockData/index.d.ts +0 -149
  73. package/dist/Callgraph/mockData/index.d.ts.map +0 -1
  74. package/dist/Callgraph/mockData/index.js +0 -594
  75. package/dist/Callgraph/utils.d.ts +0 -20
  76. package/dist/Callgraph/utils.d.ts.map +0 -1
  77. package/dist/Callgraph/utils.js +0 -97
  78. package/dist/GraphTooltip/ExpandOnHoverValue.d.ts +0 -7
  79. package/dist/GraphTooltip/ExpandOnHoverValue.d.ts.map +0 -1
  80. package/dist/GraphTooltip/ExpandOnHoverValue.js +0 -4
  81. package/dist/GraphTooltip/index.d.ts +0 -41
  82. package/dist/GraphTooltip/index.d.ts.map +0 -1
  83. package/dist/GraphTooltip/index.js +0 -201
  84. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.d.ts +0 -6
  85. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.d.ts.map +0 -1
  86. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.js +0 -59
  87. package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.d.ts +0 -47
  88. package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.d.ts.map +0 -1
  89. package/dist/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.js +0 -93
  90. package/dist/ProfileIcicleGraph/IcicleGraph/index.d.ts +0 -14
  91. package/dist/ProfileIcicleGraph/IcicleGraph/index.d.ts.map +0 -1
  92. package/dist/ProfileIcicleGraph/IcicleGraph/index.js +0 -48
  93. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.d.ts +0 -15
  94. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.d.ts.map +0 -1
  95. package/dist/ProfileIcicleGraph/IcicleGraph/useColoredGraph.js +0 -57
  96. package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.d.ts +0 -8
  97. package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.d.ts.map +0 -1
  98. package/dist/ProfileIcicleGraph/IcicleGraph/useNodeColor.js +0 -32
  99. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts +0 -7
  100. package/dist/ProfileIcicleGraph/IcicleGraph/utils.d.ts.map +0 -1
  101. package/dist/ProfileIcicleGraph/IcicleGraph/utils.js +0 -66
  102. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleChartRootNode.d.ts +0 -9
  103. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleChartRootNode.d.ts.map +0 -1
  104. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleChartRootNode.js +0 -45
  105. package/dist/ProfileView/hooks/useGraphviz.d.ts +0 -12
  106. package/dist/ProfileView/hooks/useGraphviz.d.ts.map +0 -1
  107. package/dist/ProfileView/hooks/useGraphviz.js +0 -42
  108. package/src/Callgraph/index.tsx +0 -177
  109. package/src/Callgraph/mockData/index.ts +0 -605
  110. package/src/Callgraph/utils.ts +0 -141
  111. package/src/GraphTooltip/ExpandOnHoverValue.tsx +0 -30
  112. package/src/GraphTooltip/index.tsx +0 -509
  113. package/src/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.tsx +0 -96
  114. package/src/ProfileIcicleGraph/IcicleGraph/IcicleGraphNodes.tsx +0 -266
  115. package/src/ProfileIcicleGraph/IcicleGraph/index.tsx +0 -123
  116. package/src/ProfileIcicleGraph/IcicleGraph/useColoredGraph.ts +0 -117
  117. package/src/ProfileIcicleGraph/IcicleGraph/useNodeColor.ts +0 -54
  118. package/src/ProfileIcicleGraph/IcicleGraph/utils.ts +0 -102
  119. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleChartRootNode.tsx +0 -130
  120. package/src/ProfileView/hooks/useGraphviz.ts +0 -69
@@ -11,184 +11,60 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {ReactNode, useMemo} from 'react';
14
+ import React, {useMemo} from 'react';
15
15
 
16
16
  import {Table} from 'apache-arrow';
17
17
  import cx from 'classnames';
18
18
 
19
19
  import {selectBinaries, useAppSelector} from '@parca/store';
20
- import {isSearchMatch, scaleLinear} from '@parca/utilities';
20
+ import {isSearchMatch} from '@parca/utilities';
21
21
 
22
22
  import 'react-contexify/dist/ReactContexify.css';
23
23
 
24
- import {ProfileType} from '@parca/parser';
24
+ import {USER_PREFERENCES} from '@parca/hooks';
25
25
 
26
+ import {ProfileSource} from '../../ProfileSource';
26
27
  import TextWithEllipsis from './TextWithEllipsis';
27
28
  import {
28
- FIELD_CHILDREN,
29
29
  FIELD_CUMULATIVE,
30
+ FIELD_DEPTH,
30
31
  FIELD_DIFF,
31
32
  FIELD_FUNCTION_FILE_NAME,
32
33
  FIELD_FUNCTION_NAME,
33
34
  FIELD_MAPPING_FILE,
35
+ FIELD_PARENT,
36
+ FIELD_TIMESTAMP,
37
+ FIELD_VALUE_OFFSET,
34
38
  } from './index';
35
39
  import useNodeColor from './useNodeColor';
36
- import {
37
- CurrentPathFrame,
38
- arrowToString,
39
- getCurrentPathFrameData,
40
- isCurrentPathFrameMatch,
41
- nodeLabel,
42
- } from './utils';
40
+ import {arrowToString, boundsFromProfileSource, nodeLabel} from './utils';
43
41
 
44
42
  export const RowHeight = 26;
45
43
 
46
- interface IcicleGraphNodesProps {
47
- table: Table<any>;
48
- row: number;
49
- colors: colorByColors;
50
- colorBy: string;
51
- childRows: number[];
52
- x: number;
53
- y: number;
54
- total: bigint;
55
- totalWidth: number;
56
- level: number;
57
- curPath: CurrentPathFrame[];
58
- setCurPath: (path: CurrentPathFrame[]) => void;
59
- setHoveringRow: (row: number | null) => void;
60
- setHoveringLevel: (level: number | null) => void;
61
- path: CurrentPathFrame[];
62
- xScale: (value: bigint) => number;
63
- searchString?: string;
64
- sortBy: string;
65
- darkMode: boolean;
66
- compareMode: boolean;
67
- profileType?: ProfileType;
68
- isContextMenuOpen: boolean;
69
- hoveringName: string | null;
70
- setHoveringName: (name: string | null) => void;
71
- hoveringRow: number | null;
72
- colorForSimilarNodes: string;
73
- highlightSimilarStacksPreference: boolean;
74
- }
75
-
76
- export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({
77
- table,
78
- childRows,
79
- colors,
80
- colorBy,
81
- x,
82
- y,
83
- xScale,
84
- total,
85
- totalWidth,
86
- level,
87
- path,
88
- setCurPath,
89
- setHoveringRow,
90
- setHoveringLevel,
91
- curPath,
92
- sortBy,
93
- searchString,
94
- darkMode,
95
- compareMode,
96
- profileType,
97
- isContextMenuOpen,
98
- hoveringName,
99
- setHoveringName,
100
- hoveringRow,
101
- colorForSimilarNodes,
102
- highlightSimilarStacksPreference,
103
- }: IcicleGraphNodesProps): React.JSX.Element {
104
- const cumulatives = table.getChild(FIELD_CUMULATIVE);
105
-
106
- if (childRows === undefined || childRows.length === 0) {
107
- return <></>;
108
- }
109
-
110
- childRows =
111
- curPath.length === 0
112
- ? childRows
113
- : childRows.filter(c => isCurrentPathFrameMatch(table, c, level, curPath[0]));
114
-
115
- let childrenCumulative = BigInt(0);
116
- const childrenElements: ReactNode[] = [];
117
- childRows.forEach((child, i) => {
118
- const xStart = Math.floor(xScale(BigInt(childrenCumulative)));
119
- const c = BigInt(cumulatives?.get(child));
120
- childrenCumulative += c;
121
-
122
- childrenElements.push(
123
- <IcicleNode
124
- key={`node-${level}-${i}`}
125
- table={table}
126
- row={child}
127
- colors={colors}
128
- colorBy={colorBy}
129
- x={xStart}
130
- y={0}
131
- totalWidth={totalWidth}
132
- height={RowHeight}
133
- path={path}
134
- setCurPath={setCurPath}
135
- setHoveringRow={setHoveringRow}
136
- setHoveringLevel={setHoveringLevel}
137
- level={level}
138
- curPath={curPath}
139
- total={total}
140
- xScale={xScale}
141
- sortBy={sortBy}
142
- searchString={searchString}
143
- darkMode={darkMode}
144
- compareMode={compareMode}
145
- profileType={profileType}
146
- isContextMenuOpen={isContextMenuOpen}
147
- hoveringName={hoveringName}
148
- setHoveringName={setHoveringName}
149
- hoveringRow={hoveringRow}
150
- colorForSimilarNodes={colorForSimilarNodes}
151
- highlightSimilarStacksPreference={highlightSimilarStacksPreference}
152
- />
153
- );
154
- });
155
-
156
- return <g transform={`translate(${x}, ${y})`}>{childrenElements}</g>;
157
- });
158
-
159
44
  export interface colorByColors {
160
45
  [key: string]: string;
161
46
  }
162
47
 
163
48
  export interface IcicleNodeProps {
164
- x: number;
165
- y: number;
166
49
  height: number;
167
50
  totalWidth: number;
168
- curPath: CurrentPathFrame[];
169
- level: number;
170
51
  table: Table<any>;
171
52
  row: number;
172
53
  colors: colorByColors;
173
54
  colorBy: string;
174
- path: CurrentPathFrame[];
175
- total: bigint;
176
- setCurPath: (path: CurrentPathFrame[]) => void;
177
- setHoveringRow: (row: number | null) => void;
178
- setHoveringLevel: (level: number | null) => void;
179
- xScale: (value: bigint) => number;
180
- isRoot?: boolean;
181
55
  searchString?: string;
182
- sortBy: string;
183
56
  darkMode: boolean;
184
57
  compareMode: boolean;
185
- profileType?: ProfileType;
186
- isContextMenuOpen: boolean;
187
- hoveringName: string | null;
188
- setHoveringName: (name: string | null) => void;
189
- hoveringRow: number | null;
58
+ onContextMenu: (e: React.MouseEvent, row: number) => void;
190
59
  colorForSimilarNodes: string;
191
- highlightSimilarStacksPreference: boolean;
60
+ selectedRow: number;
61
+ onClick: () => void;
62
+ isIcicleChart: boolean;
63
+ profileSource: ProfileSource;
64
+
65
+ // Hovering row must only ever be used for highlighting similar nodes, otherwise it will cause performance issues as it causes the full iciclegraph to get rerendered every time the hovering row changes.
66
+ hoveringRow?: number;
67
+ setHoveringRow: (row: number | undefined) => void;
192
68
  }
193
69
 
194
70
  export const icicleRectStyles = {
@@ -206,136 +82,49 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
206
82
  row,
207
83
  colors,
208
84
  colorBy,
209
- x,
210
- y,
211
85
  height,
212
- setCurPath,
213
- curPath,
214
- level,
215
- path,
216
- total,
217
86
  totalWidth,
218
- xScale,
219
- isRoot = false,
220
87
  searchString,
221
- setHoveringRow,
222
- setHoveringLevel,
223
- sortBy,
224
88
  darkMode,
225
89
  compareMode,
226
- profileType,
227
- isContextMenuOpen,
228
- hoveringName,
229
- setHoveringName,
230
- hoveringRow,
231
90
  colorForSimilarNodes,
232
- highlightSimilarStacksPreference,
91
+ selectedRow,
92
+ onClick,
93
+ onContextMenu,
94
+ hoveringRow,
95
+ setHoveringRow,
96
+ isIcicleChart,
97
+ profileSource,
233
98
  }: IcicleNodeProps): React.JSX.Element {
234
99
  // get the columns to read from
235
100
  const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
236
101
  const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
237
102
  const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
103
+ const depthColumn = table.getChild(FIELD_DEPTH);
238
104
  const diffColumn = table.getChild(FIELD_DIFF);
239
105
  const filenameColumn = table.getChild(FIELD_FUNCTION_FILE_NAME);
106
+ const valueOffsetColumn = table.getChild(FIELD_VALUE_OFFSET);
107
+ const tsColumn = table.getChild(FIELD_TIMESTAMP);
108
+
240
109
  // get the actual values from the columns
241
110
  const mappingFile: string | null = arrowToString(mappingColumn?.get(row));
242
111
  const functionName: string | null = arrowToString(functionNameColumn?.get(row));
243
112
  const cumulative = cumulativeColumn?.get(row) !== null ? BigInt(cumulativeColumn?.get(row)) : 0n;
244
113
  const diff: bigint | null = diffColumn?.get(row) !== null ? BigInt(diffColumn?.get(row)) : null;
245
- const childRows: number[] = Array.from(table.getChild(FIELD_CHILDREN)?.get(row) ?? []);
246
114
  const filename: string | null = arrowToString(filenameColumn?.get(row));
115
+ const depth: number = depthColumn?.get(row) ?? 0;
116
+ const valueOffset: bigint =
117
+ valueOffsetColumn?.get(row) !== null ? BigInt(valueOffsetColumn?.get(row)) : 0n;
247
118
 
248
- const colorAttribute: string | null = useMemo(() => {
249
- let attr: string | null | undefined;
250
-
251
- if (colorBy === 'filename') {
252
- attr = filename;
253
- } else if (colorBy === 'binary') {
254
- attr = mappingFile;
255
- }
256
-
257
- return attr ?? null; // Provide a default value of null if attr is undefined
258
- }, [colorBy, filename, mappingFile]);
119
+ const colorAttribute =
120
+ colorBy === 'filename' ? filename : colorBy === 'binary' ? mappingFile : null;
259
121
 
260
122
  const colorsMap = colors;
261
123
 
262
- const highlightedNodes = useMemo(() => {
263
- if (!highlightSimilarStacksPreference) {
264
- return null;
265
- }
266
-
267
- if (functionName != null && functionName === hoveringName) {
268
- return {functionName, row: hoveringRow};
269
- }
270
- return null; // Nothing to highlight
271
- }, [functionName, hoveringName, hoveringRow, highlightSimilarStacksPreference]);
272
-
273
- const shouldBeHighlightedIfSimilarStacks = useMemo(() => {
274
- return highlightedNodes !== null && row !== highlightedNodes.row;
275
- }, [row, highlightedNodes]);
276
-
277
- // TODO: Maybe it's better to pass down the sorter function as prop instead of figuring this out here.
278
- switch (sortBy) {
279
- case FIELD_FUNCTION_NAME:
280
- childRows.sort((a, b) => {
281
- // TODO: Support fallthrough to comparing addresses or something
282
- const afn: string | null = arrowToString(functionNameColumn?.get(a));
283
- const bfn: string | null = arrowToString(functionNameColumn?.get(b));
284
- if (afn !== null && bfn !== null) {
285
- return afn.localeCompare(bfn);
286
- }
287
- if (afn === null && bfn !== null) {
288
- return -1;
289
- }
290
- if (afn !== null && bfn === null) {
291
- return 1;
292
- }
293
- // both are null
294
- return 0;
295
- });
296
- break;
297
- case FIELD_CUMULATIVE:
298
- childRows.sort((a, b) => {
299
- const aCumulative: bigint = cumulativeColumn?.get(a);
300
- const bCumulative: bigint = cumulativeColumn?.get(b);
301
- return Number(bCumulative - aCumulative);
302
- });
303
- break;
304
- case FIELD_DIFF:
305
- childRows.sort((a, b) => {
306
- let aRatio: number | null = null;
307
- const aDiff: bigint | null =
308
- diffColumn?.get(a) !== null ? BigInt(diffColumn?.get(a)) : null;
309
- if (aDiff !== null) {
310
- const cumulative: bigint =
311
- cumulativeColumn?.get(a) !== null ? BigInt(cumulativeColumn?.get(a)) : 0n;
312
- const prev: bigint = cumulative - aDiff;
313
- aRatio = Number(aDiff) / Number(prev);
314
- }
315
- let bRatio: number | null = null;
316
- const bDiff: bigint | null =
317
- diffColumn?.get(b) !== null ? BigInt(diffColumn?.get(b)) : null;
318
- if (bDiff !== null) {
319
- const cumulative: bigint =
320
- cumulativeColumn?.get(b) !== null ? BigInt(cumulativeColumn?.get(b)) : 0n;
321
- const prev: bigint = cumulative - bDiff;
322
- bRatio = Number(bDiff) / Number(prev);
323
- }
324
-
325
- if (aRatio !== null && bRatio !== null) {
326
- return bRatio - aRatio;
327
- }
328
- if (aRatio === null && bRatio !== null) {
329
- return -1;
330
- }
331
- if (aRatio !== null && bRatio === null) {
332
- return 1;
333
- }
334
- // both are null
335
- return 0;
336
- });
337
- break;
338
- }
124
+ const hoveringName =
125
+ hoveringRow !== undefined ? arrowToString(functionNameColumn?.get(hoveringRow)) : '';
126
+ const shouldBeHighlighted =
127
+ functionName != null && hoveringName != null && functionName === hoveringName;
339
128
 
340
129
  const binaries = useAppSelector(selectBinaries);
341
130
  const colorResult = useNodeColor({
@@ -347,24 +136,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
347
136
  colorAttribute,
348
137
  });
349
138
  const name = useMemo(() => {
350
- return isRoot ? 'root' : nodeLabel(table, row, level, binaries.length > 1);
351
- }, [table, row, level, isRoot, binaries]);
352
- const currentPathFrame: CurrentPathFrame = getCurrentPathFrameData(table, row, level);
353
- const nextPath = path.concat([currentPathFrame]);
354
- const isFaded =
355
- curPath.length > 0 && !isCurrentPathFrameMatch(table, row, level, curPath[curPath.length - 1]);
356
- const styles = isFaded ? fadedIcicleRectStyles : icicleRectStyles;
357
- const nextLevel = level + 1;
358
- const nextCurPath = curPath.length === 0 ? [] : curPath.slice(1);
359
- const newXScale =
360
- nextCurPath.length === 0 && curPath.length === 1
361
- ? scaleLinear([0n, BigInt(cumulative)], [0, totalWidth])
362
- : xScale;
363
-
364
- const width: number =
365
- nextCurPath.length > 0 || (nextCurPath.length === 0 && curPath.length === 1)
366
- ? totalWidth
367
- : xScale(BigInt(cumulative));
139
+ return row === 0 ? 'root' : nodeLabel(table, row, binaries.length > 1);
140
+ }, [table, row, binaries]);
368
141
 
369
142
  const {isHighlightEnabled = false, isHighlighted = false} = useMemo(() => {
370
143
  if (searchString === undefined || searchString === '') {
@@ -373,24 +146,69 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
373
146
  return {isHighlightEnabled: true, isHighlighted: isSearchMatch(searchString, name)};
374
147
  }, [searchString, name]);
375
148
 
149
+ const selectionOffset =
150
+ valueOffsetColumn?.get(selectedRow) !== null ? BigInt(valueOffsetColumn?.get(selectedRow)) : 0n;
151
+ const selectionCumulative =
152
+ cumulativeColumn?.get(selectedRow) !== null ? BigInt(cumulativeColumn?.get(selectedRow)) : 0n;
153
+ if (
154
+ valueOffset + cumulative <= selectionOffset ||
155
+ valueOffset >= selectionOffset + selectionCumulative
156
+ ) {
157
+ // If the end of the node is before the selection offset or the start of the node is after the selection offset + totalWidth, we don't render it.
158
+ return <></>;
159
+ }
160
+ if (row === 0 && isIcicleChart) {
161
+ // The root node is not rendered in the icicle chart, so we return null.
162
+ return <></>;
163
+ }
164
+
165
+ // Cumulative can be larger than total when a selection is made. All parents of the selection are likely larger, but we want to only show them as 100% in the graph.
166
+ const tsBounds = boundsFromProfileSource(profileSource);
167
+ const total = cumulativeColumn?.get(selectedRow);
168
+ const totalRatio = cumulative > total ? 1 : Number(cumulative) / Number(total);
169
+ const width: number = isIcicleChart
170
+ ? (Number(cumulative) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
171
+ : totalRatio * totalWidth;
172
+
376
173
  if (width <= 1) {
377
- return <>{null}</>;
174
+ return <></>;
378
175
  }
379
176
 
177
+ const selectedDepth = depthColumn?.get(selectedRow);
178
+ const styles =
179
+ selectedDepth !== undefined && selectedDepth > depth ? fadedIcicleRectStyles : icicleRectStyles;
180
+
380
181
  const onMouseEnter = (): void => {
381
- if (isContextMenuOpen) return;
382
182
  setHoveringRow(row);
383
- setHoveringLevel(level);
384
- setHoveringName(name);
183
+ window.dispatchEvent(
184
+ new CustomEvent('icicle-tooltip-update', {
185
+ detail: {row},
186
+ })
187
+ );
385
188
  };
386
189
 
387
190
  const onMouseLeave = (): void => {
388
- if (isContextMenuOpen) return;
389
- setHoveringRow(null);
390
- setHoveringLevel(null);
391
- setHoveringName(null);
191
+ setHoveringRow(undefined);
192
+ window.dispatchEvent(
193
+ new CustomEvent('icicle-tooltip-update', {
194
+ detail: {row: null},
195
+ })
196
+ );
392
197
  };
393
198
 
199
+ const handleContextMenu = (e: React.MouseEvent): void => {
200
+ onContextMenu(e, row);
201
+ };
202
+
203
+ const ts = tsColumn !== null ? Number(tsColumn.get(row)) : 0;
204
+ const x =
205
+ isIcicleChart && tsColumn !== null
206
+ ? ((ts - Number(tsBounds[0])) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
207
+ : selectedDepth > depth
208
+ ? 0
209
+ : ((Number(valueOffset) - Number(selectionOffset)) / Number(total)) * totalWidth;
210
+ const y = isIcicleChart ? (depth - 1) * height : depth * height;
211
+
394
212
  return (
395
213
  <>
396
214
  <g
@@ -398,9 +216,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
398
216
  style={styles}
399
217
  onMouseEnter={onMouseEnter}
400
218
  onMouseLeave={onMouseLeave}
401
- onClick={() => {
402
- setCurPath(nextPath);
403
- }}
219
+ onClick={onClick}
220
+ onContextMenu={handleContextMenu}
404
221
  >
405
222
  <rect
406
223
  x={0}
@@ -411,7 +228,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
411
228
  fill: colorResult,
412
229
  }}
413
230
  className={cx(
414
- shouldBeHighlightedIfSimilarStacks
231
+ shouldBeHighlighted
415
232
  ? `${colorForSimilarNodes} stroke-[3] [stroke-dasharray:6,4] [stroke-linecap:round] [stroke-linejoin:round] h-6`
416
233
  : 'stroke-white dark:stroke-gray-700',
417
234
  {
@@ -430,37 +247,6 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
430
247
  </svg>
431
248
  )}
432
249
  </g>
433
- {childRows.length > 0 && (
434
- <IcicleGraphNodes
435
- table={table}
436
- row={row}
437
- colors={colors}
438
- colorBy={colorBy}
439
- childRows={childRows}
440
- x={x}
441
- y={RowHeight}
442
- xScale={newXScale}
443
- total={total}
444
- totalWidth={totalWidth}
445
- level={nextLevel}
446
- path={nextPath}
447
- curPath={nextCurPath}
448
- setCurPath={setCurPath}
449
- setHoveringRow={setHoveringRow}
450
- setHoveringLevel={setHoveringLevel}
451
- searchString={searchString}
452
- sortBy={sortBy}
453
- darkMode={darkMode}
454
- profileType={profileType}
455
- compareMode={compareMode}
456
- isContextMenuOpen={isContextMenuOpen}
457
- hoveringName={hoveringName}
458
- setHoveringName={setHoveringName}
459
- hoveringRow={hoveringRow}
460
- colorForSimilarNodes={colorForSimilarNodes}
461
- highlightSimilarStacksPreference={highlightSimilarStacksPreference}
462
- />
463
- )}
464
250
  </>
465
251
  );
466
252
  });
@@ -0,0 +1,78 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import React, {memo, useEffect, useState} from 'react';
15
+
16
+ import GraphTooltipArrow from '../../GraphTooltipArrow';
17
+ import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
18
+ import {DockedGraphTooltip} from '../../GraphTooltipArrow/DockedGraphTooltip';
19
+ import {useTooltipContext} from './TooltipContext';
20
+
21
+ interface MemoizedTooltipProps {
22
+ contextElement: Element | null;
23
+ dockedMetainfo: boolean;
24
+ }
25
+
26
+ export const MemoizedTooltip = memo(function MemoizedTooltip({
27
+ contextElement,
28
+ dockedMetainfo,
29
+ }: MemoizedTooltipProps): React.JSX.Element | null {
30
+ const [tooltipRow, setTooltipRow] = useState<number | null>(null);
31
+ const {table, total, totalUnfiltered, profileType, unit, compareAbsolute} = useTooltipContext();
32
+
33
+ // This component subscribes to tooltip updates through a callback
34
+ // passed to the TooltipProvider, avoiding the need to lift state
35
+ useEffect(() => {
36
+ const handleTooltipUpdate = (event: CustomEvent<{row: number | null}>): void => {
37
+ setTooltipRow(event.detail.row);
38
+ };
39
+
40
+ window.addEventListener('icicle-tooltip-update' as any, handleTooltipUpdate as any);
41
+ return () => {
42
+ window.removeEventListener('icicle-tooltip-update' as any, handleTooltipUpdate as any);
43
+ };
44
+ }, []);
45
+
46
+ if (dockedMetainfo) {
47
+ return (
48
+ <DockedGraphTooltip
49
+ table={table}
50
+ row={tooltipRow}
51
+ total={total}
52
+ totalUnfiltered={totalUnfiltered}
53
+ profileType={profileType}
54
+ unit={unit}
55
+ compareAbsolute={compareAbsolute}
56
+ />
57
+ );
58
+ }
59
+
60
+ if (tooltipRow === null) {
61
+ return null;
62
+ }
63
+
64
+ return (
65
+ <GraphTooltipArrow contextElement={contextElement}>
66
+ <GraphTooltipArrowContent
67
+ table={table}
68
+ row={tooltipRow}
69
+ isFixed={false}
70
+ total={total}
71
+ totalUnfiltered={totalUnfiltered}
72
+ profileType={profileType}
73
+ unit={unit}
74
+ compareAbsolute={compareAbsolute}
75
+ />
76
+ </GraphTooltipArrow>
77
+ );
78
+ });
@@ -0,0 +1,93 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import React, {createContext, useCallback, useContext, useMemo, useRef} from 'react';
15
+
16
+ import {Table} from 'apache-arrow';
17
+
18
+ import {ProfileType} from '@parca/parser';
19
+
20
+ interface TooltipState {
21
+ row: number | null;
22
+ x: number;
23
+ y: number;
24
+ }
25
+
26
+ interface TooltipContextValue {
27
+ table: Table<any>;
28
+ total: bigint;
29
+ totalUnfiltered: bigint;
30
+ profileType?: ProfileType;
31
+ unit?: string;
32
+ compareAbsolute: boolean;
33
+ updateTooltip: (row: number | null, x?: number, y?: number) => void;
34
+ tooltipState: TooltipState;
35
+ }
36
+
37
+ const TooltipContext = createContext<TooltipContextValue | null>(null);
38
+
39
+ export const useTooltipContext = (): TooltipContextValue => {
40
+ const context = useContext(TooltipContext);
41
+ if (context === undefined || context === null) {
42
+ throw new Error('useTooltipContext must be used within TooltipProvider');
43
+ }
44
+ return context;
45
+ };
46
+
47
+ interface TooltipProviderProps {
48
+ children: React.ReactNode;
49
+ table: Table<any>;
50
+ total: bigint;
51
+ totalUnfiltered: bigint;
52
+ profileType?: ProfileType;
53
+ unit?: string;
54
+ compareAbsolute: boolean;
55
+ onTooltipUpdate?: (state: TooltipState) => void;
56
+ }
57
+
58
+ export const TooltipProvider: React.FC<TooltipProviderProps> = ({
59
+ children,
60
+ table,
61
+ total,
62
+ totalUnfiltered,
63
+ profileType,
64
+ unit,
65
+ compareAbsolute,
66
+ onTooltipUpdate,
67
+ }) => {
68
+ const tooltipStateRef = useRef<TooltipState>({row: null, x: 0, y: 0});
69
+
70
+ const updateTooltip = useCallback(
71
+ (row: number | null, x = 0, y = 0) => {
72
+ tooltipStateRef.current = {row, x, y};
73
+ onTooltipUpdate?.(tooltipStateRef.current);
74
+ },
75
+ [onTooltipUpdate]
76
+ );
77
+
78
+ const value = useMemo(
79
+ () => ({
80
+ table,
81
+ total,
82
+ totalUnfiltered,
83
+ profileType,
84
+ unit,
85
+ compareAbsolute,
86
+ updateTooltip,
87
+ tooltipState: tooltipStateRef.current,
88
+ }),
89
+ [table, total, totalUnfiltered, profileType, unit, compareAbsolute, updateTooltip]
90
+ );
91
+
92
+ return <TooltipContext.Provider value={value}>{children}</TooltipContext.Provider>;
93
+ };