import {useCallback, useMemo, useState} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import type {SelectOption} from 'sentry/components/compactSelect'; import {CompactSelect} from 'sentry/components/compactSelect'; import Count from 'sentry/components/count'; import Link from 'sentry/components/links/link'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Pagination from 'sentry/components/pagination'; import PerformanceDuration from 'sentry/components/performanceDuration'; import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOverflow'; import {t, tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization, Project} from 'sentry/types'; import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {Frame} from 'sentry/utils/profiling/frame'; import type {EventsResultsDataRow} from 'sentry/utils/profiling/hooks/types'; import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam'; import {useProfileFunctions} from 'sentry/utils/profiling/hooks/useProfileFunctions'; import {formatSort} from 'sentry/utils/profiling/hooks/utils'; import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; const SLOWEST_FUNCTIONS_LIMIT = 15; const SLOWEST_FUNCTIONS_CURSOR = 'functionsCursor'; const functionsFields = [ 'package', 'function', 'count()', 'p75()', 'sum()', 'examples()', ] as const; type FunctionsField = (typeof functionsFields)[number]; interface SlowestProfileFunctionsProps { transaction: string; } export function SlowestProfileFunctions(props: SlowestProfileFunctionsProps) { const organization = useOrganization(); const project = useCurrentProjectFromRouteParam(); const [functionType, setFunctionType] = useState<'application' | 'system' | 'all'>( 'application' ); const location = useLocation(); const functionsCursor = useMemo( () => decodeScalar(location.query[SLOWEST_FUNCTIONS_CURSOR]), [location.query] ); const functionsSort = useMemo( () => formatSort( decodeScalar(location.query.functionsSort), functionsFields, { key: 'sum()', order: 'desc', } ), [location.query.functionsSort] ); const handleFunctionsCursor = useCallback((cursor, pathname, query) => { browserHistory.push({ pathname, query: {...query, [SLOWEST_FUNCTIONS_CURSOR]: cursor}, }); }, []); const query = useMemo(() => { const conditions = new MutableSearch(''); conditions.setFilterValues('transaction', [props.transaction]); if (functionType === 'application') { conditions.setFilterValues('is_application', ['1']); } else if (functionType === 'system') { conditions.setFilterValues('is_application', ['0']); } return conditions.formatString(); }, [functionType, props.transaction]); const functionsQuery = useProfileFunctions({ fields: functionsFields, referrer: 'api.profiling.profile-summary-functions-table', sort: functionsSort, query, limit: SLOWEST_FUNCTIONS_LIMIT, cursor: functionsCursor, }); const onChangeFunctionType = useCallback(v => setFunctionType(v.value), []); const functions = functionsQuery.data?.data ?? []; const onSlowestFunctionClick = useCallback(() => { trackAnalytics('profiling_views.go_to_flamegraph', { organization, source: `profiling_transaction.slowest_functions_table`, }); }, [organization]); return ( {functionsQuery.isLoading ? ( ) : functionsQuery.isError ? ( {t('Failed to fetch slowest functions')} ) : !functions.length ? ( {t('The fastest code is one that never runs.')} ) : ( functions.map((fn, i) => { return ( ); }) )} ); } interface SlowestFunctionEntryProps { func: EventsResultsDataRow<'function' | 'package' | 'count()' | 'p75()' | 'sum()'>; onSlowestFunctionClick: () => void; organization: Organization; project: Project | null; } function SlowestFunctionEntry(props: SlowestFunctionEntryProps) { const frame = useMemo(() => { return new Frame( { key: 0, name: props.func.function as string, package: props.func.package as string, }, // Ensures that the frame runs through the normalization code path props.project?.platform && /node|javascript/.test(props.project.platform) ? props.project.platform : undefined, 'aggregate' ); }, [props.func, props.project]); let rendered = {frame.name}; if (defined(props.func['examples()']?.[0])) { rendered = ( {rendered} ); } return (
{rendered}
{frame.package}
{' '} {tn('time', 'times', props.func['count()'])} {', '}
); } const SlowestFunctionsList = styled('div')` flex-basis: 100%; overflow: auto; min-height: 0; `; const SlowestFunctionsContainer = styled('div')` margin-top: ${space(0.5)}; min-height: 0; display: flex; flex-direction: column; padding: 0 ${space(1)}; border-bottom: 1px solid ${p => p.theme.border}; `; const SlowestFunctionsPagination = styled(Pagination)` margin: 0; button { height: 16px; width: 16px; min-width: 16px; min-height: 16px; svg { width: 10px; height: 10px; } } `; const SlowestFunctionsTitleContainer = styled('div')` display: flex; align-items: center; justify-content: space-between; margin-bottom: ${space(1)}; `; const SlowestFunctionsTypeSelect = styled(CompactSelect)` button { margin: 0; padding: 0; } `; const SlowestFunctionsQueryState = styled('div')` text-align: center; padding: ${space(2)} ${space(0.5)}; color: ${p => p.theme.subText}; `; const SlowestFunctionRow = styled('div')` margin-bottom: ${space(1)}; `; const SlowestFunctionMainRow = styled('div')` display: flex; align-items: center; justify-content: space-between; > div:first-child { min-width: 0; } > div:last-child { white-space: nowrap; } `; const SlowestFunctionMetricsRow = styled('div')` display: flex; align-items: center; justify-content: space-between; font-size: ${p => p.theme.fontSizeSmall}; color: ${p => p.theme.subText}; margin-top: ${space(0.25)}; `; const TRIGGER_PROPS = {borderless: true, size: 'zero' as const}; const SLOWEST_FUNCTION_OPTIONS: SelectOption<'application' | 'system' | 'all'>[] = [ { label: t('Slowest Application Functions'), value: 'application' as const, }, { label: t('Slowest System Functions'), value: 'system' as const, }, { label: t('Slowest Functions'), value: 'all' as const, }, ];