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