@@ -15,7 +15,7 @@ const {
15
15
16
16
const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require ( '../webspan-utils' )
17
17
18
- const beforeCh = dc . channel ( 'dd-trace:storage:before' )
18
+ let beforeCh
19
19
const enterCh = dc . channel ( 'dd-trace:storage:enter' )
20
20
const spanFinishCh = dc . channel ( 'dd-trace:span:finish' )
21
21
const profilerTelemetryMetrics = telemetryMetrics . manager . namespace ( 'profilers' )
@@ -29,40 +29,94 @@ function getActiveSpan () {
29
29
return store && store . span
30
30
}
31
31
32
+ function updateContext ( context ) {
33
+ // Converting spanIDs to strings is not necessary as generateLabels will do it
34
+ // too. When we don't use async context frame, we can convert them when the
35
+ // sample is taken though so we amortize the latency of operations. It is an
36
+ // optimization.
37
+ if ( typeof context . spanId === 'object' ) {
38
+ context . spanId = context . spanId . toString ( 10 )
39
+ }
40
+ if ( typeof context . rootSpanId === 'object' ) {
41
+ context . rootSpanId = context . rootSpanId . toString ( 10 )
42
+ }
43
+ if ( context . webTags !== undefined && context . endpoint === undefined ) {
44
+ // endpoint may not be determined yet, but keep it as fallback
45
+ // if tags are not available anymore during serialization
46
+ context . endpoint = endpointNameFromTags ( context . webTags )
47
+ }
48
+ }
49
+
32
50
let channelsActivated = false
33
- function ensureChannelsActivated ( ) {
51
+ function ensureChannelsActivated ( useAsyncContextFrame ) {
34
52
if ( channelsActivated ) return
35
53
36
- const { AsyncLocalStorage, createHook } = require ( 'async_hooks' )
37
54
const shimmer = require ( '../../../../datadog-shimmer' )
38
55
39
- createHook ( { before : ( ) => beforeCh . publish ( ) } ) . enable ( )
56
+ let interceptingAsyncContextFrameSet = false
57
+ // When using the async context frame to store sample context (available with
58
+ // Node 24), we do not need to use the async hooks anymore.
59
+ if ( ! useAsyncContextFrame ) {
60
+ const { createHook } = require ( 'async_hooks' )
61
+ beforeCh = dc . channel ( 'dd-trace:storage:before' )
62
+ createHook ( { before : ( ) => beforeCh . publish ( ) } ) . enable ( )
63
+ } else if ( process . execArgv . includes ( '--expose-internals' ) ) {
64
+ const AsyncContextFrame = require ( 'internal/async_context_frame' )
65
+ shimmer . wrap ( AsyncContextFrame , 'set' , function ( original ) {
66
+ return function ( frame ) {
67
+ original . call ( this , frame )
68
+ enterCh . publish ( )
69
+ }
70
+ } )
71
+ interceptingAsyncContextFrameSet = true
72
+ }
40
73
41
- let inRun = false
42
- shimmer . wrap ( AsyncLocalStorage . prototype , 'enterWith' , function ( original ) {
43
- return function ( ...args ) {
44
- const retVal = original . apply ( this , args )
45
- if ( ! inRun ) enterCh . publish ( )
46
- return retVal
47
- }
48
- } )
74
+ if ( ! interceptingAsyncContextFrameSet ) {
75
+ const { AsyncLocalStorage, AsyncResource } = require ( 'async_hooks' )
49
76
50
- shimmer . wrap ( AsyncLocalStorage . prototype , 'run' , function ( original ) {
51
- return function ( store , callback , ...args ) {
52
- const wrappedCb = shimmer . wrapFunction ( callback , cb => function ( ...args ) {
53
- inRun = false
77
+ let inRun = false
78
+ shimmer . wrap ( AsyncLocalStorage . prototype , 'enterWith' , function ( original ) {
79
+ return function ( ...args ) {
80
+ const retVal = original . apply ( this , args )
81
+ if ( ! inRun ) enterCh . publish ( )
82
+ return retVal
83
+ }
84
+ } )
85
+
86
+ shimmer . wrap ( AsyncLocalStorage . prototype , 'run' , function ( original ) {
87
+ return function ( store , callback , ...args ) {
88
+ const wrappedCb = shimmer . wrapFunction ( callback , cb => function ( ...args ) {
89
+ inRun = false
90
+ enterCh . publish ( )
91
+ const retVal = cb . apply ( this , args )
92
+ inRun = true
93
+ return retVal
94
+ } )
95
+ inRun = true
96
+ const retVal = original . call ( this , store , wrappedCb , ...args )
54
97
enterCh . publish ( )
55
- const retVal = cb . apply ( this , args )
98
+ inRun = false
99
+ return retVal
100
+ }
101
+ } )
102
+
103
+ shimmer . wrap ( AsyncResource . prototype , 'runInAsyncScope' , function ( original ) {
104
+ return function ( callback , thisArg , ...args ) {
105
+ const wrappedCb = shimmer . wrapFunction ( callback , cb => function ( ...args ) {
106
+ inRun = false
107
+ enterCh . publish ( )
108
+ const retVal = cb . apply ( this , args )
109
+ inRun = true
110
+ return retVal
111
+ } )
56
112
inRun = true
113
+ const retVal = original . call ( this , wrappedCb , thisArg , ...args )
114
+ enterCh . publish ( )
115
+ inRun = false
57
116
return retVal
58
- } )
59
- inRun = true
60
- const retVal = original . call ( this , store , wrappedCb , ...args )
61
- enterCh . publish ( )
62
- inRun = false
63
- return retVal
64
- }
65
- } )
117
+ }
118
+ } )
119
+ }
66
120
67
121
channelsActivated = true
68
122
}
@@ -76,6 +130,8 @@ class NativeWallProfiler {
76
130
this . _endpointCollectionEnabled = ! ! options . endpointCollectionEnabled
77
131
this . _timelineEnabled = ! ! options . timelineEnabled
78
132
this . _cpuProfilingEnabled = ! ! options . cpuProfilingEnabled
133
+ this . _useAsyncContextFrame = ! ! options . useAsyncContextFrame
134
+
79
135
// We need to capture span data into the sample context for either code hotspots
80
136
// or endpoint collection.
81
137
this . _captureSpanData = this . _codeHotspotsEnabled || this . _endpointCollectionEnabled
@@ -131,19 +187,24 @@ class NativeWallProfiler {
131
187
withContexts : this . _withContexts ,
132
188
lineNumbers : false ,
133
189
workaroundV8Bug : this . _v8ProfilerBugWorkaroundEnabled ,
134
- collectCpuTime : this . _cpuProfilingEnabled
190
+ collectCpuTime : this . _cpuProfilingEnabled ,
191
+ useCPED : this . _useAsyncContextFrame
135
192
} )
136
193
137
194
if ( this . _withContexts ) {
138
- this . _setNewContext ( )
195
+ if ( ! this . _useAsyncContextFrame ) {
196
+ this . _setNewContext ( )
197
+ }
139
198
140
199
if ( this . _captureSpanData ) {
141
200
this . _profilerState = this . _pprof . time . getState ( )
142
201
this . _lastSampleCount = 0
143
202
144
- ensureChannelsActivated ( )
203
+ ensureChannelsActivated ( this . _useAsyncContextFrame )
145
204
146
- beforeCh . subscribe ( this . _enter )
205
+ if ( ! this . _useAsyncContextFrame ) {
206
+ beforeCh . subscribe ( this . _enter )
207
+ }
147
208
enterCh . subscribe ( this . _enter )
148
209
spanFinishCh . subscribe ( this . _spanFinished )
149
210
}
@@ -155,17 +216,37 @@ class NativeWallProfiler {
155
216
_enter ( ) {
156
217
if ( ! this . _started ) return
157
218
158
- const sampleCount = this . _profilerState [ kSampleCount ]
159
- if ( sampleCount !== this . _lastSampleCount ) {
160
- this . _lastSampleCount = sampleCount
161
- const context = this . _currentContext . ref
162
- this . _setNewContext ( )
219
+ const span = getActiveSpan ( )
220
+ const sampleContext = span ? this . _getProfilingContext ( span ) : { }
221
+
222
+ // Note that we store the sample context differently with and without the
223
+ // async context frame. With the async context frame, we tell the profiler
224
+ // to store the sample context directly in the frame on each enterWith.
225
+ // Without the async context frame, we store one holder object as the
226
+ // profiler's single sample context, and reassign its "ref" property on
227
+ // every async context change. Then when we detect that the profiler took a
228
+ // sample (and thus bound the holder as that sample's context), we create a
229
+ // new holder object so that we no longer mutate the old one. This is really
230
+ // an optimization to avoid going to profiler's native SetContext every
231
+ // time. With async context frame however, we can't have that optimization,
232
+ // as we can't tell from which async context frame was the sampling context
233
+ // taken. For the same reason we can't call updateContext() on the old
234
+ // context -- we simply can't tell which one it might've been across all
235
+ // possible async context frames.
236
+ if ( this . _useAsyncContextFrame ) {
237
+ this . _pprof . time . setContext ( sampleContext )
238
+ } else {
239
+ const sampleCount = this . _profilerState [ kSampleCount ]
240
+ if ( sampleCount !== this . _lastSampleCount ) {
241
+ this . _lastSampleCount = sampleCount
242
+ const context = this . _currentContext . ref
243
+ this . _setNewContext ( )
163
244
164
- this . _updateContext ( context )
165
- }
245
+ updateContext ( context )
246
+ }
166
247
167
- const span = getActiveSpan ( )
168
- this . _currentContext . ref = span ? this . _getProfilingContext ( span ) : { }
248
+ this . _currentContext . ref = sampleContext
249
+ }
169
250
}
170
251
171
252
_getProfilingContext ( span ) {
@@ -246,7 +327,7 @@ class NativeWallProfiler {
246
327
_stop ( restart ) {
247
328
if ( ! this . _started ) return
248
329
249
- if ( this . _captureSpanData ) {
330
+ if ( this . _captureSpanData && ! this . _useAsyncContextFrame ) {
250
331
// update last sample context if needed
251
332
this . _enter ( )
252
333
this . _lastSampleCount = 0
@@ -260,7 +341,9 @@ class NativeWallProfiler {
260
341
}
261
342
} else {
262
343
if ( this . _captureSpanData ) {
263
- beforeCh . unsubscribe ( this . _enter )
344
+ if ( ! this . _useAsyncContextFrame ) {
345
+ beforeCh . unsubscribe ( this . _enter )
346
+ }
264
347
enterCh . unsubscribe ( this . _enter )
265
348
spanFinishCh . unsubscribe ( this . _spanFinished )
266
349
this . _profilerState = undefined
@@ -297,19 +380,20 @@ class NativeWallProfiler {
297
380
}
298
381
299
382
// Native profiler doesn't set context.context for some samples, such as idle samples or when
300
- // the context was otherwise unavailable when the sample was taken.
301
- const ref = context . context ?. ref
383
+ // the context was otherwise unavailable when the sample was taken. Note that with async context
384
+ // frame, we don't use the "ref" indirection.
385
+ const ref = this . _useAsyncContextFrame ? context . context : context . context ?. ref
302
386
if ( typeof ref !== 'object' ) {
303
387
return labels
304
388
}
305
389
306
390
const { spanId, rootSpanId, webTags, endpoint } = ref
307
391
308
392
if ( spanId !== undefined ) {
309
- labels [ SPAN_ID_LABEL ] = spanId
393
+ labels [ SPAN_ID_LABEL ] = typeof spanId === 'object' ? spanId . toString ( 10 ) : spanId
310
394
}
311
395
if ( rootSpanId !== undefined ) {
312
- labels [ LOCAL_ROOT_SPAN_ID_LABEL ] = rootSpanId
396
+ labels [ LOCAL_ROOT_SPAN_ID_LABEL ] = typeof rootSpanId === 'object' ? rootSpanId . toString ( 10 ) : rootSpanId
313
397
}
314
398
if ( webTags !== undefined && Object . keys ( webTags ) . length !== 0 ) {
315
399
labels [ 'trace endpoint' ] = endpointNameFromTags ( webTags )
0 commit comments