11/* eslint-disable complexity */
2- import { getCurrentHub } from '@sentry/core' ;
32import type { Transaction } from '@sentry/types' ;
43import { logger , uuid4 } from '@sentry/utils' ;
54
65import { WINDOW } from '../helpers' ;
7- import type { JSSelfProfile , JSSelfProfiler , JSSelfProfilerConstructor } from './jsSelfProfiling' ;
8- import { addProfileToMap , isValidSampleRate } from './utils' ;
9-
10- export const MAX_PROFILE_DURATION_MS = 30_000 ;
11- // Keep a flag value to avoid re-initializing the profiler constructor. If it fails
12- // once, it will always fail and this allows us to early return.
13- let PROFILING_CONSTRUCTOR_FAILED = false ;
14-
15- /**
16- * Check if profiler constructor is available.
17- * @param maybeProfiler
18- */
19- function isJSProfilerSupported ( maybeProfiler : unknown ) : maybeProfiler is typeof JSSelfProfilerConstructor {
20- return typeof maybeProfiler === 'function' ;
21- }
6+ import type { JSSelfProfile } from './jsSelfProfiling' ;
7+ import {
8+ addProfileToGlobalCache ,
9+ MAX_PROFILE_DURATION_MS ,
10+ shouldProfileTransaction ,
11+ startJSSelfProfile ,
12+ } from './utils' ;
2213
2314/**
2415 * Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported -
@@ -35,98 +26,24 @@ export function onProfilingStartRouteTransaction(transaction: Transaction | unde
3526 return transaction ;
3627 }
3728
38- return wrapTransactionWithProfiling ( transaction ) ;
29+ if ( shouldProfileTransaction ( transaction ) ) {
30+ return startProfileForTransaction ( transaction ) ;
31+ }
32+
33+ return transaction ;
3934}
4035
4136/**
4237 * Wraps startTransaction and stopTransaction with profiling related logic.
43- * startProfiling is called after the call to startTransaction in order to avoid our own code from
38+ * startProfileForTransaction is called after the call to startTransaction in order to avoid our own code from
4439 * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
4540 */
46- export function wrapTransactionWithProfiling ( transaction : Transaction ) : Transaction {
47- // Feature support check first
48- const JSProfilerConstructor = WINDOW . Profiler ;
49-
50- if ( ! isJSProfilerSupported ( JSProfilerConstructor ) ) {
51- if ( __DEBUG_BUILD__ ) {
52- logger . log (
53- '[Profiling] Profiling is not supported by this browser, Profiler interface missing on window object.' ,
54- ) ;
55- }
56- return transaction ;
57- }
58-
59- // If constructor failed once, it will always fail, so we can early return.
60- if ( PROFILING_CONSTRUCTOR_FAILED ) {
61- if ( __DEBUG_BUILD__ ) {
62- logger . log ( '[Profiling] Profiling has been disabled for the duration of the current user session.' ) ;
63- }
64- return transaction ;
65- }
66-
67- const client = getCurrentHub ( ) . getClient ( ) ;
68- const options = client && client . getOptions ( ) ;
69- if ( ! options ) {
70- __DEBUG_BUILD__ && logger . log ( '[Profiling] Profiling disabled, no options found.' ) ;
71- return transaction ;
72- }
41+ export function startProfileForTransaction ( transaction : Transaction ) : Transaction {
42+ // Start the profiler and get the profiler instance.
43+ const profiler = startJSSelfProfile ( ) ;
7344
74- // @ts -expect-error profilesSampleRate is not part of the browser options yet
75- const profilesSampleRate : number | boolean | undefined = options . profilesSampleRate ;
76-
77- // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
78- // only valid values are booleans or numbers between 0 and 1.)
79- if ( ! isValidSampleRate ( profilesSampleRate ) ) {
80- __DEBUG_BUILD__ && logger . warn ( '[Profiling] Discarding profile because of invalid sample rate.' ) ;
81- return transaction ;
82- }
83-
84- // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped
85- if ( ! profilesSampleRate ) {
86- __DEBUG_BUILD__ &&
87- logger . log (
88- '[Profiling] Discarding profile because a negative sampling decision was inherited or profileSampleRate is set to 0' ,
89- ) ;
90- return transaction ;
91- }
92-
93- // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
94- // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
95- const sampled = profilesSampleRate === true ? true : Math . random ( ) < profilesSampleRate ;
96- // Check if we should sample this profile
97- if ( ! sampled ) {
98- __DEBUG_BUILD__ &&
99- logger . log (
100- `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${ Number (
101- profilesSampleRate ,
102- ) } )`,
103- ) ;
104- return transaction ;
105- }
106-
107- // From initial testing, it seems that the minimum value for sampleInterval is 10ms.
108- const samplingIntervalMS = 10 ;
109- // Start the profiler
110- const maxSamples = Math . floor ( MAX_PROFILE_DURATION_MS / samplingIntervalMS ) ;
111- let profiler : JSSelfProfiler | undefined ;
112-
113- // Attempt to initialize the profiler constructor, if it fails, we disable profiling for the current user session.
114- // This is likely due to a missing 'Document-Policy': 'js-profiling' header. We do not want to throw an error if this happens
115- // as we risk breaking the user's application, so just disable profiling and log an error.
116- try {
117- profiler = new JSProfilerConstructor ( { sampleInterval : samplingIntervalMS , maxBufferSize : maxSamples } ) ;
118- } catch ( e ) {
119- if ( __DEBUG_BUILD__ ) {
120- logger . log (
121- "[Profiling] Failed to initialize the Profiling constructor, this is likely due to a missing 'Document-Policy': 'js-profiling' header." ,
122- ) ;
123- logger . log ( '[Profiling] Disabling profiling for current user session.' ) ;
124- }
125- PROFILING_CONSTRUCTOR_FAILED = true ;
126- }
127-
128- // We failed to construct the profiler, fallback to original transaction - there is no need to log
129- // anything as we already did that in the try/catch block.
45+ // We failed to construct the profiler, fallback to original transaction.
46+ // No need to log anything as this has already been logged in startProfile.
13047 if ( ! profiler ) {
13148 return transaction ;
13249 }
@@ -172,19 +89,9 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
17289 return null ;
17390 }
17491
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-
18392 return profiler
18493 . stop ( )
185- . then ( ( p : JSSelfProfile ) : null => {
186- stopProfilerSpan . finish ( ) ;
187-
94+ . then ( ( profile : JSSelfProfile ) : null => {
18895 if ( maxDurationTimeoutID ) {
18996 WINDOW . clearTimeout ( maxDurationTimeoutID ) ;
19097 maxDurationTimeoutID = undefined ;
@@ -195,7 +102,7 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
195102 }
196103
197104 // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
198- if ( ! p ) {
105+ if ( ! profile ) {
199106 if ( __DEBUG_BUILD__ ) {
200107 logger . log (
201108 `[Profiling] profiler returned null profile for: ${ transaction . name || transaction . description } ` ,
@@ -205,11 +112,10 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
205112 return null ;
206113 }
207114
208- addProfileToMap ( profileId , p ) ;
115+ addProfileToGlobalCache ( profileId , profile ) ;
209116 return null ;
210117 } )
211118 . catch ( error => {
212- stopProfilerSpan . finish ( ) ;
213119 if ( __DEBUG_BUILD__ ) {
214120 logger . log ( '[Profiling] error while stopping profiler:' , error ) ;
215121 }
0 commit comments