dd-trace 5.92.0 → 5.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -11
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
- package/packages/datadog-instrumentations/src/jest.js +118 -32
- package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
- package/packages/datadog-instrumentations/src/playwright.js +10 -0
- package/packages/datadog-instrumentations/src/vitest.js +119 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +11 -0
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-vitest/src/index.js +9 -0
- package/packages/datadog-webpack/index.js +187 -0
- package/packages/datadog-webpack/src/loader.js +27 -0
- package/packages/datadog-webpack/src/log.js +32 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +103 -32
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
- package/packages/dd-trace/src/config/supported-configurations.json +2 -2
- package/packages/dd-trace/src/crashtracking/index.js +7 -1
- package/packages/dd-trace/src/exporters/common/docker.js +1 -0
- package/packages/dd-trace/src/exporters/common/request.js +26 -17
- package/packages/dd-trace/src/opentracing/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/util/test.js +76 -0
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profiler.js +78 -47
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
- package/packages/dd-trace/src/proxy.js +4 -3
- package/packages/dd-trace/src/tracer_metadata.js +10 -1
- package/webpack.js +3 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require('events')
|
|
4
|
-
const { promisify } = require('util')
|
|
5
|
-
const zlib = require('zlib')
|
|
6
4
|
const dc = require('dc-polyfill')
|
|
7
5
|
const crashtracker = require('../crashtracking')
|
|
8
6
|
const log = require('../log')
|
|
@@ -34,6 +32,14 @@ function findWebSpan (startedSpans, spanId) {
|
|
|
34
32
|
return false
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
const MISSING_SOURCE_MAPS_TOKEN = 'dd:has-missing-map-files'
|
|
36
|
+
|
|
37
|
+
function profileHasMissingSourceMaps (profile) {
|
|
38
|
+
const strings = profile?.stringTable?.strings
|
|
39
|
+
if (!strings) return false
|
|
40
|
+
return profile.comment?.some(idx => strings[idx] === MISSING_SOURCE_MAPS_TOKEN) ?? false
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
function processInfo (infos, info, type) {
|
|
38
44
|
if (Object.keys(info).length > 0) {
|
|
39
45
|
infos[type] = info
|
|
@@ -42,6 +48,7 @@ function processInfo (infos, info, type) {
|
|
|
42
48
|
|
|
43
49
|
class Profiler extends EventEmitter {
|
|
44
50
|
#compressionFn
|
|
51
|
+
#compressionFnInitialized = false
|
|
45
52
|
#compressionOptions
|
|
46
53
|
#config
|
|
47
54
|
#enabled = false
|
|
@@ -50,7 +57,6 @@ class Profiler extends EventEmitter {
|
|
|
50
57
|
#logger
|
|
51
58
|
#profileSeq = 0
|
|
52
59
|
#spanFinishListener
|
|
53
|
-
#sourceMapCount = 0
|
|
54
60
|
#timer
|
|
55
61
|
|
|
56
62
|
constructor () {
|
|
@@ -116,11 +122,13 @@ class Profiler extends EventEmitter {
|
|
|
116
122
|
reportHostname,
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
try {
|
|
126
|
+
return this._start(options)
|
|
127
|
+
} catch (err) {
|
|
120
128
|
logError(logger, 'Error starting profiler. For troubleshooting tips, see ' +
|
|
121
129
|
'<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
|
|
122
130
|
return false
|
|
123
|
-
}
|
|
131
|
+
}
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
get enabled () {
|
|
@@ -131,7 +139,50 @@ class Profiler extends EventEmitter {
|
|
|
131
139
|
logError(this.#logger, err)
|
|
132
140
|
}
|
|
133
141
|
|
|
134
|
-
|
|
142
|
+
#getCompressionFn () {
|
|
143
|
+
if (!this.#compressionFnInitialized) {
|
|
144
|
+
this.#compressionFnInitialized = true
|
|
145
|
+
try {
|
|
146
|
+
const { promisify } = require('util')
|
|
147
|
+
const zlib = require('zlib')
|
|
148
|
+
const { method, level: clevel } = this.#config.uploadCompression
|
|
149
|
+
switch (method) {
|
|
150
|
+
case 'gzip':
|
|
151
|
+
this.#compressionFn = promisify(zlib.gzip)
|
|
152
|
+
if (clevel !== undefined) {
|
|
153
|
+
this.#compressionOptions = {
|
|
154
|
+
level: clevel,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break
|
|
158
|
+
case 'zstd':
|
|
159
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
160
|
+
if (typeof zlib.zstdCompress === 'function') {
|
|
161
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
162
|
+
this.#compressionFn = promisify(zlib.zstdCompress)
|
|
163
|
+
if (clevel !== undefined) {
|
|
164
|
+
this.#compressionOptions = {
|
|
165
|
+
params: {
|
|
166
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
167
|
+
[zlib.constants.ZSTD_c_compressionLevel]: clevel,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const zstdCompress = require('@datadog/libdatadog').load('datadog-js-zstd').zstd_compress
|
|
173
|
+
const level = clevel ?? 0 // 0 is zstd default compression level
|
|
174
|
+
this.#compressionFn = (buffer) => Promise.resolve(Buffer.from(zstdCompress(buffer, level)))
|
|
175
|
+
}
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
this.#logError(err)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return this.#compressionFn
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_start (options) {
|
|
135
186
|
if (this.enabled) return true
|
|
136
187
|
|
|
137
188
|
const config = this.#config = new Config(options)
|
|
@@ -148,45 +199,21 @@ class Profiler extends EventEmitter {
|
|
|
148
199
|
setLogger(config.logger)
|
|
149
200
|
|
|
150
201
|
if (config.sourceMap) {
|
|
151
|
-
mapper =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const clevel = config.uploadCompression.level
|
|
163
|
-
switch (config.uploadCompression.method) {
|
|
164
|
-
case 'gzip':
|
|
165
|
-
this.#compressionFn = promisify(zlib.gzip)
|
|
166
|
-
if (clevel !== undefined) {
|
|
167
|
-
this.#compressionOptions = {
|
|
168
|
-
level: clevel,
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
break
|
|
172
|
-
case 'zstd':
|
|
173
|
-
if (typeof zlib.zstdCompress === 'function') { // eslint-disable-line n/no-unsupported-features/node-builtins
|
|
174
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
175
|
-
this.#compressionFn = promisify(zlib.zstdCompress)
|
|
176
|
-
if (clevel !== undefined) {
|
|
177
|
-
this.#compressionOptions = {
|
|
178
|
-
params: {
|
|
179
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
180
|
-
[zlib.constants.ZSTD_c_compressionLevel]: clevel,
|
|
181
|
-
},
|
|
182
|
-
}
|
|
202
|
+
mapper = new SourceMapper(config.debugSourceMaps)
|
|
203
|
+
mapper.loadDirectory(process.cwd())
|
|
204
|
+
.then(() => {
|
|
205
|
+
if (config.debugSourceMaps) {
|
|
206
|
+
const count = mapper.infoMap.size
|
|
207
|
+
this.#logger.debug(() => {
|
|
208
|
+
return count === 0
|
|
209
|
+
? 'Found no source maps'
|
|
210
|
+
: `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
|
|
211
|
+
})
|
|
183
212
|
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
break
|
|
213
|
+
})
|
|
214
|
+
.catch((err) => {
|
|
215
|
+
this.#logError(err)
|
|
216
|
+
})
|
|
190
217
|
}
|
|
191
218
|
} catch (err) {
|
|
192
219
|
this.#logError(err)
|
|
@@ -295,7 +322,7 @@ class Profiler extends EventEmitter {
|
|
|
295
322
|
return {
|
|
296
323
|
serverless: this.serverless,
|
|
297
324
|
settings: this.#config.systemInfoReport,
|
|
298
|
-
|
|
325
|
+
hasMissingSourceMaps: false,
|
|
299
326
|
}
|
|
300
327
|
}
|
|
301
328
|
|
|
@@ -332,15 +359,19 @@ class Profiler extends EventEmitter {
|
|
|
332
359
|
|
|
333
360
|
const encodedProfiles = {}
|
|
334
361
|
const infos = this.#createInitialInfos()
|
|
362
|
+
const compressionFn = this.#getCompressionFn()
|
|
335
363
|
|
|
336
364
|
// encode and export asynchronously
|
|
337
365
|
await Promise.all(profiles.map(async ({ profiler, profile, info }) => {
|
|
338
366
|
try {
|
|
339
367
|
const encoded = await profiler.encode(profile)
|
|
340
|
-
const compressed = encoded instanceof Buffer &&
|
|
341
|
-
? await
|
|
368
|
+
const compressed = encoded instanceof Buffer && compressionFn !== undefined
|
|
369
|
+
? await compressionFn(encoded, this.#compressionOptions)
|
|
342
370
|
: encoded
|
|
343
371
|
encodedProfiles[profiler.type] = compressed
|
|
372
|
+
if (profileHasMissingSourceMaps(profile)) {
|
|
373
|
+
infos.hasMissingSourceMaps = true
|
|
374
|
+
}
|
|
344
375
|
processInfo(infos, info, profiler.type)
|
|
345
376
|
this.#logger.debug(() => {
|
|
346
377
|
const profileJson = JSON.stringify(profile, (_, value) => {
|
|
@@ -20,6 +20,7 @@ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
|
|
|
20
20
|
let beforeCh
|
|
21
21
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
22
22
|
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
23
|
+
const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
|
|
23
24
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
24
25
|
|
|
25
26
|
const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')
|
|
@@ -58,33 +59,30 @@ function ensureChannelsActivated (asyncContextFrameEnabled) {
|
|
|
58
59
|
if (channelsActivated) return
|
|
59
60
|
|
|
60
61
|
const shimmer = require('../../../../datadog-shimmer')
|
|
61
|
-
const asyncHooks = require('async_hooks')
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
const { createHook } = asyncHooks
|
|
67
|
-
beforeCh = dc.channel('dd-trace:storage:before')
|
|
68
|
-
createHook({ before: () => beforeCh.publish() }).enable()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { AsyncLocalStorage } = asyncHooks
|
|
72
|
-
|
|
73
|
-
// We need to instrument AsyncLocalStorage.enterWith() both with and without AsyncContextFrame.
|
|
63
|
+
// We need to instrument enterWith() on the legacy storage — that's the storage
|
|
64
|
+
// carrying span data and the only one the profiler cares about.
|
|
65
|
+
const legacyStorage = storage('legacy')
|
|
74
66
|
let inRun = false
|
|
75
|
-
shimmer.wrap(
|
|
76
|
-
return function (
|
|
77
|
-
const retVal = original.
|
|
67
|
+
shimmer.wrap(legacyStorage, 'enterWith', function (original) {
|
|
68
|
+
return function (store) {
|
|
69
|
+
const retVal = original.call(this, store)
|
|
78
70
|
if (!inRun) enterCh.publish()
|
|
79
71
|
return retVal
|
|
80
72
|
}
|
|
81
73
|
})
|
|
82
74
|
|
|
83
|
-
//
|
|
84
|
-
// AsyncContextFrame-based implementation of AsyncLocalStorage.run() delegates
|
|
85
|
-
// to AsyncLocalStorage.enterWith() so it doesn't need to be separately instrumented.
|
|
75
|
+
// When not using AsyncContextFrame, we need additional instrumentation.
|
|
86
76
|
if (!asyncContextFrameEnabled) {
|
|
87
|
-
|
|
77
|
+
// We need async_hooks.createHook to create a "before" callback.
|
|
78
|
+
const { createHook } = require('async_hooks')
|
|
79
|
+
beforeCh = dc.channel('dd-trace:storage:before')
|
|
80
|
+
createHook({ before: () => beforeCh.publish() }).enable()
|
|
81
|
+
|
|
82
|
+
// In ACF-based implementation run() delegates to enterWith() so it doesn't
|
|
83
|
+
// need to be separately instrumented. in non-ACF implementation run()
|
|
84
|
+
// doesn't delegate to enterWith(), so separate instrumentation is necessary.
|
|
85
|
+
shimmer.wrap(legacyStorage, 'run', function (original) {
|
|
88
86
|
return function (store, callback, ...args) {
|
|
89
87
|
const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
|
|
90
88
|
inRun = false
|
|
@@ -125,6 +123,7 @@ class NativeWallProfiler {
|
|
|
125
123
|
// Bind these to this so they can be used as callbacks
|
|
126
124
|
#boundEnter = this.#enter.bind(this)
|
|
127
125
|
#boundSpanFinished = this.#spanFinished.bind(this)
|
|
126
|
+
#boundSpanTagsUpdated = this.#spanTagsUpdated.bind(this)
|
|
128
127
|
#boundGenerateLabels = this._generateLabels.bind(this)
|
|
129
128
|
|
|
130
129
|
get type () { return 'wall' }
|
|
@@ -204,6 +203,9 @@ class NativeWallProfiler {
|
|
|
204
203
|
}
|
|
205
204
|
enterCh.subscribe(this.#boundEnter)
|
|
206
205
|
spanFinishCh.subscribe(this.#boundSpanFinished)
|
|
206
|
+
if (this.#endpointCollectionEnabled) {
|
|
207
|
+
tagsUpdateCh.subscribe(this.#boundSpanTagsUpdated)
|
|
208
|
+
}
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
@@ -290,15 +292,7 @@ class NativeWallProfiler {
|
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
profilingContext = { spanId, rootSpanId, webTags }
|
|
293
|
-
|
|
294
|
-
// the span's type hasn't been set yet. TracingPlugin.startSpan() calls
|
|
295
|
-
// enterWith() before the plugin sets span.type='web' via addRequestTags(),
|
|
296
|
-
// so the first enterCh event fires before the type is known. Without this
|
|
297
|
-
// guard we'd cache webTags=undefined and then serve that stale value on the
|
|
298
|
-
// subsequent activation (when span.type='web' is already set).
|
|
299
|
-
if (!this.#endpointCollectionEnabled || webTags !== undefined || context._tags['span.type']) {
|
|
300
|
-
span[ProfilingContext] = profilingContext
|
|
301
|
-
}
|
|
295
|
+
span[ProfilingContext] = profilingContext
|
|
302
296
|
}
|
|
303
297
|
return profilingContext
|
|
304
298
|
}
|
|
@@ -317,6 +311,16 @@ class NativeWallProfiler {
|
|
|
317
311
|
}
|
|
318
312
|
}
|
|
319
313
|
|
|
314
|
+
#spanTagsUpdated (span) {
|
|
315
|
+
if (!this.#started) return
|
|
316
|
+
const profilingContext = span[ProfilingContext]
|
|
317
|
+
if (profilingContext === undefined || profilingContext.webTags !== undefined) return
|
|
318
|
+
const tags = span.context()._tags
|
|
319
|
+
if (isWebServerSpan(tags)) {
|
|
320
|
+
profilingContext.webTags = tags
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
320
324
|
#reportV8bug (maybeBug) {
|
|
321
325
|
const tag = `v8_profiler_bug_workaround_enabled:${this.#v8ProfilerBugWorkaroundEnabled}`
|
|
322
326
|
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
@@ -355,6 +359,9 @@ class NativeWallProfiler {
|
|
|
355
359
|
}
|
|
356
360
|
enterCh.unsubscribe(this.#boundEnter)
|
|
357
361
|
spanFinishCh.unsubscribe(this.#boundSpanFinished)
|
|
362
|
+
if (this.#endpointCollectionEnabled) {
|
|
363
|
+
tagsUpdateCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
364
|
+
}
|
|
358
365
|
this._profilerState = undefined
|
|
359
366
|
}
|
|
360
367
|
this.#started = false
|
|
@@ -180,7 +180,7 @@ class Tracer extends NoopProxy {
|
|
|
180
180
|
if (config.profiling.enabled === 'true') {
|
|
181
181
|
this._profilerStarted = this._startProfiler(config)
|
|
182
182
|
} else {
|
|
183
|
-
this._profilerStarted =
|
|
183
|
+
this._profilerStarted = false
|
|
184
184
|
if (config.profiling.enabled === 'auto') {
|
|
185
185
|
const { SSIHeuristics } = require('./profiling/ssi-heuristics')
|
|
186
186
|
const ssiHeuristics = new SSIHeuristics(config)
|
|
@@ -252,6 +252,7 @@ class Tracer extends NoopProxy {
|
|
|
252
252
|
'Error starting profiler. For troubleshooting tips, see <https://dtdg.co/nodejs-profiler-troubleshooting>',
|
|
253
253
|
e
|
|
254
254
|
)
|
|
255
|
+
return false
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
|
|
@@ -329,11 +330,11 @@ class Tracer extends NoopProxy {
|
|
|
329
330
|
* @override
|
|
330
331
|
*/
|
|
331
332
|
profilerStarted () {
|
|
332
|
-
if (
|
|
333
|
+
if (this._profilerStarted === undefined) {
|
|
333
334
|
// injection hardening: this is only ever invoked from tests.
|
|
334
335
|
throw new Error('profilerStarted() must be called after init()')
|
|
335
336
|
}
|
|
336
|
-
return this._profilerStarted
|
|
337
|
+
return Promise.resolve(this._profilerStarted)
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
/**
|
|
@@ -11,13 +11,22 @@ function storeConfig (config) {
|
|
|
11
11
|
return
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const { containerId } = require('./exporters/common/docker')
|
|
15
|
+
const processTags = require('./process-tags')
|
|
16
|
+
|
|
17
|
+
const processTagsSerialized = config.propagateProcessTags?.enabled
|
|
18
|
+
? (processTags.serialized || null)
|
|
19
|
+
: null
|
|
20
|
+
|
|
14
21
|
const metadata = new processDiscovery.TracerMetadata(
|
|
15
22
|
config.tags['runtime-id'],
|
|
16
23
|
tracerVersion,
|
|
17
24
|
config.hostname,
|
|
18
25
|
config.service || null,
|
|
19
26
|
config.env || null,
|
|
20
|
-
config.version || null
|
|
27
|
+
config.version || null,
|
|
28
|
+
processTagsSerialized,
|
|
29
|
+
containerId || null
|
|
21
30
|
)
|
|
22
31
|
|
|
23
32
|
return processDiscovery.storeMetadata(metadata)
|
package/webpack.js
ADDED