dd-trace 6.13.0 → 6.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 (208) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/index.d.ts +8 -0
  14. package/package.json +13 -10
  15. package/packages/datadog-esbuild/index.js +3 -1
  16. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  17. package/packages/datadog-instrumentations/src/fs.js +24 -20
  18. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  19. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  20. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +14 -5
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  34. package/packages/datadog-instrumentations/src/jest.js +166 -45
  35. package/packages/datadog-instrumentations/src/knex.js +76 -0
  36. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  37. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  38. package/packages/datadog-instrumentations/src/mocha/main.js +66 -20
  39. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  40. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  41. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  42. package/packages/datadog-instrumentations/src/net.js +14 -17
  43. package/packages/datadog-instrumentations/src/next.js +212 -62
  44. package/packages/datadog-instrumentations/src/openai.js +42 -40
  45. package/packages/datadog-instrumentations/src/pg.js +15 -0
  46. package/packages/datadog-instrumentations/src/playwright.js +113 -13
  47. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  48. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  49. package/packages/datadog-instrumentations/src/undici.js +53 -4
  50. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  51. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  52. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  53. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  54. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  56. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  58. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  59. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  60. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  61. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  62. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  63. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  64. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  65. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  66. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  67. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  68. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  69. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  70. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  71. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  72. package/packages/datadog-plugin-jest/src/index.js +0 -6
  73. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  74. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  75. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  76. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  77. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  78. package/packages/datadog-plugin-next/src/index.js +63 -29
  79. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  80. package/packages/datadog-plugin-undici/src/index.js +27 -41
  81. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  82. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  83. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  85. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  86. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  87. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  88. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  90. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  91. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  93. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -95
  94. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  95. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  96. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  97. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  101. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  102. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  103. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  104. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  105. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  106. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  107. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  108. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  109. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  110. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  111. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  112. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  113. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +180 -0
  114. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  115. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  116. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  117. package/packages/dd-trace/src/config/index.js +73 -13
  118. package/packages/dd-trace/src/config/parsers.js +11 -1
  119. package/packages/dd-trace/src/config/stable.js +3 -3
  120. package/packages/dd-trace/src/config/supported-configurations.json +22 -2
  121. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  122. package/packages/dd-trace/src/debugger/config.js +12 -1
  123. package/packages/dd-trace/src/debugger/constants.js +1 -0
  124. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  125. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +28 -0
  126. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  127. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  128. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  129. package/packages/dd-trace/src/debugger/index.js +23 -3
  130. package/packages/dd-trace/src/dogstatsd.js +510 -76
  131. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  132. package/packages/dd-trace/src/encode/0.4.js +5 -5
  133. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  134. package/packages/dd-trace/src/evp_proxy/direct.js +3 -25
  135. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  136. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  137. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  138. package/packages/dd-trace/src/exporters/agentless/writer.js +103 -119
  139. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  140. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  141. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  142. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  143. package/packages/dd-trace/src/exporters/common/request.js +42 -14
  144. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  145. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  146. package/packages/dd-trace/src/flush.js +6 -3
  147. package/packages/dd-trace/src/id.js +14 -0
  148. package/packages/dd-trace/src/index.js +5 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  150. package/packages/dd-trace/src/llmobs/index.js +32 -32
  151. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  152. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  153. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  154. package/packages/dd-trace/src/llmobs/util.js +0 -33
  155. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  156. package/packages/dd-trace/src/log/index.js +7 -1
  157. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  158. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  159. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  160. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  161. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  162. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  163. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  164. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  166. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  167. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  168. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  169. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  170. package/packages/dd-trace/src/opentracing/propagation/text_map.js +101 -20
  171. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  172. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  173. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  174. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  175. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  176. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  177. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  178. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  179. package/packages/dd-trace/src/priority_sampler.js +4 -0
  180. package/packages/dd-trace/src/profiling/config.js +1 -0
  181. package/packages/dd-trace/src/profiling/oom.js +7 -2
  182. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  183. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  184. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  185. package/packages/dd-trace/src/proxy.js +75 -5
  186. package/packages/dd-trace/src/remote_config/index.js +319 -101
  187. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  188. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  189. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  190. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  191. package/packages/dd-trace/src/sampling_rule.js +39 -10
  192. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  193. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  194. package/packages/dd-trace/src/serverless.js +10 -3
  195. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  196. package/packages/dd-trace/src/span_processor.js +18 -3
  197. package/packages/dd-trace/src/span_stats.js +24 -28
  198. package/packages/dd-trace/src/startup-log.js +3 -2
  199. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  200. package/packages/dd-trace/src/telemetry/send-data.js +23 -20
  201. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  202. package/packages/dd-trace/src/tracer.js +4 -5
  203. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  204. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  205. package/vendor/dist/protobufjs/index.js +1 -1
  206. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  208. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const MAX_DI_LOG_BUFFERED_BYTES = 64 * 1024 * 1024
4
+ const MAX_REQUEST_BUFFERED_BYTES = 64 * 1024 * 1024
5
+
6
+ module.exports = {
7
+ MAX_DI_LOG_BUFFERED_BYTES,
8
+ MAX_REQUEST_BUFFERED_BYTES,
9
+ }
@@ -9,7 +9,168 @@ const {
9
9
  getRetryDelay,
10
10
  isRetriableNetworkError,
11
11
  } = require('../../exporters/common/retry')
12
+ const { FINAL_FLUSH_TIMEOUT_CODE } = require('../final-flush')
12
13
  const { getRateLimitResetDelay } = require('../requests/rate-limit')
14
+ const { MAX_REQUEST_BUFFERED_BYTES } = require('./limits')
15
+
16
+ const BACKPRESSURE_RETRY_MS = 50
17
+ const REQUEST_BUFFER_FULL_CODE = 'ERR_DD_REQUEST_BUFFER_FULL'
18
+
19
+ let bufferedBytes = 0
20
+ let backpressureTimer
21
+ let backpressureTimerDue
22
+ let ownedBackpressureWaiters = 0
23
+
24
+ /**
25
+ * @typedef {object} BackpressureWaiter
26
+ * @property {(attemptIndex: number) => void} attempt
27
+ * @property {number} attemptIndex
28
+ * @property {number} due
29
+ * @property {boolean} keepProcessAlive
30
+ */
31
+
32
+ /** @type {Set<BackpressureWaiter>} */
33
+ const backpressureWaiters = new Set()
34
+
35
+ /**
36
+ * Keeps the shared polling timer alive only while an owned request is waiting.
37
+ *
38
+ * @returns {void}
39
+ */
40
+ function updateBackpressureTimerRef () {
41
+ if (ownedBackpressureWaiters > 0) backpressureTimer.ref?.()
42
+ else backpressureTimer.unref?.()
43
+ }
44
+
45
+ /**
46
+ * Schedules the next shared backpressure poll.
47
+ *
48
+ * @param {number} due
49
+ * @returns {void}
50
+ */
51
+ function setBackpressureTimer (due) {
52
+ clearTimeout(backpressureTimer)
53
+ backpressureTimerDue = due
54
+ backpressureTimer = setTimeout(runBackpressureWaiters, Math.max(0, due - Date.now()))
55
+ updateBackpressureTimerRef()
56
+ }
57
+
58
+ /**
59
+ * Adds a request to the shared backpressure poll.
60
+ *
61
+ * @param {BackpressureWaiter} waiter
62
+ * @param {number} delay
63
+ * @param {number} attemptIndex
64
+ * @returns {void}
65
+ */
66
+ function scheduleBackpressureRetry (waiter, delay, attemptIndex) {
67
+ waiter.attemptIndex = attemptIndex
68
+ waiter.due = Date.now() + delay
69
+
70
+ backpressureWaiters.add(waiter)
71
+ if (waiter.keepProcessAlive) ownedBackpressureWaiters++
72
+
73
+ if (backpressureTimer === undefined || waiter.due < backpressureTimerDue) {
74
+ setBackpressureTimer(waiter.due)
75
+ } else {
76
+ updateBackpressureTimerRef()
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Removes a settled request from the shared backpressure poll.
82
+ *
83
+ * @param {BackpressureWaiter} waiter
84
+ * @returns {void}
85
+ */
86
+ function removeBackpressureWaiter (waiter) {
87
+ if (!backpressureWaiters.delete(waiter)) return
88
+ if (waiter.keepProcessAlive) ownedBackpressureWaiters--
89
+
90
+ if (backpressureWaiters.size === 0) {
91
+ clearTimeout(backpressureTimer)
92
+ backpressureTimer = undefined
93
+ backpressureTimerDue = undefined
94
+ } else {
95
+ updateBackpressureTimerRef()
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Retries every request whose shared backpressure delay has elapsed.
101
+ *
102
+ * @returns {void}
103
+ */
104
+ function runBackpressureWaiters () {
105
+ const now = Date.now()
106
+ const ready = []
107
+ backpressureTimer = undefined
108
+ backpressureTimerDue = undefined
109
+
110
+ for (const waiter of backpressureWaiters) {
111
+ if (waiter.due > now) continue
112
+ backpressureWaiters.delete(waiter)
113
+ if (waiter.keepProcessAlive) ownedBackpressureWaiters--
114
+ ready.push(waiter)
115
+ }
116
+
117
+ let nextDue = Infinity
118
+ for (const waiter of backpressureWaiters) {
119
+ if (waiter.due < nextDue) nextDue = waiter.due
120
+ }
121
+ if (nextDue !== Infinity) setBackpressureTimer(nextDue)
122
+
123
+ for (const waiter of ready) waiter.attempt(waiter.attemptIndex)
124
+ }
125
+
126
+ /**
127
+ * @param {Buffer|string|Array<Buffer|string>|(() => Readable)} data
128
+ * @returns {number}
129
+ */
130
+ function getPayloadSize (data) {
131
+ // Factory-backed payloads are streamed by their custom transport and enforce their own concurrency limit.
132
+ if (typeof data === 'function') return 0
133
+ if (!Array.isArray(data)) return Buffer.byteLength(data)
134
+
135
+ let size = 0
136
+ for (const chunk of data) size += Buffer.byteLength(chunk)
137
+ return size
138
+ }
139
+
140
+ /**
141
+ * @returns {Error & { code: string }}
142
+ */
143
+ function createQueueFullError () {
144
+ const error = new Error('Test Optimization request queue reached its payload limit')
145
+ error.code = 'ERR_DD_TEST_OPTIMIZATION_QUEUE_FULL'
146
+ return error
147
+ }
148
+
149
+ /**
150
+ * @returns {Error & { code: string }}
151
+ */
152
+ function createBackpressureTimeoutError () {
153
+ const error = new Error('Test Optimization request remained blocked by exporter backpressure')
154
+ error.code = 'ERR_DD_TEST_OPTIMIZATION_BACKPRESSURE_TIMEOUT'
155
+ return error
156
+ }
157
+
158
+ /**
159
+ * @returns {Error & { code: string }}
160
+ */
161
+ function createRequestTimeoutError () {
162
+ const error = new Error('Test Optimization transport attempt timed out')
163
+ error.code = 'ERR_DD_TEST_OPTIMIZATION_REQUEST_TIMEOUT'
164
+ return error
165
+ }
166
+
167
+ /**
168
+ * @param {number} statusCode
169
+ * @returns {boolean}
170
+ */
171
+ function isRetriableHttpStatusCode (statusCode) {
172
+ return statusCode === 408 || statusCode === 429 || (statusCode >= 500 && statusCode <= 599)
173
+ }
13
174
 
14
175
  /**
15
176
  * @param {AbortSignal} signal
@@ -24,36 +185,57 @@ function getAbortError (signal) {
24
185
  *
25
186
  * @param {Readable} data
26
187
  * @param {AbortSignal} [signal]
27
- * @param {(error: Error|null, data?: Buffer) => void} callback
188
+ * @param {(error: Error|null, data?: Buffer, payloadSize?: number) => void} callback
28
189
  * @returns {void}
29
190
  */
30
191
  function bufferReadable (data, signal, callback) {
31
192
  const chunks = []
193
+ let payloadSize = 0
32
194
  let settled = false
33
195
 
196
+ const release = () => {
197
+ bufferedBytes -= payloadSize
198
+ payloadSize = 0
199
+ }
34
200
  const cleanup = (keepErrorListener = false) => {
35
201
  data.removeListener('data', onData)
36
202
  data.removeListener('end', onEnd)
37
203
  if (!keepErrorListener) data.removeListener('error', onError)
38
204
  signal?.removeEventListener('abort', onAbort)
39
205
  }
40
- const onData = chunk => chunks.push(chunk)
206
+ const onData = chunk => {
207
+ const chunkSize = Buffer.byteLength(chunk)
208
+ if (chunkSize > MAX_REQUEST_BUFFERED_BYTES - bufferedBytes) {
209
+ settled = true
210
+ cleanup(true)
211
+ release()
212
+ data.once('close', () => data.removeListener('error', onError))
213
+ data.destroy()
214
+ callback(createQueueFullError())
215
+ return
216
+ }
217
+ bufferedBytes += chunkSize
218
+ payloadSize += chunkSize
219
+ chunks.push(chunk)
220
+ }
41
221
  const onEnd = () => {
42
222
  if (settled) return
43
223
  settled = true
44
224
  cleanup()
45
- callback(null, Buffer.concat(chunks))
225
+ callback(null, chunks, payloadSize)
46
226
  }
47
227
  const onError = error => {
48
228
  if (settled) return
49
229
  settled = true
50
230
  cleanup()
231
+ release()
51
232
  callback(error)
52
233
  }
53
234
  const onAbort = () => {
54
235
  if (settled) return
55
236
  settled = true
56
237
  cleanup(true)
238
+ release()
57
239
  data.once('close', () => data.removeListener('error', onError))
58
240
  data.destroy()
59
241
  callback(getAbortError(signal))
@@ -70,7 +252,7 @@ function bufferReadable (data, signal, callback) {
70
252
  * Sends Test Optimization exporter data through the common single-attempt transport while
71
253
  * keeping retry and finalization policy scoped to Test Optimization.
72
254
  *
73
- * @param {Buffer|string|Readable|Array<Buffer|string>} data
255
+ * @param {Buffer|string|Readable|Array<Buffer|string>|(() => Readable)} data
74
256
  * @param {object} options
75
257
  * @param {(error: Error|null, result?: string|null, statusCode?: number,
76
258
  * headers?: import('node:http').IncomingHttpHeaders) => void} callback
@@ -81,9 +263,9 @@ function request (data, options, callback) {
81
263
  if (signal?.aborted) return callback(getAbortError(signal))
82
264
 
83
265
  if (data instanceof Readable) {
84
- bufferReadable(data, signal, (error, bufferedData) => {
266
+ bufferReadable(data, signal, (error, bufferedData, payloadSize) => {
85
267
  if (error) return callback(error)
86
- requestBuffered(bufferedData, options, callback)
268
+ requestBuffered(bufferedData, options, callback, payloadSize)
87
269
  })
88
270
  return
89
271
  }
@@ -94,75 +276,180 @@ function request (data, options, callback) {
94
276
  /**
95
277
  * Applies the Test Optimization retry policy to replayable request data.
96
278
  *
97
- * @param {Buffer|string|Array<Buffer|string>} data
279
+ * @param {Buffer|string|Array<Buffer|string>|(() => Readable)} data
98
280
  * @param {object} options
99
281
  * @param {(error: Error|null, result?: string|null, statusCode?: number,
100
282
  * headers?: import('node:http').IncomingHttpHeaders) => void} callback
283
+ * @param {number} [reservedPayloadSize]
101
284
  * @returns {void}
102
285
  */
103
- function requestBuffered (data, options, callback) {
286
+ function requestBuffered (data, options, callback, reservedPayloadSize) {
104
287
  const { signal } = options
288
+ const transport = options.transport || commonRequest
105
289
  const timeout = options.timeout || 2000
290
+ const payloadSize = reservedPayloadSize ?? getPayloadSize(data)
106
291
  let retryTimer
292
+ let attemptTimer
293
+ let attemptTimerImmediate
294
+ let attemptController
107
295
  let settled = false
296
+ let lastError
297
+ let waitingForBackpressure = false
298
+
299
+ const backpressureWaiter = {
300
+ attempt,
301
+ attemptIndex: 1,
302
+ due: 0,
303
+ keepProcessAlive: options.keepProcessAlive === true,
304
+ }
305
+
306
+ if (reservedPayloadSize === undefined) {
307
+ if (payloadSize > MAX_REQUEST_BUFFERED_BYTES - bufferedBytes) {
308
+ callback(createQueueFullError())
309
+ return
310
+ }
311
+ bufferedBytes += payloadSize
312
+ }
108
313
 
109
314
  const complete = (error, result, statusCode, headers) => {
110
315
  if (settled) return
111
316
  settled = true
112
317
  clearTimeout(retryTimer)
318
+ clearTimeout(attemptTimer)
319
+ clearImmediate(attemptTimerImmediate)
320
+ removeBackpressureWaiter(backpressureWaiter)
113
321
  signal?.removeEventListener('abort', onAbort)
322
+ bufferedBytes -= payloadSize
114
323
  callback(error, result, statusCode, headers)
115
324
  }
116
- const onAbort = () => complete(getAbortError(signal))
325
+ const onAbort = () => {
326
+ const abortError = getAbortError(signal)
327
+ const controller = attemptController
328
+ let error = lastError || abortError
329
+
330
+ if (abortError.code === FINAL_FLUSH_TIMEOUT_CODE) {
331
+ if (waitingForBackpressure) error = createBackpressureTimeoutError()
332
+ else if (controller) error = createRequestTimeoutError()
333
+ }
334
+
335
+ try {
336
+ complete(error, null, error.status)
337
+ } finally {
338
+ controller?.abort(abortError)
339
+ }
340
+ }
117
341
 
118
342
  signal?.addEventListener('abort', onAbort, { once: true })
119
343
  if (signal?.aborted) return onAbort()
120
344
 
121
- /** @param {number} attemptIndex */
122
- const attempt = attemptIndex => {
345
+ /**
346
+ * @param {number} attemptIndex
347
+ * @returns {void}
348
+ */
349
+ function attempt (attemptIndex) {
123
350
  if (settled) return
124
351
  if (signal?.aborted) return onAbort()
125
352
 
126
353
  const deadline = options.deadline
127
354
  const remaining = deadline === undefined ? Infinity : deadline - Date.now()
128
355
  if (remaining <= 0) {
129
- const error = new Error('Test Optimization request reached its finalization deadline')
130
- error.code = 'ERR_DD_TEST_OPTIMIZATION_FLUSH_TIMEOUT'
131
- complete(error)
356
+ if (waitingForBackpressure) {
357
+ complete(createBackpressureTimeoutError())
358
+ } else if (lastError) {
359
+ complete(lastError, null, lastError.status)
360
+ } else {
361
+ const error = new Error('Test Optimization request reached its finalization deadline')
362
+ error.code = FINAL_FLUSH_TIMEOUT_CODE
363
+ complete(error)
364
+ }
132
365
  return
133
366
  }
134
367
 
135
- if (deadline !== undefined && !commonRequest.writable) {
136
- retryTimer = setTimeout(attempt, Math.min(50, remaining), attemptIndex)
137
- retryTimer.unref?.()
368
+ if (!transport.writable) {
369
+ waitingForBackpressure = true
370
+ scheduleBackpressureRetry(
371
+ backpressureWaiter,
372
+ Math.min(BACKPRESSURE_RETRY_MS, remaining),
373
+ attemptIndex
374
+ )
138
375
  return
139
376
  }
377
+ waitingForBackpressure = false
140
378
 
141
379
  const attemptOptions = {
142
380
  ...options,
381
+ deferTimeoutAbort: true,
143
382
  headers: options.headers ? { ...options.headers } : undefined,
144
383
  retry: false,
145
384
  }
385
+ delete attemptOptions.transport
146
386
  if (deadline !== undefined) attemptOptions.timeout = Math.max(1, Math.min(timeout, remaining))
147
387
 
148
- commonRequest(data, attemptOptions, (error, result, statusCode, headers) => {
388
+ let attemptData
389
+ try {
390
+ attemptData = typeof data === 'function' ? data() : data
391
+ } catch (error) {
392
+ complete(error)
393
+ return
394
+ }
395
+
396
+ const controller = new AbortController()
397
+ const attemptTimeout = attemptOptions.timeout || timeout
398
+ let attemptTimedOut = false
399
+
400
+ attemptController = controller
401
+ attemptOptions.signal = controller.signal
402
+ if (options.timeoutFromCreation !== false) {
403
+ attemptTimer = setTimeout(() => {
404
+ // Let a response that became ready while the event loop was blocked win before aborting it.
405
+ attemptTimerImmediate = setImmediate(() => {
406
+ if (settled || attemptController !== controller) return
407
+ attemptTimedOut = true
408
+ controller.abort(createRequestTimeoutError())
409
+ })
410
+ if (!options.keepProcessAlive) attemptTimerImmediate.unref?.()
411
+ }, attemptTimeout)
412
+ if (!options.keepProcessAlive) attemptTimer.unref?.()
413
+ }
414
+
415
+ transport(attemptData, attemptOptions, (error, result, statusCode, headers) => {
416
+ clearTimeout(attemptTimer)
417
+ clearImmediate(attemptTimerImmediate)
418
+ if (attemptController === controller) attemptController = undefined
149
419
  if (settled) return
150
420
  if (!error) {
151
421
  complete(null, result, statusCode, headers)
152
422
  return
153
423
  }
154
424
 
155
- const responseStatus = statusCode ?? error.status
156
- const isRetriableHttpError = options.deadline !== undefined &&
157
- (responseStatus === 429 || responseStatus >= 500)
158
- if (options.retry === false || (attemptIndex >= getMaxAttempts(attemptOptions) &&
159
- (isRetriableNetworkError(error) || isRetriableHttpError))) {
160
- complete(error, result, statusCode, headers)
425
+ if (error.code === REQUEST_BUFFER_FULL_CODE) {
426
+ waitingForBackpressure = true
427
+ scheduleBackpressureRetry(
428
+ backpressureWaiter,
429
+ Math.min(BACKPRESSURE_RETRY_MS, remaining),
430
+ attemptIndex
431
+ )
161
432
  return
162
433
  }
163
434
 
164
- if (!isRetriableNetworkError(error) && !isRetriableHttpError) {
165
- complete(error, result, statusCode, headers)
435
+ const requestError = attemptTimedOut ? createRequestTimeoutError() : error
436
+ lastError = requestError
437
+
438
+ const responseStatus = statusCode ?? error.status
439
+ const isUnknownNetworkError = responseStatus === undefined && error.code === undefined
440
+ const isRetriableError = error.retryable !== false &&
441
+ (attemptTimedOut || isRetriableNetworkError(error) || isUnknownNetworkError ||
442
+ isRetriableHttpStatusCode(responseStatus))
443
+ const retryUntilDeadline = options.deadline !== undefined && options.retryUntilDeadline !== false
444
+ const reachedAttemptLimit = !retryUntilDeadline && attemptIndex >= getMaxAttempts(attemptOptions)
445
+ const reachedUnknownNetworkAttemptLimit = isUnknownNetworkError && attemptIndex >= 2
446
+ if (
447
+ options.retry === false ||
448
+ !isRetriableError ||
449
+ reachedAttemptLimit ||
450
+ reachedUnknownNetworkAttemptLimit
451
+ ) {
452
+ complete(requestError, result, statusCode, headers)
166
453
  return
167
454
  }
168
455
 
@@ -170,9 +457,14 @@ function requestBuffered (data, options, callback) {
170
457
  if (responseStatus === 429) {
171
458
  const resetDelay = getRateLimitResetDelay(headers)
172
459
  if (Number.isFinite(resetDelay)) {
173
- const retryRemaining = options.deadline - Date.now()
174
- if (resetDelay > RATE_LIMIT_MAX_WAIT_MS || resetDelay >= retryRemaining) {
175
- complete(error, result, statusCode, headers)
460
+ if (options.deadline !== undefined) {
461
+ const retryRemaining = options.deadline - Date.now()
462
+ if (resetDelay >= retryRemaining) {
463
+ complete(requestError, result, statusCode, headers)
464
+ return
465
+ }
466
+ } else if (resetDelay > RATE_LIMIT_MAX_WAIT_MS) {
467
+ complete(requestError, result, statusCode, headers)
176
468
  return
177
469
  }
178
470
  retryDelay = resetDelay
@@ -183,7 +475,7 @@ function requestBuffered (data, options, callback) {
183
475
  if (options.deadline !== undefined && retryDelay === undefined) {
184
476
  const retryRemaining = options.deadline - Date.now()
185
477
  if (retryRemaining <= 0) {
186
- complete(error, result, statusCode, headers)
478
+ complete(requestError, result, statusCode, headers)
187
479
  return
188
480
  }
189
481
  const retryAttemptTimeout = timeout < retryRemaining ? timeout : Math.ceil(retryRemaining / 2)
@@ -191,7 +483,7 @@ function requestBuffered (data, options, callback) {
191
483
  }
192
484
 
193
485
  retryTimer = setTimeout(attempt, delay, attemptIndex + 1)
194
- retryTimer.unref?.()
486
+ if (!options.keepProcessAlive) retryTimer.unref?.()
195
487
  })
196
488
  }
197
489
 
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const { version: tracerVersion } = require('../../../../../package.json')
4
+ const { buildCacheKey } = require('../requests/fs-cache')
5
+ const getConfig = require('../../config')
6
+
7
+ /**
8
+ * Builds the cross-process filesystem cache key for the settings request from the
9
+ * request configuration. The key also isolates the tracer version and local flags
10
+ * applied while parsing the response.
11
+ *
12
+ * @param {object} configuration - Request configuration for the settings endpoint.
13
+ * @returns {string}
14
+ */
15
+ function buildSettingsCacheKey (configuration) {
16
+ const config = getConfig()
17
+ const { testOptimization } = config
18
+ return buildCacheKey('settings', [
19
+ tracerVersion,
20
+ configuration.url?.href,
21
+ configuration.isEvpProxy,
22
+ configuration.evpProxyPrefix,
23
+ configuration.sha,
24
+ configuration.service,
25
+ configuration.env,
26
+ configuration.repositoryUrl,
27
+ configuration.branch,
28
+ configuration.tag,
29
+ configuration.testLevel,
30
+ configuration.osPlatform,
31
+ configuration.osVersion,
32
+ configuration.osArchitecture,
33
+ configuration.runtimeName,
34
+ configuration.runtimeVersion,
35
+ configuration.custom,
36
+ testOptimization.DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE,
37
+ testOptimization.DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING,
38
+ testOptimization.DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED,
39
+ ])
40
+ }
41
+
42
+ module.exports = buildSettingsCacheKey
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const FINAL_FLUSH_TIMEOUT = 10_000
3
+ const FINAL_FLUSH_TIMEOUT = 60_000
4
4
  const FINAL_FLUSH_FALLBACK_DELAY = 100
5
5
  const FINAL_FLUSH_TIMEOUT_CODE = 'ERR_DD_TEST_OPTIMIZATION_FLUSH_TIMEOUT'
6
6
 
@@ -17,4 +17,5 @@ module.exports = {
17
17
  createFinalFlushTimeoutError,
18
18
  FINAL_FLUSH_FALLBACK_DELAY,
19
19
  FINAL_FLUSH_TIMEOUT,
20
+ FINAL_FLUSH_TIMEOUT_CODE,
20
21
  }