// 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 React from 'react'; import {Icon} from '@iconify/react'; import {Table} from '@uwdata/flechette'; import {useParcaContext} from '@parca/components'; import {ProfileType} from '@parca/parser'; import {formatDateTimeDownToMS, getLastItem} from '@parca/utilities'; import {getLabelPairs} from '../ProfileFlameGraph/FlameGraphArrow/utils'; import {hexifyAddress, truncateString, truncateStringReverse} from '../utils'; import {ExpandOnHover} from './ExpandOnHoverValue'; import {gpuFrameInfosFromLabels, type GpuFrameInfo} from './gpuFrameDescriptions'; import {openInNewTab} from './openInNewTab'; import {useGraphTooltip} from './useGraphTooltip'; import {useGraphTooltipMetaInfo} from './useGraphTooltipMetaInfo'; interface GraphTooltipArrowContentProps { table: Table; profileType?: ProfileType; unit?: string; total: bigint; totalUnfiltered: bigint; row: number | null; isFixed: boolean; compareAbsolute: boolean; frozen?: boolean; } const NoData = (): React.JSX.Element => { return Not available; }; const GraphTooltipArrowContent = ({ table, profileType, unit, total, totalUnfiltered, row, isFixed, compareAbsolute, frozen = false, }: GraphTooltipArrowContentProps): React.JSX.Element => { const graphTooltipData = useGraphTooltip({ table, profileType, unit, total, totalUnfiltered, row, compareAbsolute, }); if (graphTooltipData === null) { return <>; } const { name, locationAddress, cumulativeText, flatText, diffText, diff, row: rowNumber, } = graphTooltipData; const gpuInfos = gpuFrameInfosFromLabels(getLabelPairs(table, rowNumber)); // Outer card gains a subtle ring when frozen, matching the design's // `.is-frozen` treatment. const cardClassName = [ 'flex w-auto max-w-[600px] min-w-[300px] flex-col justify-start rounded-lg border bg-gray-50 p-3 shadow-lg dark:bg-gray-900', frozen ? 'border-indigo-400/60 ring-2 ring-indigo-400/20 dark:border-indigo-400/40' : 'border-gray-300 dark:border-gray-500', ].join(' '); return (
{row === 0 ? (

root

) : (

{name !== '' ? name : locationAddress !== 0n ? hexifyAddress(locationAddress) : 'unknown'}

)}
{diff !== 0n && ( )}
Cumulative

{cumulativeText}

Flat

{flatText}

Diff

{diffText}

{gpuInfos.map((info, i) => ( 1 && info.kind === 'stall' ? 2 : undefined} /> ))}
); }; // Trims a description to at most `max` sentences, appending an ellipsis when // anything was dropped. const truncateToSentences = (text: string, max: number): string => { const sentences = text.match(/[^.!?]+[.!?]+(\s|$)|[^.!?]+$/g); if (sentences === null || sentences.length <= max) return text; return `${sentences.slice(0, max).join('').trimEnd()} …`; }; const GpuDescriptionBlock = ({ info, maxSentences, }: { info: GpuFrameInfo; maxSentences?: number; }): React.JSX.Element => { const chipPrefix = info.kind === 'stall' ? 'Stall reason' : 'SASS instruction'; const description = maxSentences === undefined ? info.entry.description : truncateToSentences(info.entry.description, maxSentences); return (
{chipPrefix} · {info.entry.reasonLabel}
Description

{description}

); }; const ShortcutFooter = ({frozen}: {frozen: boolean}): React.JSX.Element => (
{frozen ? ( Frozen · release to resume hover ) : ( Hold Shift to freeze · interact )} Right-click for context menu
); const TooltipMetaInfo = ({table, row}: {table: Table; row: number}): React.JSX.Element => { const { labelPairs, functionFilename, file, locationAddress, mappingFile, mappingBuildID, inlined, timestamp, } = useGraphTooltipMetaInfo({table, row}); const {timezone} = useParcaContext(); const labels = labelPairs.map( (l): React.JSX.Element => ( {`${l[0]}="${l[1]}"`} ) ); const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== ''; const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no'; return ( <> {timestamp != null && timestamp !== 0n && ( Timestamp {formatDateTimeDownToMS(new Date(Number(timestamp / 1000000n)), timezone)} )} File {functionFilename === '' ? ( ) : (
)} Address {locationAddress === 0n ? :
{hexifyAddress(locationAddress)}
} Inlined {inlinedText} Binary {(mappingFile != null ? getLastItem(mappingFile) : null) ?? } Build Id {isMappingBuildIDAvailable ?
{truncateString(mappingBuildID, 28)}
: } {labelPairs.length > 0 && ( Labels {labels} )} ); }; export default GraphTooltipArrowContent;