dd-trace 5.67.0 → 5.68.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 (104) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +0 -2
  3. package/ci/init.js +52 -54
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +47 -2
  7. package/initialize.mjs +1 -1
  8. package/package.json +8 -11
  9. package/packages/datadog-esbuild/index.js +56 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
  11. package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
  12. package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
  13. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  14. package/packages/datadog-instrumentations/src/connect.js +6 -2
  15. package/packages/datadog-instrumentations/src/cucumber.js +31 -6
  16. package/packages/datadog-instrumentations/src/express.js +5 -6
  17. package/packages/datadog-instrumentations/src/fastify.js +3 -3
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +11 -2
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
  22. package/packages/datadog-instrumentations/src/http2/client.js +1 -0
  23. package/packages/datadog-instrumentations/src/http2/server.js +0 -1
  24. package/packages/datadog-instrumentations/src/ioredis.js +12 -1
  25. package/packages/datadog-instrumentations/src/jest.js +48 -36
  26. package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
  27. package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
  28. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
  29. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  30. package/packages/datadog-instrumentations/src/oracledb.js +19 -13
  31. package/packages/datadog-instrumentations/src/pg.js +9 -5
  32. package/packages/datadog-instrumentations/src/pino.js +18 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +15 -1
  34. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  35. package/packages/datadog-instrumentations/src/vitest.js +155 -62
  36. package/packages/datadog-plugin-ai/src/tracing.js +3 -3
  37. package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
  38. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
  39. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
  40. package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
  41. package/packages/datadog-plugin-cucumber/src/index.js +4 -56
  42. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -2
  43. package/packages/datadog-plugin-cypress/src/support.js +4 -0
  44. package/packages/datadog-plugin-express/src/code_origin.js +2 -2
  45. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
  46. package/packages/datadog-plugin-jest/src/index.js +0 -21
  47. package/packages/datadog-plugin-mocha/src/index.js +3 -57
  48. package/packages/datadog-plugin-mongodb-core/src/index.js +20 -7
  49. package/packages/datadog-plugin-playwright/src/index.js +11 -5
  50. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  51. package/packages/datadog-plugin-ws/src/close.js +1 -1
  52. package/packages/datadog-plugin-ws/src/producer.js +6 -1
  53. package/packages/datadog-plugin-ws/src/receiver.js +6 -1
  54. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
  55. package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
  57. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
  58. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
  59. package/packages/dd-trace/src/config.js +69 -304
  60. package/packages/dd-trace/src/config_defaults.js +186 -0
  61. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
  62. package/packages/dd-trace/src/datastreams/fnv.js +2 -2
  63. package/packages/dd-trace/src/datastreams/writer.js +3 -2
  64. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  65. package/packages/dd-trace/src/dogstatsd.js +4 -3
  66. package/packages/dd-trace/src/encode/0.4.js +1 -5
  67. package/packages/dd-trace/src/exporter.js +1 -0
  68. package/packages/dd-trace/src/exporters/agent/index.js +3 -2
  69. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  70. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
  71. package/packages/dd-trace/src/exporters/common/request.js +2 -1
  72. package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
  73. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  74. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +4 -3
  75. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -1
  76. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
  77. package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
  78. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  79. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  80. package/packages/dd-trace/src/log/index.js +28 -17
  81. package/packages/dd-trace/src/log/log.js +29 -5
  82. package/packages/dd-trace/src/log/writer.js +5 -5
  83. package/packages/dd-trace/src/noop/span.js +1 -0
  84. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  85. package/packages/dd-trace/src/opentracing/span.js +18 -4
  86. package/packages/dd-trace/src/plugin_manager.js +20 -2
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
  88. package/packages/dd-trace/src/plugins/index.js +2 -0
  89. package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
  90. package/packages/dd-trace/src/plugins/util/git.js +40 -26
  91. package/packages/dd-trace/src/plugins/util/test.js +37 -27
  92. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  93. package/packages/dd-trace/src/profiler.js +4 -1
  94. package/packages/dd-trace/src/profiling/config.js +73 -42
  95. package/packages/dd-trace/src/profiling/profiler.js +3 -1
  96. package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
  97. package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
  98. package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
  99. package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
  100. package/packages/dd-trace/src/remote_config/manager.js +3 -2
  101. package/packages/dd-trace/src/startup-log.js +2 -1
  102. package/packages/dd-trace/src/supported-configurations.json +3 -0
  103. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  104. package/register.js +1 -1
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const cp = require('child_process')
4
3
  const os = require('os')
5
4
  const path = require('path')
6
5
  const fs = require('fs')
@@ -36,6 +35,7 @@ const {
36
35
  } = require('../../ci-visibility/telemetry')
37
36
  const { filterSensitiveInfoFromRepository } = require('./url')
38
37
  const { storage } = require('../../../../datadog-core')
38
+ const { cachedExec } = require('./git-cache')
39
39
 
40
40
  const GIT_REV_LIST_MAX_BUFFER = 12 * 1024 * 1024 // 12MB
41
41
 
@@ -58,10 +58,12 @@ function sanitizedExec (
58
58
  startTime = Date.now()
59
59
  }
60
60
  try {
61
- let result = cp.execFileSync(cmd, flags, { stdio: 'pipe' }).toString()
61
+ let result = cachedExec(cmd, flags, { stdio: 'pipe' }).toString()
62
+
62
63
  if (shouldTrim) {
63
64
  result = result.replaceAll(/(\r\n|\n|\r)/gm, '')
64
65
  }
66
+
65
67
  if (durationMetric) {
66
68
  distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
67
69
  }
@@ -94,7 +96,7 @@ function isGitAvailable () {
94
96
  const isWindows = os.platform() === 'win32'
95
97
  const command = isWindows ? 'where' : 'which'
96
98
  try {
97
- cp.execFileSync(command, ['git'], { stdio: 'pipe' })
99
+ cachedExec(command, ['git'])
98
100
  return true
99
101
  } catch {
100
102
  incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'check_git', exitCode: 'missing' })
@@ -150,27 +152,32 @@ function unshallowRepository (parentOnly = false) {
150
152
 
151
153
  incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'unshallow' })
152
154
  const start = Date.now()
155
+ let flags = [
156
+ ...baseGitOptions,
157
+ revParseHead
158
+ ]
153
159
  try {
154
- cp.execFileSync('git', [
155
- ...baseGitOptions,
156
- revParseHead
157
- ], { stdio: 'pipe' })
160
+ cachedExec('git', flags)
158
161
  } catch (err) {
159
162
  // If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
160
- log.error('Git plugin error executing git command', err)
163
+ log.warn(`Git unshallow failed: ${flags.join(' ')}`)
161
164
  incrementCountMetric(
162
165
  TELEMETRY_GIT_COMMAND_ERRORS,
163
166
  { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
164
167
  )
165
- const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
168
+ const upstreamRemote = sanitizedExec(
169
+ 'git',
170
+ ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']
171
+ )
172
+ flags = [
173
+ ...baseGitOptions,
174
+ upstreamRemote
175
+ ]
166
176
  try {
167
- cp.execFileSync('git', [
168
- ...baseGitOptions,
169
- upstreamRemote
170
- ], { stdio: 'pipe' })
177
+ cachedExec('git', flags)
171
178
  } catch (err) {
172
- // If the CI is working on a detached HEAD or branch tracking hasnt been set up, the above command will fail.
173
- log.error('Git plugin error executing fallback git command', err)
179
+ // If the CI is working on a detached HEAD or branch tracking hasn't been set up, the above command will fail.
180
+ log.warn(`Git unshallow failed again: ${flags.join(' ')}`)
174
181
  incrementCountMetric(
175
182
  TELEMETRY_GIT_COMMAND_ERRORS,
176
183
  { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
@@ -202,7 +209,7 @@ function getLatestCommits () {
202
209
  incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_local_commits' })
203
210
  const startTime = Date.now()
204
211
  try {
205
- const result = cp.execFileSync('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'], { stdio: 'pipe' })
212
+ const result = cachedExec('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'])
206
213
  .toString()
207
214
  .split('\n')
208
215
  .filter(Boolean)
@@ -272,10 +279,9 @@ function checkAndFetchBranch (branch, remoteName) {
272
279
  try {
273
280
  // `git show-ref --verify --quiet refs/remotes/${remoteName}/${branch}` will exit 0 if the branch exists
274
281
  // Otherwise it will exit 1
275
- cp.execFileSync(
282
+ cachedExec(
276
283
  'git',
277
284
  ['show-ref', '--verify', '--quiet', `refs/remotes/${remoteName}/${branch}`],
278
- { stdio: 'pipe' }
279
285
  )
280
286
  // branch exists locally, so we finish
281
287
  } catch {
@@ -285,22 +291,22 @@ function checkAndFetchBranch (branch, remoteName) {
285
291
  // `git ls-remote --heads origin my-branch` will exit 0 even if the branch doesn't exist.
286
292
  // The piece of information we need is whether the command outputs anything.
287
293
  // `git ls-remote --heads origin my-branch` could exit an error code if the remote does not exist.
288
- const remoteHeads = cp.execFileSync(
294
+ const remoteHeads = cachedExec(
289
295
  'git',
290
296
  ['ls-remote', '--heads', remoteName, branch],
291
297
  { stdio: 'pipe', timeout: 2000 }
292
298
  )
293
299
  if (remoteHeads) {
294
300
  // branch exists, so we'll fetch it
295
- cp.execFileSync(
301
+ cachedExec(
296
302
  'git',
297
303
  ['fetch', '--depth', '1', remoteName, branch],
298
304
  { stdio: 'pipe', timeout: 5000 }
299
305
  )
300
306
  }
301
- } catch (e) {
307
+ } catch (err) {
302
308
  // branch does not exist or couldn't be fetched, so we can't do anything
303
- log.error('Git plugin error checking and fetching branch', e)
309
+ log.debug('Git plugin error checking and fetching branch', err)
304
310
  }
305
311
  }
306
312
  }
@@ -358,7 +364,7 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
358
364
  incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_objects' })
359
365
  const startTime = Date.now()
360
366
  try {
361
- result = cp.execFileSync(
367
+ result = cachedExec(
362
368
  'git',
363
369
  [
364
370
  'rev-list',
@@ -403,7 +409,7 @@ function generatePackFilesForCommits (commitsToUpload) {
403
409
  // Generates pack files to upload and
404
410
  // returns the ordered list of packfiles' paths
405
411
  function execGitPackObjects (targetPath) {
406
- return cp.execFileSync(
412
+ return cachedExec(
407
413
  'git',
408
414
  [
409
415
  'pack-objects',
@@ -450,6 +456,13 @@ function generatePackFilesForCommits (commitsToUpload) {
450
456
  return result
451
457
  }
452
458
 
459
+ function getRepositoryRoot () {
460
+ return sanitizedExec(
461
+ 'git',
462
+ ['rev-parse', '--show-toplevel']
463
+ )
464
+ }
465
+
453
466
  // If there is ciMetadata, it takes precedence.
454
467
  function getGitMetadata (ciMetadata) {
455
468
  const {
@@ -480,7 +493,7 @@ function getGitMetadata (ciMetadata) {
480
493
  commitMessage || sanitizedExec('git', ['show', '-s', '--format=%B'], null, null, null, false),
481
494
  [GIT_BRANCH]: branch || sanitizedExec('git', ['rev-parse', '--abbrev-ref', 'HEAD']),
482
495
  [GIT_COMMIT_SHA]: commitSHA || sanitizedExec('git', ['rev-parse', 'HEAD']),
483
- [CI_WORKSPACE_PATH]: ciWorkspacePath || sanitizedExec('git', ['rev-parse', '--show-toplevel']),
496
+ [CI_WORKSPACE_PATH]: ciWorkspacePath || getRepositoryRoot(),
484
497
  }
485
498
 
486
499
  if (headCommitSha) {
@@ -596,5 +609,6 @@ module.exports = {
596
609
  getLocalBranches,
597
610
  getMergeBase,
598
611
  getCounts,
599
- fetchHeadCommitSha
612
+ fetchHeadCommitSha,
613
+ getRepositoryRoot
600
614
  }
@@ -119,6 +119,10 @@ const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
119
119
  // playwright worker variables
120
120
  const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
121
121
 
122
+ // vitest worker variables
123
+ const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
124
+ const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
125
+
122
126
  // Early flake detection util strings
123
127
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
124
128
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + String.raw` \(#\d+\): `, 'g')
@@ -218,6 +222,8 @@ module.exports = {
218
222
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
219
223
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
220
224
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
225
+ VITEST_WORKER_TRACE_PAYLOAD_CODE,
226
+ VITEST_WORKER_LOGS_PAYLOAD_CODE,
221
227
  TEST_SOURCE_START,
222
228
  TEST_SKIPPED_BY_ITR,
223
229
  TEST_IS_NEW,
@@ -438,36 +444,40 @@ function checkShaDiscrepancies (ciMetadata, userProvidedGitMetadata) {
438
444
  )
439
445
  }
440
446
 
441
- function getTestEnvironmentMetadata (testFramework, config) {
442
- // TODO: eventually these will come from the tracer (generally available)
447
+ function getTestEnvironmentMetadata (testFramework, config, shouldSkipGitMetadataExtraction = false) {
443
448
  const ciMetadata = getCIMetadata()
444
- const {
445
- [GIT_COMMIT_SHA]: commitSHA,
446
- [GIT_BRANCH]: branch,
447
- [GIT_REPOSITORY_URL]: repositoryUrl,
448
- [GIT_TAG]: tag,
449
- [GIT_COMMIT_AUTHOR_NAME]: authorName,
450
- [GIT_COMMIT_AUTHOR_EMAIL]: authorEmail,
451
- [GIT_COMMIT_MESSAGE]: commitMessage,
452
- [CI_WORKSPACE_PATH]: ciWorkspacePath,
453
- [GIT_COMMIT_HEAD_SHA]: headCommitSha
454
- } = ciMetadata
455
-
456
- const gitMetadata = getGitMetadata({
457
- commitSHA,
458
- branch,
459
- repositoryUrl,
460
- tag,
461
- authorName,
462
- authorEmail,
463
- commitMessage,
464
- ciWorkspacePath,
465
- headCommitSha
466
- })
467
-
468
449
  const userProvidedGitMetadata = getUserProviderGitMetadata()
469
450
 
470
- checkShaDiscrepancies(ciMetadata, userProvidedGitMetadata)
451
+ let gitMetadata = {}
452
+
453
+ // We don't execute git in test framework workers since the information is in the parent process
454
+ // and git metadata does not affect the execution of the tests
455
+ if (!shouldSkipGitMetadataExtraction) {
456
+ checkShaDiscrepancies(ciMetadata, userProvidedGitMetadata)
457
+
458
+ const {
459
+ [GIT_COMMIT_SHA]: commitSHA,
460
+ [GIT_BRANCH]: branch,
461
+ [GIT_REPOSITORY_URL]: repositoryUrl,
462
+ [GIT_TAG]: tag,
463
+ [GIT_COMMIT_AUTHOR_NAME]: authorName,
464
+ [GIT_COMMIT_AUTHOR_EMAIL]: authorEmail,
465
+ [GIT_COMMIT_MESSAGE]: commitMessage,
466
+ [CI_WORKSPACE_PATH]: ciWorkspacePath,
467
+ [GIT_COMMIT_HEAD_SHA]: headCommitSha
468
+ } = ciMetadata
469
+ gitMetadata = getGitMetadata({
470
+ commitSHA,
471
+ branch,
472
+ repositoryUrl,
473
+ tag,
474
+ authorName,
475
+ authorEmail,
476
+ commitMessage,
477
+ ciWorkspacePath,
478
+ headCommitSha
479
+ })
480
+ }
471
481
 
472
482
  const runtimeAndOSMetadata = getRuntimeAndOSMetadata()
473
483
 
@@ -387,7 +387,7 @@ const web = {
387
387
 
388
388
  return function (statusCode, statusMessage, headers) {
389
389
  headers = typeof statusMessage === 'string' ? headers : statusMessage
390
- headers = Object.assign(res.getHeaders(), headers)
390
+ headers = { ...res.getHeaders(), ...headers }
391
391
 
392
392
  if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
393
393
  addAllowHeaders(req, res, headers)
@@ -10,6 +10,8 @@ module.exports = {
10
10
  start: config => {
11
11
  const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA, injectionEnabled } = config
12
12
  const { enabled, sourceMap, exporters } = config.profiling
13
+ const { heartbeatInterval } = config.telemetry
14
+
13
15
  const logger = {
14
16
  debug: (message) => log.debug(message),
15
17
  info: (message) => log.info(message),
@@ -39,7 +41,8 @@ module.exports = {
39
41
  repositoryUrl,
40
42
  commitSHA,
41
43
  libraryInjected,
42
- activation
44
+ activation,
45
+ heartbeatInterval
43
46
  })
44
47
  },
45
48
 
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const coalesce = require('koalas')
4
3
  const os = require('os')
5
4
  const path = require('path')
6
5
  const { URL, format, pathToFileURL } = require('url')
6
+ const satisfies = require('semifies')
7
7
  const { AgentExporter } = require('./exporters/agent')
8
8
  const { FileExporter } = require('./exporters/file')
9
9
  const { ConsoleLogger } = require('./loggers/console')
@@ -16,6 +16,7 @@ const { tagger } = require('./tagger')
16
16
  const { isFalse, isTrue } = require('../util')
17
17
  const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata')
18
18
  const { getEnvironmentVariables } = require('../config-helper')
19
+ const defaults = require('../config_defaults')
19
20
 
20
21
  class Config {
21
22
  constructor (options = {}) {
@@ -41,27 +42,26 @@ class Config {
41
42
  DD_PROFILING_TIMELINE_ENABLED,
42
43
  DD_PROFILING_UPLOAD_PERIOD,
43
44
  DD_PROFILING_UPLOAD_TIMEOUT,
45
+ DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED,
44
46
  DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
45
47
  DD_PROFILING_WALLTIME_ENABLED,
46
48
  DD_SERVICE,
47
49
  DD_TAGS,
48
50
  DD_TRACE_AGENT_PORT,
49
51
  DD_TRACE_AGENT_URL,
50
- DD_VERSION
52
+ DD_VERSION,
53
+ NODE_OPTIONS
51
54
  } = getEnvironmentVariables()
52
55
 
53
- const env = coalesce(options.env, DD_ENV)
56
+ const env = options.env ?? DD_ENV
54
57
  const service = options.service || DD_SERVICE || 'node'
55
58
  const host = os.hostname()
56
- const version = coalesce(options.version, DD_VERSION)
59
+ const version = options.version ?? DD_VERSION
57
60
  // Must be longer than one minute so pad with five seconds
58
- const flushInterval = coalesce(options.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000)
59
- const uploadTimeout = coalesce(options.uploadTimeout,
60
- Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
61
- const sourceMap = coalesce(options.sourceMap,
62
- DD_PROFILING_SOURCE_MAP, true)
63
- const pprofPrefix = coalesce(options.pprofPrefix,
64
- DD_PROFILING_PPROF_PREFIX, '')
61
+ const flushInterval = options.interval ?? (Number(DD_PROFILING_UPLOAD_PERIOD) * 1000 || 65 * 1000)
62
+ const uploadTimeout = options.uploadTimeout ?? (Number(DD_PROFILING_UPLOAD_TIMEOUT) || 60 * 1000)
63
+ const sourceMap = options.sourceMap ?? DD_PROFILING_SOURCE_MAP ?? true
64
+ const pprofPrefix = options.pprofPrefix ?? DD_PROFILING_PPROF_PREFIX ?? ''
65
65
 
66
66
  this.service = service
67
67
  this.env = env
@@ -104,21 +104,21 @@ class Config {
104
104
  this.flushInterval = flushInterval
105
105
  this.uploadTimeout = uploadTimeout
106
106
  this.sourceMap = sourceMap
107
- this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
108
- this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
109
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable))
107
+ this.debugSourceMaps = isTrue(options.debugSourceMaps ?? DD_PROFILING_DEBUG_SOURCE_MAPS)
108
+ this.endpointCollectionEnabled = isTrue(options.endpointCollection ??
109
+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED ?? samplingContextsAvailable)
110
110
  checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
111
111
 
112
112
  this.pprofPrefix = pprofPrefix
113
- this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
114
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
115
- const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
116
- const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
117
- this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
113
+ this.v8ProfilerBugWorkaroundEnabled = isTrue(options.v8ProfilerBugWorkaround ??
114
+ DD_PROFILING_V8_PROFILER_BUG_WORKAROUND ?? true)
115
+ const hostname = (options.hostname ?? DD_AGENT_HOST) || defaults.hostname
116
+ const port = (options.port ?? DD_TRACE_AGENT_PORT) || defaults.port
117
+ this.url = new URL(options.url ?? DD_TRACE_AGENT_URL ?? format({
118
118
  protocol: 'http:',
119
119
  hostname,
120
120
  port
121
- })))
121
+ }))
122
122
 
123
123
  this.libraryInjected = options.libraryInjected
124
124
  this.activation = options.activation
@@ -129,17 +129,17 @@ class Config {
129
129
  // OOM monitoring does not work well on Windows, so it is disabled by default.
130
130
  const oomMonitoringSupported = process.platform !== 'win32'
131
131
 
132
- const oomMonitoringEnabled = isTrue(coalesce(options.oomMonitoring,
133
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED, oomMonitoringSupported))
132
+ const oomMonitoringEnabled = isTrue(options.oomMonitoring ??
133
+ DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED ?? oomMonitoringSupported)
134
134
  checkOptionAllowed(oomMonitoringEnabled, 'OOM monitoring', oomMonitoringSupported)
135
135
 
136
- const heapLimitExtensionSize = coalesce(options.oomHeapLimitExtensionSize,
137
- Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE), 0)
138
- const maxHeapExtensionCount = coalesce(options.oomMaxHeapExtensionCount,
139
- Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT), 0)
136
+ const heapLimitExtensionSize = options.oomHeapLimitExtensionSize ??
137
+ (Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE) || 0)
138
+ const maxHeapExtensionCount = options.oomMaxHeapExtensionCount ??
139
+ (Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT) || 0)
140
140
  const exportStrategies = oomMonitoringEnabled
141
- ? ensureOOMExportStrategies(coalesce(options.oomExportStrategies, DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
142
- [oomExportStrategies.PROCESS]), this)
141
+ ? ensureOOMExportStrategies(options.oomExportStrategies ?? DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES ??
142
+ [oomExportStrategies.PROCESS], this)
143
143
  : []
144
144
  const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
145
145
  this.oomMonitoring = {
@@ -156,26 +156,29 @@ class Config {
156
156
  DD_PROFILING_PROFILERS
157
157
  })
158
158
 
159
- this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
160
- DD_PROFILING_TIMELINE_ENABLED, samplingContextsAvailable))
159
+ this.timelineEnabled = isTrue(
160
+ options.timelineEnabled ?? DD_PROFILING_TIMELINE_ENABLED ?? samplingContextsAvailable
161
+ )
161
162
  checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
162
- this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
163
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
163
+ this.timelineSamplingEnabled = isTrue(
164
+ options.timelineSamplingEnabled ?? DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED ?? true
165
+ )
164
166
 
165
- this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
166
- DD_PROFILING_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
167
+ this.codeHotspotsEnabled = isTrue(
168
+ options.codeHotspotsEnabled ?? DD_PROFILING_CODEHOTSPOTS_ENABLED ?? samplingContextsAvailable
169
+ )
167
170
  checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
168
171
 
169
- this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
170
- DD_PROFILING_CPU_ENABLED,
171
- samplingContextsAvailable))
172
+ this.cpuProfilingEnabled = isTrue(
173
+ options.cpuProfilingEnabled ?? DD_PROFILING_CPU_ENABLED ?? samplingContextsAvailable
174
+ )
172
175
  checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
173
176
 
174
- this.samplingInterval = coalesce(options.samplingInterval, 1e3 / 99) // 99hz in millis
177
+ this.samplingInterval = options.samplingInterval || 1e3 / 99 // 99hz in millis
175
178
 
176
- this.heapSamplingInterval = coalesce(options.heapSamplingInterval,
177
- Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL))
178
- const uploadCompression0 = coalesce(options.uploadCompression, DD_PROFILING_DEBUG_UPLOAD_COMPRESSION, 'on')
179
+ this.heapSamplingInterval = options.heapSamplingInterval ??
180
+ (Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL) || 512 * 1024)
181
+ const uploadCompression0 = options.uploadCompression ?? DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ?? 'on'
179
182
  let [uploadCompression, level0] = uploadCompression0.split('-')
180
183
  if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) {
181
184
  this.logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`)
@@ -209,6 +212,34 @@ class Config {
209
212
 
210
213
  this.uploadCompression = { method: uploadCompression, level }
211
214
 
215
+ const that = this
216
+ function turnOffAsyncContextFrame (msg) {
217
+ that.logger.warn(
218
+ `DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set ${msg}, it will have no effect.`)
219
+ that.asyncContextFrameEnabled = false
220
+ }
221
+
222
+ const hasExecArg = (arg) => process.execArgv.includes(arg) || String(NODE_OPTIONS).includes(arg)
223
+
224
+ this.asyncContextFrameEnabled = isTrue(options.useAsyncContextFrame ?? DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED)
225
+ if (this.asyncContextFrameEnabled) {
226
+ if (satisfies(process.versions.node, '>=24.0.0')) {
227
+ if (hasExecArg('--no-async-context-frame')) {
228
+ turnOffAsyncContextFrame('with --no-async-context-frame')
229
+ }
230
+ } else if (satisfies(process.versions.node, '>=23.0.0')) {
231
+ if (!hasExecArg('--experimental-async-context-frame')) {
232
+ turnOffAsyncContextFrame('without --experimental-async-context-frame')
233
+ }
234
+ } else {
235
+ // NOTE: technically, this should work starting with 22.7.0 which is when
236
+ // AsyncContextFrame debuted, but it would require a change in pprof-nodejs too.
237
+ turnOffAsyncContextFrame('but it requires at least Node.js 23')
238
+ }
239
+ }
240
+
241
+ this.heartbeatInterval = options.heartbeatInterval || 60 * 1000 // 1 minute
242
+
212
243
  this.profilers = ensureProfilers(profilers, this)
213
244
  }
214
245
  }
@@ -220,7 +251,7 @@ function getProfilers ({
220
251
  }) {
221
252
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
222
253
  // Use a Set to avoid duplicates
223
- const profilers = new Set(coalesce(DD_PROFILING_PROFILERS, 'wall,space').split(','))
254
+ const profilers = new Set((DD_PROFILING_PROFILERS ?? 'wall,space').split(','))
224
255
 
225
256
  // Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED
226
257
  if (DD_PROFILING_WALLTIME_ENABLED != null) {
@@ -110,11 +110,13 @@ class Profiler extends EventEmitter {
110
110
  }
111
111
  break
112
112
  case 'zstd':
113
- if (typeof zlib.zstdCompress === 'function') {
113
+ if (typeof zlib.zstdCompress === 'function') { // eslint-disable-line n/no-unsupported-features/node-builtins
114
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
114
115
  this.#compressionFn = promisify(zlib.zstdCompress)
115
116
  if (clevel !== undefined) {
116
117
  this.#compressionOptions = {
117
118
  params: {
119
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
118
120
  [zlib.constants.ZSTD_c_compressionLevel]: clevel
119
121
  }
120
122
  }
@@ -41,14 +41,9 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
41
41
  return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
42
42
  }
43
43
 
44
- function getSamplingIntervalMillis (options) {
45
- return (options.samplingInterval || 1e3 / 99) // 99Hz
46
- }
47
-
48
44
  function getMaxSamples (options) {
49
- const cpuSamplingInterval = getSamplingIntervalMillis(options)
50
- const flushInterval = options.flushInterval || 65 * 1e3 // 60 seconds
51
- const maxCpuSamples = flushInterval / cpuSamplingInterval
45
+ const flushInterval = options.flushInterval || 65 * 1e3 // 65 seconds
46
+ const maxCpuSamples = flushInterval / options.samplingInterval
52
47
 
53
48
  // The lesser of max parallelism and libuv thread pool size, plus one so we can detect
54
49
  // oversubscription on libuv thread pool, plus another one for GC.
@@ -399,7 +394,7 @@ class EventsProfiler {
399
394
 
400
395
  const eventHandler = event => this.#eventSerializer.addEvent(event)
401
396
  const eventFilter = options.timelineSamplingEnabled
402
- ? createPoissonProcessSamplingFilter(getSamplingIntervalMillis(options))
397
+ ? createPoissonProcessSamplingFilter(options.samplingInterval)
403
398
  : () => true
404
399
  const filteringEventHandler = event => {
405
400
  if (eventFilter(event)) {
@@ -13,6 +13,7 @@ class NativeSpaceProfiler {
13
13
  _started = false
14
14
 
15
15
  constructor (options = {}) {
16
+ // TODO: Remove default value. It is only used in testing.
16
17
  this._samplingInterval = options.heapSamplingInterval || 512 * 1024
17
18
  this._stackDepth = options.stackDepth || 64
18
19
  this._oomMonitoring = options.oomMonitoring || {}