1
- import { spanToJSON } from '@sentry/core' ;
2
- import type { Transaction } from '@sentry/types' ;
1
+ import { getCurrentScope , spanToJSON } from '@sentry/core' ;
2
+ import type { Span } from '@sentry/types' ;
3
3
import { logger , timestampInSeconds , uuid4 } from '@sentry/utils' ;
4
4
5
5
import { DEBUG_BUILD } from '../debug-build' ;
6
6
import { WINDOW } from '../helpers' ;
7
7
import type { JSSelfProfile } from './jsSelfProfiling' ;
8
- import {
9
- MAX_PROFILE_DURATION_MS ,
10
- addProfileToGlobalCache ,
11
- isAutomatedPageLoadTransaction ,
12
- startJSSelfProfile ,
13
- } from './utils' ;
8
+ import { MAX_PROFILE_DURATION_MS , addProfileToGlobalCache , isAutomatedPageLoadSpan , startJSSelfProfile } from './utils' ;
14
9
15
10
/**
16
11
* Wraps startTransaction and stopTransaction with profiling related logic.
17
12
* startProfileForTransaction is called after the call to startTransaction in order to avoid our own code from
18
13
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
19
14
*/
20
- export function startProfileForTransaction ( transaction : Transaction ) : Transaction {
15
+ export function startProfileForSpan ( span : Span ) : void {
21
16
// Start the profiler and get the profiler instance.
22
17
let startTimestamp : number | undefined ;
23
- if ( isAutomatedPageLoadTransaction ( transaction ) ) {
18
+ if ( isAutomatedPageLoadSpan ( span ) ) {
24
19
startTimestamp = timestampInSeconds ( ) * 1000 ;
25
20
}
26
21
27
22
const profiler = startJSSelfProfile ( ) ;
28
23
29
- // We failed to construct the profiler, fallback to original transaction .
24
+ // We failed to construct the profiler, so we skip .
30
25
// No need to log anything as this has already been logged in startProfile.
31
26
if ( ! profiler ) {
32
- return transaction ;
27
+ return ;
33
28
}
34
29
35
30
if ( DEBUG_BUILD ) {
36
- logger . log ( `[Profiling] started profiling transaction : ${ spanToJSON ( transaction ) . description } ` ) ;
31
+ logger . log ( `[Profiling] started profiling span : ${ spanToJSON ( span ) . description } ` ) ;
37
32
}
38
33
39
- // We create "unique" transaction names to avoid concurrent transactions with same names
40
- // from being ignored by the profiler. From here on, only this transaction name should be used when
34
+ // We create "unique" span names to avoid concurrent spans with same names
35
+ // from being ignored by the profiler. From here on, only this span name should be used when
41
36
// calling the profiler methods. Note: we log the original name to the user to avoid confusion.
42
37
const profileId = uuid4 ( ) ;
43
38
44
39
// A couple of important things to note here:
45
40
// `CpuProfilerBindings.stopProfiling` will be scheduled to run in 30seconds in order to exceed max profile duration.
46
- // Whichever of the two (transaction .finish/timeout) is first to run, the profiling will be stopped and the gathered profile
47
- // will be processed when the original transaction is finished. Since onProfileHandler can be invoked multiple times in the
48
- // event of an error or user mistake (calling transaction .finish multiple times), it is important that the behavior of onProfileHandler
41
+ // Whichever of the two (span .finish/timeout) is first to run, the profiling will be stopped and the gathered profile
42
+ // will be processed when the original span is finished. Since onProfileHandler can be invoked multiple times in the
43
+ // event of an error or user mistake (calling span .finish multiple times), it is important that the behavior of onProfileHandler
49
44
// is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler.
50
45
// After the original finish method is called, the event will be reported through the integration and delegated to transport.
51
46
const processedProfile : JSSelfProfile | null = null ;
52
47
48
+ getCurrentScope ( ) . setContext ( 'profile' , {
49
+ profile_id : profileId ,
50
+ start_timestamp : startTimestamp ,
51
+ } ) ;
52
+
53
53
/**
54
54
* Idempotent handler for profile stop
55
55
*/
56
- async function onProfileHandler ( ) : Promise < null > {
57
- // Check if the profile exists and return it the behavior has to be idempotent as users may call transaction .finish multiple times.
58
- if ( ! transaction ) {
59
- return null ;
56
+ async function onProfileHandler ( ) : Promise < void > {
57
+ // Check if the profile exists and return it the behavior has to be idempotent as users may call span .finish multiple times.
58
+ if ( ! span ) {
59
+ return ;
60
60
}
61
61
// Satisfy the type checker, but profiler will always be defined here.
62
62
if ( ! profiler ) {
63
- return null ;
63
+ return ;
64
64
}
65
65
if ( processedProfile ) {
66
66
if ( DEBUG_BUILD ) {
67
- logger . log ( '[Profiling] profile for:' , spanToJSON ( transaction ) . description , 'already exists, returning early' ) ;
67
+ logger . log ( '[Profiling] profile for:' , spanToJSON ( span ) . description , 'already exists, returning early' ) ;
68
68
}
69
- return null ;
69
+ return ;
70
70
}
71
71
72
72
return profiler
73
73
. stop ( )
74
- . then ( ( profile : JSSelfProfile ) : null => {
74
+ . then ( ( profile : JSSelfProfile ) : void => {
75
75
if ( maxDurationTimeoutID ) {
76
76
WINDOW . clearTimeout ( maxDurationTimeoutID ) ;
77
77
maxDurationTimeoutID = undefined ;
78
78
}
79
79
80
80
if ( DEBUG_BUILD ) {
81
- logger . log ( `[Profiling] stopped profiling of transaction : ${ spanToJSON ( transaction ) . description } ` ) ;
81
+ logger . log ( `[Profiling] stopped profiling of span : ${ spanToJSON ( span ) . description } ` ) ;
82
82
}
83
83
84
- // In case of an overlapping transaction , stopProfiling may return null and silently ignore the overlapping profile.
84
+ // In case of an overlapping span , stopProfiling may return null and silently ignore the overlapping profile.
85
85
if ( ! profile ) {
86
86
if ( DEBUG_BUILD ) {
87
87
logger . log (
88
- `[Profiling] profiler returned null profile for: ${ spanToJSON ( transaction ) . description } ` ,
89
- 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started' ,
88
+ `[Profiling] profiler returned null profile for: ${ spanToJSON ( span ) . description } ` ,
89
+ 'this may indicate an overlapping span or a call to stopProfiling with a profile title that was never started' ,
90
90
) ;
91
91
}
92
- return null ;
92
+ return ;
93
93
}
94
94
95
95
addProfileToGlobalCache ( profileId , profile ) ;
96
- return null ;
97
96
} )
98
97
. catch ( error => {
99
98
if ( DEBUG_BUILD ) {
100
99
logger . log ( '[Profiling] error while stopping profiler:' , error ) ;
101
100
}
102
- return null ;
103
101
} ) ;
104
102
}
105
103
106
104
// Enqueue a timeout to prevent profiles from running over max duration.
107
105
let maxDurationTimeoutID : number | undefined = WINDOW . setTimeout ( ( ) => {
108
106
if ( DEBUG_BUILD ) {
109
- logger . log (
110
- '[Profiling] max profile duration elapsed, stopping profiling for:' ,
111
- spanToJSON ( transaction ) . description ,
112
- ) ;
107
+ logger . log ( '[Profiling] max profile duration elapsed, stopping profiling for:' , spanToJSON ( span ) . description ) ;
113
108
}
114
- // If the timeout exceeds, we want to stop profiling, but not finish the transaction
109
+ // If the timeout exceeds, we want to stop profiling, but not finish the span
115
110
// eslint-disable-next-line @typescript-eslint/no-floating-promises
116
111
onProfileHandler ( ) ;
117
112
} , MAX_PROFILE_DURATION_MS ) ;
118
113
119
114
// We need to reference the original end call to avoid creating an infinite loop
120
- const originalEnd = transaction . end . bind ( transaction ) ;
115
+ const originalEnd = span . end . bind ( span ) ;
121
116
122
117
/**
123
- * Wraps startTransaction and stopTransaction with profiling related logic.
124
- * startProfiling is called after the call to startTransaction in order to avoid our own code from
125
- * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction .
118
+ * Wraps span `end()` with profiling related logic.
119
+ * startProfiling is called after the call to spanStart in order to avoid our own code from
120
+ * being profiled. Because of that same reason, stopProfiling is called before the call to spanEnd .
126
121
*/
127
- function profilingWrappedTransactionEnd ( ) : Transaction {
128
- if ( ! transaction ) {
122
+ function profilingWrappedSpanEnd ( ) : Span {
123
+ if ( ! span ) {
129
124
return originalEnd ( ) ;
130
125
}
131
126
// onProfileHandler should always return the same profile even if this is called multiple times.
132
127
// Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.
133
128
void onProfileHandler ( ) . then (
134
129
( ) => {
135
- // TODO: Can we rewrite this to use attributes?
136
- // eslint-disable-next-line deprecation/deprecation
137
- transaction . setContext ( 'profile' , { profile_id : profileId , start_timestamp : startTimestamp } ) ;
138
130
originalEnd ( ) ;
139
131
} ,
140
132
( ) => {
@@ -143,9 +135,8 @@ export function startProfileForTransaction(transaction: Transaction): Transactio
143
135
} ,
144
136
) ;
145
137
146
- return transaction ;
138
+ return span ;
147
139
}
148
140
149
- transaction . end = profilingWrappedTransactionEnd ;
150
- return transaction ;
141
+ span . end = profilingWrappedSpanEnd ;
151
142
}
0 commit comments