import {Fragment, useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import clamp from 'lodash/clamp'; import {Button} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import {LineChart, type LineChartProps} from 'sentry/components/charts/lineChart'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import ProjectBadge from 'sentry/components/idBadge/projectBadge'; import Link from 'sentry/components/links/link'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import TextOverflow from 'sentry/components/textOverflow'; import {Tooltip} from 'sentry/components/tooltip'; import {IconChevron, IconWarning} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Series} from 'sentry/types/echarts'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts'; import {getShortEventId} from 'sentry/utils/events'; import {useAggregateFlamegraphQuery} from 'sentry/utils/profiling/hooks/useAggregateFlamegraphQuery'; import {useProfilingFunctionMetrics} from 'sentry/utils/profiling/hooks/useProfilingFunctionMetrics'; import {generateProfileRouteFromProfileReference} from 'sentry/utils/profiling/routes'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import { Table, TableBody, TableBodyCell, TableHead, TableHeadCell, TableHeader, TableHeaderActions, TableHeaderTitle, TableRow, TableStatus, useTableStyles, } from 'sentry/views/explore/components/table'; import {getPerformanceDuration} from 'sentry/views/performance/utils/getPerformanceDuration'; function sortFunctions(a: Profiling.FunctionMetric, b: Profiling.FunctionMetric) { return b.sum - a.sum; } function makeProfileLinkFromExample( organization: Organization, f: Profiling.FunctionMetric, example: Profiling.FunctionMetric['examples'][0], projectsLookupTable: Record ) { if ('project_id' in example) { return generateProfileRouteFromProfileReference({ frameName: f.name, framePackage: f.package, orgSlug: organization.slug, projectSlug: projectsLookupTable[example.project_id]?.slug, reference: example, }); } return null; } function useMemoryPagination(items: any[], size: number) { const [pagination, setPagination] = useState({ start: 0, end: size, }); const page = Math.floor(pagination.start / size); const toPage = useCallback( (p: number) => { const next = clamp(p, 0, Math.floor(items.length / size)); setPagination({ start: clamp(next * size, 0, items.length - size), end: Math.min(next * size + size, items.length), }); }, [size, items] ); return { page, start: pagination.start, end: pagination.end, nextButtonProps: { disabled: pagination.end >= items.length, onClick: () => toPage(page + 1), }, previousButtonProps: { disabled: pagination.start <= 0, onClick: () => toPage(page - 1), }, }; } export function SlowestFunctionsTable({userQuery}: {userQuery?: string}) { const {projects} = useProjects(); const query = useAggregateFlamegraphQuery({ // User query is only permitted when using transactions. // If this is to be reused for strictly continuous profiling, // it'll need to be swapped to use the `profiles` data source // with no user query. dataSource: 'transactions', query: userQuery ?? '', metrics: true, }); const sortedMetrics = useMemo(() => { return query.data?.metrics?.sort(sortFunctions) ?? []; }, [query.data?.metrics]); const pagination = useMemoryPagination(sortedMetrics, 5); const projectsLookupTable = useMemo(() => { return projects.reduce( (acc, project) => { acc[project.id] = project; return acc; }, {} as Record ); }, [projects]); const hasFunctions = query.data?.metrics && query.data.metrics.length > 0; const columns = [ {label: '', value: '', width: 62}, {label: t('Function'), value: 'function'}, {label: t('Project'), value: 'project'}, {label: t('Package'), value: 'package'}, {label: t('p75()'), value: 'p75', width: 'min-content' as const}, {label: t('p95()'), value: 'p95', width: 'min-content' as const}, {label: t('p99()'), value: 'p99', width: 'min-content' as const}, ]; const {tableStyles} = useTableStyles({items: columns}); return ( {t('Slowest Functions')}