import {useCallback, useEffect, useMemo, useState} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import {CompactSelect, SelectOption} 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} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {trackAnalytics} from 'sentry/utils/analytics'; 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, }); useEffect(() => { if ( functionsQuery.isLoading || functionsQuery.isError || functionsQuery.data?.data?.length > 0 ) { return; } Sentry.captureMessage('No regressed functions detected for flamegraph'); }, [functionsQuery.data, functionsQuery.isLoading, functionsQuery.isError]); 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('Yikes, you have no slow functions? This should not happen.')} ) : ( functions.map((fn, i) => { return (
{fn.function}
{fn.package}
{', '}
); }) )}
); } 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; `; const SlowestFunctionsPagination = styled(Pagination)` margin: 0; `; 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; `; 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, }, ];