@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.
- package/CHANGELOG.md +8 -0
- package/dist/GraphTooltipArrow/Content.js +1 -1
- package/dist/GraphTooltipArrow/index.js +2 -2
- package/dist/MatchersInput/index.d.ts +3 -1
- package/dist/MatchersInput/index.d.ts.map +1 -1
- package/dist/MatchersInput/index.js +8 -4
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
- package/dist/ProfileIcicleGraph/index.d.ts +3 -1
- package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/index.js +6 -4
- package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
- package/dist/ProfileSelector/QueryControls.js +1 -1
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +4 -2
- package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
- package/dist/ProfileView/components/DashboardItems/index.js +1 -1
- package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ShareButton/index.js +1 -1
- package/dist/ProfileView/components/Toolbars/index.d.ts +0 -2
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +4 -5
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +18 -11
- package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
- package/dist/ProfileView/context/DashboardContext.js +5 -0
- package/dist/ProfileView/index.d.ts.map +1 -1
- package/dist/ProfileView/index.js +4 -3
- package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
- package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CalleesSection.js +2 -6
- package/dist/Sandwich/components/CallersSection.d.ts +4 -2
- package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CallersSection.js +45 -9
- package/dist/Sandwich/components/TableSection.js +1 -1
- package/dist/Sandwich/index.d.ts +0 -1
- package/dist/Sandwich/index.d.ts.map +1 -1
- package/dist/Sandwich/index.js +27 -79
- package/dist/SimpleMatchers/index.d.ts +2 -0
- package/dist/SimpleMatchers/index.d.ts.map +1 -1
- package/dist/SimpleMatchers/index.js +16 -6
- package/dist/Table/MoreDropdown.d.ts.map +1 -1
- package/dist/Table/MoreDropdown.js +1 -2
- package/dist/Table/TableContextMenu.d.ts +9 -0
- package/dist/Table/TableContextMenu.d.ts.map +1 -0
- package/dist/Table/TableContextMenu.js +38 -0
- package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
- package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
- package/dist/Table/TableContextMenuWrapper.js +30 -0
- package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
- package/dist/Table/hooks/useTableConfiguration.js +2 -20
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +65 -5
- package/dist/ViewMatchers/index.d.ts +2 -0
- package/dist/ViewMatchers/index.d.ts.map +1 -1
- package/dist/ViewMatchers/index.js +14 -4
- package/dist/contexts/MatchersInputLabelsContext.d.ts +3 -1
- package/dist/contexts/MatchersInputLabelsContext.d.ts.map +1 -1
- package/dist/contexts/MatchersInputLabelsContext.js +3 -3
- package/dist/contexts/SimpleMatchersLabelContext.d.ts +3 -1
- package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +1 -1
- package/dist/contexts/SimpleMatchersLabelContext.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/GraphTooltipArrow/Content.tsx +3 -3
- package/src/GraphTooltipArrow/index.tsx +2 -2
- package/src/MatchersInput/index.tsx +17 -4
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
- package/src/ProfileIcicleGraph/index.tsx +20 -2
- package/src/ProfileSelector/QueryControls.tsx +6 -0
- package/src/ProfileSelector/index.tsx +12 -2
- package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
- package/src/ProfileView/components/ShareButton/index.tsx +9 -3
- package/src/ProfileView/components/Toolbars/index.tsx +7 -23
- package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
- package/src/ProfileView/context/DashboardContext.tsx +6 -0
- package/src/ProfileView/index.tsx +12 -4
- package/src/Sandwich/components/CalleesSection.tsx +1 -7
- package/src/Sandwich/components/CallersSection.tsx +92 -35
- package/src/Sandwich/components/TableSection.tsx +2 -2
- package/src/Sandwich/index.tsx +20 -107
- package/src/SimpleMatchers/index.tsx +20 -4
- package/src/Table/MoreDropdown.tsx +1 -2
- package/src/Table/TableContextMenu.tsx +70 -0
- package/src/Table/TableContextMenuWrapper.tsx +48 -0
- package/src/Table/hooks/useTableConfiguration.tsx +2 -25
- package/src/Table/index.tsx +84 -5
- package/src/ViewMatchers/index.tsx +17 -3
- package/src/contexts/MatchersInputLabelsContext.tsx +10 -2
- 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 {
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
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
|
package/src/Sandwich/index.tsx
CHANGED
|
@@ -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, {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
274
|
-
|
|
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
|
-
{
|
|
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"> 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;
|