@parca/profile 0.18.4 → 0.19.1

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 (117) 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/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +2 -1
  5. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +13 -3
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -0
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +4 -0
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +15 -6
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +5 -4
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts +2 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.js +3 -2
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +3 -0
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +4 -4
  20. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +1 -1
  21. package/dist/ProfileIcicleGraph/index.d.ts +4 -1
  22. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  23. package/dist/ProfileIcicleGraph/index.js +22 -6
  24. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
  25. package/dist/ProfileSelector/QueryControls.js +1 -1
  26. package/dist/ProfileView/components/DashboardItems/index.d.ts +3 -1
  27. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/DashboardItems/index.js +4 -1
  29. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  30. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +0 -13
  31. package/dist/ProfileView/components/Toolbars/index.d.ts +8 -0
  32. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/Toolbars/index.js +6 -2
  34. package/dist/ProfileView/components/ViewSelector/Dropdown.d.ts +1 -0
  35. package/dist/ProfileView/components/ViewSelector/Dropdown.d.ts.map +1 -1
  36. package/dist/ProfileView/components/ViewSelector/Dropdown.js +1 -1
  37. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  38. package/dist/ProfileView/components/ViewSelector/index.js +9 -0
  39. package/dist/ProfileView/hooks/useVisualizationState.d.ts +4 -0
  40. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  41. package/dist/ProfileView/hooks/useVisualizationState.js +9 -1
  42. package/dist/ProfileView/index.d.ts.map +1 -1
  43. package/dist/ProfileView/index.js +3 -2
  44. package/dist/ProfileView/types/visualization.d.ts +1 -1
  45. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  46. package/dist/ProfileViewWithData.js +1 -1
  47. package/dist/Sandwich/components/CalleesSection.d.ts +25 -0
  48. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -0
  49. package/dist/Sandwich/components/CalleesSection.js +11 -0
  50. package/dist/Sandwich/components/CallersSection.d.ts +25 -0
  51. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -0
  52. package/dist/Sandwich/components/CallersSection.js +11 -0
  53. package/dist/Sandwich/components/TableSection.d.ts +21 -0
  54. package/dist/Sandwich/components/TableSection.d.ts.map +1 -0
  55. package/dist/Sandwich/components/TableSection.js +7 -0
  56. package/dist/Sandwich/index.d.ts +19 -0
  57. package/dist/Sandwich/index.d.ts.map +1 -0
  58. package/dist/Sandwich/index.js +182 -0
  59. package/dist/Sandwich/utils/processRowData.d.ts +11 -0
  60. package/dist/Sandwich/utils/processRowData.d.ts.map +1 -0
  61. package/dist/Sandwich/utils/processRowData.js +53 -0
  62. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  63. package/dist/SimpleMatchers/index.js +18 -4
  64. package/dist/Table/ColorCell.d.ts +7 -0
  65. package/dist/Table/ColorCell.d.ts.map +1 -0
  66. package/dist/Table/ColorCell.js +2 -0
  67. package/dist/Table/MoreDropdown.d.ts +5 -0
  68. package/dist/Table/MoreDropdown.d.ts.map +1 -0
  69. package/dist/Table/MoreDropdown.js +39 -0
  70. package/dist/Table/hooks/useColorManagement.d.ts +14 -0
  71. package/dist/Table/hooks/useColorManagement.d.ts.map +1 -0
  72. package/dist/Table/hooks/useColorManagement.js +32 -0
  73. package/dist/Table/hooks/useTableConfiguration.d.ts +21 -0
  74. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -0
  75. package/dist/Table/hooks/useTableConfiguration.js +204 -0
  76. package/dist/Table/index.d.ts +14 -4
  77. package/dist/Table/index.d.ts.map +1 -1
  78. package/dist/Table/index.js +34 -332
  79. package/dist/Table/utils/functions.d.ts +1 -0
  80. package/dist/Table/utils/functions.d.ts.map +1 -1
  81. package/dist/styles.css +1 -1
  82. package/dist/useQuery.d.ts +1 -0
  83. package/dist/useQuery.d.ts.map +1 -1
  84. package/dist/useQuery.js +7 -1
  85. package/package.json +7 -7
  86. package/src/GraphTooltipArrow/useGraphTooltip/index.ts +6 -3
  87. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +23 -1
  88. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +1 -0
  89. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +34 -5
  90. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +6 -4
  91. package/src/ProfileIcicleGraph/IcicleGraphArrow/TooltipContext.tsx +5 -1
  92. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +13 -1
  93. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +1 -1
  94. package/src/ProfileIcicleGraph/index.tsx +50 -18
  95. package/src/ProfileSelector/QueryControls.tsx +1 -0
  96. package/src/ProfileView/components/DashboardItems/index.tsx +21 -0
  97. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +11 -25
  98. package/src/ProfileView/components/Toolbars/index.tsx +42 -1
  99. package/src/ProfileView/components/ViewSelector/Dropdown.tsx +2 -1
  100. package/src/ProfileView/components/ViewSelector/index.tsx +11 -0
  101. package/src/ProfileView/hooks/useVisualizationState.ts +16 -1
  102. package/src/ProfileView/index.tsx +7 -0
  103. package/src/ProfileView/types/visualization.ts +7 -1
  104. package/src/ProfileViewWithData.tsx +1 -1
  105. package/src/Sandwich/components/CalleesSection.tsx +87 -0
  106. package/src/Sandwich/components/CallersSection.tsx +88 -0
  107. package/src/Sandwich/components/TableSection.tsx +67 -0
  108. package/src/Sandwich/index.tsx +342 -0
  109. package/src/Sandwich/utils/processRowData.ts +78 -0
  110. package/src/SimpleMatchers/index.tsx +20 -4
  111. package/src/Table/ColorCell.tsx +26 -0
  112. package/src/Table/MoreDropdown.tsx +75 -0
  113. package/src/Table/hooks/useColorManagement.ts +58 -0
  114. package/src/Table/hooks/useTableConfiguration.tsx +237 -0
  115. package/src/Table/index.tsx +37 -470
  116. package/src/Table/utils/functions.ts +1 -0
  117. 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.4",
3
+ "version": "0.19.1",
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": "50183ea5ca7122a2450eacb3f49cbebf7e6046fc"
80
+ "gitHead": "958d63de0b6b7b583315a476e8836e25c8777bdb"
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
 
@@ -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
  );
@@ -159,6 +159,7 @@ export function QueryControls({
159
159
  />
160
160
  ) : (
161
161
  <SimpleMatchers
162
+ key={query.toString()}
162
163
  setMatchersString={setMatchersString}
163
164
  runQuery={setQueryExpression}
164
165
  currentQuery={query}
@@ -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
@@ -13,15 +13,15 @@
13
13
 
14
14
  import {useEffect, useMemo, useState} from 'react';
15
15
 
16
- import {createColumnHelper, type CellContext, type ColumnDef} from '@tanstack/table-core';
16
+ import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
17
 
18
18
  import {useURLState} from '@parca/components';
19
19
  import {ProfileType} from '@parca/parser';
20
20
  import {valueFormatter} from '@parca/utilities';
21
21
 
22
- import {Row, isDummyRow} from '../../../Table';
22
+ import {Row} from '../../../Table';
23
23
  import ColumnsVisibility from '../../../Table/ColumnsVisibility';
24
- import {ColumnName, DataRow, addPlusSign, getRatioString} from '../../../Table/utils/functions';
24
+ import {ColumnName, addPlusSign, getRatioString} from '../../../Table/utils/functions';
25
25
  import {useProfileViewContext} from '../../context/ProfileViewContext';
26
26
 
27
27
  interface Props {
@@ -45,7 +45,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
45
45
  columnHelper.accessor('flat', {
46
46
  id: 'flat',
47
47
  header: 'Flat',
48
- cell: info => valueFormatter((info as CellContext<DataRow, bigint>).getValue(), unit, 2),
48
+ cell: info => valueFormatter(info.getValue(), unit, 2),
49
49
  size: 80,
50
50
  meta: {
51
51
  align: 'right',
@@ -56,10 +56,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
56
56
  id: 'flatPercentage',
57
57
  header: 'Flat (%)',
58
58
  cell: info => {
59
- if (isDummyRow(info.row.original)) {
60
- return '';
61
- }
62
- return getRatioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
59
+ return getRatioString(info.getValue(), total, filtered);
63
60
  },
64
61
  size: 120,
65
62
  meta: {
@@ -70,8 +67,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
70
67
  columnHelper.accessor('flatDiff', {
71
68
  id: 'flatDiff',
72
69
  header: 'Flat Diff',
73
- cell: info =>
74
- addPlusSign(valueFormatter((info as CellContext<DataRow, bigint>).getValue(), unit, 2)),
70
+ cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
75
71
  size: 120,
76
72
  meta: {
77
73
  align: 'right',
@@ -82,10 +78,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
82
78
  id: 'flatDiffPercentage',
83
79
  header: 'Flat Diff (%)',
84
80
  cell: info => {
85
- if (isDummyRow(info.row.original)) {
86
- return '';
87
- }
88
- return getRatioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
81
+ return getRatioString(info.getValue(), total, filtered);
89
82
  },
90
83
  size: 120,
91
84
  meta: {
@@ -96,7 +89,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
96
89
  columnHelper.accessor('cumulative', {
97
90
  id: 'cumulative',
98
91
  header: 'Cumulative',
99
- cell: info => valueFormatter((info as CellContext<DataRow, bigint>).getValue(), unit, 2),
92
+ cell: info => valueFormatter(info.getValue(), unit, 2),
100
93
  size: 150,
101
94
  meta: {
102
95
  align: 'right',
@@ -107,10 +100,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
107
100
  id: 'cumulativePercentage',
108
101
  header: 'Cumulative (%)',
109
102
  cell: info => {
110
- if (isDummyRow(info.row.original)) {
111
- return '';
112
- }
113
- return getRatioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
103
+ return getRatioString(info.getValue(), total, filtered);
114
104
  },
115
105
  size: 150,
116
106
  meta: {
@@ -121,8 +111,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
121
111
  columnHelper.accessor('cumulativeDiff', {
122
112
  id: 'cumulativeDiff',
123
113
  header: 'Cumulative Diff',
124
- cell: info =>
125
- addPlusSign(valueFormatter((info as CellContext<DataRow, bigint>).getValue(), unit, 2)),
114
+ cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
126
115
  size: 170,
127
116
  meta: {
128
117
  align: 'right',
@@ -133,10 +122,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
133
122
  id: 'cumulativeDiffPercentage',
134
123
  header: 'Cumulative Diff (%)',
135
124
  cell: info => {
136
- if (isDummyRow(info.row.original)) {
137
- return '';
138
- }
139
- return getRatioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
125
+ return getRatioString(info.getValue(), total, filtered);
140
126
  },
141
127
  size: 170,
142
128
  meta: {