dd-trace 5.14.1 → 5.15.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/LICENSE-3rdparty.csv +0 -3
- package/README.md +8 -18
- package/ci/init.js +7 -0
- package/ext/exporters.d.ts +1 -0
- package/ext/exporters.js +2 -1
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +18 -3
- package/initialize.mjs +52 -0
- package/package.json +9 -12
- package/packages/datadog-instrumentations/src/amqplib.js +5 -2
- package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
- package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
- package/packages/datadog-instrumentations/src/body-parser.js +0 -1
- package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
- package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
- package/packages/datadog-instrumentations/src/express.js +0 -1
- package/packages/datadog-instrumentations/src/graphql.js +0 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
- package/packages/datadog-instrumentations/src/http/server.js +0 -1
- package/packages/datadog-instrumentations/src/jest.js +6 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
- package/packages/datadog-instrumentations/src/mocha.js +4 -673
- package/packages/datadog-instrumentations/src/openai.js +188 -17
- package/packages/datadog-instrumentations/src/playwright.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +1 -1
- package/packages/datadog-instrumentations/src/selenium.js +13 -6
- package/packages/datadog-plugin-mocha/src/index.js +82 -8
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/index.js +219 -73
- package/packages/dd-trace/src/appsec/addresses.js +4 -2
- package/packages/dd-trace/src/appsec/blocking.js +19 -25
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/graphql.js +10 -3
- package/packages/dd-trace/src/appsec/index.js +11 -4
- package/packages/dd-trace/src/appsec/rasp.js +35 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
- package/packages/dd-trace/src/config.js +59 -16
- package/packages/dd-trace/src/encode/0.4.js +47 -8
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/flare/file.js +44 -0
- package/packages/dd-trace/src/flare/index.js +98 -0
- package/packages/dd-trace/src/log/channels.js +54 -29
- package/packages/dd-trace/src/log/writer.js +7 -49
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +6 -0
- package/packages/dd-trace/src/profiler.js +2 -1
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
- package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
- package/packages/dd-trace/src/proxy.js +49 -15
- package/packages/dd-trace/src/ritm.js +13 -1
- package/packages/dd-trace/src/startup-log.js +19 -15
- package/packages/dd-trace/src/telemetry/index.js +6 -2
- package/packages/dd-trace/src/tracer.js +3 -0
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
|
@@ -3,44 +3,69 @@
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
5
|
const Level = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
trace: 20,
|
|
7
|
+
debug: 20,
|
|
8
|
+
info: 30,
|
|
9
|
+
warn: 40,
|
|
10
|
+
error: 50,
|
|
11
|
+
critical: 50,
|
|
12
|
+
off: 100
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
const
|
|
15
|
+
const debugChannel = channel('datadog:log:debug')
|
|
16
|
+
const infoChannel = channel('datadog:log:info')
|
|
17
|
+
const warnChannel = channel('datadog:log:warn')
|
|
18
|
+
const errorChannel = channel('datadog:log:error')
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
const logChannels = {
|
|
16
|
-
[Level.Debug]: createLogChannel(Level.Debug, 20),
|
|
17
|
-
[Level.Info]: createLogChannel(Level.Info, 30),
|
|
18
|
-
[Level.Warn]: createLogChannel(Level.Warn, 40),
|
|
19
|
-
[Level.Error]: createLogChannel(Level.Error, 50)
|
|
20
|
-
}
|
|
20
|
+
const defaultLevel = Level.debug
|
|
21
21
|
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
function getChannelLogLevel (level) {
|
|
23
|
+
return level && typeof level === 'string'
|
|
24
|
+
? Level[level.toLowerCase().trim()] || defaultLevel
|
|
25
|
+
: defaultLevel
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
class LogChannel {
|
|
29
|
+
constructor (level) {
|
|
30
|
+
this._level = getChannelLogLevel(level)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
subscribe (logger) {
|
|
34
|
+
if (Level.debug >= this._level) {
|
|
35
|
+
debugChannel.subscribe(logger.debug)
|
|
36
|
+
}
|
|
37
|
+
if (Level.info >= this._level) {
|
|
38
|
+
infoChannel.subscribe(logger.info)
|
|
39
|
+
}
|
|
40
|
+
if (Level.warn >= this._level) {
|
|
41
|
+
warnChannel.subscribe(logger.warn)
|
|
42
|
+
}
|
|
43
|
+
if (Level.error >= this._level) {
|
|
44
|
+
errorChannel.subscribe(logger.error)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
unsubscribe (logger) {
|
|
49
|
+
if (debugChannel.hasSubscribers) {
|
|
50
|
+
debugChannel.unsubscribe(logger.debug)
|
|
51
|
+
}
|
|
52
|
+
if (infoChannel.hasSubscribers) {
|
|
53
|
+
infoChannel.unsubscribe(logger.info)
|
|
54
|
+
}
|
|
55
|
+
if (warnChannel.hasSubscribers) {
|
|
56
|
+
warnChannel.unsubscribe(logger.warn)
|
|
57
|
+
}
|
|
58
|
+
if (errorChannel.hasSubscribers) {
|
|
59
|
+
errorChannel.unsubscribe(logger.error)
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
|
-
return logChannel.logLevel
|
|
36
62
|
}
|
|
37
63
|
|
|
38
64
|
module.exports = {
|
|
39
|
-
|
|
40
|
-
getChannelLogLevel,
|
|
65
|
+
LogChannel,
|
|
41
66
|
|
|
42
|
-
debugChannel
|
|
43
|
-
infoChannel
|
|
44
|
-
warnChannel
|
|
45
|
-
errorChannel
|
|
67
|
+
debugChannel,
|
|
68
|
+
infoChannel,
|
|
69
|
+
warnChannel,
|
|
70
|
+
errorChannel
|
|
46
71
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../datadog-core')
|
|
4
|
-
const {
|
|
5
|
-
|
|
4
|
+
const { LogChannel } = require('./channels')
|
|
6
5
|
const defaultLogger = {
|
|
7
6
|
debug: msg => console.debug(msg), /* eslint-disable-line no-console */
|
|
8
7
|
info: msg => console.info(msg), /* eslint-disable-line no-console */
|
|
@@ -12,7 +11,7 @@ const defaultLogger = {
|
|
|
12
11
|
|
|
13
12
|
let enabled = false
|
|
14
13
|
let logger = defaultLogger
|
|
15
|
-
let
|
|
14
|
+
let logChannel = new LogChannel()
|
|
16
15
|
|
|
17
16
|
function withNoop (fn) {
|
|
18
17
|
const store = storage.getStore()
|
|
@@ -23,45 +22,21 @@ function withNoop (fn) {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
function unsubscribeAll () {
|
|
26
|
-
|
|
27
|
-
debugChannel.unsubscribe(onDebug)
|
|
28
|
-
}
|
|
29
|
-
if (infoChannel.hasSubscribers) {
|
|
30
|
-
infoChannel.unsubscribe(onInfo)
|
|
31
|
-
}
|
|
32
|
-
if (warnChannel.hasSubscribers) {
|
|
33
|
-
warnChannel.unsubscribe(onWarn)
|
|
34
|
-
}
|
|
35
|
-
if (errorChannel.hasSubscribers) {
|
|
36
|
-
errorChannel.unsubscribe(onError)
|
|
37
|
-
}
|
|
25
|
+
logChannel.unsubscribe({ debug, info, warn, error })
|
|
38
26
|
}
|
|
39
27
|
|
|
40
|
-
function toggleSubscription (enable) {
|
|
28
|
+
function toggleSubscription (enable, level) {
|
|
41
29
|
unsubscribeAll()
|
|
42
30
|
|
|
43
31
|
if (enable) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
if (infoChannel.logLevel >= logLevel) {
|
|
48
|
-
infoChannel.subscribe(onInfo)
|
|
49
|
-
}
|
|
50
|
-
if (warnChannel.logLevel >= logLevel) {
|
|
51
|
-
warnChannel.subscribe(onWarn)
|
|
52
|
-
}
|
|
53
|
-
if (errorChannel.logLevel >= logLevel) {
|
|
54
|
-
errorChannel.subscribe(onError)
|
|
55
|
-
}
|
|
32
|
+
logChannel = new LogChannel(level)
|
|
33
|
+
logChannel.subscribe({ debug, info, warn, error })
|
|
56
34
|
}
|
|
57
35
|
}
|
|
58
36
|
|
|
59
37
|
function toggle (enable, level) {
|
|
60
|
-
if (level !== undefined) {
|
|
61
|
-
logLevel = getChannelLogLevel(level)
|
|
62
|
-
}
|
|
63
38
|
enabled = enable
|
|
64
|
-
toggleSubscription(enabled)
|
|
39
|
+
toggleSubscription(enabled, level)
|
|
65
40
|
}
|
|
66
41
|
|
|
67
42
|
function use (newLogger) {
|
|
@@ -73,26 +48,9 @@ function use (newLogger) {
|
|
|
73
48
|
function reset () {
|
|
74
49
|
logger = defaultLogger
|
|
75
50
|
enabled = false
|
|
76
|
-
logLevel = getChannelLogLevel()
|
|
77
51
|
toggleSubscription(false)
|
|
78
52
|
}
|
|
79
53
|
|
|
80
|
-
function onError (err) {
|
|
81
|
-
if (enabled) error(err)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function onWarn (message) {
|
|
85
|
-
if (enabled) warn(message)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function onInfo (message) {
|
|
89
|
-
if (enabled) info(message)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function onDebug (message) {
|
|
93
|
-
if (enabled) debug(message)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
54
|
function error (err) {
|
|
97
55
|
if (typeof err !== 'object' || !err) {
|
|
98
56
|
err = String(err)
|
|
@@ -5,6 +5,7 @@ const id = require('../../id')
|
|
|
5
5
|
const DatadogSpanContext = require('../span_context')
|
|
6
6
|
const log = require('../../log')
|
|
7
7
|
const TraceState = require('./tracestate')
|
|
8
|
+
const tags = require('../../../../../ext/tags')
|
|
8
9
|
|
|
9
10
|
const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
|
|
10
11
|
|
|
@@ -39,6 +40,7 @@ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g
|
|
|
39
40
|
// Tag values in tracestate replace ',', '~' and ';' with '_'
|
|
40
41
|
const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g
|
|
41
42
|
const invalidSegment = /^0+$/
|
|
43
|
+
const zeroTraceId = '0000000000000000'
|
|
42
44
|
|
|
43
45
|
class TextMapPropagator {
|
|
44
46
|
constructor (config) {
|
|
@@ -175,9 +177,9 @@ class TextMapPropagator {
|
|
|
175
177
|
// SpanContext was created by a ddtrace span.
|
|
176
178
|
// Last datadog span id should be set to the current span.
|
|
177
179
|
state.set('p', spanContext._spanId)
|
|
178
|
-
} else if (spanContext._trace.tags[
|
|
180
|
+
} else if (spanContext._trace.tags[tags.DD_PARENT_ID]) {
|
|
179
181
|
// Propagate the last Datadog span id set on the remote span.
|
|
180
|
-
state.set('p', spanContext._trace.tags[
|
|
182
|
+
state.set('p', spanContext._trace.tags[tags.DD_PARENT_ID])
|
|
181
183
|
}
|
|
182
184
|
state.set('s', priority)
|
|
183
185
|
if (mechanism) {
|
|
@@ -214,9 +216,56 @@ class TextMapPropagator {
|
|
|
214
216
|
return this._config.tracePropagationStyle[mode].includes(name)
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
_hasTraceIdConflict (w3cSpanContext, firstSpanContext) {
|
|
220
|
+
return w3cSpanContext !== null &&
|
|
221
|
+
firstSpanContext.toTraceId(true) === w3cSpanContext.toTraceId(true) &&
|
|
222
|
+
firstSpanContext.toSpanId() !== w3cSpanContext.toSpanId()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_hasParentIdInTags (spanContext) {
|
|
226
|
+
return tags.DD_PARENT_ID in spanContext._trace.tags &&
|
|
227
|
+
spanContext._trace.tags[tags.DD_PARENT_ID] !== zeroTraceId
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_updateParentIdFromDdHeaders (carrier, firstSpanContext) {
|
|
231
|
+
const ddCtx = this._extractDatadogContext(carrier)
|
|
232
|
+
if (ddCtx !== null) {
|
|
233
|
+
firstSpanContext._trace.tags[tags.DD_PARENT_ID] = ddCtx._spanId.toString().padStart(16, '0')
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_resolveTraceContextConflicts (w3cSpanContext, firstSpanContext, carrier) {
|
|
238
|
+
if (!this._hasTraceIdConflict(w3cSpanContext, firstSpanContext)) {
|
|
239
|
+
return firstSpanContext
|
|
240
|
+
}
|
|
241
|
+
if (this._hasParentIdInTags(w3cSpanContext)) {
|
|
242
|
+
// tracecontext headers contain a p value, ensure this value is sent to backend
|
|
243
|
+
firstSpanContext._trace.tags[tags.DD_PARENT_ID] = w3cSpanContext._trace.tags[tags.DD_PARENT_ID]
|
|
244
|
+
} else {
|
|
245
|
+
// if p value is not present in tracestate, use the parent id from the datadog headers
|
|
246
|
+
this._updateParentIdFromDdHeaders(carrier, firstSpanContext)
|
|
247
|
+
}
|
|
248
|
+
// the span_id in tracecontext takes precedence over the first extracted propagation style
|
|
249
|
+
firstSpanContext._spanId = w3cSpanContext._spanId
|
|
250
|
+
return firstSpanContext
|
|
251
|
+
}
|
|
252
|
+
|
|
217
253
|
_extractSpanContext (carrier) {
|
|
254
|
+
let spanContext = null
|
|
218
255
|
for (const extractor of this._config.tracePropagationStyle.extract) {
|
|
219
|
-
|
|
256
|
+
// add logic to ensure tracecontext headers takes precedence over other extracted headers
|
|
257
|
+
if (spanContext !== null) {
|
|
258
|
+
if (this._config.tracePropagationExtractFirst) {
|
|
259
|
+
return spanContext
|
|
260
|
+
}
|
|
261
|
+
if (extractor !== 'tracecontext') {
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
spanContext = this._resolveTraceContextConflicts(
|
|
265
|
+
this._extractTraceparentContext(carrier), spanContext, carrier)
|
|
266
|
+
break
|
|
267
|
+
}
|
|
268
|
+
|
|
220
269
|
switch (extractor) {
|
|
221
270
|
case 'datadog':
|
|
222
271
|
spanContext = this._extractDatadogContext(carrier)
|
|
@@ -238,13 +287,9 @@ class TextMapPropagator {
|
|
|
238
287
|
default:
|
|
239
288
|
log.warn(`Unknown propagation style: ${extractor}`)
|
|
240
289
|
}
|
|
241
|
-
|
|
242
|
-
if (spanContext !== null) {
|
|
243
|
-
return spanContext
|
|
244
|
-
}
|
|
245
290
|
}
|
|
246
291
|
|
|
247
|
-
return this._extractSqsdContext(carrier)
|
|
292
|
+
return spanContext || this._extractSqsdContext(carrier)
|
|
248
293
|
}
|
|
249
294
|
|
|
250
295
|
_extractDatadogContext (carrier) {
|
|
@@ -354,7 +399,7 @@ class TextMapPropagator {
|
|
|
354
399
|
for (const [key, value] of state.entries()) {
|
|
355
400
|
switch (key) {
|
|
356
401
|
case 'p': {
|
|
357
|
-
spanContext._trace.tags[
|
|
402
|
+
spanContext._trace.tags[tags.DD_PARENT_ID] = value
|
|
358
403
|
break
|
|
359
404
|
}
|
|
360
405
|
case 's': {
|
|
@@ -387,8 +432,8 @@ class TextMapPropagator {
|
|
|
387
432
|
}
|
|
388
433
|
})
|
|
389
434
|
|
|
390
|
-
if (!spanContext._trace.tags[
|
|
391
|
-
spanContext._trace.tags[
|
|
435
|
+
if (!spanContext._trace.tags[tags.DD_PARENT_ID]) {
|
|
436
|
+
spanContext._trace.tags[tags.DD_PARENT_ID] = zeroTraceId
|
|
392
437
|
}
|
|
393
438
|
|
|
394
439
|
this._extractBaggageItems(carrier, spanContext)
|
|
@@ -531,7 +576,7 @@ class TextMapPropagator {
|
|
|
531
576
|
|
|
532
577
|
const tid = traceId.substring(0, 16)
|
|
533
578
|
|
|
534
|
-
if (tid ===
|
|
579
|
+
if (tid === zeroTraceId) return
|
|
535
580
|
|
|
536
581
|
spanContext._trace.tags['_dd.p.tid'] = tid
|
|
537
582
|
}
|
|
@@ -54,6 +54,7 @@ module.exports = {
|
|
|
54
54
|
get 'microgateway-core' () { return require('../../../datadog-plugin-microgateway-core/src') },
|
|
55
55
|
get mocha () { return require('../../../datadog-plugin-mocha/src') },
|
|
56
56
|
get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
|
|
57
|
+
get workerpool () { return require('../../../datadog-plugin-mocha/src') },
|
|
57
58
|
get moleculer () { return require('../../../datadog-plugin-moleculer/src') },
|
|
58
59
|
get mongodb () { return require('../../../datadog-plugin-mongodb-core/src') },
|
|
59
60
|
get 'mongodb-core' () { return require('../../../datadog-plugin-mongodb-core/src') },
|
|
@@ -62,6 +62,7 @@ const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
|
62
62
|
const JEST_DISPLAY_NAME = 'test.jest.display_name'
|
|
63
63
|
|
|
64
64
|
const CUCUMBER_IS_PARALLEL = 'test.cucumber.is_parallel'
|
|
65
|
+
const MOCHA_IS_PARALLEL = 'test.mocha.is_parallel'
|
|
65
66
|
|
|
66
67
|
const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
|
|
67
68
|
const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
|
|
@@ -87,6 +88,9 @@ const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
|
|
|
87
88
|
// cucumber worker variables
|
|
88
89
|
const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
|
|
89
90
|
|
|
91
|
+
// mocha worker variables
|
|
92
|
+
const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
|
|
93
|
+
|
|
90
94
|
// Early flake detection util strings
|
|
91
95
|
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
92
96
|
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
@@ -98,6 +102,7 @@ module.exports = {
|
|
|
98
102
|
JEST_TEST_RUNNER,
|
|
99
103
|
JEST_DISPLAY_NAME,
|
|
100
104
|
CUCUMBER_IS_PARALLEL,
|
|
105
|
+
MOCHA_IS_PARALLEL,
|
|
101
106
|
TEST_TYPE,
|
|
102
107
|
TEST_NAME,
|
|
103
108
|
TEST_SUITE,
|
|
@@ -111,6 +116,7 @@ module.exports = {
|
|
|
111
116
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
112
117
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
113
118
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
119
|
+
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
114
120
|
TEST_SOURCE_START,
|
|
115
121
|
TEST_SKIPPED_BY_ITR,
|
|
116
122
|
TEST_CONFIGURATION_BROWSER_NAME,
|
|
@@ -9,7 +9,7 @@ process.once('beforeExit', () => { profiler.stop() })
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
start: config => {
|
|
11
11
|
const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
|
|
12
|
-
const { enabled, sourceMap, exporters } = config.profiling
|
|
12
|
+
const { enabled, sourceMap, exporters, heuristicsEnabled } = config.profiling
|
|
13
13
|
const logger = {
|
|
14
14
|
debug: (message) => log.debug(message),
|
|
15
15
|
info: (message) => log.info(message),
|
|
@@ -19,6 +19,7 @@ module.exports = {
|
|
|
19
19
|
|
|
20
20
|
return profiler.start({
|
|
21
21
|
enabled,
|
|
22
|
+
heuristicsEnabled,
|
|
22
23
|
service,
|
|
23
24
|
version,
|
|
24
25
|
env,
|
|
@@ -55,7 +55,7 @@ class Profiler extends EventEmitter {
|
|
|
55
55
|
if (this._enabled) return true
|
|
56
56
|
|
|
57
57
|
const config = this._config = new Config(options)
|
|
58
|
-
if (!config.enabled) return false
|
|
58
|
+
if (!config.enabled && !config.heuristicsEnabled) return false
|
|
59
59
|
|
|
60
60
|
this._logger = config.logger
|
|
61
61
|
this._enabled = true
|
|
@@ -2,28 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
4
4
|
const profilersNamespace = telemetryMetrics.manager.namespace('profilers')
|
|
5
|
-
const performance = require('perf_hooks').performance
|
|
6
5
|
const dc = require('dc-polyfill')
|
|
7
|
-
const { isTrue, isFalse } = require('../util')
|
|
8
6
|
|
|
9
|
-
// If the process
|
|
10
|
-
const
|
|
7
|
+
// If the process lives for at least 30 seconds, it's considered long-lived
|
|
8
|
+
const DEFAULT_LONG_LIVED_THRESHOLD = 30000
|
|
11
9
|
|
|
12
10
|
const EnablementChoice = {
|
|
13
11
|
MANUALLY_ENABLED: Symbol('SSITelemetry.EnablementChoice.MANUALLY_ENABLED'),
|
|
14
12
|
SSI_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_ENABLED'),
|
|
15
13
|
SSI_NOT_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_NOT_ENABLED'),
|
|
16
|
-
DISABLED: Symbol('SSITelemetry.EnablementChoice.
|
|
14
|
+
DISABLED: Symbol('SSITelemetry.EnablementChoice.DISABLED')
|
|
17
15
|
}
|
|
18
16
|
Object.freeze(EnablementChoice)
|
|
19
17
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
if (DD_INJECTION_ENABLED === undefined || isFalse(DD_PROFILING_ENABLED)) {
|
|
18
|
+
function getEnablementChoiceFromConfig (config) {
|
|
19
|
+
if (config.ssi === false || config.enabled === false) {
|
|
23
20
|
return EnablementChoice.DISABLED
|
|
24
|
-
} else if (
|
|
21
|
+
} else if (config.heuristicsEnabled === true) {
|
|
25
22
|
return EnablementChoice.SSI_ENABLED
|
|
26
|
-
} else if (
|
|
23
|
+
} else if (config.enabled === true) {
|
|
27
24
|
return EnablementChoice.MANUALLY_ENABLED
|
|
28
25
|
} else {
|
|
29
26
|
return EnablementChoice.SSI_NOT_ENABLED
|
|
@@ -38,39 +35,38 @@ function enablementChoiceToTagValue (enablementChoice) {
|
|
|
38
35
|
return 'ssi_enabled'
|
|
39
36
|
case EnablementChoice.SSI_NOT_ENABLED:
|
|
40
37
|
return 'not_enabled'
|
|
41
|
-
case EnablementChoice.
|
|
38
|
+
case EnablementChoice.DISABLED:
|
|
42
39
|
// Can't emit this one as a tag
|
|
43
40
|
throw new Error('Invalid enablement choice')
|
|
44
41
|
}
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
|
-
* This class
|
|
49
|
-
*
|
|
45
|
+
* This class embodies the SSI profiler-triggering heuristics and also emits telemetry metrics about
|
|
46
|
+
* the profiler behavior under SSI. It emits the following metrics:
|
|
50
47
|
* - `number_of_profiles`: The number of profiles that were submitted
|
|
51
|
-
* - `number_of_runtime_id`: The number of runtime IDs in the app (always 1 for Node.js
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* the profiler
|
|
56
|
-
*
|
|
48
|
+
* - `number_of_runtime_id`: The number of runtime IDs in the app (always 1 for Node.js, emitted
|
|
49
|
+
* once when the tags won't change for the remaineder of of the app's lifetime.)
|
|
50
|
+
* It will also add tags describing the state of heuristics triggers, the enablement choice, and
|
|
51
|
+
* whether actual profiles were sent (as opposed to mock profiles). There is a mock profiler that is
|
|
52
|
+
* activated when the profiler is not enabled, and it will emit mock profile submission events at
|
|
53
|
+
* the same cadence the profiler would, providing insight into how many profiles would've been
|
|
54
|
+
* emitted if SSI enabled profiling. Note that heuristics (and thus telemetry) is per tracer
|
|
55
|
+
* instance, and each worker thread will have its own instance.
|
|
57
56
|
*/
|
|
58
|
-
class
|
|
59
|
-
constructor ({
|
|
60
|
-
enablementChoice =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
throw new Error('
|
|
65
|
-
}
|
|
66
|
-
if (typeof shortLivedThreshold !== 'number' || shortLivedThreshold <= 0) {
|
|
67
|
-
throw new Error('Short-lived threshold must be a positive number')
|
|
57
|
+
class SSIHeuristics {
|
|
58
|
+
constructor (config) {
|
|
59
|
+
this.enablementChoice = getEnablementChoiceFromConfig(config)
|
|
60
|
+
|
|
61
|
+
const longLivedThreshold = config.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
|
|
62
|
+
if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
|
|
63
|
+
throw new Error('Long-lived threshold must be a positive number')
|
|
68
64
|
}
|
|
69
|
-
this.
|
|
70
|
-
this.shortLivedThreshold = shortLivedThreshold
|
|
65
|
+
this.longLivedThreshold = longLivedThreshold
|
|
71
66
|
|
|
72
67
|
this.hasSentProfiles = false
|
|
73
68
|
this.noSpan = true
|
|
69
|
+
this.shortLived = true
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
enabled () {
|
|
@@ -83,7 +79,10 @@ class SSITelemetry {
|
|
|
83
79
|
// reference point, but the tracer initialization point is more relevant, as we couldn't be
|
|
84
80
|
// collecting profiles earlier anyway. The difference is not particularly significant if the
|
|
85
81
|
// tracer is initialized early in the process lifetime.
|
|
86
|
-
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.shortLived = false
|
|
84
|
+
this._maybeTriggered()
|
|
85
|
+
}, this.longLivedThreshold).unref()
|
|
87
86
|
|
|
88
87
|
this._onSpanCreated = this._onSpanCreated.bind(this)
|
|
89
88
|
this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
|
|
@@ -97,8 +96,31 @@ class SSITelemetry {
|
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
onTriggered (callback) {
|
|
100
|
+
switch (typeof callback) {
|
|
101
|
+
case 'undefined':
|
|
102
|
+
case 'function':
|
|
103
|
+
this.triggeredCallback = callback
|
|
104
|
+
process.nextTick(() => {
|
|
105
|
+
this._maybeTriggered()
|
|
106
|
+
})
|
|
107
|
+
break
|
|
108
|
+
default:
|
|
109
|
+
throw new TypeError('callback must be a function or undefined')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_maybeTriggered () {
|
|
114
|
+
if (!this.shortLived && !this.noSpan) {
|
|
115
|
+
if (typeof this.triggeredCallback === 'function') {
|
|
116
|
+
this.triggeredCallback.call(null)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
100
121
|
_onSpanCreated () {
|
|
101
122
|
this.noSpan = false
|
|
123
|
+
this._maybeTriggered()
|
|
102
124
|
dc.unsubscribe('dd-trace:span:start', this._onSpanCreated)
|
|
103
125
|
}
|
|
104
126
|
|
|
@@ -121,7 +143,7 @@ class SSITelemetry {
|
|
|
121
143
|
if (this.noSpan) {
|
|
122
144
|
decision.push('no_span')
|
|
123
145
|
}
|
|
124
|
-
if (
|
|
146
|
+
if (this.shortLived) {
|
|
125
147
|
decision.push('short_lived')
|
|
126
148
|
}
|
|
127
149
|
if (decision.length === 0) {
|
|
@@ -138,8 +160,14 @@ class SSITelemetry {
|
|
|
138
160
|
this._profileCount = profilersNamespace.count('ssi_heuristic.number_of_profiles', tags)
|
|
139
161
|
this._runtimeIdCount = profilersNamespace.count('ssi_heuristic.number_of_runtime_id', tags)
|
|
140
162
|
|
|
141
|
-
if (
|
|
142
|
-
|
|
163
|
+
if (
|
|
164
|
+
!this._emittedRuntimeId &&
|
|
165
|
+
decision[0] === 'triggered' &&
|
|
166
|
+
// When enablement choice is SSI_ENABLED, hasSentProfiles can transition from false to true when the
|
|
167
|
+
// profiler gets started and the first profile is submitted, so we have to wait for it.
|
|
168
|
+
(this.enablementChoice !== EnablementChoice.SSI_ENABLED || this.hasSentProfiles)
|
|
169
|
+
) {
|
|
170
|
+
// Tags won't change anymore, so we can emit the runtime ID metric now.
|
|
143
171
|
this._emittedRuntimeId = true
|
|
144
172
|
this._runtimeIdCount.inc()
|
|
145
173
|
}
|
|
@@ -164,4 +192,4 @@ class SSITelemetry {
|
|
|
164
192
|
}
|
|
165
193
|
}
|
|
166
194
|
|
|
167
|
-
module.exports = {
|
|
195
|
+
module.exports = { SSIHeuristics, EnablementChoice }
|
|
@@ -12,16 +12,11 @@ module.exports = {
|
|
|
12
12
|
// Copied from packages/dd-trace/src/profiler.js
|
|
13
13
|
const flushInterval = coalesce(config.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
timerId = setTimeout(emitProfileSubmit, flushInterval)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function emitProfileSubmit () {
|
|
15
|
+
timerId = setTimeout(() => {
|
|
20
16
|
profileSubmittedChannel.publish()
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
scheduleProfileSubmit()
|
|
17
|
+
timerId.refresh()
|
|
18
|
+
}, flushInterval)
|
|
19
|
+
timerId.unref()
|
|
25
20
|
},
|
|
26
21
|
|
|
27
22
|
stop: () => {
|