// Copyright 2022 The Parca Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {Icon} from '@iconify/react'; import {Table} from '@uwdata/flechette'; import cx from 'classnames'; import {useQueryState} from 'nuqs'; import {Item, Menu, Separator, Submenu} from 'react-contexify'; import {Tooltip} from 'react-tooltip'; import {useParcaContext} from '@parca/components'; import {USER_PREFERENCES, useUserPreference} from '@parca/hooks'; import {ProfileType} from '@parca/parser'; import {TEST_IDS} from '@parca/test-utils'; import {getLastItem} from '@parca/utilities'; import {useGraphTooltip} from '../../GraphTooltipArrow/useGraphTooltip'; import {useGraphTooltipMetaInfo} from '../../GraphTooltipArrow/useGraphTooltipMetaInfo'; import {stringParam} from '../../hooks/urlParsers'; import {useDashboardItems} from '../../hooks/useDashboardItems'; import {hexifyAddress, truncateString} from '../../utils'; interface ContextMenuProps { menuId: string; table: Table; profileType?: ProfileType; unit?: string; total: bigint; totalUnfiltered: bigint; row: number; compareAbsolute: boolean; resetPath: () => void; hideMenu: () => void; hideBinary: (binaryToRemove: string) => void; isSandwich?: boolean; } const ContextMenu = ({ menuId, table, total, totalUnfiltered, row, compareAbsolute, hideMenu, profileType, unit, hideBinary, resetPath, isSandwich = false, }: ContextMenuProps): JSX.Element => { const {isDarkMode} = useParcaContext(); const {enableSourcesView, enableSandwichView, checkDebuginfoStatusHandler} = useParcaContext(); const [isGraphTooltipDocked, setIsDocked] = useUserPreference( USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key ); const contextMenuData = useGraphTooltip({ table, profileType, unit, total, totalUnfiltered, row, compareAbsolute, }); const { functionFilename, functionSystemName, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({table, row}); const {dashboardItems, setDashboardItems} = useDashboardItems(); const [_sandwichFunctionName, setSandwichFunctionName] = useQueryState( 'sandwich_function_name', stringParam ); if (contextMenuData === null) { return <>; } const {name, cumulativeText, diffText, diff} = contextMenuData; const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== ''; const handleViewSourceFile = (): void => openFile(); const handleResetView = (): void => { resetPath(); return hideMenu(); }; const handleDockTooltip = (): void => { return isGraphTooltipDocked ? setIsDocked(false) : setIsDocked(true); }; const handleCopyItem = (text: string): void => { void navigator.clipboard.writeText(text); }; const functionName = row === 0 ? '' : name !== '' ? name : locationAddress !== 0n ? hexifyAddress(locationAddress) : ''; const buildIdText = !isMappingBuildIDAvailable ? '' : mappingBuildID; const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no'; const valuesToCopy = [ {id: 'Function name', value: functionName}, { id: 'Function system name', value: functionSystemName === functionName ? '' : functionSystemName, }, // an empty string will be filtered out below {id: 'Cumulative', value: cumulativeText ?? ''}, {id: 'Diff', value: diff !== 0n ? diffText : ''}, { id: 'File', value: functionFilename === '' ? functionFilename : file, }, {id: 'Address', value: locationAddress === 0n ? '' : hexifyAddress(locationAddress)}, {id: 'Inlined', value: inlinedText}, {id: 'Binary', value: mappingFile ?? ''}, {id: 'Build Id', value: buildIdText}, ]; const nonEmptyValuesToCopy = valuesToCopy.filter(({value}) => value !== ''); return (
View source file
{!isSourceAvailable ? : null}
{ if (isSandwich) { setDashboardItems(['table']); } else { setDashboardItems([...dashboardItems, 'table']); } }} >
Show in table
{enableSandwichView === true && ( { if (functionName === '' || functionName == null) { return; } if (dashboardItems.includes('sandwich')) { void setSandwichFunctionName(functionName); hideMenu(); return; } void setSandwichFunctionName(functionName); setDashboardItems([...dashboardItems, 'sandwich']); hideMenu(); }} disabled={functionName === '' || functionName == null} >
{dashboardItems.includes('sandwich') ? 'Focus sandwich on this frame.' : 'Show in sandwich'}  alpha
)}
Reset graph
hideBinary(getLastItem(mappingFile) as string)} disabled={mappingFile === null || mappingFile === ''} >
Hide binary {mappingFile !== null && `(${getLastItem(mappingFile) as string})`}
Copy
} >
{nonEmptyValuesToCopy.map(({id, value}: {id: string; value: string}) => ( handleCopyItem(value)} className="dark:bg-gray-800" >
{id}
{truncateString(value, 30)}
))}
{checkDebuginfoStatusHandler !== undefined ? ( checkDebuginfoStatusHandler(mappingBuildID as string)} disabled={!isMappingBuildIDAvailable} >
Check debuginfo status
) : null}
{isGraphTooltipDocked ? 'Undock tooltip' : 'Dock tooltip'}
); }; export default ContextMenu;