From 354ab75d00199d4d6afef7b6b6d7ff9f63142298 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Thu, 14 Nov 2024 17:08:49 -0500 Subject: [PATCH 1/5] feat(functions): Use flamegraph as data source for slowest functions This switches the slowest functions table within transaction summary to use flamegraphs as a data source instead of the functions table. This is because with continuous profiling, the functions table does not have any transaction information for continuous profiles. So to be compatible with both profiling modes, we need to switch to using flamegraphs. --- .../suspectFunctionsTable.tsx | 356 +++++++++++++----- static/app/views/explore/components/table.tsx | 4 +- .../transactionOverview/content.tsx | 4 +- .../landing/slowestFunctionsTable.tsx | 1 + 4 files changed, 264 insertions(+), 101 deletions(-) diff --git a/static/app/components/profiling/suspectFunctions/suspectFunctionsTable.tsx b/static/app/components/profiling/suspectFunctions/suspectFunctionsTable.tsx index 25a65552978045..e5928a8c0a51d9 100644 --- a/static/app/components/profiling/suspectFunctions/suspectFunctionsTable.tsx +++ b/static/app/components/profiling/suspectFunctions/suspectFunctionsTable.tsx @@ -1,130 +1,292 @@ import {Fragment, useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; +import clamp from 'lodash/clamp'; -import {CompactSelect} from 'sentry/components/compactSelect'; -import Pagination from 'sentry/components/pagination'; -import type {TableColumnKey as FunctionsField} from 'sentry/components/profiling/suspectFunctions/functionsTable'; -import { - functionsFields, - FunctionsTable, -} from 'sentry/components/profiling/suspectFunctions/functionsTable'; +import {Button} from 'sentry/components/button'; +import ButtonBar from 'sentry/components/buttonBar'; +import {SectionHeading} from 'sentry/components/charts/styles'; +import EmptyStateWarning from 'sentry/components/emptyStateWarning'; +import LoadingIndicator from 'sentry/components/loadingIndicator'; +import {ArrayLinks} from 'sentry/components/profiling/arrayLinks'; +import {IconChevron} from 'sentry/icons/iconChevron'; +import {IconWarning} from 'sentry/icons/iconWarning'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; +import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; -import {browserHistory} from 'sentry/utils/browserHistory'; -import {useProfileFunctions} from 'sentry/utils/profiling/hooks/useProfileFunctions'; -import {formatSort} from 'sentry/utils/profiling/hooks/utils'; -import {decodeScalar} from 'sentry/utils/queryString'; -import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {trackAnalytics} from 'sentry/utils/analytics'; +import type EventView from 'sentry/utils/discover/eventView'; +import type {RenderFunctionBaggage} from 'sentry/utils/discover/fieldRenderers'; +import {FIELD_FORMATTERS} from 'sentry/utils/discover/fieldRenderers'; +import {getShortEventId} from 'sentry/utils/events'; +import {useAggregateFlamegraphQuery} from 'sentry/utils/profiling/hooks/useAggregateFlamegraphQuery'; +import {generateProfileRouteFromProfileReference} from 'sentry/utils/profiling/routes'; import {useLocation} from 'sentry/utils/useLocation'; +import useOrganization from 'sentry/utils/useOrganization'; +import { + Table, + TableBody, + TableBodyCell, + TableHead, + TableHeadCell, + TableRow, + TableStatus, + useTableStyles, +} from 'sentry/views/explore/components/table'; +import {getProfileTargetId} from 'sentry/views/profiling/utils'; + +function sortFunctions(a: Profiling.FunctionMetric, b: Profiling.FunctionMetric) { + return b.sum - a.sum; +} + +type Column = { + label: React.ReactNode; + value: keyof Profiling.FunctionMetric; +}; + +const COLUMNS: Column[] = [ + { + label: t('function'), + value: 'name', + }, + { + label: t('package'), + value: 'package', + }, + { + label: t('avg()'), + value: 'avg', + }, + { + label: t('p75()'), + value: 'p75', + }, + { + label: t('p95()'), + value: 'p95', + }, + { + label: t('p99()'), + value: 'p99', + }, + { + label: t('examples'), + value: 'examples', + }, +]; interface SuspectFunctionsTableProps { analyticsPageSource: 'performance_transaction' | 'profiling_transaction'; - project: Project | undefined; - transaction: string; + eventView: EventView; + project: Project; } -const FUNCTIONS_CURSOR_NAME = 'functionsCursor'; - export function SuspectFunctionsTable({ analyticsPageSource, + eventView, project, - transaction, }: SuspectFunctionsTableProps) { - const [functionType, setFunctionType] = useState<'application' | 'system' | 'all'>( - 'application' - ); const location = useLocation(); - const functionsCursor = useMemo( - () => decodeScalar(location.query[FUNCTIONS_CURSOR_NAME]), - [location.query] - ); - const functionsSort = useMemo( - () => - formatSort( - decodeScalar(location.query.functionsSort), - functionsFields, - { - key: 'sum()', - order: 'desc', - } - ), - [location.query.functionsSort] - ); + const organization = useOrganization(); - const handleFunctionsCursor = useCallback((cursor, pathname, query) => { - browserHistory.push({ - pathname, - query: {...query, [FUNCTIONS_CURSOR_NAME]: cursor}, - }); - }, []); - - const query = useMemo(() => { - const conditions = new MutableSearch(''); - conditions.setFilterValues('transaction', [transaction]); - if (functionType === 'application') { - conditions.setFilterValues('is_application', ['1']); - } else if (functionType === 'system') { - conditions.setFilterValues('is_application', ['0']); - } - return conditions.formatString(); - }, [functionType, transaction]); - - const functionsQuery = useProfileFunctions({ - fields: functionsFields, - referrer: 'api.profiling.profile-summary-functions-table', - sort: functionsSort, - query, - limit: 5, - cursor: functionsCursor, + const flamegraphQuery = 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: eventView.query, + metrics: true, }); + const sortedMetrics = useMemo(() => { + const metrics = flamegraphQuery.data?.metrics || []; + return metrics.sort(sortFunctions); + }, [flamegraphQuery.data?.metrics]); + + const pagination = useMemoryPagination(sortedMetrics, 5); + + const metrics = useMemo(() => { + return sortedMetrics.slice(pagination.start, pagination.end); + }, [sortedMetrics, pagination]); + + const {tableStyles} = useTableStyles({ + items: COLUMNS, + }); + + const baggage: RenderFunctionBaggage = { + location, + organization, + unit: 'nanosecond', + }; + return ( - setFunctionType(value)} - /> - + {t('Suspect Functions')} + +