@parca/profile 0.19.12 → 0.19.13

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 (79) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/GraphTooltipArrow/Content.js +1 -1
  3. package/dist/GraphTooltipArrow/index.js +2 -2
  4. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  5. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
  17. package/dist/ProfileIcicleGraph/index.d.ts +3 -1
  18. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/index.js +6 -4
  20. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  21. package/dist/ProfileView/components/DashboardItems/index.js +1 -1
  22. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  23. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  24. package/dist/ProfileView/components/Toolbars/index.d.ts +0 -2
  25. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  26. package/dist/ProfileView/components/Toolbars/index.js +4 -5
  27. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/ViewSelector/index.js +18 -11
  29. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  30. package/dist/ProfileView/context/DashboardContext.js +5 -0
  31. package/dist/ProfileView/index.d.ts.map +1 -1
  32. package/dist/ProfileView/index.js +4 -3
  33. package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
  34. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
  35. package/dist/Sandwich/components/CalleesSection.js +2 -6
  36. package/dist/Sandwich/components/CallersSection.d.ts +4 -2
  37. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
  38. package/dist/Sandwich/components/CallersSection.js +45 -9
  39. package/dist/Sandwich/components/TableSection.js +1 -1
  40. package/dist/Sandwich/index.d.ts +0 -1
  41. package/dist/Sandwich/index.d.ts.map +1 -1
  42. package/dist/Sandwich/index.js +27 -79
  43. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  44. package/dist/Table/MoreDropdown.js +1 -2
  45. package/dist/Table/TableContextMenu.d.ts +9 -0
  46. package/dist/Table/TableContextMenu.d.ts.map +1 -0
  47. package/dist/Table/TableContextMenu.js +38 -0
  48. package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
  49. package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
  50. package/dist/Table/TableContextMenuWrapper.js +30 -0
  51. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  52. package/dist/Table/hooks/useTableConfiguration.js +2 -20
  53. package/dist/Table/index.d.ts.map +1 -1
  54. package/dist/Table/index.js +65 -5
  55. package/dist/styles.css +1 -1
  56. package/package.json +3 -3
  57. package/src/GraphTooltipArrow/Content.tsx +3 -3
  58. package/src/GraphTooltipArrow/index.tsx +2 -2
  59. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
  60. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
  61. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
  62. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
  63. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
  64. package/src/ProfileIcicleGraph/index.tsx +20 -2
  65. package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
  66. package/src/ProfileView/components/ShareButton/index.tsx +9 -3
  67. package/src/ProfileView/components/Toolbars/index.tsx +7 -23
  68. package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
  69. package/src/ProfileView/context/DashboardContext.tsx +6 -0
  70. package/src/ProfileView/index.tsx +12 -4
  71. package/src/Sandwich/components/CalleesSection.tsx +1 -7
  72. package/src/Sandwich/components/CallersSection.tsx +92 -35
  73. package/src/Sandwich/components/TableSection.tsx +2 -2
  74. package/src/Sandwich/index.tsx +20 -107
  75. package/src/Table/MoreDropdown.tsx +1 -2
  76. package/src/Table/TableContextMenu.tsx +70 -0
  77. package/src/Table/TableContextMenuWrapper.tsx +48 -0
  78. package/src/Table/hooks/useTableConfiguration.tsx +2 -25
  79. package/src/Table/index.tsx +84 -5
@@ -18,7 +18,12 @@ import {AnimatePresence, motion} from 'framer-motion';
18
18
  import {useMeasure} from 'react-use';
19
19
 
20
20
  import {FlamegraphArrow} from '@parca/client';
21
- import {IcicleGraphSkeleton, useParcaContext, useURLState} from '@parca/components';
21
+ import {
22
+ FlamegraphSkeleton,
23
+ IcicleGraphSkeleton,
24
+ useParcaContext,
25
+ useURLState,
26
+ } from '@parca/components';
22
27
  import {ProfileType} from '@parca/parser';
23
28
  import {capitalizeOnlyFirstLetter, divide} from '@parca/utilities';
24
29
 
@@ -53,6 +58,8 @@ interface ProfileIcicleGraphProps {
53
58
  isSandwichIcicleGraph?: boolean;
54
59
  isFlamegraph?: boolean;
55
60
  tooltipId?: string;
61
+ maxFrameCount?: number;
62
+ isExpanded?: boolean;
56
63
  }
57
64
 
58
65
  const ErrorContent = ({errorMessage}: {errorMessage: string | ReactNode}): JSX.Element => {
@@ -88,6 +95,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
88
95
  isSandwichIcicleGraph = false,
89
96
  isFlamegraph = false,
90
97
  tooltipId,
98
+ maxFrameCount,
99
+ isExpanded = false,
91
100
  }: ProfileIcicleGraphProps): JSX.Element {
92
101
  const {onError, authenticationErrorMessage, isDarkMode, iciclechartHelpText} = useParcaContext();
93
102
  const {compareMode} = useProfileViewContext();
@@ -184,10 +193,15 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
184
193
  ? validateIcicleChartQuery(profileSource as MergedProfileSource)
185
194
  : {isValid: true, isNonDelta: false, isDurationTooLong: false};
186
195
  const isInvalidIcicleChartQuery = isIcicleChart && !isIcicleChartValid;
196
+
187
197
  if (isLoading && !isInvalidIcicleChartQuery) {
188
198
  return (
189
199
  <div className="h-auto overflow-clip">
190
- <IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
200
+ {isFlamegraph ? (
201
+ <FlamegraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
202
+ ) : (
203
+ <IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
204
+ )}
191
205
  </div>
192
206
  );
193
207
  }
@@ -268,6 +282,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
268
282
  isFlamegraph={isFlamegraph}
269
283
  isSandwich={isSandwichIcicleGraph}
270
284
  tooltipId={tooltipId}
285
+ maxFrameCount={maxFrameCount}
286
+ isExpanded={isExpanded}
271
287
  />
272
288
  </div>
273
289
  </div>
@@ -295,6 +311,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
295
311
  effectiveCurPathArrow,
296
312
  setCurPathArrowWrapper,
297
313
  tooltipId,
314
+ maxFrameCount,
315
+ isExpanded,
298
316
  ]);
299
317
 
300
318
  useEffect(() => {
@@ -155,7 +155,6 @@ export const getDashboardItem = ({
155
155
  data={topTableData.arrow?.record}
156
156
  unit={topTableData.unit}
157
157
  profileType={profileSource?.ProfileType()}
158
- isHalfScreen={isHalfScreen}
159
158
  metadataMappingFiles={flamegraphData.metadataMappingFiles}
160
159
  profileSource={profileSource}
161
160
  queryClient={queryClient}
@@ -182,11 +182,17 @@ const ShareButton = ({
182
182
  element={
183
183
  <Button
184
184
  variant="neutral"
185
- className="flex items-center gap-2"
185
+ className="flex items-center gap-2 pr-[1.7rem]"
186
186
  id="h-share-dropdown-button"
187
187
  >
188
- <Icon icon="material-symbols:share" className="h-4 w-4" />
189
- Share
188
+ <div className="flex items-center gap-2">
189
+ <Icon icon="material-symbols:share" className="w-4 h-4" />
190
+
191
+ <span>Share</span>
192
+ </div>
193
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400">
194
+ <Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
195
+ </div>
190
196
  </Button>
191
197
  }
192
198
  >
@@ -16,7 +16,7 @@ import {FC} from 'react';
16
16
  import {Icon} from '@iconify/react';
17
17
 
18
18
  import {QueryServiceClient} from '@parca/client';
19
- import {Button, UserPreferencesModal} from '@parca/components';
19
+ import {Button} from '@parca/components';
20
20
  import {ProfileType} from '@parca/parser';
21
21
 
22
22
  import {CurrentPathFrame} from '../../../ProfileIcicleGraph/IcicleGraphArrow/utils';
@@ -52,8 +52,6 @@ export interface VisualisationToolbarProps {
52
52
  setGroupByLabels: (labels: string[]) => void;
53
53
  showVisualizationSelector?: boolean;
54
54
  sandwichFunctionName?: string;
55
- setSandwichFunctionName: (sandwichFunctionName: string | undefined) => void;
56
- resetSandwichFunctionName: () => void;
57
55
  }
58
56
 
59
57
  export interface TableToolbarProps {
@@ -150,7 +148,6 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
150
148
  groupByLabels,
151
149
  setGroupByLabels,
152
150
  profileType,
153
- preferencesModal,
154
151
  profileSource,
155
152
  queryClient,
156
153
  onDownloadPProf,
@@ -163,17 +160,13 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
163
160
  currentSearchString,
164
161
  clearSelection,
165
162
  showVisualizationSelector = true,
166
- resetSandwichFunctionName,
167
- sandwichFunctionName,
168
163
  }) => {
169
164
  const {dashboardItems} = useDashboard();
170
165
 
171
166
  const isTableViz = dashboardItems?.includes('table');
172
167
  const isTableVizOnly = dashboardItems?.length === 1 && isTableViz;
173
168
  const isGraphViz = dashboardItems?.includes('icicle');
174
- const isSandwichIcicleGraphViz = dashboardItems?.includes('sandwich');
175
-
176
- const isTableView = isTableVizOnly || isSandwichIcicleGraphViz;
169
+ const isGraphVizOnly = dashboardItems?.length === 1 && isGraphViz;
177
170
 
178
171
  const req = profileSource?.QueryRequest();
179
172
  if (req !== null && req !== undefined) {
@@ -186,7 +179,7 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
186
179
  <>
187
180
  <div className="flex w-full justify-between items-end">
188
181
  <div className="flex gap-3 items-end">
189
- {!isTableView && (
182
+ {isGraphViz && (
190
183
  <>
191
184
  <GroupByDropdown
192
185
  groupBy={groupBy}
@@ -203,13 +196,12 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
203
196
  {profileViewExternalSubActions != null ? profileViewExternalSubActions : null}
204
197
  </div>
205
198
  <div className="flex gap-3">
206
- {preferencesModal === true && <UserPreferencesModal />}
207
199
  <MultiLevelDropdown
208
200
  groupBy={groupBy}
209
201
  toggleGroupBy={toggleGroupBy}
210
202
  profileType={profileType}
211
203
  onSelect={() => {}}
212
- isTableVizOnly={isTableView}
204
+ isTableVizOnly={isTableVizOnly}
213
205
  />
214
206
 
215
207
  <ShareButton
@@ -224,13 +216,14 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
224
216
  {showVisualizationSelector ? <ViewSelector profileSource={profileSource} /> : null}
225
217
  </div>
226
218
  </div>
227
- {isGraphViz && !isTableViz && (
219
+
220
+ {isGraphVizOnly && (
228
221
  <>
229
222
  <Divider />
230
223
  <IcicleGraphToolbar curPath={curPath} setNewCurPath={setNewCurPath} />
231
224
  </>
232
225
  )}
233
- {isTableViz && !isGraphViz && (
226
+ {isTableVizOnly && (
234
227
  <>
235
228
  <Divider />
236
229
  <TableToolbar
@@ -242,15 +235,6 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
242
235
  />
243
236
  </>
244
237
  )}
245
- {isSandwichIcicleGraphViz && (
246
- <>
247
- <Divider />
248
- <SandwichIcicleGraphToolbar
249
- resetSandwichFunctionName={resetSandwichFunctionName}
250
- sandwichFunctionName={sandwichFunctionName}
251
- />
252
- </>
253
- )}
254
238
  </>
255
239
  );
256
240
  };
@@ -29,6 +29,7 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
29
29
  alwaysReturnArray: true,
30
30
  }
31
31
  );
32
+ const [, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
32
33
  const {enableSourcesView, enableSandwichView} = useParcaContext();
33
34
 
34
35
  const allItems: Array<{
@@ -38,8 +39,8 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
38
39
  supportingText?: string;
39
40
  disabledText?: string;
40
41
  }> = [
41
- {key: 'table', label: 'Table', canBeSelected: !dashboardItems.includes('table')},
42
42
  {key: 'icicle', label: 'icicle', canBeSelected: !dashboardItems.includes('icicle')},
43
+ {key: 'table', label: 'Table', canBeSelected: !dashboardItems.includes('table')},
43
44
  {
44
45
  key: 'iciclechart',
45
46
  label: (
@@ -105,14 +106,8 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
105
106
  }): InnerAction | undefined => {
106
107
  if (dashboardItems.length === 1 && item.key === dashboardItems[0]) return undefined;
107
108
 
108
- // For sandwich view, return a no-op action
109
- if (item.key === 'sandwich') {
110
- return {
111
- text: 'Add Panel',
112
- onClick: () => {},
113
- isDisabled: true, // Custom property to control button state
114
- };
115
- }
109
+ // If we already have 2 panels and this item isn't selected, don't show any action
110
+ if (dashboardItems.length >= 2 && !dashboardItems.includes(item.key)) return undefined;
116
111
 
117
112
  return {
118
113
  text:
@@ -120,12 +115,20 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
120
115
  ? 'Add Panel'
121
116
  : item.canBeSelected
122
117
  ? 'Add Panel'
123
- : 'Close Panel',
118
+ : dashboardItems.includes(item.key)
119
+ ? 'Close Panel'
120
+ : 'Add Panel',
124
121
  onClick: () => {
125
122
  if (item.canBeSelected) {
126
123
  setDashboardItems([...dashboardItems, item.key]);
127
124
  } else {
128
- setDashboardItems(dashboardItems.filter(v => v !== item.key));
125
+ const newDashboardItems = dashboardItems.filter(v => v !== item.key);
126
+ setDashboardItems(newDashboardItems);
127
+
128
+ // Reset sandwich function name when removing sandwich panel
129
+ if (item.key === 'sandwich') {
130
+ setSandwichFunctionName(undefined);
131
+ }
129
132
  }
130
133
  },
131
134
  isDisabled: dashboardItems.length === 1 && dashboardItems.includes('sandwich'),
@@ -142,6 +145,12 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
142
145
 
143
146
  const onSelection = (value: string): void => {
144
147
  const isOnlyChart = dashboardItems.length === 1;
148
+
149
+ if (isOnlyChart && value === 'sandwich') {
150
+ setDashboardItems([...dashboardItems, value]);
151
+ return;
152
+ }
153
+
145
154
  if (isOnlyChart) {
146
155
  setDashboardItems([value]);
147
156
  return;
@@ -30,10 +30,16 @@ export const DashboardProvider: FC<PropsWithChildren> = ({children}) => {
30
30
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
31
31
  alwaysReturnArray: true,
32
32
  });
33
+ const [, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
33
34
 
34
35
  const handleClosePanel = (visualizationType: VisualizationType): void => {
35
36
  const newDashboardItems = dashboardItems.filter(item => item !== visualizationType);
36
37
  setDashboardItems(newDashboardItems);
38
+
39
+ // Reset sandwich function name when closing sandwich panel
40
+ if (visualizationType === 'sandwich') {
41
+ setSandwichFunctionName(undefined);
42
+ }
37
43
  };
38
44
 
39
45
  const isMultiPanelView = dashboardItems.length > 1;
@@ -19,7 +19,12 @@ import ColorStackLegend from './components/ColorStackLegend';
19
19
  import {getDashboardItem} from './components/DashboardItems';
20
20
  import {DashboardLayout} from './components/DashboardLayout';
21
21
  import {ProfileHeader} from './components/ProfileHeader';
22
- import {IcicleGraphToolbar, TableToolbar, VisualisationToolbar} from './components/Toolbars';
22
+ import {
23
+ IcicleGraphToolbar,
24
+ SandwichIcicleGraphToolbar,
25
+ TableToolbar,
26
+ VisualisationToolbar,
27
+ } from './components/Toolbars';
23
28
  import {DashboardProvider} from './context/DashboardContext';
24
29
  import {ProfileViewContextProvider} from './context/ProfileViewContext';
25
30
  import {useProfileMetadata} from './hooks/useProfileMetadata';
@@ -63,7 +68,6 @@ export const ProfileView = ({
63
68
  clearSelection,
64
69
  setGroupByLabels,
65
70
  sandwichFunctionName,
66
- setSandwichFunctionName,
67
71
  resetSandwichFunctionName,
68
72
  } = useVisualizationState();
69
73
 
@@ -119,6 +123,12 @@ export const ProfileView = ({
119
123
  currentSearchString={currentSearchString}
120
124
  />
121
125
  ),
126
+ sandwich: (
127
+ <SandwichIcicleGraphToolbar
128
+ resetSandwichFunctionName={resetSandwichFunctionName}
129
+ sandwichFunctionName={sandwichFunctionName}
130
+ />
131
+ ),
122
132
  };
123
133
 
124
134
  const hasProfileSource = profileSource !== undefined && profileSource.toString(timezone) !== '';
@@ -154,8 +164,6 @@ export const ProfileView = ({
154
164
  setGroupByLabels={setGroupByLabels}
155
165
  showVisualizationSelector={showVisualizationSelector}
156
166
  sandwichFunctionName={sandwichFunctionName}
157
- setSandwichFunctionName={setSandwichFunctionName}
158
- resetSandwichFunctionName={resetSandwichFunctionName}
159
167
  />
160
168
 
161
169
  {isColorStackLegendEnabled && (
@@ -21,7 +21,6 @@ import {type ProfileSource} from '../../ProfileSource';
21
21
 
22
22
  interface CalleesSectionProps {
23
23
  calleesRef: React.RefObject<HTMLDivElement>;
24
- isHalfScreen: boolean;
25
24
  calleesFlamegraphResponse?: {
26
25
  report: {
27
26
  oneofKind: string;
@@ -40,7 +39,6 @@ interface CalleesSectionProps {
40
39
 
41
40
  export function CalleesSection({
42
41
  calleesRef,
43
- isHalfScreen,
44
42
  calleesFlamegraphResponse,
45
43
  calleesFlamegraphLoading,
46
44
  calleesFlamegraphError,
@@ -68,11 +66,7 @@ export function CalleesSection({
68
66
  error={calleesFlamegraphError}
69
67
  isHalfScreen={true}
70
68
  width={
71
- calleesRef.current != null
72
- ? isHalfScreen
73
- ? (calleesRef.current.getBoundingClientRect().width - 54) / 2
74
- : calleesRef.current.getBoundingClientRect().width - 16
75
- : 0
69
+ calleesRef.current != null ? calleesRef.current.getBoundingClientRect().width - 25 : 0
76
70
  }
77
71
  metadataMappingFiles={metadataMappingFiles}
78
72
  metadataLoading={false}
@@ -11,17 +11,33 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React from 'react';
14
+ import React, {useMemo} from 'react';
15
+
16
+ import {Vector, tableFromIPC} from 'apache-arrow';
17
+ import {Tooltip} from 'react-tooltip';
15
18
 
16
19
  import {type FlamegraphArrow} from '@parca/client';
20
+ import {Button} from '@parca/components';
17
21
 
18
22
  import ProfileIcicleGraph from '../../ProfileIcicleGraph';
19
23
  import {type CurrentPathFrame} from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
20
24
  import {type ProfileSource} from '../../ProfileSource';
21
25
 
26
+ const FIELD_DEPTH = 'depth';
27
+
28
+ function getMaxDepth(depthColumn: Vector<any> | null): number {
29
+ if (depthColumn === null) return 0;
30
+
31
+ let max = 0;
32
+ for (const val of depthColumn) {
33
+ const numVal = Number(val);
34
+ if (numVal > max) max = numVal;
35
+ }
36
+ return max;
37
+ }
38
+
22
39
  interface CallersSectionProps {
23
40
  callersRef: React.RefObject<HTMLDivElement>;
24
- isHalfScreen: boolean;
25
41
  callersFlamegraphResponse?: {
26
42
  report: {
27
43
  oneofKind: string;
@@ -36,11 +52,13 @@ interface CallersSectionProps {
36
52
  curPathArrow: CurrentPathFrame[];
37
53
  setCurPathArrow: (path: CurrentPathFrame[]) => void;
38
54
  metadataMappingFiles?: string[];
55
+ isExpanded: boolean;
56
+ setIsExpanded: (isExpanded: boolean) => void;
57
+ defaultMaxFrames: number;
39
58
  }
40
59
 
41
60
  export function CallersSection({
42
61
  callersRef,
43
- isHalfScreen,
44
62
  callersFlamegraphResponse,
45
63
  callersFlamegraphLoading,
46
64
  callersFlamegraphError,
@@ -49,40 +67,79 @@ export function CallersSection({
49
67
  curPathArrow,
50
68
  setCurPathArrow,
51
69
  metadataMappingFiles,
70
+ isExpanded,
71
+ setIsExpanded,
72
+ defaultMaxFrames,
52
73
  }: CallersSectionProps): JSX.Element {
74
+ const maxDepth = useMemo(() => {
75
+ if (
76
+ callersFlamegraphResponse?.report.oneofKind === 'flamegraphArrow' &&
77
+ callersFlamegraphResponse?.report?.flamegraphArrow != null
78
+ ) {
79
+ const table = tableFromIPC(callersFlamegraphResponse.report.flamegraphArrow.record);
80
+ const depthColumn = table.getChild(FIELD_DEPTH);
81
+ return getMaxDepth(depthColumn);
82
+ }
83
+ return 0;
84
+ }, [callersFlamegraphResponse]);
85
+
86
+ const shouldShowButton = maxDepth > defaultMaxFrames;
87
+
53
88
  return (
54
- <div className="flex relative flex-row" ref={callersRef}>
55
- <div className="[writing-mode:vertical-lr] -rotate-180 px-1 uppercase text-[10px] text-left">
56
- Callers {'->'}
89
+ <>
90
+ {shouldShowButton && (
91
+ <Button
92
+ variant="neutral"
93
+ onClick={() => setIsExpanded(!isExpanded)}
94
+ className="absolute right-8 top-[-46px] z-10"
95
+ type="button"
96
+ >
97
+ <span
98
+ data-tooltip-content={
99
+ !isExpanded
100
+ ? `This profile has ${maxDepth} frames, showing only the top ${defaultMaxFrames} frames. Click to show more frames.`
101
+ : `This profile has ${maxDepth} frames, showing all frames. Click to hide frames.`
102
+ }
103
+ data-tooltip-id="show-more-frames"
104
+ >
105
+ {isExpanded ? 'Hide frames' : 'Show more frames'}
106
+ </span>
107
+ <Tooltip id="show-more-frames" />
108
+ </Button>
109
+ )}
110
+ <div className="flex relative flex-row overflow-hidden" ref={callersRef}>
111
+ <div className="[writing-mode:vertical-lr] -rotate-180 px-1 uppercase text-[10px] text-left flex-shrink-0">
112
+ Callers {'->'}
113
+ </div>
114
+ <div className="flex-1 overflow-hidden relative">
115
+ <ProfileIcicleGraph
116
+ arrow={
117
+ callersFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
118
+ ? callersFlamegraphResponse?.report?.flamegraphArrow
119
+ : undefined
120
+ }
121
+ total={BigInt(callersFlamegraphResponse?.total ?? '0')}
122
+ filtered={filtered}
123
+ profileType={profileSource?.ProfileType()}
124
+ loading={callersFlamegraphLoading}
125
+ error={callersFlamegraphError}
126
+ isHalfScreen={true}
127
+ width={
128
+ callersRef.current != null ? callersRef.current.getBoundingClientRect().width - 25 : 0
129
+ }
130
+ metadataMappingFiles={metadataMappingFiles}
131
+ metadataLoading={false}
132
+ isSandwichIcicleGraph={true}
133
+ curPathArrow={curPathArrow}
134
+ setNewCurPathArrow={setCurPathArrow}
135
+ isFlamegraph={true}
136
+ profileSource={profileSource}
137
+ tooltipId="callers"
138
+ maxFrameCount={defaultMaxFrames}
139
+ isExpanded={isExpanded}
140
+ />
141
+ </div>
57
142
  </div>
58
- <ProfileIcicleGraph
59
- arrow={
60
- callersFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
61
- ? callersFlamegraphResponse?.report?.flamegraphArrow
62
- : undefined
63
- }
64
- total={BigInt(callersFlamegraphResponse?.total ?? '0')}
65
- filtered={filtered}
66
- profileType={profileSource?.ProfileType()}
67
- loading={callersFlamegraphLoading}
68
- error={callersFlamegraphError}
69
- isHalfScreen={true}
70
- width={
71
- callersRef.current != null
72
- ? isHalfScreen
73
- ? (callersRef.current.getBoundingClientRect().width - 54) / 2
74
- : callersRef.current.getBoundingClientRect().width - 16
75
- : 0
76
- }
77
- metadataMappingFiles={metadataMappingFiles}
78
- metadataLoading={false}
79
- isSandwichIcicleGraph={true}
80
- curPathArrow={curPathArrow}
81
- setNewCurPathArrow={setCurPathArrow}
82
- isFlamegraph={true}
83
- profileSource={profileSource}
84
- tooltipId="callers"
85
- />
86
- </div>
143
+ </>
87
144
  );
88
145
  }
@@ -46,8 +46,8 @@ export function TableSection({
46
46
  return (
47
47
  <div
48
48
  style={{height: height !== undefined ? `${height}px` : '80vh'}}
49
- className={`font-robotoMono w-full cursor-pointer ${
50
- selectedRow != null && sandwichFunctionName !== undefined ? 'w-[50%]' : ''
49
+ className={`font-robotoMono cursor-pointer ${
50
+ selectedRow != null && sandwichFunctionName !== undefined ? 'w-[50%]' : 'w-full'
51
51
  }`}
52
52
  >
53
53
  <TableComponent