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.
- package/ci/init.js +4 -3
- package/index.d.ts +66 -24
- package/loader-hook.mjs +88 -2
- package/package.json +18 -14
- package/packages/datadog-esbuild/index.js +26 -0
- package/packages/datadog-esbuild/src/utils.js +46 -3
- package/packages/datadog-instrumentations/src/connect.js +4 -3
- package/packages/datadog-instrumentations/src/fastify.js +4 -12
- package/packages/datadog-instrumentations/src/graphql.js +17 -6
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
- package/packages/datadog-instrumentations/src/koa.js +3 -2
- package/packages/datadog-instrumentations/src/mariadb.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
- package/packages/datadog-instrumentations/src/next.js +10 -10
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/restify.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +18 -5
- package/packages/datadog-instrumentations/src/selenium.js +1 -2
- package/packages/datadog-instrumentations/src/tedious.js +28 -0
- package/packages/datadog-instrumentations/src/vitest.js +264 -7
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
- package/packages/datadog-plugin-http/src/client.js +8 -3
- package/packages/datadog-plugin-http2/src/client.js +5 -2
- package/packages/datadog-plugin-mysql/src/index.js +7 -2
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-undici/src/index.js +5 -2
- package/packages/datadog-plugin-vitest/src/index.js +7 -1
- package/packages/datadog-webpack/index.js +17 -1
- package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
- package/packages/dd-trace/src/aiguard/sdk.js +13 -1
- package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
- package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
- package/packages/dd-trace/src/appsec/index.js +10 -6
- package/packages/dd-trace/src/appsec/reporter.js +1 -0
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
- package/packages/dd-trace/src/config/defaults.js +22 -3
- package/packages/dd-trace/src/config/generated-config-types.d.ts +535 -1
- package/packages/dd-trace/src/config/helper.js +133 -33
- package/packages/dd-trace/src/config/index.js +4 -1
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +21 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
- package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
- package/packages/dd-trace/src/debugger/index.js +5 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
- package/packages/dd-trace/src/exporters/common/request.js +1 -20
- package/packages/dd-trace/src/exporters/common/url.js +34 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
- package/packages/dd-trace/src/guardrails/index.js +8 -5
- package/packages/dd-trace/src/index.js +6 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
- package/packages/dd-trace/src/llmobs/index.js +16 -5
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
- package/packages/dd-trace/src/llmobs/sdk.js +5 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
- package/packages/dd-trace/src/llmobs/tagger.js +55 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
- package/packages/dd-trace/src/log/index.js +18 -20
- package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
- package/packages/dd-trace/src/opentracing/span_context.js +17 -0
- package/packages/dd-trace/src/plugin_manager.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
- package/packages/dd-trace/src/plugins/util/test.js +80 -0
- package/packages/dd-trace/src/plugins/util/url.js +68 -1
- package/packages/dd-trace/src/plugins/util/web.js +19 -36
- package/packages/dd-trace/src/priority_sampler.js +1 -13
- package/packages/dd-trace/src/profiling/config.js +129 -198
- package/packages/dd-trace/src/profiling/constants.js +4 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
- package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
- package/packages/dd-trace/src/profiling/oom.js +71 -0
- package/packages/dd-trace/src/profiling/profiler.js +35 -23
- package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
- package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +1 -2
- package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
- package/packages/dd-trace/src/span_format.js +2 -1
- package/packages/dd-trace/src/span_processor.js +6 -0
- package/packages/dd-trace/src/telemetry/send-data.js +12 -10
- package/packages/dd-trace/src/tracer.js +15 -1
- package/packages/dd-trace/src/util.js +14 -0
- package/register.js +58 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
|
@@ -149,7 +149,140 @@ function validateAccess (name) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
let configurationsTable
|
|
153
|
+
let configDefaults
|
|
154
|
+
|
|
155
|
+
// Lazy require to keep the `config -> helper` import direction acyclic: helper
|
|
156
|
+
// must not pull `config/defaults` at module load. `config/defaults` defers its
|
|
157
|
+
// own instrumented `dns` require until after it exports, so by the time any
|
|
158
|
+
// caller resolves a value the table is fully populated.
|
|
159
|
+
function loadConfigurationsTable () {
|
|
160
|
+
if (configurationsTable === undefined) {
|
|
161
|
+
const defaultsModule = require('./defaults')
|
|
162
|
+
configurationsTable = defaultsModule.configurationsTable
|
|
163
|
+
configDefaults = defaultsModule.defaults
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parses and transforms a raw environment value with the same parser and
|
|
169
|
+
* transformer Config applies when it reads environment sources, so callers
|
|
170
|
+
* receive the typed value instead of the raw string.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} name Canonical or alias environment variable name.
|
|
173
|
+
* @param {string} value Raw value read from an environment source.
|
|
174
|
+
* @param {'env_var'|'fleet_stable_config'|'local_stable_config'} source Source the value was read from.
|
|
175
|
+
* @returns {string | number | boolean | object | undefined}
|
|
176
|
+
*/
|
|
177
|
+
function parseConfigurationValue (name, value, source) {
|
|
178
|
+
loadConfigurationsTable()
|
|
179
|
+
const canonical = aliasToCanonical[name] ?? name
|
|
180
|
+
const entry = configurationsTable[canonical]
|
|
181
|
+
const parsed = entry.parser(value, canonical, source)
|
|
182
|
+
return parsed !== undefined && entry.transformer ? entry.transformer(parsed, canonical, source) : parsed
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Returns the registered default for a configuration — the same value Config
|
|
187
|
+
* resolves when no environment source sets it.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} name Canonical or alias environment variable name.
|
|
190
|
+
* @returns {string | number | boolean | object | undefined}
|
|
191
|
+
*/
|
|
192
|
+
function getRegisteredDefault (name) {
|
|
193
|
+
loadConfigurationsTable()
|
|
194
|
+
const canonical = aliasToCanonical[name] ?? name
|
|
195
|
+
const entry = configurationsTable[canonical]
|
|
196
|
+
return configDefaults[entry.property ?? canonical]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @typedef {import('./generated-config-types').GeneratedEnvVarConfig} GeneratedEnvVarConfig */
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Returns the value stored at the given name, assumed to be in environment variable format,
|
|
203
|
+
* from the supported env sources (process.env, local stable config, fleet stable config).
|
|
204
|
+
* Falls back to aliases if the canonical name is not set.
|
|
205
|
+
*
|
|
206
|
+
* The raw value is parsed and transformed exactly as Config does when it reads environment
|
|
207
|
+
* sources, so the returned value is typed (boolean, number, array, map, ...) rather than the
|
|
208
|
+
* raw string. A literal supported name resolves to its specific configured type.
|
|
209
|
+
*
|
|
210
|
+
* When the name is not set in any source, the registered default is returned, so callers can
|
|
211
|
+
* use the value directly. Callers that must distinguish an explicitly configured value from the
|
|
212
|
+
* default (e.g. an OTel fallback that only applies when the option is unset) pass `skipDefault`
|
|
213
|
+
* to receive `undefined` for an unset value instead. All sources (process.env, local and fleet
|
|
214
|
+
* stable config) are read in both modes.
|
|
215
|
+
*
|
|
216
|
+
* @template {keyof GeneratedEnvVarConfig} TName
|
|
217
|
+
* @overload
|
|
218
|
+
* @param {TName} name Environment variable name
|
|
219
|
+
* @returns {GeneratedEnvVarConfig[TName]}
|
|
220
|
+
*/
|
|
221
|
+
/**
|
|
222
|
+
* @template {keyof GeneratedEnvVarConfig} TName
|
|
223
|
+
* @overload
|
|
224
|
+
* @param {TName} name Environment variable name
|
|
225
|
+
* @param {boolean} skipDefault Return `undefined` instead of the registered default when unset.
|
|
226
|
+
* @returns {GeneratedEnvVarConfig[TName] | undefined}
|
|
227
|
+
*/
|
|
228
|
+
/**
|
|
229
|
+
* @overload
|
|
230
|
+
* @param {string} name Environment variable name
|
|
231
|
+
* @param {boolean} [skipDefault] Return `undefined` instead of the registered default when unset.
|
|
232
|
+
* @returns {string | number | boolean | object | undefined}
|
|
233
|
+
*/
|
|
234
|
+
/**
|
|
235
|
+
* @param {string} name
|
|
236
|
+
* @param {boolean} [skipDefault]
|
|
237
|
+
* @throws {Error} if the configuration is not supported
|
|
238
|
+
*/
|
|
239
|
+
function getValueFromEnvSources (name, skipDefault) {
|
|
240
|
+
validateAccess(name)
|
|
241
|
+
|
|
242
|
+
if (!stableConfigLoaded) {
|
|
243
|
+
loadStableConfig()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Sources are read in priority order (fleet > env > local). A value the parser
|
|
247
|
+
// or transformer rejects resolves to `undefined` and is ignored, falling
|
|
248
|
+
// through to the next source and finally the registered default — mirroring
|
|
249
|
+
// how Config applies env sources (`setAndTrack` skips such values). This keeps
|
|
250
|
+
// a malformed higher-priority value (e.g. a fleet-stable `DD_TRACE_DEBUG=maybe`)
|
|
251
|
+
// from masking a valid `DD_TRACE_DEBUG=true`.
|
|
252
|
+
if (fleetStableConfig !== undefined) {
|
|
253
|
+
const fromFleet = getValueFromSource(name, fleetStableConfig)
|
|
254
|
+
if (fromFleet !== undefined) {
|
|
255
|
+
const parsed = parseConfigurationValue(name, fromFleet, 'fleet_stable_config')
|
|
256
|
+
if (parsed !== undefined) {
|
|
257
|
+
return parsed
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const fromEnv = getValueFromSource(name, process.env)
|
|
263
|
+
if (fromEnv !== undefined) {
|
|
264
|
+
const parsed = parseConfigurationValue(name, fromEnv, 'env_var')
|
|
265
|
+
if (parsed !== undefined) {
|
|
266
|
+
return parsed
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (localStableConfig !== undefined) {
|
|
271
|
+
const fromLocal = getValueFromSource(name, localStableConfig)
|
|
272
|
+
if (fromLocal !== undefined) {
|
|
273
|
+
const parsed = parseConfigurationValue(name, fromLocal, 'local_stable_config')
|
|
274
|
+
if (parsed !== undefined) {
|
|
275
|
+
return parsed
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return skipDefault ? undefined : getRegisteredDefault(name)
|
|
281
|
+
}
|
|
282
|
+
|
|
152
283
|
module.exports = {
|
|
284
|
+
getValueFromEnvSources,
|
|
285
|
+
|
|
153
286
|
/**
|
|
154
287
|
* Expose raw stable config maps and warnings for consumers that need
|
|
155
288
|
* per-source access (e.g. telemetry in Config).
|
|
@@ -209,39 +342,6 @@ module.exports = {
|
|
|
209
342
|
return getValueFromSource(name, process.env)
|
|
210
343
|
},
|
|
211
344
|
|
|
212
|
-
/**
|
|
213
|
-
* Returns the value stored at the given name, assumed to be in environment variable format,
|
|
214
|
-
* from the supported env sources (process.env, local stable config, fleet stable config).
|
|
215
|
-
* Falls back to aliases if the canonical name is not set.
|
|
216
|
-
*
|
|
217
|
-
* @param {string} name Environment variable name
|
|
218
|
-
* @returns {string|undefined}
|
|
219
|
-
* @throws {Error} if the configuration is not supported
|
|
220
|
-
*/
|
|
221
|
-
getValueFromEnvSources (name) {
|
|
222
|
-
validateAccess(name)
|
|
223
|
-
|
|
224
|
-
if (!stableConfigLoaded) {
|
|
225
|
-
loadStableConfig()
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (fleetStableConfig !== undefined) {
|
|
229
|
-
const fromFleet = getValueFromSource(name, fleetStableConfig)
|
|
230
|
-
if (fromFleet !== undefined) {
|
|
231
|
-
return fromFleet
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const fromEnv = getValueFromSource(name, process.env)
|
|
236
|
-
if (fromEnv !== undefined) {
|
|
237
|
-
return fromEnv
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (localStableConfig !== undefined) {
|
|
241
|
-
return getValueFromSource(name, localStableConfig)
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
|
|
245
345
|
/**
|
|
246
346
|
* Returns the actual environment variable name used for a supported configuration
|
|
247
347
|
* from a specific environment-based source.
|
|
@@ -7,7 +7,7 @@ const { URL, format } = require('node:url')
|
|
|
7
7
|
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
8
8
|
const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
|
|
9
9
|
const set = require('../../../datadog-core/src/utils/src/set')
|
|
10
|
-
const { DD_MAJOR } = require('../../../../version')
|
|
10
|
+
const { DD_MAJOR, NODE_MAJOR } = require('../../../../version')
|
|
11
11
|
const log = require('../log')
|
|
12
12
|
const pkg = require('../pkg')
|
|
13
13
|
const { isTrue } = require('../util')
|
|
@@ -361,6 +361,9 @@ class Config extends ConfigBase {
|
|
|
361
361
|
if (this.telemetry.heartbeatInterval) {
|
|
362
362
|
setAndTrack(this, 'telemetry.heartbeatInterval', Math.floor(this.telemetry.heartbeatInterval * 1000))
|
|
363
363
|
}
|
|
364
|
+
|
|
365
|
+
// Allocation profiling needs a sampling hook only available on Node.js 26+.
|
|
366
|
+
setAndTrack(this, 'DD_PROFILING_ALLOCATION_ENABLED', NODE_MAJOR >= 26 && this.DD_PROFILING_ALLOCATION_ENABLED)
|
|
364
367
|
if (this.telemetry.extendedHeartbeatInterval) {
|
|
365
368
|
setAndTrack(this, 'telemetry.extendedHeartbeatInterval',
|
|
366
369
|
Math.floor(this.telemetry.extendedHeartbeatInterval * 1000))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
|
|
5
|
-
const { DD_MAJOR } = require('../../../../version')
|
|
5
|
+
const { DD_MAJOR, NODE_MAJOR } = require('../../../../version')
|
|
6
6
|
const tagger = require('../tagger')
|
|
7
7
|
|
|
8
8
|
let warnInvalidValue
|
|
@@ -100,6 +100,24 @@ const transformers = {
|
|
|
100
100
|
}
|
|
101
101
|
return configValue
|
|
102
102
|
},
|
|
103
|
+
/**
|
|
104
|
+
* Parses DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ('on' | 'off' | 'gzip[-1..9]' | 'zstd[-1..22]')
|
|
105
|
+
* into the codec and level the profiler uploads with. The value's shape is already range-checked
|
|
106
|
+
* by the `allowed` pattern, so no validation is needed here.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} value
|
|
109
|
+
* @returns {{ method: string, level: number | undefined }}
|
|
110
|
+
*/
|
|
111
|
+
normalizeProfilingUploadCompression (value) {
|
|
112
|
+
let [method, level] = value.toLowerCase().split('-')
|
|
113
|
+
// Default "on" to zstd on Node.js 24+ and gzip earlier. zstd ships everywhere via a Rust
|
|
114
|
+
// compressor, but 24+ has a built-in that runs on libuv worker threads just as gzip does, so it
|
|
115
|
+
// is the least disruptive default.
|
|
116
|
+
if (method === 'on') {
|
|
117
|
+
method = NODE_MAJOR >= 24 ? 'zstd' : 'gzip'
|
|
118
|
+
}
|
|
119
|
+
return { method, level: level ? Number.parseInt(level, 10) : undefined }
|
|
120
|
+
},
|
|
103
121
|
sampleRate (value, optionName, source) {
|
|
104
122
|
const number = Number(value)
|
|
105
123
|
if (Number.isNaN(number) || value === null) {
|
|
@@ -1238,6 +1238,16 @@
|
|
|
1238
1238
|
"default": null
|
|
1239
1239
|
}
|
|
1240
1240
|
],
|
|
1241
|
+
"DD_LLMOBS_SAMPLE_RATE": [
|
|
1242
|
+
{
|
|
1243
|
+
"implementation": "A",
|
|
1244
|
+
"type": "decimal",
|
|
1245
|
+
"configurationNames": [
|
|
1246
|
+
"llmobs.sampleRate"
|
|
1247
|
+
],
|
|
1248
|
+
"default": "1.0"
|
|
1249
|
+
}
|
|
1250
|
+
],
|
|
1241
1251
|
"DD_LOGS_INJECTION": [
|
|
1242
1252
|
{
|
|
1243
1253
|
"implementation": "B",
|
|
@@ -1398,8 +1408,8 @@
|
|
|
1398
1408
|
"implementation": "B",
|
|
1399
1409
|
"type": "string",
|
|
1400
1410
|
"default": "on",
|
|
1401
|
-
"allowed": "on|off|(
|
|
1402
|
-
"transform": "
|
|
1411
|
+
"allowed": "on|off|gzip(-[1-9])?|zstd(-([1-9]|1[0-9]|2[0-2]))?",
|
|
1412
|
+
"transform": "normalizeProfilingUploadCompression"
|
|
1403
1413
|
}
|
|
1404
1414
|
],
|
|
1405
1415
|
"DD_PROFILING_ENABLED": [
|
|
@@ -1486,7 +1496,8 @@
|
|
|
1486
1496
|
{
|
|
1487
1497
|
"implementation": "A",
|
|
1488
1498
|
"type": "int",
|
|
1489
|
-
"default": "524288"
|
|
1499
|
+
"default": "524288",
|
|
1500
|
+
"allowed": "[1-9]\\d*"
|
|
1490
1501
|
}
|
|
1491
1502
|
],
|
|
1492
1503
|
"DD_PROFILING_PPROF_PREFIX": [
|
|
@@ -3320,6 +3331,13 @@
|
|
|
3320
3331
|
"default": "true"
|
|
3321
3332
|
}
|
|
3322
3333
|
],
|
|
3334
|
+
"DD_TRACE_OTEL_SEMANTICS_ENABLED": [
|
|
3335
|
+
{
|
|
3336
|
+
"implementation": "A",
|
|
3337
|
+
"type": "boolean",
|
|
3338
|
+
"default": "false"
|
|
3339
|
+
}
|
|
3340
|
+
],
|
|
3323
3341
|
"DD_TRACE_OTEL_ENABLED": [
|
|
3324
3342
|
{
|
|
3325
3343
|
"implementation": "A",
|
|
@@ -5,6 +5,7 @@ const { getGeneratedPosition } = require('./source-maps')
|
|
|
5
5
|
const session = require('./session')
|
|
6
6
|
const { compile, compileSegments, templateRequiresEvaluation } = require('./condition')
|
|
7
7
|
const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
|
|
8
|
+
const { compileBreakpointCondition, getRemoveProbeExpression } = require('./probe_sampler')
|
|
8
9
|
const {
|
|
9
10
|
DEFAULT_MAX_REFERENCE_DEPTH,
|
|
10
11
|
DEFAULT_MAX_COLLECTION_SIZE,
|
|
@@ -17,6 +18,7 @@ const {
|
|
|
17
18
|
locationToBreakpoint,
|
|
18
19
|
breakpointToProbes,
|
|
19
20
|
probeToLocation,
|
|
21
|
+
samplingIndexToProbe,
|
|
20
22
|
} = require('./state')
|
|
21
23
|
const log = require('./log')
|
|
22
24
|
|
|
@@ -26,6 +28,7 @@ const log = require('./log')
|
|
|
26
28
|
|
|
27
29
|
let sessionStarted = false
|
|
28
30
|
const probes = new Map()
|
|
31
|
+
let nextSamplingIndex = 0
|
|
29
32
|
let scriptLoadingStabilizedResolve
|
|
30
33
|
const scriptLoadingStabilized = new Promise((resolve) => { scriptLoadingStabilizedResolve = resolve })
|
|
31
34
|
|
|
@@ -55,6 +58,8 @@ async function addBreakpoint (probe) {
|
|
|
55
58
|
if (!sessionStarted) await start()
|
|
56
59
|
|
|
57
60
|
probes.set(probe.id, probe)
|
|
61
|
+
probe.samplingIndex = nextSamplingIndex++
|
|
62
|
+
samplingIndexToProbe.set(probe.samplingIndex, probe)
|
|
58
63
|
|
|
59
64
|
const file = probe.where.sourceFile
|
|
60
65
|
let lineNumber = Number(probe.where.lines[0]) // Tracer doesn't support multiple-line breakpoints
|
|
@@ -75,8 +80,6 @@ async function addBreakpoint (probe) {
|
|
|
75
80
|
? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
|
|
76
81
|
: MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE)
|
|
77
82
|
probe.nsBetweenSampling = BigInt(Math.trunc(1 / snapshotsPerSecond * 1e9))
|
|
78
|
-
// Initialize to a large negative value to ensure first probe hit is always captured
|
|
79
|
-
probe.lastCaptureNs = BigInt(Number.MIN_SAFE_INTEGER)
|
|
80
83
|
|
|
81
84
|
// Warning: The code below relies on undocumented behavior of the inspector!
|
|
82
85
|
// It expects that `await session.post('Debugger.enable')` will wait for all loaded scripts to be emitted as
|
|
@@ -174,7 +177,7 @@ async function addBreakpoint (probe) {
|
|
|
174
177
|
try {
|
|
175
178
|
result = /** @type {SetBreakpointResponse} */ (await session.post('Debugger.setBreakpoint', {
|
|
176
179
|
location,
|
|
177
|
-
condition: probe
|
|
180
|
+
condition: compileBreakpointCondition([probe]),
|
|
178
181
|
}))
|
|
179
182
|
} catch (err) {
|
|
180
183
|
throw new Error(`Error setting breakpoint for probe ${probe.id} (version: ${probe.version})`, { cause: err })
|
|
@@ -195,11 +198,14 @@ async function removeBreakpoint ({ id }) {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
probes.delete(id)
|
|
201
|
+
await removeProbeFromSampler(id)
|
|
198
202
|
|
|
199
203
|
const locationKey = probeToLocation.get(id)
|
|
200
204
|
const breakpoint = locationToBreakpoint.get(locationKey)
|
|
201
205
|
const probesAtLocation = breakpointToProbes.get(breakpoint.id)
|
|
206
|
+
const probe = probesAtLocation.get(id)
|
|
202
207
|
|
|
208
|
+
samplingIndexToProbe.delete(probe.samplingIndex)
|
|
203
209
|
probesAtLocation.delete(id)
|
|
204
210
|
probeToLocation.delete(id)
|
|
205
211
|
|
|
@@ -229,35 +235,37 @@ async function modifyBreakpoint (probe) {
|
|
|
229
235
|
|
|
230
236
|
async function updateBreakpointInternal (breakpoint, probe) {
|
|
231
237
|
const probesAtLocation = breakpointToProbes.get(breakpoint.id)
|
|
232
|
-
const conditionBeforeNewProbe = compileCompoundCondition([...probesAtLocation.values()])
|
|
233
238
|
|
|
234
|
-
// If a probe is provided, add it to the breakpoint. If not, it's because we're removing a probe
|
|
235
|
-
//
|
|
239
|
+
// If a probe is provided, add it to the breakpoint. If not, it's because we're removing a probe. In both cases the
|
|
240
|
+
// breakpoint condition must be rebuilt to match the remaining probes at the location.
|
|
236
241
|
if (probe) {
|
|
237
242
|
probesAtLocation.set(probe.id, probe)
|
|
238
243
|
probeToLocation.set(probe.id, breakpoint.locationKey)
|
|
239
244
|
}
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
246
|
+
try {
|
|
247
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
248
|
+
} catch (err) {
|
|
249
|
+
const message = probe
|
|
250
|
+
? `Error replacing breakpoint while adding probe ${probe.id} (version: ${probe.version})`
|
|
251
|
+
: `Error replacing breakpoint after removing probe from ${breakpoint.locationKey}`
|
|
252
|
+
throw new Error(message, { cause: err })
|
|
253
|
+
}
|
|
254
|
+
breakpointToProbes.delete(breakpoint.id)
|
|
255
|
+
let result
|
|
256
|
+
try {
|
|
257
|
+
result = /** @type {SetBreakpointResponse} */ (await session.post('Debugger.setBreakpoint', {
|
|
258
|
+
location: breakpoint.location,
|
|
259
|
+
condition: compileBreakpointCondition([...probesAtLocation.values()]),
|
|
260
|
+
}))
|
|
261
|
+
} catch (err) {
|
|
262
|
+
const message = probe
|
|
263
|
+
? `Error setting breakpoint while adding probe ${probe.id} (version: ${probe.version})`
|
|
264
|
+
: `Error setting breakpoint after removing probe from ${breakpoint.locationKey}`
|
|
265
|
+
throw new Error(message, { cause: err })
|
|
260
266
|
}
|
|
267
|
+
breakpoint.id = result.breakpointId
|
|
268
|
+
breakpointToProbes.set(result.breakpointId, probesAtLocation)
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
async function reEvaluateProbe (probe) {
|
|
@@ -305,16 +313,20 @@ function lock (fn) {
|
|
|
305
313
|
}
|
|
306
314
|
}
|
|
307
315
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Remove cached sampling state for a probe from the runtime sampler.
|
|
318
|
+
*
|
|
319
|
+
* @param {string} id - The probe id.
|
|
320
|
+
* @returns {Promise<void>}
|
|
321
|
+
*/
|
|
322
|
+
async function removeProbeFromSampler (id) {
|
|
323
|
+
try {
|
|
324
|
+
await session.post('Runtime.evaluate', {
|
|
325
|
+
expression: getRemoveProbeExpression(id),
|
|
326
|
+
})
|
|
327
|
+
} catch (err) {
|
|
328
|
+
log.error('[debugger:devtools_client] Error removing probe %s from sampler', id, err)
|
|
329
|
+
}
|
|
318
330
|
}
|
|
319
331
|
|
|
320
332
|
function generateLocationKey (scriptId, lineNumber, columnNumber) {
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { randomUUID } = require('crypto')
|
|
4
|
+
const { workerData: { probeSamplerBuffer } } = require('worker_threads')
|
|
4
5
|
const { version } = require('../../../../../package.json')
|
|
5
|
-
const { NODE_MAJOR } = require('../../../../../version')
|
|
6
6
|
const processTags = require('../../process-tags')
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
MAX_SAMPLED_PROBES_PER_PAUSE,
|
|
9
|
+
SAMPLED_PROBE_COUNT_INDEX,
|
|
10
|
+
SAMPLED_PROBE_INDEXES_START,
|
|
11
|
+
SAMPLED_PROBE_OVERFLOW_INDEX,
|
|
12
|
+
} = require('../probe_sampler_constants')
|
|
13
|
+
const { breakpointToProbes, samplingIndexToProbe } = require('./state')
|
|
8
14
|
const session = require('./session')
|
|
9
15
|
const { getLocalStateForCallFrame, evaluateCaptureExpressions } = require('./snapshot')
|
|
10
16
|
const send = require('./send')
|
|
11
17
|
const { getStackFromCallFrames } = require('./state')
|
|
12
18
|
const { ackEmitting } = require('./status')
|
|
13
19
|
const config = require('./config')
|
|
14
|
-
const { MAX_SNAPSHOTS_PER_SECOND_GLOBALLY } = require('./defaults')
|
|
15
20
|
const log = require('./log')
|
|
16
21
|
|
|
17
22
|
require('./remote_config')
|
|
@@ -38,25 +43,7 @@ const getDDTagsExpression = `(() => {
|
|
|
38
43
|
// something that should be useful to a Node.js developer.
|
|
39
44
|
const threadId = config.parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${config.parentThreadId}`
|
|
40
45
|
const threadName = config.parentThreadId === 0 ? 'MainThread' : `WorkerThread:${config.parentThreadId}`
|
|
41
|
-
|
|
42
|
-
const SUPPORT_ARRAY_BUFFER_RESIZE = NODE_MAJOR >= 20
|
|
43
|
-
const oneSecondNs = 1_000_000_000n
|
|
44
|
-
let globalSnapshotSamplingRateWindowStart = 0n
|
|
45
|
-
let snapshotsSampledWithinTheLastSecond = 0
|
|
46
|
-
|
|
47
|
-
// TODO: Change to const once we drop support for Node.js 18
|
|
48
|
-
let snapshotProbeIndexBuffer, snapshotProbeIndex
|
|
49
|
-
|
|
50
|
-
if (SUPPORT_ARRAY_BUFFER_RESIZE) {
|
|
51
|
-
// TODO: Is a limit of 256 snapshots ever going to be a problem?
|
|
52
|
-
// @ts-ignore - ArrayBuffer constructor with maxByteLength is available in Node.js 20+ but not in @types/node@18
|
|
53
|
-
// eslint-disable-next-line n/no-unsupported-features/es-syntax
|
|
54
|
-
snapshotProbeIndexBuffer = new ArrayBuffer(1, { maxByteLength: 256 })
|
|
55
|
-
// TODO: Is a limit of 256 probes ever going to be a problem?
|
|
56
|
-
snapshotProbeIndex = new Uint8Array(snapshotProbeIndexBuffer)
|
|
57
|
-
} else {
|
|
58
|
-
snapshotProbeIndex = new Uint8Array(1)
|
|
59
|
-
}
|
|
46
|
+
const sampledProbeIndexes = new Int32Array(probeSamplerBuffer)
|
|
60
47
|
|
|
61
48
|
// WARNING: The code above the line `await session.post('Debugger.resume')` is highly optimized. Please edit with care!
|
|
62
49
|
session.on('Debugger.paused', async ({ params }) => {
|
|
@@ -71,7 +58,6 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
71
58
|
let maxCollectionSize = 0
|
|
72
59
|
let maxFieldCount = 0
|
|
73
60
|
let maxLength = 0
|
|
74
|
-
let sampled = false
|
|
75
61
|
let numberOfProbesWithSnapshots = 0
|
|
76
62
|
let probesWithCaptureExpressions = false
|
|
77
63
|
const probes = []
|
|
@@ -80,7 +66,15 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
80
66
|
// V8 doesn't allow setting more than one breakpoint at a specific location, however, it's possible to set two
|
|
81
67
|
// breakpoints just next to each other that will "snap" to the same logical location, which in turn will be hit at the
|
|
82
68
|
// same time. E.g. index.js:1:1 and index.js:1:2.
|
|
83
|
-
|
|
69
|
+
const numberOfSampledProbeIndexes = Math.min(
|
|
70
|
+
Atomics.exchange(sampledProbeIndexes, SAMPLED_PROBE_COUNT_INDEX, 0),
|
|
71
|
+
MAX_SAMPLED_PROBES_PER_PAUSE
|
|
72
|
+
)
|
|
73
|
+
if (Atomics.exchange(sampledProbeIndexes, SAMPLED_PROBE_OVERFLOW_INDEX, 0) === 1) {
|
|
74
|
+
log.error(
|
|
75
|
+
'[debugger:devtools_client] Too many probes sampled at the same breakpoint location; skipping excess probes'
|
|
76
|
+
)
|
|
77
|
+
}
|
|
84
78
|
|
|
85
79
|
// TODO: Investigate if it will improve performance to create a fast-path for when there's only a single breakpoint
|
|
86
80
|
for (let i = 0; i < params.hitBreakpoints.length; i++) {
|
|
@@ -92,36 +86,23 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
92
86
|
continue
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (SUPPORT_ARRAY_BUFFER_RESIZE) {
|
|
99
|
-
snapshotProbeIndexBuffer.resize(numberOfProbesOnBreakpoint)
|
|
100
|
-
} else {
|
|
101
|
-
snapshotProbeIndex = new Uint8Array(numberOfProbesOnBreakpoint)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
89
|
+
for (let j = 0; j < numberOfSampledProbeIndexes; j++) {
|
|
90
|
+
const samplingIndex = Atomics.load(sampledProbeIndexes, SAMPLED_PROBE_INDEXES_START + j)
|
|
91
|
+
const probe = samplingIndexToProbe.get(samplingIndex)
|
|
105
92
|
|
|
106
|
-
|
|
107
|
-
|
|
93
|
+
if (probe === undefined) {
|
|
94
|
+
log.error('[debugger:devtools_client] No probe found for sampled probe index %d', samplingIndex)
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
if (!probesAtLocation.has(probe.id)) {
|
|
98
|
+
log.error('[debugger:devtools_client] Sampled probe %s was not found at breakpoint %s',
|
|
99
|
+
probe.id, params.hitBreakpoints[i])
|
|
108
100
|
continue
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
if (probe.captureSnapshot === true || probe.compiledCaptureExpressions !== undefined) {
|
|
112
|
-
// This algorithm to calculate number of sampled snapshots within the last second is not perfect, as it's not a
|
|
113
|
-
// sliding window. But it's quick and easy :)
|
|
114
|
-
if (i === 0 && start - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
|
|
115
|
-
snapshotsSampledWithinTheLastSecond = 1
|
|
116
|
-
globalSnapshotSamplingRateWindowStart = start
|
|
117
|
-
} else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
|
|
118
|
-
continue
|
|
119
|
-
} else {
|
|
120
|
-
snapshotsSampledWithinTheLastSecond++
|
|
121
|
-
}
|
|
122
|
-
|
|
123
104
|
if (probe.captureSnapshot === true) {
|
|
124
|
-
|
|
105
|
+
numberOfProbesWithSnapshots++
|
|
125
106
|
maxReferenceDepth = Math.max(probe.capture.maxReferenceDepth, maxReferenceDepth)
|
|
126
107
|
maxCollectionSize = Math.max(probe.capture.maxCollectionSize, maxCollectionSize)
|
|
127
108
|
maxFieldCount = Math.max(probe.capture.maxFieldCount, maxFieldCount)
|
|
@@ -131,23 +112,6 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
131
112
|
}
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
if (probe.condition !== undefined) {
|
|
135
|
-
// TODO: Bundle all conditions and evaluate them in a single call
|
|
136
|
-
// TODO: Handle errors
|
|
137
|
-
const { result } = /** @type {EvaluateOnCallFrameResult} */ (
|
|
138
|
-
// eslint-disable-next-line no-await-in-loop
|
|
139
|
-
await session.post('Debugger.evaluateOnCallFrame', {
|
|
140
|
-
callFrameId: params.callFrames[0].callFrameId,
|
|
141
|
-
expression: probe.condition,
|
|
142
|
-
returnByValue: true,
|
|
143
|
-
})
|
|
144
|
-
)
|
|
145
|
-
if (result.value !== true) continue
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
sampled = true
|
|
149
|
-
probe.lastCaptureNs = start
|
|
150
|
-
|
|
151
115
|
if (probe.templateRequiresEvaluation) {
|
|
152
116
|
templateExpressions += `,${probe.template}`
|
|
153
117
|
}
|
|
@@ -156,7 +120,8 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
156
120
|
}
|
|
157
121
|
}
|
|
158
122
|
|
|
159
|
-
if
|
|
123
|
+
// This can happen if sampled probe indexes are inconsistent with the worker state. Those cases are logged above.
|
|
124
|
+
if (probes.length === 0) {
|
|
160
125
|
return session.post('Debugger.resume')
|
|
161
126
|
}
|
|
162
127
|
|