1
1
/* eslint-disable complexity */
2
- import { getCurrentHub } from '@sentry/core' ;
3
2
import type { Transaction } from '@sentry/types' ;
4
3
import { logger , uuid4 } from '@sentry/utils' ;
5
4
6
5
import { 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' ;
22
13
23
14
/**
24
15
* 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
35
26
return transaction ;
36
27
}
37
28
38
- return wrapTransactionWithProfiling ( transaction ) ;
29
+ if ( shouldProfileTransaction ( transaction ) ) {
30
+ return startProfileForTransaction ( transaction ) ;
31
+ }
32
+
33
+ return transaction ;
39
34
}
40
35
41
36
/**
42
37
* 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
44
39
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
45
40
*/
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 ( ) ;
73
44
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.
130
47
if ( ! profiler ) {
131
48
return transaction ;
132
49
}
@@ -172,19 +89,9 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
172
89
return null ;
173
90
}
174
91
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
-
183
92
return profiler
184
93
. stop ( )
185
- . then ( ( p : JSSelfProfile ) : null => {
186
- stopProfilerSpan . finish ( ) ;
187
-
94
+ . then ( ( profile : JSSelfProfile ) : null => {
188
95
if ( maxDurationTimeoutID ) {
189
96
WINDOW . clearTimeout ( maxDurationTimeoutID ) ;
190
97
maxDurationTimeoutID = undefined ;
@@ -195,7 +102,7 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
195
102
}
196
103
197
104
// In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
198
- if ( ! p ) {
105
+ if ( ! profile ) {
199
106
if ( __DEBUG_BUILD__ ) {
200
107
logger . log (
201
108
`[Profiling] profiler returned null profile for: ${ transaction . name || transaction . description } ` ,
@@ -205,11 +112,10 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact
205
112
return null ;
206
113
}
207
114
208
- addProfileToMap ( profileId , p ) ;
115
+ addProfileToGlobalCache ( profileId , profile ) ;
209
116
return null ;
210
117
} )
211
118
. catch ( error => {
212
- stopProfilerSpan . finish ( ) ;
213
119
if ( __DEBUG_BUILD__ ) {
214
120
logger . log ( '[Profiling] error while stopping profiler:' , error ) ;
215
121
}
0 commit comments