@parca/profile 0.18.3 → 0.19.0

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 (121) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts.map +1 -1
  3. package/dist/GraphTooltipArrow/useGraphTooltip/index.js +6 -3
  4. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
  5. package/dist/MetricsGraph/UtilizationMetrics/index.js +6 -2
  6. package/dist/MetricsGraph/index.d.ts.map +1 -1
  7. package/dist/MetricsGraph/index.js +8 -4
  8. package/dist/MetricsSeries/index.d.ts +2 -1
  9. package/dist/MetricsSeries/index.d.ts.map +1 -1
  10. package/dist/MetricsSeries/index.js +2 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -1
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +13 -3
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +4 -0
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +15 -6
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  20. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +5 -4
  21. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts +2 -0
  22. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts.map +1 -1
  23. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.js +3 -2
  24. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +3 -0
  25. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  26. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +4 -4
  27. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +1 -1
  28. package/dist/ProfileIcicleGraph/index.d.ts +4 -1
  29. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  30. package/dist/ProfileIcicleGraph/index.js +22 -6
  31. package/dist/ProfileView/components/DashboardItems/index.d.ts +3 -1
  32. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/DashboardItems/index.js +4 -1
  34. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  35. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +0 -13
  36. package/dist/ProfileView/components/Toolbars/index.d.ts +8 -0
  37. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  38. package/dist/ProfileView/components/Toolbars/index.js +6 -2
  39. package/dist/ProfileView/components/ViewSelector/Dropdown.d.ts +1 -0
  40. package/dist/ProfileView/components/ViewSelector/Dropdown.d.ts.map +1 -1
  41. package/dist/ProfileView/components/ViewSelector/Dropdown.js +1 -1
  42. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  43. package/dist/ProfileView/components/ViewSelector/index.js +9 -0
  44. package/dist/ProfileView/hooks/useVisualizationState.d.ts +4 -0
  45. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  46. package/dist/ProfileView/hooks/useVisualizationState.js +9 -1
  47. package/dist/ProfileView/index.d.ts.map +1 -1
  48. package/dist/ProfileView/index.js +3 -2
  49. package/dist/ProfileView/types/visualization.d.ts +1 -1
  50. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  51. package/dist/ProfileViewWithData.js +1 -1
  52. package/dist/Sandwich/components/CalleesSection.d.ts +25 -0
  53. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -0
  54. package/dist/Sandwich/components/CalleesSection.js +11 -0
  55. package/dist/Sandwich/components/CallersSection.d.ts +25 -0
  56. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -0
  57. package/dist/Sandwich/components/CallersSection.js +11 -0
  58. package/dist/Sandwich/components/TableSection.d.ts +21 -0
  59. package/dist/Sandwich/components/TableSection.d.ts.map +1 -0
  60. package/dist/Sandwich/components/TableSection.js +7 -0
  61. package/dist/Sandwich/index.d.ts +19 -0
  62. package/dist/Sandwich/index.d.ts.map +1 -0
  63. package/dist/Sandwich/index.js +182 -0
  64. package/dist/Sandwich/utils/processRowData.d.ts +11 -0
  65. package/dist/Sandwich/utils/processRowData.d.ts.map +1 -0
  66. package/dist/Sandwich/utils/processRowData.js +53 -0
  67. package/dist/Table/ColorCell.d.ts +7 -0
  68. package/dist/Table/ColorCell.d.ts.map +1 -0
  69. package/dist/Table/ColorCell.js +2 -0
  70. package/dist/Table/MoreDropdown.d.ts +5 -0
  71. package/dist/Table/MoreDropdown.d.ts.map +1 -0
  72. package/dist/Table/MoreDropdown.js +39 -0
  73. package/dist/Table/hooks/useColorManagement.d.ts +14 -0
  74. package/dist/Table/hooks/useColorManagement.d.ts.map +1 -0
  75. package/dist/Table/hooks/useColorManagement.js +32 -0
  76. package/dist/Table/hooks/useTableConfiguration.d.ts +21 -0
  77. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -0
  78. package/dist/Table/hooks/useTableConfiguration.js +204 -0
  79. package/dist/Table/index.d.ts +14 -4
  80. package/dist/Table/index.d.ts.map +1 -1
  81. package/dist/Table/index.js +34 -332
  82. package/dist/Table/utils/functions.d.ts +1 -0
  83. package/dist/Table/utils/functions.d.ts.map +1 -1
  84. package/dist/styles.css +1 -1
  85. package/dist/useQuery.d.ts +1 -0
  86. package/dist/useQuery.d.ts.map +1 -1
  87. package/dist/useQuery.js +7 -1
  88. package/package.json +7 -7
  89. package/src/GraphTooltipArrow/useGraphTooltip/index.ts +6 -3
  90. package/src/MetricsGraph/UtilizationMetrics/index.tsx +6 -2
  91. package/src/MetricsGraph/index.tsx +12 -2
  92. package/src/MetricsSeries/index.tsx +3 -0
  93. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +23 -1
  94. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +1 -0
  95. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +34 -5
  96. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +6 -4
  97. package/src/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.tsx +5 -1
  98. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +13 -1
  99. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +1 -1
  100. package/src/ProfileIcicleGraph/index.tsx +50 -18
  101. package/src/ProfileView/components/DashboardItems/index.tsx +21 -0
  102. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +11 -25
  103. package/src/ProfileView/components/Toolbars/index.tsx +42 -1
  104. package/src/ProfileView/components/ViewSelector/Dropdown.tsx +2 -1
  105. package/src/ProfileView/components/ViewSelector/index.tsx +11 -0
  106. package/src/ProfileView/hooks/useVisualizationState.ts +16 -1
  107. package/src/ProfileView/index.tsx +7 -0
  108. package/src/ProfileView/types/visualization.ts +7 -1
  109. package/src/ProfileViewWithData.tsx +1 -1
  110. package/src/Sandwich/components/CalleesSection.tsx +87 -0
  111. package/src/Sandwich/components/CallersSection.tsx +88 -0
  112. package/src/Sandwich/components/TableSection.tsx +67 -0
  113. package/src/Sandwich/index.tsx +342 -0
  114. package/src/Sandwich/utils/processRowData.ts +78 -0
  115. package/src/Table/ColorCell.tsx +26 -0
  116. package/src/Table/MoreDropdown.tsx +75 -0
  117. package/src/Table/hooks/useColorManagement.ts +58 -0
  118. package/src/Table/hooks/useTableConfiguration.tsx +237 -0
  119. package/src/Table/index.tsx +37 -470
  120. package/src/Table/utils/functions.ts +1 -0
  121. package/src/useQuery.tsx +10 -1
package/dist/useQuery.js CHANGED
@@ -28,6 +28,7 @@ export const useQuery = (client, profileSource, reportType, options) => {
28
28
  options?.invertCallStack ?? false,
29
29
  options?.binaryFrameFilter ?? '',
30
30
  profileSource.excludeFunction ?? false,
31
+ options?.sandwichByFunction ?? '',
31
32
  ],
32
33
  queryFn: async () => {
33
34
  const req = profileSource.QueryRequest();
@@ -44,8 +45,9 @@ export const useQuery = (client, profileSource, reportType, options) => {
44
45
  };
45
46
  }
46
47
  req.invertCallStack = options?.invertCallStack ?? false;
48
+ // Handle filter from ProfileSource (filter by function toolbar)
47
49
  const functionToFilter = req.filterQuery;
48
- if (functionToFilter !== undefined) {
50
+ if (functionToFilter !== undefined && functionToFilter !== '') {
49
51
  req.filter = [
50
52
  ...req.filter,
51
53
  {
@@ -64,6 +66,10 @@ export const useQuery = (client, profileSource, reportType, options) => {
64
66
  },
65
67
  ];
66
68
  }
69
+ // Handle sandwich view filter separately
70
+ if (options?.sandwichByFunction !== undefined) {
71
+ req.sandwichByFunction = options.sandwichByFunction;
72
+ }
67
73
  if (options?.binaryFrameFilter !== undefined && options?.binaryFrameFilter.length > 0) {
68
74
  req.filter = [
69
75
  ...req.filter,
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@headlessui/react": "^1.7.19",
7
7
  "@iconify/react": "^4.0.0",
8
- "@parca/client": "0.16.130",
9
- "@parca/components": "0.16.341",
8
+ "@parca/client": "0.17.0",
9
+ "@parca/components": "0.16.342",
10
10
  "@parca/dynamicsize": "0.16.65",
11
- "@parca/hooks": "0.0.90",
11
+ "@parca/hooks": "0.0.91",
12
12
  "@parca/icons": "0.16.72",
13
13
  "@parca/parser": "0.16.79",
14
- "@parca/store": "0.16.174",
15
- "@parca/utilities": "0.0.99",
14
+ "@parca/store": "0.16.175",
15
+ "@parca/utilities": "0.0.100",
16
16
  "@popperjs/core": "^2.11.8",
17
17
  "@protobuf-ts/runtime-rpc": "^2.5.0",
18
18
  "@storybook/preview-api": "^8.4.3",
@@ -77,5 +77,5 @@
77
77
  "access": "public",
78
78
  "registry": "https://registry.npmjs.org/"
79
79
  },
80
- "gitHead": "53e4671f7b81c6fac99ef14e80dad4d3cdbacd54"
80
+ "gitHead": "f8b6abd23b6f0a18f9ee64a0bdb08ae43451849a"
81
81
  }
@@ -61,15 +61,18 @@ export const useGraphTooltip = ({
61
61
  unit = unit ?? profileType.sampleUnit;
62
62
 
63
63
  const cumulative: bigint =
64
- table.getChild(FIELD_CUMULATIVE)?.get(row) !== null
64
+ table.getChild(FIELD_CUMULATIVE)?.get(row) !== null &&
65
+ table.getChild(FIELD_CUMULATIVE)?.get(row) !== undefined
65
66
  ? BigInt(table.getChild(FIELD_CUMULATIVE)?.get(row))
66
67
  : 0n;
67
68
  const flat: bigint =
68
- table.getChild(FIELD_FLAT)?.get(row) !== null
69
+ table.getChild(FIELD_FLAT)?.get(row) !== null &&
70
+ table.getChild(FIELD_FLAT)?.get(row) !== undefined
69
71
  ? BigInt(table.getChild(FIELD_FLAT)?.get(row))
70
72
  : 0n;
71
73
  const diff: bigint =
72
- table.getChild(FIELD_DIFF)?.get(row) !== null
74
+ table.getChild(FIELD_DIFF)?.get(row) !== null &&
75
+ table.getChild(FIELD_DIFF)?.get(row) !== undefined
73
76
  ? BigInt(table.getChild(FIELD_DIFF)?.get(row))
74
77
  : 0n;
75
78
 
@@ -129,7 +129,11 @@ const RawUtilizationMetrics = ({
129
129
  const lineStrokeHover = '2px';
130
130
  const lineStrokeSelected = '3px';
131
131
 
132
- const graphWidth = width - margin * 1.5 - margin / 2;
132
+ const graphWidth = useMemo(() => width - margin * 1.5 - margin / 2, [width, margin]);
133
+ const graphTransform = useMemo(() => {
134
+ // Adds 10px padding which aligns the graph on the grid
135
+ return `translate(10, 0) scale(${(graphWidth - 10) / graphWidth}, 1)`;
136
+ }, [graphWidth]);
133
137
 
134
138
  const paddedFrom = from;
135
139
  const paddedTo = to;
@@ -461,7 +465,7 @@ const RawUtilizationMetrics = ({
461
465
  </text>
462
466
  </g>
463
467
  </g>
464
- <g className="lines fill-transparent">
468
+ <g className="lines fill-transparent" transform={graphTransform}>
465
469
  {series.map((s, i) => {
466
470
  const isLimit =
467
471
  s.metric.findIndex(m => m.name === '__type__' && m.value === 'limit') > -1;
@@ -158,7 +158,11 @@ export const RawMetricsGraph = ({
158
158
  width = 0;
159
159
  }
160
160
 
161
- const graphWidth = width - margin * 1.5 - margin / 2;
161
+ const graphWidth = useMemo(() => width - margin * 1.5 - margin / 2, [width, margin]);
162
+ const graphTransform = useMemo(() => {
163
+ // Adds 6px padding which aligns the graph on the grid
164
+ return `translate(6, 0) scale(${(graphWidth - 6) / graphWidth}, 1)`;
165
+ }, [graphWidth]);
162
166
 
163
167
  const series: Series[] = data.reduce<Series[]>(function (agg: Series[], s: MetricsSeriesPb) {
164
168
  if (s.labelset !== undefined) {
@@ -595,7 +599,11 @@ export const RawMetricsGraph = ({
595
599
  </text>
596
600
  </g>
597
601
  </g>
598
- <g className="lines fill-transparent">
602
+ <g
603
+ className="lines fill-transparent"
604
+ transform={graphTransform}
605
+ width={graphWidth - 100}
606
+ >
599
607
  {series.map((s, i) => (
600
608
  <g key={i} className="line">
601
609
  <MetricsSeries
@@ -618,6 +626,7 @@ export const RawMetricsGraph = ({
618
626
  className="circle-group"
619
627
  ref={metricPointRef}
620
628
  style={{fill: color(highlighted.seriesIndex.toString())}}
629
+ transform={graphTransform}
621
630
  >
622
631
  <MetricsCircle cx={highlighted.x} cy={highlighted.y} />
623
632
  </g>
@@ -630,6 +639,7 @@ export const RawMetricsGraph = ({
630
639
  ? {fill: color(selected.seriesIndex.toString())}
631
640
  : {}
632
641
  }
642
+ transform={graphTransform}
633
643
  >
634
644
  <MetricsCircle cx={selected.x} cy={selected.y} radius={5} />
635
645
  </g>
@@ -19,6 +19,7 @@ interface MetricsSeriesProps {
19
19
  color: string;
20
20
  strokeWidth: string;
21
21
  strokeDasharray?: string;
22
+ strokeLinecap?: React.CSSProperties['strokeLinecap'];
22
23
  xScale: (input: number) => number;
23
24
  yScale: (input: number) => number;
24
25
  onClick?: () => void;
@@ -30,6 +31,7 @@ const MetricsSeries = ({
30
31
  color,
31
32
  strokeWidth,
32
33
  strokeDasharray = '',
34
+ strokeLinecap = 'round',
33
35
  onClick,
34
36
  }: MetricsSeriesProps): JSX.Element => (
35
37
  <g className="line-group">
@@ -40,6 +42,7 @@ const MetricsSeries = ({
40
42
  stroke: color,
41
43
  strokeWidth,
42
44
  strokeDasharray,
45
+ strokeLinecap,
43
46
  }}
44
47
  onClick={onClick}
45
48
  />
@@ -37,6 +37,7 @@ interface ContextMenuProps {
37
37
  resetPath: () => void;
38
38
  hideMenu: () => void;
39
39
  hideBinary: (binaryToRemove: string) => void;
40
+ isSandwich?: boolean;
40
41
  }
41
42
 
42
43
  const ContextMenu = ({
@@ -51,6 +52,7 @@ const ContextMenu = ({
51
52
  unit,
52
53
  hideBinary,
53
54
  resetPath,
55
+ isSandwich = false,
54
56
  }: ContextMenuProps): JSX.Element => {
55
57
  const {isDarkMode} = useParcaContext();
56
58
  const {enableSourcesView, checkDebuginfoStatusHandler} = useParcaContext();
@@ -83,6 +85,10 @@ const ContextMenu = ({
83
85
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
84
86
  alwaysReturnArray: true,
85
87
  });
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ const [sandwichFunctionName, setSandwichFunctionName] = useURLState<string | undefined>(
90
+ 'sandwich_function_name'
91
+ );
86
92
 
87
93
  if (contextMenuData === null) {
88
94
  return <></>;
@@ -159,7 +165,11 @@ const ContextMenu = ({
159
165
  id="show-in-table"
160
166
  onClick={() => {
161
167
  setSearchString(functionName);
162
- setDashboardItems([...dashboardItems, 'table']);
168
+ if (isSandwich) {
169
+ setDashboardItems(['table']);
170
+ } else {
171
+ setDashboardItems([...dashboardItems, 'table']);
172
+ }
163
173
  }}
164
174
  >
165
175
  <div className="flex w-full items-center gap-2">
@@ -167,6 +177,18 @@ const ContextMenu = ({
167
177
  <div>Show in table</div>
168
178
  </div>
169
179
  </Item>
180
+ <Item
181
+ id="show-in-sandwich"
182
+ onClick={() => {
183
+ setSandwichFunctionName(functionName);
184
+ setDashboardItems(['sandwich']);
185
+ }}
186
+ >
187
+ <div className="flex w-full items-center gap-2">
188
+ <Icon icon="tdesign:sandwich-filled" />
189
+ <div>Show in sandwich</div>
190
+ </div>
191
+ </Item>
170
192
  <Item id="reset-view" onClick={handleResetView}>
171
193
  <div className="flex w-full items-center gap-2">
172
194
  <Icon icon="system-uicons:reset" />
@@ -30,6 +30,7 @@ interface ContextMenuWrapperProps {
30
30
  hideMenu: () => void;
31
31
  hideBinary: (binaryToRemove: string) => void;
32
32
  unit?: string;
33
+ isSandwich?: boolean;
33
34
  }
34
35
 
35
36
  export interface ContextMenuWrapperRef {
@@ -61,6 +61,10 @@ export interface IcicleNodeProps {
61
61
  onClick: () => void;
62
62
  isIcicleChart: boolean;
63
63
  profileSource: ProfileSource;
64
+ isFlamegraph?: boolean;
65
+ isSandwich?: boolean;
66
+ maxDepth?: number;
67
+ tooltipId?: string;
64
68
 
65
69
  // 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
70
  hoveringRow?: number;
@@ -95,6 +99,10 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
95
99
  setHoveringRow,
96
100
  isIcicleChart,
97
101
  profileSource,
102
+ isFlamegraph = false,
103
+ isSandwich = false,
104
+ maxDepth = 0,
105
+ tooltipId = 'default',
98
106
  }: IcicleNodeProps): React.JSX.Element {
99
107
  // get the columns to read from
100
108
  const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
@@ -162,8 +170,9 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
162
170
  // 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.
163
171
  return <></>;
164
172
  }
165
- if (row === 0 && isIcicleChart) {
166
- // The root node is not rendered in the icicle chart, so we return null.
173
+
174
+ if (row === 0 && (isIcicleChart || isSandwich)) {
175
+ // The root node is not rendered in the icicle chart or sandwich view, so we return null.
167
176
  return <></>;
168
177
  }
169
178
 
@@ -186,7 +195,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
186
195
  const onMouseEnter = (): void => {
187
196
  setHoveringRow(row);
188
197
  window.dispatchEvent(
189
- new CustomEvent('icicle-tooltip-update', {
198
+ new CustomEvent(`icicle-tooltip-update-${tooltipId}`, {
190
199
  detail: {row},
191
200
  })
192
201
  );
@@ -195,7 +204,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
195
204
  const onMouseLeave = (): void => {
196
205
  setHoveringRow(undefined);
197
206
  window.dispatchEvent(
198
- new CustomEvent('icicle-tooltip-update', {
207
+ new CustomEvent(`icicle-tooltip-update-${tooltipId}`, {
199
208
  detail: {row: null},
200
209
  })
201
210
  );
@@ -212,7 +221,27 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
212
221
  : selectedDepth > depth
213
222
  ? 0
214
223
  : ((Number(valueOffset) - Number(selectionOffset)) / Number(total)) * totalWidth;
215
- const y = isIcicleChart ? (depth - 1) * height : depth * height;
224
+
225
+ const calculateY = (
226
+ isFlamegraph: boolean,
227
+ isSandwich: boolean,
228
+ isIcicleChart: boolean,
229
+ maxDepth: number,
230
+ depth: number,
231
+ height: number
232
+ ): number => {
233
+ if (isFlamegraph) {
234
+ return (maxDepth - depth) * height; // Flamegraph is inverted
235
+ }
236
+
237
+ if (isIcicleChart || isSandwich) {
238
+ return (depth - 1) * height;
239
+ }
240
+
241
+ return depth * height;
242
+ };
243
+
244
+ const y = calculateY(isFlamegraph, isSandwich, isIcicleChart, maxDepth, depth, height);
216
245
 
217
246
  return (
218
247
  <>
@@ -28,7 +28,8 @@ export const MemoizedTooltip = memo(function MemoizedTooltip({
28
28
  dockedMetainfo,
29
29
  }: MemoizedTooltipProps): React.JSX.Element | null {
30
30
  const [tooltipRow, setTooltipRow] = useState<number | null>(null);
31
- const {table, total, totalUnfiltered, profileType, unit, compareAbsolute} = useTooltipContext();
31
+ const {table, total, totalUnfiltered, profileType, unit, compareAbsolute, tooltipId} =
32
+ useTooltipContext();
32
33
 
33
34
  // This component subscribes to tooltip updates through a callback
34
35
  // passed to the TooltipProvider, avoiding the need to lift state
@@ -37,11 +38,12 @@ export const MemoizedTooltip = memo(function MemoizedTooltip({
37
38
  setTooltipRow(event.detail.row);
38
39
  };
39
40
 
40
- window.addEventListener('icicle-tooltip-update' as any, handleTooltipUpdate as any);
41
+ const eventName = `icicle-tooltip-update-${tooltipId}`;
42
+ window.addEventListener(eventName as any, handleTooltipUpdate as any);
41
43
  return () => {
42
- window.removeEventListener('icicle-tooltip-update' as any, handleTooltipUpdate as any);
44
+ window.removeEventListener(eventName as any, handleTooltipUpdate as any);
43
45
  };
44
- }, []);
46
+ }, [tooltipId]);
45
47
 
46
48
  if (dockedMetainfo) {
47
49
  return (
@@ -32,6 +32,7 @@ interface TooltipContextValue {
32
32
  compareAbsolute: boolean;
33
33
  updateTooltip: (row: number | null, x?: number, y?: number) => void;
34
34
  tooltipState: TooltipState;
35
+ tooltipId: string;
35
36
  }
36
37
 
37
38
  const TooltipContext = createContext<TooltipContextValue | null>(null);
@@ -53,6 +54,7 @@ interface TooltipProviderProps {
53
54
  unit?: string;
54
55
  compareAbsolute: boolean;
55
56
  onTooltipUpdate?: (state: TooltipState) => void;
57
+ tooltipId?: string;
56
58
  }
57
59
 
58
60
  export const TooltipProvider: React.FC<TooltipProviderProps> = ({
@@ -64,6 +66,7 @@ export const TooltipProvider: React.FC<TooltipProviderProps> = ({
64
66
  unit,
65
67
  compareAbsolute,
66
68
  onTooltipUpdate,
69
+ tooltipId = 'default',
67
70
  }) => {
68
71
  const tooltipStateRef = useRef<TooltipState>({row: null, x: 0, y: 0});
69
72
 
@@ -85,8 +88,9 @@ export const TooltipProvider: React.FC<TooltipProviderProps> = ({
85
88
  compareAbsolute,
86
89
  updateTooltip,
87
90
  tooltipState: tooltipStateRef.current,
91
+ tooltipId,
88
92
  }),
89
- [table, total, totalUnfiltered, profileType, unit, compareAbsolute, updateTooltip]
93
+ [table, total, totalUnfiltered, profileType, unit, compareAbsolute, updateTooltip, tooltipId]
90
94
  );
91
95
 
92
96
  return <TooltipContext.Provider value={value}>{children}</TooltipContext.Provider>;
@@ -74,6 +74,9 @@ interface IcicleGraphArrowProps {
74
74
  mappingsListFromMetadata: string[];
75
75
  compareAbsolute: boolean;
76
76
  isIcicleChart?: boolean;
77
+ isFlamegraph?: boolean;
78
+ isSandwich?: boolean;
79
+ tooltipId?: string;
77
80
  }
78
81
 
79
82
  export const getMappingColors = (
@@ -129,6 +132,9 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
129
132
  mappingsListFromMetadata,
130
133
  compareAbsolute,
131
134
  isIcicleChart = false,
135
+ isFlamegraph = false,
136
+ isSandwich = false,
137
+ tooltipId = 'default',
132
138
  }: IcicleGraphArrowProps): React.JSX.Element {
133
139
  const [highlightSimilarStacksPreference] = useUserPreference<boolean>(
134
140
  USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key
@@ -249,7 +255,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
249
255
 
250
256
  const depthColumn = table.getChild(FIELD_DEPTH);
251
257
  const maxDepth = getMaxDepth(depthColumn);
252
- const height = (maxDepth + 1) * RowHeight;
258
+ const height = isSandwich ? maxDepth * RowHeight : (maxDepth + 1) * RowHeight;
253
259
 
254
260
  // To find the selected row, we must walk the current path and look at which
255
261
  // children of the current frame matches the path element exactly. Until the
@@ -284,6 +290,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
284
290
  profileType={profileType}
285
291
  unit={arrow.unit}
286
292
  compareAbsolute={compareAbsolute}
293
+ tooltipId={tooltipId}
287
294
  >
288
295
  <div className="relative">
289
296
  <ContextMenuWrapper
@@ -298,6 +305,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
298
305
  hideBinary={hideBinary}
299
306
  unit={arrow.unit}
300
307
  profileType={profileType}
308
+ isSandwich={isSandwich}
301
309
  />
302
310
  <MemoizedTooltip contextElement={svg.current} dockedMetainfo={dockedMetainfo} />
303
311
  <svg
@@ -333,6 +341,10 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
333
341
  setHoveringRow={highlightSimilarStacksPreference ? setHoveringRow : noop}
334
342
  isIcicleChart={isIcicleChart}
335
343
  profileSource={profileSource}
344
+ isFlamegraph={isFlamegraph}
345
+ isSandwich={isSandwich}
346
+ maxDepth={maxDepth}
347
+ tooltipId={tooltipId}
336
348
  />
337
349
  ))}
338
350
  </svg>
@@ -167,7 +167,7 @@ export const getCurrentPathFrameData = (table: Table<any>, row: number): Current
167
167
  systemName: systemName ?? '',
168
168
  fileName: fileName ?? '',
169
169
  lineNumber: Number(lineNumber),
170
- address: address,
170
+ address,
171
171
  inlined: inlined ?? false,
172
172
  labels: labels ?? undefined,
173
173
  };
@@ -11,8 +11,9 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {LegacyRef, ReactNode, useEffect, useMemo, useState} from 'react';
14
+ import React, {LegacyRef, ReactNode, useCallback, useEffect, useMemo, useState} from 'react';
15
15
 
16
+ import cx from 'classnames';
16
17
  import {AnimatePresence, motion} from 'framer-motion';
17
18
  import {useMeasure} from 'react-use';
18
19
 
@@ -25,7 +26,7 @@ import {MergedProfileSource, ProfileSource} from '../ProfileSource';
25
26
  import DiffLegend from '../ProfileView/components/DiffLegend';
26
27
  import {useProfileViewContext} from '../ProfileView/context/ProfileViewContext';
27
28
  import {TimelineGuide} from '../TimelineGuide';
28
- import {FIELD_FUNCTION_NAME, IcicleGraphArrow} from './IcicleGraphArrow';
29
+ import {IcicleGraphArrow} from './IcicleGraphArrow';
29
30
  import useMappingList from './IcicleGraphArrow/useMappingList';
30
31
  import {CurrentPathFrame, boundsFromProfileSource} from './IcicleGraphArrow/utils';
31
32
 
@@ -49,6 +50,9 @@ interface ProfileIcicleGraphProps {
49
50
  metadataMappingFiles?: string[];
50
51
  metadataLoading?: boolean;
51
52
  isIcicleChart?: boolean;
53
+ isSandwichIcicleGraph?: boolean;
54
+ isFlamegraph?: boolean;
55
+ tooltipId?: string;
52
56
  }
53
57
 
54
58
  const ErrorContent = ({errorMessage}: {errorMessage: string | ReactNode}): JSX.Element => {
@@ -81,12 +85,32 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
81
85
  metadataMappingFiles,
82
86
  isIcicleChart = false,
83
87
  profileSource,
88
+ isSandwichIcicleGraph = false,
89
+ isFlamegraph = false,
90
+ tooltipId,
84
91
  }: ProfileIcicleGraphProps): JSX.Element {
85
92
  const {onError, authenticationErrorMessage, isDarkMode, iciclechartHelpText} = useParcaContext();
86
93
  const {compareMode} = useProfileViewContext();
87
94
  const [isLoading, setIsLoading] = useState<boolean>(true);
88
95
  const [icicleChartRef, {height: icicleChartHeight}] = useMeasure();
89
96
 
97
+ // Create local state for paths when in sandwich view to avoid URL updates
98
+ const [localCurPathArrow, setLocalCurPathArrow] = useState<CurrentPathFrame[]>([]);
99
+
100
+ const setCurPathArrowWrapper = useCallback(
101
+ (path: CurrentPathFrame[]) => {
102
+ if (isSandwichIcicleGraph) {
103
+ setLocalCurPathArrow(path);
104
+ } else {
105
+ setNewCurPathArrow(path);
106
+ }
107
+ },
108
+ [isSandwichIcicleGraph, setNewCurPathArrow]
109
+ );
110
+
111
+ // Determine which paths to use based on isSandwichIcicleGraph flag
112
+ const effectiveCurPathArrow = isSandwichIcicleGraph ? localCurPathArrow : curPathArrow;
113
+
90
114
  const mappingsList = useMappingList(metadataMappingFiles);
91
115
 
92
116
  const [colorBy, setColorBy] = useURLState('color_by');
@@ -233,14 +257,17 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
233
257
  arrow={arrow}
234
258
  total={total}
235
259
  filtered={filtered}
236
- curPath={curPathArrow}
237
- setCurPath={setNewCurPathArrow}
260
+ curPath={effectiveCurPathArrow}
261
+ setCurPath={setCurPathArrowWrapper}
238
262
  profileType={profileType}
239
263
  isHalfScreen={isHalfScreen}
240
264
  mappingsListFromMetadata={mappingsList}
241
265
  compareAbsolute={isCompareAbsolute}
242
266
  isIcicleChart={isIcicleChart}
243
267
  profileSource={profileSource}
268
+ isFlamegraph={isFlamegraph}
269
+ isSandwich={isSandwichIcicleGraph}
270
+ tooltipId={tooltipId}
244
271
  />
245
272
  </div>
246
273
  </div>
@@ -253,8 +280,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
253
280
  loading,
254
281
  width,
255
282
  filtered,
256
- curPathArrow,
257
- setNewCurPathArrow,
258
283
  profileType,
259
284
  isHalfScreen,
260
285
  isDarkMode,
@@ -265,6 +290,11 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
265
290
  icicleChartHeight,
266
291
  icicleChartRef,
267
292
  iciclechartHelpText,
293
+ isFlamegraph,
294
+ isSandwichIcicleGraph,
295
+ effectiveCurPathArrow,
296
+ setCurPathArrowWrapper,
297
+ tooltipId,
268
298
  ]);
269
299
 
270
300
  useEffect(() => {
@@ -332,20 +362,22 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
332
362
  transition={{duration: 0.5}}
333
363
  >
334
364
  {compareMode ? <DiffLegend /> : null}
335
- <div className="min-h-48" id="h-icicle-graph">
365
+ <div className={cx(!isSandwichIcicleGraph ? 'min-h-48' : '')} id="h-icicle-graph">
336
366
  <>{icicleGraph}</>
337
367
  </div>
338
- <p className="my-2 text-xs">
339
- Showing {totalFormatted}{' '}
340
- {isFiltered ? (
341
- <span>
342
- ({filteredPercentage}%) filtered of {totalUnfilteredFormatted}{' '}
343
- </span>
344
- ) : (
345
- <></>
346
- )}
347
- values.{' '}
348
- </p>
368
+ {!isSandwichIcicleGraph && (
369
+ <p className="my-2 text-xs">
370
+ Showing {totalFormatted}{' '}
371
+ {isFiltered ? (
372
+ <span>
373
+ ({filteredPercentage}%) filtered of {totalUnfilteredFormatted}{' '}
374
+ </span>
375
+ ) : (
376
+ <></>
377
+ )}
378
+ values.{' '}
379
+ </p>
380
+ )}
349
381
  </motion.div>
350
382
  </AnimatePresence>
351
383
  );
@@ -13,11 +13,13 @@
13
13
 
14
14
  import {Profiler, ProfilerOnRenderCallback} from 'react';
15
15
 
16
+ import {QueryServiceClient} from '@parca/client';
16
17
  import {ConditionalWrapper} from '@parca/components';
17
18
 
18
19
  import ProfileIcicleGraph from '../../../ProfileIcicleGraph';
19
20
  import {CurrentPathFrame} from '../../../ProfileIcicleGraph/IcicleGraphArrow/utils';
20
21
  import {ProfileSource} from '../../../ProfileSource';
22
+ import Sandwich from '../../../Sandwich';
21
23
  import {SourceView} from '../../../SourceView';
22
24
  import {Table} from '../../../Table';
23
25
  import type {
@@ -47,6 +49,7 @@ interface GetDashboardItemProps {
47
49
  perf?: {
48
50
  onRender?: ProfilerOnRenderCallback;
49
51
  };
52
+ queryClient?: QueryServiceClient;
50
53
  }
51
54
 
52
55
  export const getDashboardItem = ({
@@ -65,6 +68,7 @@ export const getDashboardItem = ({
65
68
  currentSearchString,
66
69
  setSearchString,
67
70
  perf,
71
+ queryClient,
68
72
  }: GetDashboardItemProps): JSX.Element => {
69
73
  switch (type) {
70
74
  case 'icicle':
@@ -142,6 +146,23 @@ export const getDashboardItem = ({
142
146
  ) : (
143
147
  <></>
144
148
  );
149
+ case 'sandwich':
150
+ return topTableData != null ? (
151
+ <Sandwich
152
+ total={total}
153
+ filtered={filtered}
154
+ loading={topTableData.loading}
155
+ data={topTableData.arrow?.record}
156
+ unit={topTableData.unit}
157
+ profileType={profileSource?.ProfileType()}
158
+ isHalfScreen={isHalfScreen}
159
+ metadataMappingFiles={flamegraphData.metadataMappingFiles}
160
+ profileSource={profileSource}
161
+ queryClient={queryClient}
162
+ />
163
+ ) : (
164
+ <></>
165
+ );
145
166
  case 'source':
146
167
  return sourceData != null ? (
147
168
  <SourceView