Skip to content

Commit add811b

Browse files
committed
feat(profiling): allow collecting of profiles that started before sentry had loaded
1 parent fdaf5f6 commit add811b

File tree

5 files changed

+89
-16
lines changed

5 files changed

+89
-16
lines changed

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ export type { SpanStatusType } from '@sentry/core';
5353
export type { Span } from '@sentry/types';
5454
export { makeBrowserOfflineTransport } from './transports/offline';
5555
export { onProfilingStartRouteTransaction } from './profiling/hubextensions';
56+
export type {JSSelfProfiler} from "./profiling/jsSelfProfiling"
5657
export { BrowserProfilingIntegration } from './profiling/integration';

packages/browser/src/profiling/hubextensions.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,19 +172,9 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
172172
return null;
173173
}
174174

175-
// This is temporary - we will use the collected span data to evaluate
176-
// if deferring txn.finish until profiler resolves is a viable approach.
177-
const stopProfilerSpan = transaction.startChild({
178-
description: 'profiler.stop',
179-
op: 'profiler',
180-
origin: 'auto.profiler.browser',
181-
});
182-
183175
return profiler
184176
.stop()
185177
.then((p: JSSelfProfile): null => {
186-
stopProfilerSpan.finish();
187-
188178
if (maxDurationTimeoutID) {
189179
WINDOW.clearTimeout(maxDurationTimeoutID);
190180
maxDurationTimeoutID = undefined;
@@ -209,7 +199,6 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
209199
return null;
210200
})
211201
.catch(error => {
212-
stopProfilerSpan.finish();
213202
if (__DEBUG_BUILD__) {
214203
logger.log('[Profiling] error while stopping profiler:', error);
215204
}

packages/browser/src/profiling/integration.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type { EventProcessor, Hub, Integration, Transaction } from '@sentry/type
22
import type { Profile } from '@sentry/types/src/profiling';
33
import { logger } from '@sentry/utils';
44

5-
import type { BrowserClient } from './../client';
65
import { wrapTransactionWithProfiling } from './hubextensions';
7-
import type { ProfiledEvent } from './utils';
6+
import { getAutomatedPageLoadProfile, ProfiledEvent, addProfileToMap, AUTOMATED_PAGELOAD_PROFILE_ID } from './utils';
7+
import { getMainCarrier } from '@sentry/core';
8+
import { JSSelfProfile } from '../../build/npm/types/profiling/jsSelfProfiling';
89
import {
910
addProfilesToEnvelope,
1011
createProfilingEvent,
12+
isAutomatedPageLoadTransaction,
1113
findProfiledTransactionsFromEnvelope,
1214
PROFILE_MAP,
1315
} from './utils';
@@ -37,13 +39,58 @@ export class BrowserProfilingIntegration implements Integration {
3739
*/
3840
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
3941
this.getCurrentHub = getCurrentHub;
40-
const client = this.getCurrentHub().getClient() as BrowserClient;
42+
43+
const hub = this.getCurrentHub();
44+
const client = hub.getClient();
45+
const carrier = getMainCarrier();
4146

4247
if (client && typeof client.on === 'function') {
4348
client.on('startTransaction', (transaction: Transaction) => {
4449
wrapTransactionWithProfiling(transaction);
4550
});
4651

52+
// If a pageload profile exists, attach finishTransaction handler and set profile_id to the reserved
53+
// automated page load profile id so that it will get picked up by the beforeEnvelope hook.
54+
const pageLoadProfile = getAutomatedPageLoadProfile(carrier);
55+
if (pageLoadProfile) {
56+
client.on('finishTransaction', (transaction: Transaction) => {
57+
if (!isAutomatedPageLoadTransaction(transaction)) {
58+
return;
59+
}
60+
61+
transaction.setContext('profile', { profile_id: AUTOMATED_PAGELOAD_PROFILE_ID });
62+
pageLoadProfile
63+
.stop()
64+
.then((p: JSSelfProfile): null => {
65+
if (__DEBUG_BUILD__) {
66+
logger.log(
67+
`[Profiling] stopped profiling of transaction: ${transaction.name || transaction.description}`,
68+
);
69+
}
70+
71+
// In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
72+
if (!p) {
73+
if (__DEBUG_BUILD__) {
74+
logger.log(
75+
`[Profiling] profiler returned null profile for: ${transaction.name || transaction.description}`,
76+
'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started',
77+
);
78+
}
79+
return null;
80+
}
81+
82+
addProfileToMap(AUTOMATED_PAGELOAD_PROFILE_ID, p);
83+
return null;
84+
})
85+
.catch(error => {
86+
if (__DEBUG_BUILD__) {
87+
logger.log('[Profiling] error while stopping profiler:', error);
88+
}
89+
return null;
90+
});
91+
});
92+
}
93+
4794
client.on('beforeEnvelope', (envelope): void => {
4895
// if not profiles are in queue, there is nothing to add to the envelope.
4996
if (!PROFILE_MAP['size']) {
@@ -59,7 +106,13 @@ export class BrowserProfilingIntegration implements Integration {
59106

60107
for (const profiledTransaction of profiledTransactionEvents) {
61108
const context = profiledTransaction && profiledTransaction.contexts;
62-
const profile_id = context && context['profile'] && (context['profile']['profile_id'] as string);
109+
const profile_id = context && context['profile'] && context['profile']['profile_id'];
110+
111+
if (typeof profile_id !== "string") {
112+
__DEBUG_BUILD__ &&
113+
logger.log('[Profiling] cannot find profile for a transaction without a profile context');
114+
continue;
115+
}
63116

64117
if (!profile_id) {
65118
__DEBUG_BUILD__ &&

packages/browser/src/profiling/utils.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
/* eslint-disable max-lines */
22

3-
import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core';
3+
import { Carrier, DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core';
44
import type { DebugImage, Envelope, Event, StackFrame, StackParser } from '@sentry/types';
55
import type { Profile, ThreadCpuProfile } from '@sentry/types/src/profiling';
66
import { browserPerformanceTimeOrigin, forEachEnvelopeItem, GLOBAL_OBJ, logger, uuid4 } from '@sentry/utils';
77

88
import { WINDOW } from '../helpers';
99
import type { JSSelfProfile, JSSelfProfileStack } from './jsSelfProfiling';
10+
import type { Transaction } from '@sentry/types';
11+
import { JSSelfProfiler } from '../../build/npm/types/profiling/jsSelfProfiling';
1012

1113
const MS_TO_NS = 1e6;
1214
// Use 0 as main thread id which is identical to threadId in node:worker_threads
1315
// where main logs 0 and workers seem to log in increments of 1
1416
const THREAD_ID_STRING = String(0);
1517
const THREAD_NAME = 'main';
18+
export const AUTOMATED_PAGELOAD_PROFILE_ID = "auto.pageload.browser"
1619

1720
// Machine properties (eval only once)
1821
let OS_PLATFORM = '';
@@ -189,6 +192,29 @@ export function isProfiledTransactionEvent(event: Event): event is ProfiledEvent
189192
return !!(event.sdkProcessingMetadata && event.sdkProcessingMetadata['profile']);
190193
}
191194

195+
export function getAutomatedPageLoadProfile(carrier: Carrier): JSSelfProfiler | undefined {
196+
const __SENTRY__ = carrier.__SENTRY__;
197+
if (
198+
__SENTRY__ &&
199+
__SENTRY__.profiling &&
200+
__SENTRY__.profiling.profiles
201+
) {
202+
const profile = __SENTRY__.profiling.profiles[AUTOMATED_PAGELOAD_PROFILE_ID];
203+
__SENTRY__.profiling.profiles[AUTOMATED_PAGELOAD_PROFILE_ID] = undefined;
204+
return profile;
205+
}
206+
207+
return undefined
208+
}
209+
210+
/*
211+
See packages/tracing-internal/src/browser/router.ts
212+
*/
213+
export function isAutomatedPageLoadTransaction(transaction: Transaction): boolean {
214+
// @ts-expect-error origin seems untyped
215+
return transaction.op === 'pageload' && transaction.origin === AUTOMATED_PAGELOAD_PROFILE_ID
216+
}
217+
192218
/**
193219
* Converts a JSSelfProfile to a our sampled format.
194220
* Does not currently perform stack indexing.

packages/core/src/hub.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { consoleSandbox, dateTimestampInSeconds, getGlobalSingleton, GLOBAL_OBJ,
2525
import { DEFAULT_ENVIRONMENT } from './constants';
2626
import { Scope } from './scope';
2727
import { closeSession, makeSession, updateSession } from './session';
28+
import type { JSSelfProfiler } from '@sentry/browser';
2829

2930
/**
3031
* API compatibility version of this hub.
@@ -89,6 +90,9 @@ export interface Carrier {
8990
// eslint-disable-next-line @typescript-eslint/ban-types
9091
[key: string]: Function;
9192
};
93+
profiling?: {
94+
profiles?: Record<"auto.pageload.browser", JSSelfProfiler>;
95+
}
9296
};
9397
}
9498

0 commit comments

Comments
 (0)