@parca/profile 0.19.12 → 0.19.14

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 (105) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.js +1 -1
  3. package/dist/GraphTooltipArrow/index.js +2 -2
  4. package/dist/MatchersInput/index.d.ts +3 -1
  5. package/dist/MatchersInput/index.d.ts.map +1 -1
  6. package/dist/MatchersInput/index.js +8 -4
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
  20. package/dist/ProfileIcicleGraph/index.d.ts +3 -1
  21. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  22. package/dist/ProfileIcicleGraph/index.js +6 -4
  23. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
  24. package/dist/ProfileSelector/QueryControls.js +1 -1
  25. package/dist/ProfileSelector/index.d.ts.map +1 -1
  26. package/dist/ProfileSelector/index.js +4 -2
  27. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/DashboardItems/index.js +1 -1
  29. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  30. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  31. package/dist/ProfileView/components/Toolbars/index.d.ts +0 -2
  32. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/Toolbars/index.js +4 -5
  34. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  35. package/dist/ProfileView/components/ViewSelector/index.js +18 -11
  36. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  37. package/dist/ProfileView/context/DashboardContext.js +5 -0
  38. package/dist/ProfileView/index.d.ts.map +1 -1
  39. package/dist/ProfileView/index.js +4 -3
  40. package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
  41. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
  42. package/dist/Sandwich/components/CalleesSection.js +2 -6
  43. package/dist/Sandwich/components/CallersSection.d.ts +4 -2
  44. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
  45. package/dist/Sandwich/components/CallersSection.js +45 -9
  46. package/dist/Sandwich/components/TableSection.js +1 -1
  47. package/dist/Sandwich/index.d.ts +0 -1
  48. package/dist/Sandwich/index.d.ts.map +1 -1
  49. package/dist/Sandwich/index.js +27 -79
  50. package/dist/SimpleMatchers/index.d.ts +2 -0
  51. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  52. package/dist/SimpleMatchers/index.js +16 -6
  53. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  54. package/dist/Table/MoreDropdown.js +1 -2
  55. package/dist/Table/TableContextMenu.d.ts +9 -0
  56. package/dist/Table/TableContextMenu.d.ts.map +1 -0
  57. package/dist/Table/TableContextMenu.js +38 -0
  58. package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
  59. package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
  60. package/dist/Table/TableContextMenuWrapper.js +30 -0
  61. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  62. package/dist/Table/hooks/useTableConfiguration.js +2 -20
  63. package/dist/Table/index.d.ts.map +1 -1
  64. package/dist/Table/index.js +65 -5
  65. package/dist/ViewMatchers/index.d.ts +2 -0
  66. package/dist/ViewMatchers/index.d.ts.map +1 -1
  67. package/dist/ViewMatchers/index.js +14 -4
  68. package/dist/contexts/MatchersInputLabelsContext.d.ts +3 -1
  69. package/dist/contexts/MatchersInputLabelsContext.d.ts.map +1 -1
  70. package/dist/contexts/MatchersInputLabelsContext.js +3 -3
  71. package/dist/contexts/SimpleMatchersLabelContext.d.ts +3 -1
  72. package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +1 -1
  73. package/dist/contexts/SimpleMatchersLabelContext.js +2 -2
  74. package/dist/styles.css +1 -1
  75. package/package.json +3 -3
  76. package/src/GraphTooltipArrow/Content.tsx +3 -3
  77. package/src/GraphTooltipArrow/index.tsx +2 -2
  78. package/src/MatchersInput/index.tsx +17 -4
  79. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
  80. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
  81. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
  82. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
  83. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
  84. package/src/ProfileIcicleGraph/index.tsx +20 -2
  85. package/src/ProfileSelector/QueryControls.tsx +6 -0
  86. package/src/ProfileSelector/index.tsx +12 -2
  87. package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
  88. package/src/ProfileView/components/ShareButton/index.tsx +9 -3
  89. package/src/ProfileView/components/Toolbars/index.tsx +7 -23
  90. package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
  91. package/src/ProfileView/context/DashboardContext.tsx +6 -0
  92. package/src/ProfileView/index.tsx +12 -4
  93. package/src/Sandwich/components/CalleesSection.tsx +1 -7
  94. package/src/Sandwich/components/CallersSection.tsx +92 -35
  95. package/src/Sandwich/components/TableSection.tsx +2 -2
  96. package/src/Sandwich/index.tsx +20 -107
  97. package/src/SimpleMatchers/index.tsx +20 -4
  98. package/src/Table/MoreDropdown.tsx +1 -2
  99. package/src/Table/TableContextMenu.tsx +70 -0
  100. package/src/Table/TableContextMenuWrapper.tsx +48 -0
  101. package/src/Table/hooks/useTableConfiguration.tsx +2 -25
  102. package/src/Table/index.tsx +84 -5
  103. package/src/ViewMatchers/index.tsx +17 -3
  104. package/src/contexts/MatchersInputLabelsContext.tsx +10 -2
  105. package/src/contexts/SimpleMatchersLabelContext.tsx +5 -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
@@ -11,32 +11,28 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
14
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {type Row as TableRow} from '@tanstack/table-core';
17
17
  import {tableFromIPC} from 'apache-arrow';
18
18
  import {AnimatePresence, motion} from 'framer-motion';
19
19
 
20
20
  import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
21
- import {TableSkeleton, useParcaContext, useURLState} from '@parca/components';
21
+ import {useParcaContext, useURLState} from '@parca/components';
22
22
  import {useCurrentColorProfile} from '@parca/hooks';
23
23
  import {ProfileType} from '@parca/parser';
24
- import {isSearchMatch} from '@parca/utilities';
25
24
 
26
25
  import useMappingList, {
27
26
  useFilenamesList,
28
27
  } from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
29
28
  import {ProfileSource} from '../ProfileSource';
30
- import {useProfileViewContext} from '../ProfileView/context/ProfileViewContext';
29
+ import {useDashboard} from '../ProfileView/context/DashboardContext';
31
30
  import {useVisualizationState} from '../ProfileView/hooks/useVisualizationState';
32
31
  import {FIELD_FUNCTION_NAME, Row} from '../Table';
33
32
  import {useColorManagement} from '../Table/hooks/useColorManagement';
34
- import {useTableConfiguration} from '../Table/hooks/useTableConfiguration';
35
- import {type DataRow} from '../Table/utils/functions';
36
33
  import {useQuery} from '../useQuery';
37
34
  import {CalleesSection} from './components/CalleesSection';
38
35
  import {CallersSection} from './components/CallersSection';
39
- import {TableSection} from './components/TableSection';
40
36
  import {processRowData} from './utils/processRowData';
41
37
 
42
38
  interface Props {
@@ -45,7 +41,6 @@ interface Props {
45
41
  filtered: bigint;
46
42
  profileType?: ProfileType;
47
43
  loading: boolean;
48
- isHalfScreen: boolean;
49
44
  unit?: string;
50
45
  metadataMappingFiles?: string[];
51
46
  queryClient?: QueryServiceClient;
@@ -54,30 +49,26 @@ interface Props {
54
49
 
55
50
  const Sandwich = React.memo(function Sandwich({
56
51
  data,
57
- total,
58
52
  filtered,
59
53
  profileType,
60
54
  loading,
61
- isHalfScreen,
62
55
  unit,
63
56
  metadataMappingFiles,
64
57
  queryClient,
65
58
  profileSource,
66
59
  }: Props): React.JSX.Element {
67
60
  const currentColorProfile = useCurrentColorProfile();
61
+ const {dashboardItems} = useDashboard();
62
+ const [sandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
68
63
 
69
- const [sandwichFunctionName, setSandwichFunctionName] = useURLState<string | undefined>(
70
- 'sandwich_function_name'
71
- );
72
64
  const {isDarkMode} = useParcaContext();
73
65
  const [selectedRow, setSelectedRow] = useState<TableRow<Row> | null>(null);
74
66
  const callersRef = React.useRef<HTMLDivElement | null>(null);
75
67
  const calleesRef = React.useRef<HTMLDivElement | null>(null);
68
+ const [isExpanded, setIsExpanded] = useState(false);
69
+ const defaultMaxFrames = 10;
76
70
 
77
71
  const callersCalleesContainerRef = useRef<HTMLDivElement | null>(null);
78
- const [tableHeight, setTableHeight] = useState<number | undefined>(undefined);
79
-
80
- const {compareMode} = useProfileViewContext();
81
72
 
82
73
  const {colorBy, setColorBy, curPathArrow, setCurPathArrow} = useVisualizationState();
83
74
 
@@ -160,15 +151,6 @@ const Sandwich = React.memo(function Sandwich({
160
151
 
161
152
  unit = useMemo(() => unit ?? profileType?.sampleUnit ?? '', [unit, profileType?.sampleUnit]);
162
153
 
163
- const tableConfig = useTableConfiguration({
164
- unit,
165
- total,
166
- filtered,
167
- compareMode,
168
- });
169
-
170
- const {columns, initialSorting, columnVisibility} = tableConfig;
171
-
172
154
  const rows = useMemo(() => {
173
155
  if (table == null || table.numRows === 0) {
174
156
  return [];
@@ -194,71 +176,6 @@ const Sandwich = React.memo(function Sandwich({
194
176
  }
195
177
  }, [sandwichFunctionName, rows, selectedRow]);
196
178
 
197
- // Update table height based on callers/callees container height
198
- useEffect(() => {
199
- const updateTableHeight = (): void => {
200
- if (callersCalleesContainerRef.current != null) {
201
- const containerHeight = callersCalleesContainerRef.current.getBoundingClientRect().height;
202
- setTableHeight(containerHeight);
203
- }
204
- };
205
-
206
- // Initial measurement
207
- updateTableHeight();
208
-
209
- // Update on window resize
210
- window.addEventListener('resize', updateTableHeight);
211
-
212
- // Use ResizeObserver if available for more accurate updates
213
- let resizeObserver: ResizeObserver | null = null;
214
- if (callersCalleesContainerRef.current != null && 'ResizeObserver' in window) {
215
- resizeObserver = new ResizeObserver(updateTableHeight);
216
- resizeObserver.observe(callersCalleesContainerRef.current);
217
- }
218
-
219
- return () => {
220
- window.removeEventListener('resize', updateTableHeight);
221
- if (resizeObserver != null) {
222
- resizeObserver.disconnect();
223
- }
224
- };
225
- }, [sandwichFunctionName, callersFlamegraphResponse, calleesFlamegraphResponse]);
226
-
227
- const onRowClick = useCallback(
228
- (row: DataRow) => {
229
- setSelectedRow(row as unknown as TableRow<Row>);
230
- setSandwichFunctionName(row.name.trim());
231
- },
232
- [setSandwichFunctionName]
233
- );
234
-
235
- const enableHighlighting = useMemo(() => {
236
- return sandwichFunctionName != null && sandwichFunctionName?.length > 0;
237
- }, [sandwichFunctionName]);
238
-
239
- const shouldHighlightRow = useCallback(
240
- (row: Row) => {
241
- if (!('name' in row)) {
242
- return false;
243
- }
244
- const name = row.name;
245
- return isSearchMatch(sandwichFunctionName as string, name);
246
- },
247
- [sandwichFunctionName]
248
- );
249
-
250
- if (loading) {
251
- return (
252
- <div className="overflow-clip h-[700px] min-h-[700px]">
253
- <TableSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
254
- </div>
255
- );
256
- }
257
-
258
- if (rows.length === 0) {
259
- return <div className="mx-auto text-center">Profile has no samples</div>;
260
- }
261
-
262
179
  return (
263
180
  <section className="flex flex-row h-full w-full">
264
181
  <AnimatePresence>
@@ -270,24 +187,10 @@ const Sandwich = React.memo(function Sandwich({
270
187
  transition={{duration: 0.5}}
271
188
  >
272
189
  <div className="relative flex flex-row">
273
- <TableSection
274
- rows={rows}
275
- columns={columns}
276
- initialSorting={initialSorting}
277
- columnVisibility={columnVisibility}
278
- selectedRow={selectedRow}
279
- onRowClick={onRowClick}
280
- shouldHighlightRow={shouldHighlightRow}
281
- enableHighlighting={enableHighlighting}
282
- height={tableHeight}
283
- sandwichFunctionName={sandwichFunctionName}
284
- />
285
-
286
- {sandwichFunctionName !== undefined && (
287
- <div className="w-[50%] flex flex-col" ref={callersCalleesContainerRef}>
190
+ {sandwichFunctionName !== undefined ? (
191
+ <div className="w-full flex flex-col" ref={callersCalleesContainerRef}>
288
192
  <CallersSection
289
193
  callersRef={callersRef}
290
- isHalfScreen={isHalfScreen}
291
194
  callersFlamegraphResponse={
292
195
  callersFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
293
196
  ? {
@@ -306,11 +209,13 @@ const Sandwich = React.memo(function Sandwich({
306
209
  curPathArrow={curPathArrow}
307
210
  setCurPathArrow={setCurPathArrow}
308
211
  metadataMappingFiles={metadataMappingFiles}
212
+ isExpanded={isExpanded}
213
+ setIsExpanded={setIsExpanded}
214
+ defaultMaxFrames={defaultMaxFrames}
309
215
  />
310
216
  <div className="h-4" />
311
217
  <CalleesSection
312
218
  calleesRef={calleesRef}
313
- isHalfScreen={isHalfScreen}
314
219
  calleesFlamegraphResponse={
315
220
  calleesFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
316
221
  ? {
@@ -331,6 +236,14 @@ const Sandwich = React.memo(function Sandwich({
331
236
  metadataMappingFiles={metadataMappingFiles}
332
237
  />
333
238
  </div>
239
+ ) : (
240
+ <div className="items-center justify-center flex h-full w-full">
241
+ <p className="text-sm">
242
+ {dashboardItems.includes('table')
243
+ ? 'Please select a function to view its callers and callees.'
244
+ : 'Use the right-click menu on the flame graph to choose a function to view its callers and callees.'}
245
+ </p>
246
+ </div>
334
247
  )}
335
248
  </div>
336
249
  </motion.div>
@@ -20,7 +20,7 @@ import cx from 'classnames';
20
20
  import {QueryServiceClient} from '@parca/client';
21
21
  import {useGrpcMetadata} from '@parca/components';
22
22
  import {Query} from '@parca/parser';
23
- import {sanitizeLabelValue} from '@parca/utilities';
23
+ import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
24
24
 
25
25
  import {LabelProvider, useLabels} from '../contexts/SimpleMatchersLabelContext';
26
26
  import {useUtilizationLabels} from '../contexts/UtilizationLabelsContext';
@@ -33,6 +33,8 @@ interface Props {
33
33
  currentQuery: Query;
34
34
  profileType: string;
35
35
  queryBrowserRef: React.RefObject<HTMLDivElement>;
36
+ start?: number;
37
+ end?: number;
36
38
  }
37
39
 
38
40
  interface QueryRow {
@@ -116,6 +118,8 @@ const SimpleMatchers = ({
116
118
  currentQuery,
117
119
  profileType,
118
120
  queryBrowserRef,
121
+ start,
122
+ end,
119
123
  }: Props): JSX.Element => {
120
124
  const utilizationLabels = useUtilizationLabels();
121
125
  const [queryRows, setQueryRows] = useState<QueryRow[]>([
@@ -140,10 +144,20 @@ const SimpleMatchers = ({
140
144
  }
141
145
  try {
142
146
  const values = await reactQueryClient.fetchQuery(
143
- [labelName, profileType],
147
+ [labelName, profileType, start, end],
144
148
  async () => {
145
149
  const response = await queryClient.values(
146
- {labelName, match: [], profileType},
150
+ {
151
+ labelName,
152
+ match: [],
153
+ profileType,
154
+ ...(start !== undefined && end !== undefined
155
+ ? {
156
+ start: millisToProtoTimestamp(start),
157
+ end: millisToProtoTimestamp(end),
158
+ }
159
+ : {}),
160
+ },
147
161
  {meta: metadata}
148
162
  ).response;
149
163
  const sanitizedValues = sanitizeLabelValue(response.labelValues);
@@ -159,7 +173,7 @@ const SimpleMatchers = ({
159
173
  return [];
160
174
  }
161
175
  },
162
- [queryClient, metadata, profileType, reactQueryClient]
176
+ [queryClient, metadata, profileType, reactQueryClient, start, end]
163
177
  );
164
178
 
165
179
  const fetchLabelValuesUtilization = useCallback(
@@ -419,6 +433,8 @@ export default function SimpleMathersWithProvider(props: Props): JSX.Element {
419
433
  queryClient={props.queryClient}
420
434
  profileType={props.profileType}
421
435
  labelNameFromMatchers={labelNameFromMatchers}
436
+ start={props.start}
437
+ end={props.end}
422
438
  >
423
439
  <SimpleMatchers {...props} />
424
440
  </LabelProvider>
@@ -18,7 +18,6 @@ import {useParcaContext, useURLState} from '@parca/components';
18
18
 
19
19
  const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element | null => {
20
20
  const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
21
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
21
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
23
22
  alwaysReturnArray: true,
24
23
  });
@@ -26,7 +25,7 @@ const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element
26
25
 
27
26
  const onSandwichViewSelect = (): void => {
28
27
  setSandwichFunctionName(functionName.trim());
29
- setDashboardItems(['sandwich']);
28
+ setDashboardItems([...dashboardItems, 'sandwich']);
30
29
  };
31
30
 
32
31
  const menuItems: Array<{label: string; action: () => void}> = [];
@@ -0,0 +1,70 @@
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 {Icon} from '@iconify/react';
15
+ import cx from 'classnames';
16
+ import {Item, Menu} from 'react-contexify';
17
+
18
+ import 'react-contexify/dist/ReactContexify.css';
19
+
20
+ import {useParcaContext, useURLState} from '@parca/components';
21
+
22
+ import {type Row} from '.';
23
+
24
+ interface TableContextMenuProps {
25
+ menuId: string;
26
+ row: Row | null;
27
+ }
28
+
29
+ const TableContextMenu = ({menuId, row}: TableContextMenuProps): React.JSX.Element => {
30
+ const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
31
+ const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
32
+ alwaysReturnArray: true,
33
+ });
34
+ const {enableSandwichView, isDarkMode} = useParcaContext();
35
+
36
+ const onSandwichViewSelect = (): void => {
37
+ if (row?.name != null && row.name.length > 0) {
38
+ setSandwichFunctionName(row.name.trim());
39
+ if (!dashboardItems.includes('sandwich')) {
40
+ setDashboardItems([...dashboardItems, 'sandwich']);
41
+ }
42
+ }
43
+ };
44
+
45
+ const isMenuDisabled = row === null || enableSandwichView !== true;
46
+
47
+ return (
48
+ <Menu
49
+ id={menuId}
50
+ theme={isDarkMode ? 'dark' : ''}
51
+ className={cx(
52
+ dashboardItems.includes('sandwich') ? 'min-w-[350px] w-[350px]' : 'min-w-[260px] w-[260px]'
53
+ )}
54
+ >
55
+ <Item id="sandwich-view" onClick={onSandwichViewSelect} disabled={isMenuDisabled}>
56
+ <div className="flex w-full items-center gap-2">
57
+ <Icon icon="tdesign:sandwich-filled" />
58
+ <div className="relative">
59
+ {dashboardItems.includes('sandwich')
60
+ ? 'Focus sandwich on this frame.'
61
+ : 'Show in sandwich'}
62
+ <span className="absolute top-[-2px] text-xs lowercase text-red-500">&nbsp;alpha</span>
63
+ </div>
64
+ </div>
65
+ </Item>
66
+ </Menu>
67
+ );
68
+ };
69
+
70
+ export default TableContextMenu;
@@ -0,0 +1,48 @@
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 {forwardRef, useImperativeHandle, useState} from 'react';
15
+
16
+ import {type Row} from '.';
17
+ import TableContextMenu from './TableContextMenu';
18
+
19
+ interface TableContextMenuWrapperProps {
20
+ menuId: string;
21
+ }
22
+
23
+ export interface TableContextMenuWrapperRef {
24
+ setRow: (row: Row | null, callback?: () => void) => void;
25
+ }
26
+
27
+ const TableContextMenuWrapper = forwardRef<
28
+ TableContextMenuWrapperRef,
29
+ TableContextMenuWrapperProps
30
+ >(({menuId}, ref) => {
31
+ const [row, setRow] = useState<Row | null>(null);
32
+
33
+ useImperativeHandle(ref, () => ({
34
+ setRow: (newRow: Row | null, callback?: () => void) => {
35
+ setRow(newRow);
36
+ // Execute callback after state update using requestAnimationFrame
37
+ if (callback != null) {
38
+ requestAnimationFrame(callback);
39
+ }
40
+ },
41
+ }));
42
+
43
+ return <TableContextMenu menuId={menuId} row={row} />;
44
+ });
45
+
46
+ TableContextMenuWrapper.displayName = 'TableContextMenuWrapper';
47
+
48
+ export default TableContextMenuWrapper;