Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
getSpanID,
getSpanOperation,
groupShouldBeHidden,
isEventFromBrowserJavaScriptSDK,
isBrowserJavaScriptSDKName,
isOrphanSpan,
parseTrace,
SpanSubTimingMark,
Expand Down Expand Up @@ -155,7 +155,7 @@ export class SpanTreeModel {
): EnhancedProcessedSpanType | undefined {
// hide gap spans (i.e. "missing instrumentation" spans) for browser js transactions,
// since they're not useful to indicate
const shouldIncludeGap = !isEventFromBrowserJavaScriptSDK(event);
const shouldIncludeGap = !isBrowserJavaScriptSDKName(event.sdk?.name);

const isValidGap =
shouldIncludeGap &&
Expand Down
5 changes: 1 addition & 4 deletions static/app/components/events/interfaces/spans/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,7 @@ function sortSpans(firstSpan: SpanType, secondSpan: SpanType) {
return 1;
}

export function isEventFromBrowserJavaScriptSDK(
event: EventTransaction | AggregateEventTransaction
): boolean {
const sdkName = event.sdk?.name;
export function isBrowserJavaScriptSDKName(sdkName: string | null | undefined): boolean {
if (!sdkName) {
return false;
}
Expand Down
19 changes: 12 additions & 7 deletions static/app/components/profiling/continuousProfileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@ import * as Layout from 'sentry/components/layouts/thirds';
import type {ProfilingBreadcrumbsProps} from 'sentry/components/profiling/profilingBreadcrumbs';
import {ProfilingBreadcrumbs} from 'sentry/components/profiling/profilingBreadcrumbs';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import {trackAnalytics} from 'sentry/utils/analytics';
import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';
import type {SpanResponse} from 'sentry/views/insights/types';
import {SpanFields} from 'sentry/views/insights/types';
import {TopBar} from 'sentry/views/navigation/topBar';
import {useHasPageFrameFeature} from 'sentry/views/navigation/useHasPageFrameFeature';

interface ContinuousProfileHeader {
transaction: Event | null;
transactionSpan:
| Pick<SpanResponse, 'trace' | 'span_id' | 'precise.finish_ts'>
| undefined;
}

export function ContinuousProfileHeader({transaction}: ContinuousProfileHeader) {
export function ContinuousProfileHeader({
transactionSpan: transaction,
}: ContinuousProfileHeader) {
const location = useLocation();
const organization = useOrganization();
const hasPageFrameFeature = useHasPageFrameFeature();
Expand All @@ -30,11 +35,11 @@ export function ContinuousProfileHeader({transaction}: ContinuousProfileHeader)
return [{type: 'landing', payload: {query: {}}}];
}, []);

const transactionTarget = transaction?.id
const transactionTarget = transaction
? generateLinkToEventInTraceView({
timestamp: transaction.endTimestamp ?? '',
eventId: transaction.id,
traceSlug: transaction.contexts?.trace?.trace_id ?? '',
timestamp: transaction[SpanFields.PRECISE_FINISH_TS],
targetId: transaction[SpanFields.SPAN_ID],
traceSlug: transaction[SpanFields.TRACE],
location,
organization,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import {FlamegraphViewSelectMenu} from 'sentry/components/profiling/flamegraph/f
import {FlamegraphZoomView} from 'sentry/components/profiling/flamegraph/flamegraphZoomView';
import {FlamegraphZoomViewMinimap} from 'sentry/components/profiling/flamegraph/flamegraphZoomViewMinimap';
import {t} from 'sentry/locale';
import type {EntrySpans, EventTransaction} from 'sentry/types/event';
import {EntryType} from 'sentry/types/event';
import {defined} from 'sentry/utils';
import {
CanvasPoolManager,
Expand All @@ -49,6 +47,7 @@ import {
initializeFlamegraphRenderer,
useResizeCanvasObserver,
} from 'sentry/utils/profiling/gl/utils';
import type {TransactionSpan} from 'sentry/utils/profiling/hooks/useTransaction';
import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
import type {Profile} from 'sentry/utils/profiling/profile/profile';
import {FlamegraphRenderer2D} from 'sentry/utils/profiling/renderers/flamegraphRenderer2D';
Expand All @@ -66,6 +65,7 @@ import {
} from 'sentry/utils/profiling/units/units';
import {useDevicePixelRatio} from 'sentry/utils/useDevicePixelRatio';
import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
import {SpanFields} from 'sentry/views/insights/types';
import {useProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
import {
useProfiles,
Expand All @@ -82,39 +82,21 @@ import {FlamegraphUIFrames} from './flamegraphUIFrames';

const PROFILE_TYPE = 'continuous profile' as const;

function collectAllSpanEntriesFromTransaction(
transaction: EventTransaction
): EntrySpans['data'] {
if (!transaction.entries.length) {
return [];
}

const spans = transaction.entries.filter(
(e): e is EntrySpans => e.type === EntryType.SPANS
);

let allSpans: EntrySpans['data'] = [];

for (const span of spans) {
allSpans = allSpans.concat(span.data);
}

return allSpans;
}

function getMaxConfigSpace(
profileGroup: ProfileGroup,
transaction: EventTransaction | null,
transactionSpan: TransactionSpan | undefined,
unit: ProfilingFormatterUnit | string,
[start, end]: readonly [number, number] | readonly [null, null]
): Rect {
const maxProfileDuration = Math.max(...profileGroup.profiles.map(p => p.duration));
const spaceDuration = start !== null && end !== null ? end - start : 0;

if (transaction) {
if (transactionSpan) {
// TODO: Adjust the alignment based on the profile's timestamp if it does
// not match the transaction's start timestamp
const transactionDuration = transaction.endTimestamp - transaction.startTimestamp;
const transactionDuration =
transactionSpan[SpanFields.PRECISE_FINISH_TS] -
transactionSpan[SpanFields.PRECISE_START_TS];
// On most platforms, profile duration < transaction duration, however
// there is one beloved platform where that is not true; android.
// Hence, we should take the max of the two to ensure both the transaction
Expand Down Expand Up @@ -144,16 +126,16 @@ function getProfileOffset(
}

function getTransactionOffset(
transaction: EventTransaction | null,
transactionSpan: TransactionSpan | undefined,
profileTimestamp: number,
startedAtMs: number | null
): Rect {
if (!transaction || !startedAtMs) {
if (!transactionSpan || !startedAtMs) {
return Rect.Empty();
}

return new Rect(
transaction.startTimestamp * 1e3 - profileTimestamp - startedAtMs,
transactionSpan[SpanFields.PRECISE_START_TS] * 1e3 - profileTimestamp - startedAtMs,
0,
0,
0
Expand Down Expand Up @@ -256,7 +238,7 @@ export function ContinuousFlamegraph(): ReactElement {

const profiles = useProfiles();
const profileGroup = useProfileGroup();
const segment = useProfileTransaction();
const transactionResult = useProfileTransaction();

const profileTimestamp = useMemo(() => {
return (
Expand Down Expand Up @@ -335,19 +317,15 @@ export function ContinuousFlamegraph(): ReactElement {
}, [profileGroup, flamegraphProfiles.threadId]);

const spanTree = useMemo(() => {
if (segment.type === 'empty') {
return null;
}

if (segment.type === 'resolved' && segment.data) {
if (!transactionResult.isPending && transactionResult.data.transactionSpan) {
return new SpanTree(
segment.data,
collectAllSpanEntriesFromTransaction(segment.data)
transactionResult.data.transactionSpan,
transactionResult.data.childSpans
);
}

return LOADING_OR_FALLBACK_SPAN_TREE;
}, [segment]);
}, [transactionResult]);

const spanChart = useMemo(() => {
if (!profile || !spanTree) {
Expand All @@ -358,12 +336,12 @@ export function ContinuousFlamegraph(): ReactElement {
unit: profile.unit,
configSpace: getMaxConfigSpace(
profileGroup,
segment.type === 'resolved' ? segment.data : null,
transactionResult.data.transactionSpan,
profile.unit,
configSpaceQueryParam
),
});
}, [spanTree, profile, profileGroup, segment, configSpaceQueryParam]);
}, [spanTree, profile, profileGroup, transactionResult, configSpaceQueryParam]);

const flamegraph = useMemo(() => {
if (typeof flamegraphProfiles.threadId !== 'number') {
Expand Down Expand Up @@ -392,7 +370,7 @@ export function ContinuousFlamegraph(): ReactElement {
sort: sorting,
configSpace: getMaxConfigSpace(
profileGroup,
segment.type === 'resolved' ? segment.data : null,
transactionResult.data.transactionSpan,
profile.unit,
configSpaceQueryParam
),
Expand All @@ -407,7 +385,7 @@ export function ContinuousFlamegraph(): ReactElement {
sorting,
flamegraphProfiles.threadId,
view,
segment,
transactionResult,
configSpaceQueryParam,
]);

Expand Down Expand Up @@ -692,7 +670,7 @@ export function ContinuousFlamegraph(): ReactElement {
flamegraphCanvas,
flamegraphTheme,
profile,
segment,
transactionResult,
configSpaceQueryParam,
]
);
Expand Down Expand Up @@ -884,7 +862,7 @@ export function ContinuousFlamegraph(): ReactElement {
barHeight: flamegraphTheme.SIZES.SPANS_BAR_HEIGHT,
depthOffset: flamegraphTheme.SIZES.SPANS_DEPTH_OFFSET,
configSpaceTransform: getTransactionOffset(
segment.type === 'resolved' ? segment.data : null,
transactionResult.data.transactionSpan,
profileTimestamp,
configSpaceQueryParam[0]
),
Expand All @@ -906,7 +884,7 @@ export function ContinuousFlamegraph(): ReactElement {
flamegraphTheme.SIZES,
profileTimestamp,
configSpaceQueryParam,
segment,
transactionResult,
]
);

Expand Down Expand Up @@ -1554,7 +1532,7 @@ export function ContinuousFlamegraph(): ReactElement {
setSpansCanvasRef={setSpansCanvasRef}
canvasPoolManager={canvasPoolManager}
spansView={spansView}
spansRequestState={segment}
spansRequestState={transactionResult}
/>
) : null
}
Expand Down Expand Up @@ -1596,7 +1574,6 @@ export function ContinuousFlamegraph(): ReactElement {
}
flamegraphDrawer={
<FlamegraphDrawer
profileTransaction={null}
profileGroup={profileGroup}
getFrameColor={getFrameColor}
referenceNode={referenceNode}
Expand Down
35 changes: 21 additions & 14 deletions static/app/components/profiling/flamegraph/flamegraph.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {ProjectFixture} from 'sentry-fixture/project';

import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import {setWindowLocation} from 'sentry-test/utils';

import {ProjectsStore} from 'sentry/stores/projectsStore';
import {FlamegraphRendererDOM as mockFlameGraphRenderer} from 'sentry/utils/profiling/renderers/testUtils';
Expand Down Expand Up @@ -90,7 +89,6 @@ describe('Flamegraph', () => {
beforeEach(() => {
const project = ProjectFixture({slug: 'foo-project'});
act(() => ProjectsStore.loadInitialData([project]));
setWindowLocation('http://localhost/');
});

it('renders a missing profile', async () => {
Expand All @@ -99,6 +97,11 @@ describe('Flamegraph', () => {
statusCode: 404,
});

MockApiClient.addMockResponse({
url: '/organizations/org-slug/events/',
body: {data: []},
});

render(<ProfilesAndTransactionProvider />, {
initialRouterConfig: {
location: {
Expand Down Expand Up @@ -128,8 +131,8 @@ describe('Flamegraph', () => {
});

MockApiClient.addMockResponse({
url: `/projects/org-slug/foo-project/events/${flamechart.transaction.id}/`,
statusCode: 404,
url: '/organizations/org-slug/events/',
body: {data: []},
});

render(<ProfilesAndTransactionProvider />, {
Expand Down Expand Up @@ -162,18 +165,21 @@ describe('Flamegraph', () => {
});

MockApiClient.addMockResponse({
url: `/projects/org-slug/foo-project/events/${flamechart.transaction.id}/`,
statusCode: 404,
url: '/organizations/org-slug/events/',
body: {data: []},
});

setWindowLocation(
'http://localhost/?colorCoding=by+library&query=&sorting=alphabetical&tid=0&view=bottom+up'
);

render(<ProfilesAndTransactionProvider />, {
initialRouterConfig: {
location: {
pathname: '/explore/profiling/profile/foo-project/profile-id/flamegraph/',
query: {
colorCoding: 'by library',
query: '',
sorting: 'alphabetical',
tid: '0',
view: 'bottom up',
},
},
route: '/explore/profiling/profile/:projectId/:eventId/',
children: [
Expand Down Expand Up @@ -202,16 +208,17 @@ describe('Flamegraph', () => {
});

MockApiClient.addMockResponse({
url: `/projects/org-slug/foo-project/events/${flamechart.transaction.id}/`,
statusCode: 404,
url: '/organizations/org-slug/events/',
body: {data: []},
});

setWindowLocation('http://localhost/?query=profiling+transaction');

render(<ProfilesAndTransactionProvider />, {
initialRouterConfig: {
location: {
pathname: '/explore/profiling/profile/foo-project/profile-id/flamegraph/',
query: {
query: 'profiling transaction',
},
},
route: '/explore/profiling/profile/:projectId/:eventId/',
children: [
Expand Down
Loading
Loading