Skip to content

Commit

Permalink
feat(profiling): Make slowest functions compatible with continuous pr… (
Browse files Browse the repository at this point in the history
#80632)

…ofiling

This makes the slowest functions widget compatible with continuous
profiling by removing the transaction breakdown for a function
timeseries.

---------

Co-authored-by: Jonas <[email protected]>
  • Loading branch information
Zylphrex and JonasBa authored Nov 14, 2024
1 parent f52502d commit dafe79d
Show file tree
Hide file tree
Showing 9 changed files with 461 additions and 366 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ function EventAffectedTransactionsInner({
const functionStats = useProfileTopEventsStats({
dataset: 'profileFunctions',
datetime,
fields: ['transaction', 'count()'],
fields: ['transaction', 'count()', 'examples()'],
query: query ?? '',
enabled: defined(query),
others: false,
referrer: 'api.profiling.functions.regression.transaction-stats',
topEvents: TRANSACTIONS_LIMIT,
yAxes: ['examples()'],
yAxes: ['count()', 'examples()'],
});

const examplesByTransaction = useMemo(() => {
Expand All @@ -155,14 +155,15 @@ function EventAffectedTransactionsInner({

transactionsDeltaQuery.data?.data?.forEach(row => {
const transaction = row.transaction as string;
const data = functionStats.data.data.find(
const data = functionStats.data?.data?.find(
({axis, label}) => axis === 'examples()' && label === transaction
);
if (!defined(data)) {
return;
}
const examples = data.values.map(values => (Array.isArray(values) ? values : []));

allExamples[transaction] = findExamplePair(data.values, breakpointIndex);
allExamples[transaction] = findExamplePair(examples, breakpointIndex);
});

return allExamples;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ import type {Project} from 'sentry/types/project';
import {trackAnalytics} from 'sentry/utils/analytics';
import {Container, NumberContainer} from 'sentry/utils/discover/styles';
import {getShortEventId} from 'sentry/utils/events';
import {
isContinuousProfileReference,
isTransactionProfileReference,
} from 'sentry/utils/profiling/guards/profile';
import type {EventsResults, Sort} from 'sentry/utils/profiling/hooks/types';
import {generateProfileRouteFromProfileReference} from 'sentry/utils/profiling/routes';
import {renderTableHead} from 'sentry/utils/profiling/tableRenderer';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {getProfileTargetId} from 'sentry/views/profiling/utils';

interface FunctionsTableProps {
analyticsPageSource: 'performance_transaction' | 'profiling_transaction';
Expand Down Expand Up @@ -47,7 +44,7 @@ export function FunctionsTable(props: FunctionsTableProps) {
...func,
'all_examples()': examples.map(example => {
return {
value: getShortEventId(getTargetId(example)),
value: getShortEventId(getProfileTargetId(example)),
onClick: () =>
trackAnalytics('profiling_views.go_to_flamegraph', {
organization,
Expand Down Expand Up @@ -226,13 +223,3 @@ const COLUMNS: Record<TableColumnKey, TableColumn> = {
width: COL_WIDTH_UNDEFINED,
},
};

function getTargetId(reference): string {
if (isTransactionProfileReference(reference)) {
return reference.profile_id;
}
if (isContinuousProfileReference(reference)) {
return reference.profiler_id;
}
return reference;
}
4 changes: 2 additions & 2 deletions static/app/utils/profiling/hooks/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type FunctionTrend = {
breakpoint: number;
change: TrendType;
'count()': number;
examples: FunctionExample[];
fingerprint: number;
function: string;
package: string;
Expand All @@ -49,7 +50,6 @@ export type FunctionTrend = {
trend_difference: number;
trend_percentage: number;
unweighted_p_value: number;
worst: FunctionExample[];
};

type EpochTime = number;
Expand All @@ -62,4 +62,4 @@ type FunctionTrendStats = {
start: number;
};

type FunctionExample = [EpochTime, string];
type FunctionExample = [EpochTime, Profiling.BaseProfileReference];
25 changes: 16 additions & 9 deletions static/app/utils/profiling/hooks/useProfileTopEventsStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type {PageFilters} from 'sentry/types/core';
import type {EventsStatsSeries} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {transformSingleSeries} from 'sentry/utils/profiling/hooks/utils';
import type {UseApiQueryResult} from 'sentry/utils/queryClient';
import {useApiQuery} from 'sentry/utils/queryClient';
import type RequestError from 'sentry/utils/requestError/requestError';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';

Expand All @@ -19,6 +21,7 @@ interface UseProfileTopEventsStatsOptions<F> {
datetime?: PageFilters['datetime'];
enabled?: boolean;
interval?: string;
projects?: PageFilters['projects'];
query?: string;
}

Expand All @@ -29,11 +32,15 @@ export function useProfileTopEventsStats<F extends string>({
interval,
others,
query,
projects,
referrer,
topEvents,
yAxes,
enabled = true,
}: UseProfileTopEventsStatsOptions<F>) {
}: UseProfileTopEventsStatsOptions<F>): UseApiQueryResult<
EventsStatsSeries<F>,
RequestError
> {
const organization = useOrganization();
const {selection} = usePageFilters();

Expand All @@ -43,7 +50,7 @@ export function useProfileTopEventsStats<F extends string>({
dataset,
field: fields,
referrer,
project: selection.projects,
project: projects ?? selection.projects,
environment: selection.environments,
...normalizeDateTimeParams(datetime ?? selection.datetime),
yAxis: yAxes,
Expand All @@ -54,20 +61,20 @@ export function useProfileTopEventsStats<F extends string>({
},
};

const {data, ...rest} = useApiQuery<any>([path, endpointOptions], {
const result = useApiQuery<any>([path, endpointOptions], {
staleTime: Infinity,
enabled,
});

const transformed = useMemo(
() => data && transformTopEventsStatsResponse(dataset, yAxes, data),
[yAxes, data, dataset]
const transformed: EventsStatsSeries<F> = useMemo(
() => transformTopEventsStatsResponse(dataset, yAxes, result.data),
[yAxes, result.data, dataset]
);

return {
...result,
data: transformed,
...rest,
};
} as UseApiQueryResult<EventsStatsSeries<F>, RequestError>;
}

function transformTopEventsStatsResponse<F extends string>(
Expand All @@ -77,7 +84,7 @@ function transformTopEventsStatsResponse<F extends string>(
): EventsStatsSeries<F> {
// the events stats endpoint has a legacy response format so here we transform it
// into the proposed update for forward compatibility and ease of use
if (yAxes.length === 0) {
if (!rawData || yAxes.length === 0) {
return {
data: [],
meta: {
Expand Down
42 changes: 19 additions & 23 deletions static/app/views/profiling/landing/functionTrendsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {browserHistory} from 'sentry/utils/browserHistory';
import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
import type {FunctionTrend, TrendType} from 'sentry/utils/profiling/hooks/types';
import {useProfileFunctionTrends} from 'sentry/utils/profiling/hooks/useProfileFunctionTrends';
import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes';
import {generateProfileRouteFromProfileReference} from 'sentry/utils/profiling/routes';
import {decodeScalar} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -211,7 +211,7 @@ function FunctionTrendsEntry({
const project = projects.find(p => p.id === func.project);

const [beforeExamples, afterExamples] = useMemo(() => {
return partition(func.worst, ([ts, _example]) => ts <= func.breakpoint);
return partition(func.examples, ([ts, _example]) => ts <= func.breakpoint);
}, [func]);

let before = <PerformanceDuration nanoseconds={func.aggregate_range_1} abbreviation />;
Expand Down Expand Up @@ -241,14 +241,12 @@ function FunctionTrendsEntry({
// occurred within the period and eliminate confusion with picking an example in
// the same bucket as the breakpoint.

const beforeTarget = generateProfileFlamechartRouteWithQuery({
const beforeTarget = generateProfileRouteFromProfileReference({
orgSlug: organization.slug,
projectSlug: project.slug,
profileId: beforeExamples[beforeExamples.length - 2][1],
query: {
frameName: func.function as string,
framePackage: func.package as string,
},
reference: beforeExamples[beforeExamples.length - 2][1],
frameName: func.function as string,
framePackage: func.package as string,
});

before = (
Expand All @@ -257,14 +255,12 @@ function FunctionTrendsEntry({
</Link>
);

const afterTarget = generateProfileFlamechartRouteWithQuery({
const afterTarget = generateProfileRouteFromProfileReference({
orgSlug: organization.slug,
projectSlug: project.slug,
profileId: afterExamples[afterExamples.length - 2][1],
query: {
frameName: func.function as string,
framePackage: func.package as string,
},
reference: afterExamples[afterExamples.length - 2][1],
frameName: func.function as string,
framePackage: func.package as string,
});

after = (
Expand All @@ -277,6 +273,14 @@ function FunctionTrendsEntry({
return (
<Fragment>
<StyledAccordionItem>
<Button
icon={<IconChevron size="xs" direction={isExpanded ? 'up' : 'down'} />}
aria-label={t('Expand')}
aria-expanded={isExpanded}
size="zero"
borderless
onClick={() => setExpanded()}
/>
{project && (
<Tooltip title={project.name}>
<IdBadge project={project} avatarSize={16} hideName />
Expand All @@ -296,14 +300,6 @@ function FunctionTrendsEntry({
{after}
</DurationChange>
</Tooltip>
<Button
icon={<IconChevron size="xs" direction={isExpanded ? 'up' : 'down'} />}
aria-label={t('Expand')}
aria-expanded={isExpanded}
size="zero"
borderless
onClick={() => setExpanded()}
/>
</StyledAccordionItem>
{isExpanded && (
<FunctionTrendsChartContainer>
Expand Down Expand Up @@ -492,7 +488,7 @@ const StyledPagination = styled(Pagination)`

const StyledAccordionItem = styled(AccordionItem)`
display: grid;
grid-template-columns: auto 1fr auto auto;
grid-template-columns: auto auto 1fr auto;
`;

const FunctionName = styled(TextOverflow)`
Expand Down
Loading

0 comments on commit dafe79d

Please sign in to comment.