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.
Files changed (68) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +8 -18
  3. package/ci/init.js +7 -0
  4. package/ext/exporters.d.ts +1 -0
  5. package/ext/exporters.js +2 -1
  6. package/ext/tags.d.ts +1 -0
  7. package/ext/tags.js +1 -0
  8. package/index.d.ts +18 -3
  9. package/initialize.mjs +52 -0
  10. package/package.json +9 -12
  11. package/packages/datadog-instrumentations/src/amqplib.js +5 -2
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
  13. package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
  14. package/packages/datadog-instrumentations/src/body-parser.js +0 -1
  15. package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
  16. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
  17. package/packages/datadog-instrumentations/src/express.js +0 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +0 -2
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
  21. package/packages/datadog-instrumentations/src/http/server.js +0 -1
  22. package/packages/datadog-instrumentations/src/jest.js +6 -3
  23. package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
  24. package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
  25. package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
  26. package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
  27. package/packages/datadog-instrumentations/src/mocha.js +4 -673
  28. package/packages/datadog-instrumentations/src/openai.js +188 -17
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -3
  30. package/packages/datadog-instrumentations/src/router.js +1 -1
  31. package/packages/datadog-instrumentations/src/selenium.js +13 -6
  32. package/packages/datadog-plugin-mocha/src/index.js +82 -8
  33. package/packages/datadog-plugin-next/src/index.js +1 -2
  34. package/packages/datadog-plugin-openai/src/index.js +219 -73
  35. package/packages/dd-trace/src/appsec/addresses.js +4 -2
  36. package/packages/dd-trace/src/appsec/blocking.js +19 -25
  37. package/packages/dd-trace/src/appsec/channels.js +2 -1
  38. package/packages/dd-trace/src/appsec/graphql.js +10 -3
  39. package/packages/dd-trace/src/appsec/index.js +11 -4
  40. package/packages/dd-trace/src/appsec/rasp.js +35 -0
  41. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  43. package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
  44. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
  45. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
  46. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
  47. package/packages/dd-trace/src/config.js +59 -16
  48. package/packages/dd-trace/src/encode/0.4.js +47 -8
  49. package/packages/dd-trace/src/exporter.js +1 -0
  50. package/packages/dd-trace/src/flare/file.js +44 -0
  51. package/packages/dd-trace/src/flare/index.js +98 -0
  52. package/packages/dd-trace/src/log/channels.js +54 -29
  53. package/packages/dd-trace/src/log/writer.js +7 -49
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
  55. package/packages/dd-trace/src/plugins/index.js +1 -0
  56. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  57. package/packages/dd-trace/src/plugins/util/test.js +6 -0
  58. package/packages/dd-trace/src/profiler.js +2 -1
  59. package/packages/dd-trace/src/profiling/config.js +1 -0
  60. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  61. package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
  62. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
  63. package/packages/dd-trace/src/proxy.js +49 -15
  64. package/packages/dd-trace/src/ritm.js +13 -1
  65. package/packages/dd-trace/src/startup-log.js +19 -15
  66. package/packages/dd-trace/src/telemetry/index.js +6 -2
  67. package/packages/dd-trace/src/tracer.js +3 -0
  68. 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
- Debug: 'debug',
7
- Info: 'info',
8
- Warn: 'warn',
9
- Error: 'error'
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 defaultLevel = Level.Debug
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
- // based on: https://github.com/trentm/node-bunyan#levels
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 createLogChannel (name, logLevel) {
23
- const logChannel = channel(`datadog:log:${name}`)
24
- logChannel.logLevel = logLevel
25
- return logChannel
22
+ function getChannelLogLevel (level) {
23
+ return level && typeof level === 'string'
24
+ ? Level[level.toLowerCase().trim()] || defaultLevel
25
+ : defaultLevel
26
26
  }
27
27
 
28
- function getChannelLogLevel (level) {
29
- let logChannel
30
- if (level && typeof level === 'string') {
31
- logChannel = logChannels[level.toLowerCase().trim()] || logChannels[defaultLevel]
32
- } else {
33
- logChannel = logChannels[defaultLevel]
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
- Level,
40
- getChannelLogLevel,
65
+ LogChannel,
41
66
 
42
- debugChannel: logChannels[Level.Debug],
43
- infoChannel: logChannels[Level.Info],
44
- warnChannel: logChannels[Level.Warn],
45
- errorChannel: logChannels[Level.Error]
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 { getChannelLogLevel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
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 logLevel = getChannelLogLevel()
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
- if (debugChannel.hasSubscribers) {
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
- if (debugChannel.logLevel >= logLevel) {
45
- debugChannel.subscribe(onDebug)
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['_dd.parent_id']) {
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['_dd.parent_id'])
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
- let spanContext = null
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['_dd.parent_id'] = value
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['_dd.parent_id']) {
391
- spanContext._trace.tags['_dd.parent_id'] = '0000000000000000'
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 === '0000000000000000') return
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') },
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const BlockList = require('./ip_blocklist')
3
+ const { BlockList } = require('net')
4
4
  const net = require('net')
5
5
 
6
6
  const ipHeaderList = [
@@ -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,
@@ -65,6 +65,7 @@ class Config {
65
65
  DD_PROFILING_PPROF_PREFIX, '')
66
66
 
67
67
  this.enabled = enabled
68
+ this.heuristicsEnabled = options.heuristicsEnabled
68
69
  this.service = service
69
70
  this.env = env
70
71
  this.host = host
@@ -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 lived for less than 30 seconds, it's considered short-lived
10
- const DEFAULT_SHORT_LIVED_THRESHOLD = 30000
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.MANUALLY_DISABLED')
14
+ DISABLED: Symbol('SSITelemetry.EnablementChoice.DISABLED')
17
15
  }
18
16
  Object.freeze(EnablementChoice)
19
17
 
20
- function getEnablementChoiceFromEnv () {
21
- const { DD_PROFILING_ENABLED, DD_INJECTION_ENABLED } = process.env
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 (DD_INJECTION_ENABLED.split(',').includes('profiling')) {
21
+ } else if (config.heuristicsEnabled === true) {
25
22
  return EnablementChoice.SSI_ENABLED
26
- } else if (isTrue(DD_PROFILING_ENABLED)) {
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.MANUALLY_DISABLED:
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 emits telemetry metrics about the profiler behavior under SSI. It will only emit metrics
49
- * when the application closes, and will emit the following metrics:
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
- * It will also add tags describing the state of heuristics triggers, the enablement choice, and whether
53
- * actual profiles were sent (as opposed to mock profiles). There is a mock profiler that is activated
54
- * when the profiler is not enabled, and it will emit mock profile submission events at the same cadence
55
- * the profiler would, providing insight into how many profiles would've been emitted if SSI enabled
56
- * profiling. Note that telemetry is per tracer instance, and each worker thread will have its own instance.
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 SSITelemetry {
59
- constructor ({
60
- enablementChoice = getEnablementChoiceFromEnv(),
61
- shortLivedThreshold = DEFAULT_SHORT_LIVED_THRESHOLD
62
- } = {}) {
63
- if (!Object.values(EnablementChoice).includes(enablementChoice)) {
64
- throw new Error('Invalid enablement choice')
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.enablementChoice = enablementChoice
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
- this.startTime = performance.now()
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 (performance.now() - this.startTime < this.shortLivedThreshold) {
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 (!this._emittedRuntimeId && decision[0] === 'triggered') {
142
- // Tags won't change anymore, so we can emit the runtime ID metric now
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 = { SSITelemetry, EnablementChoice }
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
- function scheduleProfileSubmit () {
16
- timerId = setTimeout(emitProfileSubmit, flushInterval)
17
- }
18
-
19
- function emitProfileSubmit () {
15
+ timerId = setTimeout(() => {
20
16
  profileSubmittedChannel.publish()
21
- scheduleProfileSubmit()
22
- }
23
-
24
- scheduleProfileSubmit()
17
+ timerId.refresh()
18
+ }, flushInterval)
19
+ timerId.unref()
25
20
  },
26
21
 
27
22
  stop: () => {