dd-trace 5.109.0 → 5.110.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 (128) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +66 -24
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +18 -14
  5. package/packages/datadog-esbuild/index.js +26 -0
  6. package/packages/datadog-esbuild/src/utils.js +46 -3
  7. package/packages/datadog-instrumentations/src/connect.js +4 -3
  8. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  9. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  12. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  13. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  14. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  16. package/packages/datadog-instrumentations/src/koa.js +3 -2
  17. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  18. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  19. package/packages/datadog-instrumentations/src/next.js +10 -10
  20. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  21. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  22. package/packages/datadog-instrumentations/src/restify.js +4 -3
  23. package/packages/datadog-instrumentations/src/router.js +18 -5
  24. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  25. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  26. package/packages/datadog-instrumentations/src/vitest.js +264 -7
  27. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  30. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  32. package/packages/datadog-plugin-http/src/client.js +8 -3
  33. package/packages/datadog-plugin-http2/src/client.js +5 -2
  34. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  35. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  36. package/packages/datadog-plugin-undici/src/index.js +5 -2
  37. package/packages/datadog-plugin-vitest/src/index.js +7 -1
  38. package/packages/datadog-webpack/index.js +17 -1
  39. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  40. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  41. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  42. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  43. package/packages/dd-trace/src/appsec/index.js +10 -6
  44. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  45. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  46. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  47. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  48. package/packages/dd-trace/src/config/defaults.js +22 -3
  49. package/packages/dd-trace/src/config/generated-config-types.d.ts +535 -1
  50. package/packages/dd-trace/src/config/helper.js +133 -33
  51. package/packages/dd-trace/src/config/index.js +4 -1
  52. package/packages/dd-trace/src/config/parsers.js +19 -1
  53. package/packages/dd-trace/src/config/supported-configurations.json +21 -3
  54. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  55. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  56. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  58. package/packages/dd-trace/src/debugger/index.js +5 -0
  59. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  60. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  61. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  62. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  63. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  64. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  65. package/packages/dd-trace/src/guardrails/index.js +8 -5
  66. package/packages/dd-trace/src/index.js +6 -4
  67. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  68. package/packages/dd-trace/src/llmobs/index.js +16 -5
  69. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  70. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  71. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  72. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  73. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  74. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  75. package/packages/dd-trace/src/log/index.js +18 -20
  76. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  77. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  78. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  79. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  80. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  81. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  82. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  83. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  84. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  85. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  86. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  87. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  88. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  89. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  91. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  92. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  93. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  94. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  95. package/packages/dd-trace/src/plugin_manager.js +7 -1
  96. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -1
  97. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  98. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  99. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  100. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  101. package/packages/dd-trace/src/priority_sampler.js +1 -13
  102. package/packages/dd-trace/src/profiling/config.js +129 -198
  103. package/packages/dd-trace/src/profiling/constants.js +4 -7
  104. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  105. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  106. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  107. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  108. package/packages/dd-trace/src/profiling/oom.js +71 -0
  109. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  110. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  111. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  112. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  113. package/packages/dd-trace/src/ritm.js +4 -2
  114. package/packages/dd-trace/src/serverless.js +1 -2
  115. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  116. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  117. package/packages/dd-trace/src/span_format.js +2 -1
  118. package/packages/dd-trace/src/span_processor.js +6 -0
  119. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  120. package/packages/dd-trace/src/tracer.js +15 -1
  121. package/packages/dd-trace/src/util.js +14 -0
  122. package/register.js +58 -1
  123. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  124. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  125. package/vendor/dist/pprof-format/index.js +1 -1
  126. package/vendor/dist/protobufjs/index.js +1 -1
  127. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  128. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -13,7 +13,8 @@ const { storage } = require('../../../../datadog-core')
13
13
  const legacyStorage = storage('legacy')
14
14
  const urlFilter = require('./urlfilter')
15
15
  const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy')
16
- const { extractURL, obfuscateQs, calculateHttpEndpoint } = require('./url')
16
+ const { extractURL, obfuscateQs, getQsObfuscator, calculateHttpEndpoint } = require('./url')
17
+ const { NETWORK_PEER_ADDRESS } = require('./http-otel-semantics')
17
18
 
18
19
  const WEB = types.WEB
19
20
  const SERVER = kinds.SERVER
@@ -389,6 +390,15 @@ function addRequestTags (context, spanType) {
389
390
  [HTTP_USERAGENT]: req.headers['user-agent'],
390
391
  })
391
392
 
393
+ // OTel `network.peer.address` is the immediate socket peer. It has no Datadog
394
+ // equivalent and the socket isn't available at serialization, so (unlike the
395
+ // other HTTP attributes, which are renamed centrally in span_format) it is set
396
+ // here directly when OTel semantics are enabled.
397
+ if (config.DD_TRACE_OTEL_SEMANTICS_ENABLED) {
398
+ const peerAddress = req.socket?.remoteAddress
399
+ if (peerAddress) span.setTag(NETWORK_PEER_ADDRESS, peerAddress)
400
+ }
401
+
392
402
  // if client ip has already been set by appsec, no need to run it again
393
403
  if (config.extractIp && !spanContext.hasTag(HTTP_CLIENT_IP)) {
394
404
  const clientIp = config.extractIp(config, req)
@@ -433,26 +443,25 @@ function applyRouteOrEndpointTag (context) {
433
443
  if (!span) return
434
444
  const spanContext = span.context()
435
445
 
436
- // AppSec calls `web.setRouteOrEndpointTag` from a pre-finish hook so the
437
- // route/endpoint tags are available for API Security sampling, and the
438
- // normal finish-time path runs this again. Either tag being present
439
- // means the work has already been done; paths are stable between the
440
- // two calls, so the second pass has nothing to add.
441
- if (spanContext.hasTag(HTTP_ROUTE) || spanContext.hasTag(HTTP_ENDPOINT)) return
446
+ // AppSec runs this from a pre-finish hook and the finish path runs it
447
+ // again. http.route is terminal once set; nothing supersedes it.
448
+ if (spanContext.hasTag(HTTP_ROUTE)) return
442
449
 
443
450
  // Skip the `Array.prototype.join` builtin in the empty / single-segment
444
451
  // cases; `paths[0]` covers both (`undefined` is falsy for the empty case).
445
452
  const route = paths.length > 1 ? paths.join('') : paths[0]
446
453
 
447
454
  if (route) {
448
- // Use http.route from trusted framework instrumentation.
455
+ // A framework route supersedes any http.endpoint fallback a pre-finish
456
+ // call stamped before the route resolved; it must not be gated on
457
+ // HTTP_ENDPOINT.
449
458
  span.setTag(HTTP_ROUTE, route)
450
459
  return
451
460
  }
452
461
 
453
- if (!config.resourceRenamingEnabled) return
462
+ // Route unavailable. Compute the http.endpoint fallback once across both calls.
463
+ if (!config.resourceRenamingEnabled || spanContext.hasTag(HTTP_ENDPOINT)) return
454
464
 
455
- // Route is unavailable, compute http.endpoint once.
456
465
  const url = spanContext.getTag(HTTP_URL)
457
466
  const endpoint = url ? calculateHttpEndpoint(url) : '/'
458
467
  span.setTag(HTTP_ENDPOINT, endpoint)
@@ -543,30 +552,4 @@ function getMiddlewareSetting (config) {
543
552
  return true
544
553
  }
545
554
 
546
- function getQsObfuscator (config) {
547
- const obfuscator = config.queryStringObfuscation
548
-
549
- if (typeof obfuscator === 'boolean') {
550
- return obfuscator
551
- }
552
-
553
- if (typeof obfuscator === 'string') {
554
- if (obfuscator === '') return false // disable obfuscator
555
-
556
- if (obfuscator === '.*') return true // optimize full redact
557
-
558
- try {
559
- return new RegExp(obfuscator, 'gi')
560
- } catch (err) {
561
- log.error('Web plugin error getting qs obfuscator', err)
562
- }
563
- }
564
-
565
- if (config.hasOwnProperty('queryStringObfuscation')) {
566
- log.error('Expected `queryStringObfuscation` to be a regex string or boolean.')
567
- }
568
-
569
- return true
570
- }
571
-
572
555
  module.exports = web
@@ -19,6 +19,7 @@ const RateLimiter = require('./rate_limiter')
19
19
  const Sampler = require('./sampler')
20
20
  const { setSamplingRules } = require('./startup-log')
21
21
  const SamplingRule = require('./sampling_rule')
22
+ const { formatKnuthRate } = require('./util')
22
23
 
23
24
  const {
24
25
  SAMPLING_MECHANISM_DEFAULT,
@@ -36,19 +37,6 @@ const {
36
37
 
37
38
  const DEFAULT_KEY = 'service:,env:'
38
39
 
39
- /**
40
- * Formats a sampling rate as a string with up to 6 decimal digits and no trailing zeros.
41
- *
42
- * @param {number} rate
43
- */
44
- function formatKnuthRate (rate) {
45
- const string = Number(rate).toFixed(6)
46
- for (let i = string.length - 1; i > 0; i--) {
47
- if (string[i] === '0') continue
48
- return string.slice(0, i + (string[i] === '.' ? 0 : 1))
49
- }
50
- }
51
-
52
40
  const defaultSampler = new Sampler(AUTO_KEEP)
53
41
 
54
42
  /**
@@ -1,15 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const path = require('path')
4
- const { pathToFileURL } = require('url')
5
-
6
3
  const satisfies = require('../../../../vendor/dist/semifies')
7
4
  const getGitMetadata = require('../git_metadata')
8
5
  const log = require('../log')
9
6
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
10
7
  const { getIsAzureFunction } = require('../serverless')
11
8
  const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
12
- const { getEnvironmentVariable } = require('../config/helper')
9
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('../config/helper')
13
10
  const { isACFActive } = require('../../../datadog-core/src/storage')
14
11
 
15
12
  const { AgentExporter } = require('./exporters/agent')
@@ -17,152 +14,93 @@ const { FileExporter } = require('./exporters/file')
17
14
  const WallProfiler = require('./profilers/wall')
18
15
  const SpaceProfiler = require('./profilers/space')
19
16
  const EventsProfiler = require('./profilers/events')
20
- const { oomExportStrategies, snapshotKinds } = require('./constants')
17
+ const { ensureOOMExportStrategies } = require('./oom')
21
18
  const { tagger } = require('./tagger')
22
19
 
23
- class Config {
24
- constructor (options) {
25
- const AWS_LAMBDA_FUNCTION_NAME = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME')
26
-
27
- this.version = options.version
28
- this.service = options.service
29
- this.env = options.env
30
- this.functionname = AWS_LAMBDA_FUNCTION_NAME
31
-
32
- this.tags = {
33
- ...options.tags,
34
- ...tagger.parse({
35
- host: options.reportHostname ? require('os').hostname() : undefined,
36
- functionname: AWS_LAMBDA_FUNCTION_NAME,
37
- }),
38
- ...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
39
- }
40
-
41
- const { commitSHA, repositoryUrl } = getGitMetadata(options)
42
- if (repositoryUrl && commitSHA) {
43
- this.tags[GIT_REPOSITORY_URL] = repositoryUrl
44
- this.tags[GIT_COMMIT_SHA] = commitSHA
45
- }
46
-
47
- // Normalize from seconds to milliseconds. Default must be longer than a minute.
48
- this.flushInterval = options.DD_PROFILING_UPLOAD_PERIOD * 1000
49
- this.uploadTimeout = options.DD_PROFILING_UPLOAD_TIMEOUT
50
- this.sourceMap = options.DD_PROFILING_SOURCE_MAP
51
- this.debugSourceMaps = options.DD_PROFILING_DEBUG_SOURCE_MAPS
52
- this.endpointCollectionEnabled = options.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED
53
- this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
54
- this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
55
-
56
- this.url = options.url
57
-
58
- this.libraryInjected = !!options.DD_INJECTION_ENABLED
59
-
60
- let activation
61
- if (options.profiling.enabled === 'auto') {
62
- activation = 'auto'
63
- } else if (options.profiling.enabled === 'true') {
64
- activation = 'manual'
65
- } // else activation = undefined
66
-
67
- this.activation = activation
68
- this.exporters = ensureExporters(options.DD_PROFILING_EXPORTERS, this)
69
-
70
- const oomMonitoringEnabled = options.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED
71
- const heapLimitExtensionSize = options.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE
72
- const maxHeapExtensionCount = options.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT
73
- const exportStrategies = oomMonitoringEnabled
74
- ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES)
75
- : []
76
- const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
77
- this.oomMonitoring = {
78
- enabled: oomMonitoringEnabled,
79
- heapLimitExtensionSize,
80
- maxHeapExtensionCount,
81
- exportStrategies,
82
- exportCommand,
83
- }
84
-
85
- const profilers = getProfilers(options)
86
-
87
- this.timelineEnabled = options.DD_PROFILING_TIMELINE_ENABLED
88
- this.timelineSamplingEnabled = options.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED
89
- this.allocationProfilingEnabled = options.DD_PROFILING_ALLOCATION_ENABLED
90
- this.codeHotspotsEnabled = options.DD_PROFILING_CODEHOTSPOTS_ENABLED
91
- this.cpuProfilingEnabled = options.DD_PROFILING_CPU_ENABLED
92
- this.heapSamplingInterval = options.DD_PROFILING_HEAP_SAMPLING_INTERVAL
93
-
94
- this.samplingInterval = 1e3 / 99 // 99hz in milliseconds
95
-
96
- const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
97
-
98
- const uploadCompression0 = options.DD_PROFILING_DEBUG_UPLOAD_COMPRESSION
99
- let [uploadCompression, level0] = uploadCompression0.split('-')
100
- let level = level0 ? Number.parseInt(level0, 10) : undefined
101
- if (level !== undefined) {
102
- const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
103
- if (level > maxLevel) {
104
- log.warn('Invalid compression level %d. Will use %d.', level, maxLevel)
105
- level = maxLevel
106
- }
107
- }
108
-
109
- // Default to either zstd (on Node.js 24+) or gzip (earlier Node.js). We could default to ztsd
110
- // everywhere as we ship a Rust zstd compressor for older Node.js versions, but on 24+ we use
111
- // the built-in one that runs asynchronously on libuv worker threads, just as gzip does. This is
112
- // the least disruptive choice.
113
- if (uploadCompression === 'on') {
114
- uploadCompression = isAtLeast24 ? 'zstd' : 'gzip'
115
- }
20
+ /** @typedef {import('../config/config-base')} TracerConfig */
21
+ /** @typedef {AgentExporter | FileExporter} ProfilingExporter */
22
+ /** @typedef {WallProfiler | SpaceProfiler | EventsProfiler} ProfilingProfiler */
23
+
24
+ /** @param {TracerConfig} config */
25
+ function getProfilingTags (config) {
26
+ const functionName = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME')
27
+
28
+ const tags = {
29
+ ...config.tags,
30
+ ...tagger.parse({
31
+ host: config.reportHostname ? require('os').hostname() : undefined,
32
+ functionname: functionName,
33
+ }),
34
+ ...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
35
+ }
116
36
 
117
- this.uploadCompression = { method: uploadCompression, level }
37
+ const { commitSHA, repositoryUrl } = getGitMetadata(config)
38
+ if (repositoryUrl && commitSHA) {
39
+ tags[GIT_REPOSITORY_URL] = repositoryUrl
40
+ tags[GIT_COMMIT_SHA] = commitSHA
41
+ }
118
42
 
119
- const that = this
120
- function turnOffAsyncContextFrame (msg) {
121
- log.warn('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set %s, it will have no effect.', msg)
122
- that.asyncContextFrameEnabled = false
123
- }
43
+ return tags
44
+ }
124
45
 
125
- this.asyncContextFrameEnabled = options.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED ?? isACFActive
126
- if (this.asyncContextFrameEnabled && !isACFActive) {
127
- if (isAtLeast24) {
128
- turnOffAsyncContextFrame('with --no-async-context-frame')
46
+ /** @param {TracerConfig} config */
47
+ function getAsyncContextFrameEnabled (config) {
48
+ const enabled = config.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED
49
+ if (enabled && !isACFActive) {
50
+ // The default value already tracks runtime support, so an unset config landing
51
+ // here is expected; only an explicit opt-in the runtime can't honor is worth a warning.
52
+ if (getValueFromEnvSources('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED', true)) {
53
+ let reason
54
+ if (satisfies(process.versions.node, '>=24.0.0')) {
55
+ reason = 'with --no-async-context-frame'
129
56
  } else if (satisfies(process.versions.node, '>=22.9.0')) {
130
- turnOffAsyncContextFrame('without --experimental-async-context-frame')
57
+ reason = 'without --experimental-async-context-frame'
131
58
  } else {
132
- turnOffAsyncContextFrame('but it requires at least Node.js 22.9.0')
59
+ reason = 'but it requires at least Node.js 22.9.0'
133
60
  }
61
+ log.warn('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set %s, it will have no effect.', reason)
134
62
  }
135
-
136
- this.heartbeatInterval = options.telemetry.heartbeatInterval
137
-
138
- this.profilers = ensureProfilers(profilers, this)
63
+ return false
139
64
  }
65
+ return enabled
66
+ }
140
67
 
141
- get systemInfoReport () {
142
- const report = {
143
- allocationProfilingEnabled: this.allocationProfilingEnabled,
144
- asyncContextFrameEnabled: this.asyncContextFrameEnabled,
145
- codeHotspotsEnabled: this.codeHotspotsEnabled,
146
- cpuProfilingEnabled: this.cpuProfilingEnabled,
147
- debugSourceMaps: this.debugSourceMaps,
148
- endpointCollectionEnabled: this.endpointCollectionEnabled,
149
- heapSamplingInterval: this.heapSamplingInterval,
150
- oomMonitoring: { ...this.oomMonitoring },
151
- profilerTypes: this.profilers.map(profiler => profiler.type),
152
- sourceMap: this.sourceMap,
153
- timelineEnabled: this.timelineEnabled,
154
- timelineSamplingEnabled: this.timelineSamplingEnabled,
155
- uploadCompression: { ...this.uploadCompression },
156
- v8ProfilerBugWorkaroundEnabled: this.v8ProfilerBugWorkaroundEnabled,
68
+ /**
69
+ * @param {TracerConfig} config
70
+ * @param {{
71
+ * asyncContextFrameEnabled: boolean,
72
+ * flushInterval: number,
73
+ * tags: Record<string, string>,
74
+ * exporters: ProfilingExporter[],
75
+ * }} runtime
76
+ */
77
+ function createProfilers (config, { asyncContextFrameEnabled, flushInterval, tags, exporters }) {
78
+ const profilers = []
79
+ for (const name of selectProfilerTypes(config)) {
80
+ switch (name) {
81
+ case 'cpu':
82
+ case 'wall':
83
+ profilers.push(new WallProfiler(config, { asyncContextFrameEnabled, flushInterval }))
84
+ break
85
+ case 'space':
86
+ profilers.push(new SpaceProfiler(config, { tags, exporters }))
87
+ break
88
+ default:
89
+ log.error('Unknown profiler "%s"', name)
157
90
  }
158
- delete report.oomMonitoring.exportCommand
159
- return report
160
91
  }
161
- }
162
92
 
163
- module.exports = { Config }
93
+ // The events profiler produces timeline events. It is only added if timeline
94
+ // is enabled and there's a wall profiler.
95
+ if (config.DD_PROFILING_TIMELINE_ENABLED && profilers.some(profiler => profiler instanceof WallProfiler)) {
96
+ profilers.push(new EventsProfiler(config, { flushInterval }))
97
+ }
98
+
99
+ return profilers
100
+ }
164
101
 
165
- function getProfilers ({
102
+ /** @param {TracerConfig} config */
103
+ function selectProfilerTypes ({
166
104
  DD_PROFILING_HEAP_ENABLED,
167
105
  DD_PROFILING_WALLTIME_ENABLED,
168
106
  DD_PROFILING_PROFILERS,
@@ -214,81 +152,74 @@ function getProfilers ({
214
152
  return profilersArray
215
153
  }
216
154
 
217
- function getExportStrategy (name) {
218
- const strategy = Object.values(oomExportStrategies).find(value => value === name)
219
- if (strategy === undefined) {
220
- log.error('Unknown oom export strategy "%s"', name)
221
- }
222
- return strategy
223
- }
224
-
225
- function ensureOOMExportStrategies (strategies) {
226
- const set = new Set()
227
- for (const strategy of strategies) {
228
- set.add(getExportStrategy(strategy))
229
- }
230
-
231
- return [...set]
232
- }
233
-
234
- function getExporter (name, options) {
155
+ /**
156
+ * @param {string} name
157
+ * @param {TracerConfig} config
158
+ */
159
+ function getExporter (name, config) {
235
160
  switch (name) {
236
161
  case 'agent':
237
- return new AgentExporter(options)
162
+ return new AgentExporter(config)
238
163
  case 'file':
239
- return new FileExporter(options)
164
+ return new FileExporter(config)
240
165
  default:
241
166
  log.error('Unknown exporter "%s"', name)
242
167
  }
243
168
  }
244
169
 
245
- function ensureExporters (exporters, options) {
246
- return exporters.map((exporter) => getExporter(exporter, options))
247
- }
248
-
249
- function getProfiler (name, options) {
250
- switch (name) {
251
- case 'cpu':
252
- case 'wall':
253
- return new WallProfiler(options)
254
- case 'space':
255
- return new SpaceProfiler(options)
256
- default:
257
- log.error('Unknown profiler "%s"', name)
258
- }
259
- }
260
-
261
- function ensureProfilers (profilers, options) {
262
- const filteredProfilers = []
263
-
264
- for (let i = 0; i < profilers.length; i++) {
265
- const profiler = getProfiler(profilers[i], options)
266
- if (profiler !== undefined) {
267
- filteredProfilers.push(profiler)
170
+ /**
171
+ * Assembles everything the profiler needs from the tracer config: the runtime objects (tags,
172
+ * exporters, profilers) the {@link import('./profiler').Profiler#start} consumes and the system
173
+ * info report sent with each profile. The leaves read the canonical DD_PROFILING_* fields straight
174
+ * off the config; only the genuinely runtime values (tags, exporters, the resolved async context
175
+ * frame flag, the flush interval) are derived here.
176
+ *
177
+ * @param {TracerConfig} config
178
+ */
179
+ function buildProfilingRuntime (config) {
180
+ const tags = getProfilingTags(config)
181
+ const exporters = []
182
+ for (const name of config.DD_PROFILING_EXPORTERS) {
183
+ const exporter = getExporter(name, config)
184
+ // getExporter logs and returns undefined for an unknown exporter name; drop it so a misconfigured
185
+ // DD_PROFILING_EXPORTERS entry can't crash the export path later.
186
+ if (exporter !== undefined) {
187
+ exporters.push(exporter)
268
188
  }
269
189
  }
270
-
271
- // Events profiler is a profiler that produces timeline events. It is only
272
- // added if timeline is enabled and there's a wall profiler.
273
- if (options.timelineEnabled && filteredProfilers.some(profiler => profiler instanceof WallProfiler)) {
274
- filteredProfilers.push(new EventsProfiler(options))
190
+ const asyncContextFrameEnabled = getAsyncContextFrameEnabled(config)
191
+ const flushInterval = config.DD_PROFILING_UPLOAD_PERIOD * 1000
192
+ const profilers = createProfilers(config, { asyncContextFrameEnabled, flushInterval, tags, exporters })
193
+ const uploadCompression = config.DD_PROFILING_DEBUG_UPLOAD_COMPRESSION
194
+
195
+ const oomMonitoringEnabled = config.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED
196
+ const systemInfoReport = {
197
+ allocationProfilingEnabled: config.DD_PROFILING_ALLOCATION_ENABLED,
198
+ asyncContextFrameEnabled,
199
+ codeHotspotsEnabled: config.DD_PROFILING_CODEHOTSPOTS_ENABLED,
200
+ cpuProfilingEnabled: config.DD_PROFILING_CPU_ENABLED,
201
+ debugSourceMaps: config.DD_PROFILING_DEBUG_SOURCE_MAPS,
202
+ endpointCollectionEnabled: config.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
203
+ heapSamplingInterval: config.DD_PROFILING_HEAP_SAMPLING_INTERVAL,
204
+ oomMonitoring: {
205
+ enabled: oomMonitoringEnabled,
206
+ heapLimitExtensionSize: config.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
207
+ maxHeapExtensionCount: config.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
208
+ exportStrategies: oomMonitoringEnabled
209
+ ? ensureOOMExportStrategies(config.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES)
210
+ : [],
211
+ },
212
+ profilerTypes: profilers.map(profiler => profiler.type),
213
+ sourceMap: config.DD_PROFILING_SOURCE_MAP,
214
+ timelineEnabled: config.DD_PROFILING_TIMELINE_ENABLED,
215
+ timelineSamplingEnabled: config.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED,
216
+ uploadCompression: { ...uploadCompression },
217
+ v8ProfilerBugWorkaroundEnabled: config.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
275
218
  }
276
219
 
277
- return filteredProfilers
220
+ return { tags, exporters, flushInterval, profilers, uploadCompression, systemInfoReport }
278
221
  }
279
222
 
280
- function buildExportCommand (options) {
281
- const tags = [...Object.entries(options.tags),
282
- ['snapshot', snapshotKinds.ON_OUT_OF_MEMORY]].map(([key, value]) => `${key}:${value}`).join(',')
283
- const urls = []
284
- for (const exporter of options.exporters) {
285
- if (exporter instanceof AgentExporter) {
286
- urls.push(options.url.toString())
287
- } else if (exporter instanceof FileExporter) {
288
- urls.push(pathToFileURL(options.pprofPrefix).toString())
289
- }
290
- }
291
- return [process.execPath,
292
- path.join(__dirname, 'exporter_cli.js'),
293
- urls.join(','), tags, 'space']
223
+ module.exports = {
224
+ buildProfilingRuntime,
294
225
  }
@@ -1,15 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ // 99hz in milliseconds.
4
+ const SAMPLING_INTERVAL = 1e3 / 99
5
+
3
6
  const snapshotKinds = Object.freeze({
4
7
  PERIODIC: 'periodic',
5
8
  ON_SHUTDOWN: 'on_shutdown',
6
9
  ON_OUT_OF_MEMORY: 'on_oom',
7
10
  })
8
11
 
9
- const oomExportStrategies = Object.freeze({
10
- PROCESS: 'process',
11
- ASYNC_CALLBACK: 'async',
12
- LOGS: 'logs',
13
- })
14
-
15
- module.exports = { snapshotKinds, oomExportStrategies }
12
+ module.exports = { SAMPLING_INTERVAL, snapshotKinds }
@@ -15,20 +15,20 @@ const timeoutMs = 15 * 1000
15
15
 
16
16
  function exporterFromURL (url) {
17
17
  if (url.protocol === 'file:') {
18
- return new FileExporter({ pprofPrefix: fileURLToPath(url) })
18
+ return new FileExporter({}, fileURLToPath(url))
19
19
  }
20
+ // The subprocess has no tracer config, so mirror the canonical config property
21
+ // names the exporters read. Normalize the raw env to the parsed profiling.enabled
22
+ // values ('true' | 'false' | 'auto') the main process would have produced.
20
23
  const profilingEnabled = (getValueFromEnvSources('DD_PROFILING_ENABLED') ?? '').toLowerCase()
21
- const activation = ['true', '1'].includes(profilingEnabled)
22
- ? 'manual'
23
- : profilingEnabled === 'auto'
24
- ? 'auto'
25
- : 'unknown'
24
+ const enabled = profilingEnabled === 'auto'
25
+ ? 'auto'
26
+ : ['true', '1'].includes(profilingEnabled) ? 'true' : 'false'
26
27
  return new AgentExporter({
27
28
  url,
28
- logger,
29
- uploadTimeout: timeoutMs,
30
- libraryInjected: !!getValueFromEnvSources('DD_INJECTION_ENABLED'),
31
- activation,
29
+ DD_PROFILING_UPLOAD_TIMEOUT: timeoutMs,
30
+ DD_INJECTION_ENABLED: getValueFromEnvSources('DD_INJECTION_ENABLED'),
31
+ profiling: { enabled },
32
32
  })
33
33
  }
34
34
 
@@ -3,12 +3,12 @@
3
3
  const { request: httpRequest } = require('http')
4
4
  const { request: httpsRequest } = require('https')
5
5
  const perf = require('perf_hooks').performance
6
- const { urlToHttpOptions } = require('url')
7
6
 
8
7
  const retry = require('../../../../../vendor/dist/retry')
9
8
  // TODO: avoid using dd-trace internals. Make this a separate module?
10
9
  const docker = require('../../exporters/common/docker')
11
10
  const FormData = require('../../exporters/common/form-data')
11
+ const { parseUrl } = require('../../exporters/common/url')
12
12
  const log = require('../../log')
13
13
  const { storage } = require('../../../../datadog-core')
14
14
  const version = require('../../../../../package.json').version
@@ -88,15 +88,22 @@ function computeRetries (uploadTimeout) {
88
88
  }
89
89
 
90
90
  class AgentExporter extends EventSerializer {
91
- constructor (config = {}) {
91
+ #backoffTime
92
+ #backoffTries
93
+
94
+ /** @param {import('./event_serializer').TracerConfig} config */
95
+ constructor (config) {
92
96
  super(config)
93
- const { url, uploadTimeout } = config
94
- this._url = url
97
+ this._url = config.url
98
+
99
+ const [backoffTries, backoffTime] = computeRetries(config.DD_PROFILING_UPLOAD_TIMEOUT)
95
100
 
96
- const [backoffTries, backoffTime] = computeRetries(uploadTimeout)
101
+ this.#backoffTime = backoffTime
102
+ this.#backoffTries = backoffTries
103
+ }
97
104
 
98
- this._backoffTime = backoffTime
99
- this._backoffTries = backoffTries
105
+ getExportUrl () {
106
+ return this._url
100
107
  }
101
108
 
102
109
  export (exportSpec) {
@@ -128,8 +135,8 @@ class AgentExporter extends EventSerializer {
128
135
  return new Promise((resolve, reject) => {
129
136
  const operation = retry.operation({
130
137
  randomize: true,
131
- minTimeout: this._backoffTime,
132
- retries: this._backoffTries,
138
+ minTimeout: this.#backoffTime,
139
+ retries: this.#backoffTries,
133
140
  unref: true,
134
141
  })
135
142
 
@@ -148,18 +155,18 @@ class AgentExporter extends EventSerializer {
148
155
  'DD-EVP-ORIGIN-VERSION': version,
149
156
  ...form.getHeaders(),
150
157
  },
151
- timeout: this._backoffTime * 2 ** attempt,
158
+ timeout: this.#backoffTime * 2 ** attempt,
152
159
  }
153
160
 
154
161
  docker.inject(options.headers)
155
162
 
156
- if (this._url.protocol === 'unix:') {
157
- options.socketPath = this._url.pathname
163
+ const url = parseUrl(this._url)
164
+ if (url.protocol === 'unix:') {
165
+ options.socketPath = url.pathname
158
166
  } else {
159
- const httpOptions = urlToHttpOptions(this._url)
160
- options.protocol = httpOptions.protocol
161
- options.hostname = httpOptions.hostname
162
- options.port = httpOptions.port
167
+ options.protocol = url.protocol
168
+ options.hostname = url.hostname
169
+ options.port = url.port
163
170
  }
164
171
 
165
172
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style