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
|
@@ -8,16 +8,47 @@ const version = require('../../../../../package.json').version
|
|
|
8
8
|
const { availableParallelism, libuvThreadPoolSize } = require('../libuv-size')
|
|
9
9
|
const processTags = require('../../process-tags')
|
|
10
10
|
|
|
11
|
+
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Maps the canonical profiling.enabled value to the activation reported in the
|
|
15
|
+
* profile event.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} [enabled] - config.profiling.enabled ('true' | 'false' | 'auto')
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function getActivation (enabled) {
|
|
21
|
+
if (enabled === 'auto') return 'auto'
|
|
22
|
+
if (enabled === 'true') return 'manual'
|
|
23
|
+
return 'unknown'
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
class EventSerializer {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
#activation
|
|
28
|
+
#appVersion
|
|
29
|
+
#env
|
|
30
|
+
#host
|
|
31
|
+
#libraryInjected
|
|
32
|
+
#service
|
|
33
|
+
|
|
34
|
+
/** @param {TracerConfig} config */
|
|
35
|
+
constructor (config) {
|
|
36
|
+
this.#env = config.env
|
|
37
|
+
this.#host = config.reportHostname ? os.hostname() : undefined
|
|
38
|
+
this.#service = config.service
|
|
39
|
+
this.#appVersion = config.version
|
|
40
|
+
this.#libraryInjected = !!config.DD_INJECTION_ENABLED
|
|
41
|
+
this.#activation = getActivation(config.profiling?.enabled)
|
|
19
42
|
}
|
|
20
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Returns the destination URL for the near-OOM export subprocess, or nothing when this exporter
|
|
46
|
+
* does not support OOM export. Overridden by {@link AgentExporter} and {@link FileExporter}.
|
|
47
|
+
*
|
|
48
|
+
* @returns {URL | undefined}
|
|
49
|
+
*/
|
|
50
|
+
getExportUrl () {}
|
|
51
|
+
|
|
21
52
|
typeToFile (type) {
|
|
22
53
|
return `${type}.pprof`
|
|
23
54
|
}
|
|
@@ -43,21 +74,21 @@ class EventSerializer {
|
|
|
43
74
|
endpoint_counts: endpointCounts,
|
|
44
75
|
info: {
|
|
45
76
|
application: {
|
|
46
|
-
env: this
|
|
47
|
-
service: this
|
|
77
|
+
env: this.#env,
|
|
78
|
+
service: this.#service,
|
|
48
79
|
start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
|
|
49
|
-
version: this
|
|
80
|
+
version: this.#appVersion,
|
|
50
81
|
},
|
|
51
82
|
platform: {
|
|
52
|
-
hostname: this
|
|
83
|
+
hostname: this.#host,
|
|
53
84
|
kernel_name: os.type(),
|
|
54
85
|
kernel_release: os.release(),
|
|
55
86
|
kernel_version: os.version(),
|
|
56
87
|
},
|
|
57
88
|
profiler: {
|
|
58
|
-
activation: this
|
|
89
|
+
activation: this.#activation,
|
|
59
90
|
ssi: {
|
|
60
|
-
mechanism: this
|
|
91
|
+
mechanism: this.#libraryInjected ? 'injected_agent' : 'none',
|
|
61
92
|
},
|
|
62
93
|
version,
|
|
63
94
|
...infos,
|
|
@@ -92,4 +123,4 @@ class EventSerializer {
|
|
|
92
123
|
}
|
|
93
124
|
}
|
|
94
125
|
|
|
95
|
-
module.exports = { EventSerializer }
|
|
126
|
+
module.exports = { EventSerializer, getActivation }
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const { promisify } = require('util')
|
|
5
|
+
const { pathToFileURL } = require('url')
|
|
5
6
|
const { threadId } = require('worker_threads')
|
|
6
7
|
const writeFile = promisify(fs.writeFile)
|
|
7
8
|
const { EventSerializer } = require('./event_serializer')
|
|
@@ -10,14 +11,24 @@ const pad = (n) => String(n).padStart(2, '0')
|
|
|
10
11
|
|
|
11
12
|
function formatDateTime (t) {
|
|
12
13
|
return `${t.getUTCFullYear()}${pad(t.getUTCMonth() + 1)}${pad(t.getUTCDate())}` +
|
|
13
|
-
|
|
14
|
+
`T${pad(t.getUTCHours())}${pad(t.getUTCMinutes())}${pad(t.getUTCSeconds())}Z`
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
class FileExporter extends EventSerializer {
|
|
17
|
-
|
|
18
|
+
#pprofPrefix
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('./event_serializer').TracerConfig} config
|
|
22
|
+
* @param {string} [pprofPrefix] - File path prefix. Defaults to the config value, but the OOM
|
|
23
|
+
* export subprocess passes a path derived from a `file:` URL, which is not a config value.
|
|
24
|
+
*/
|
|
25
|
+
constructor (config, pprofPrefix = config.DD_PROFILING_PPROF_PREFIX) {
|
|
18
26
|
super(config)
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
this.#pprofPrefix = pprofPrefix
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getExportUrl () {
|
|
31
|
+
return pathToFileURL(this.#pprofPrefix)
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
export (exportSpec) {
|
|
@@ -25,7 +36,7 @@ class FileExporter extends EventSerializer {
|
|
|
25
36
|
const types = Object.keys(profiles)
|
|
26
37
|
const dateStr = formatDateTime(end)
|
|
27
38
|
const tasks = types.map(type => {
|
|
28
|
-
return writeFile(`${this
|
|
39
|
+
return writeFile(`${this.#pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
|
|
29
40
|
})
|
|
30
41
|
tasks.push(writeFile(`event_worker_${threadId}_${dateStr}.json`, this.getEventJSON(exportSpec)))
|
|
31
42
|
return Promise.all(tasks)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const log = require('../log')
|
|
6
|
+
const { snapshotKinds } = require('./constants')
|
|
7
|
+
|
|
8
|
+
const oomExportStrategies = Object.freeze({
|
|
9
|
+
PROCESS: 'process',
|
|
10
|
+
ASYNC_CALLBACK: 'async',
|
|
11
|
+
LOGS: 'logs',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/** @param {string} name */
|
|
15
|
+
function getExportStrategy (name) {
|
|
16
|
+
const strategy = Object.values(oomExportStrategies).find(value => value === name)
|
|
17
|
+
if (strategy === undefined) {
|
|
18
|
+
log.error('Unknown oom export strategy "%s"', name)
|
|
19
|
+
}
|
|
20
|
+
return strategy
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates the configured strategies, dropping any the tracer does not recognize (each logged once).
|
|
25
|
+
*
|
|
26
|
+
* @param {string[]} strategies
|
|
27
|
+
* @returns {string[]}
|
|
28
|
+
*/
|
|
29
|
+
function ensureOOMExportStrategies (strategies) {
|
|
30
|
+
const valid = new Set()
|
|
31
|
+
for (const strategy of strategies) {
|
|
32
|
+
const resolved = getExportStrategy(strategy)
|
|
33
|
+
if (resolved !== undefined) {
|
|
34
|
+
valid.add(resolved)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [...valid]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string[]} strategies
|
|
42
|
+
* @param {{ Async: number }} callbackMode
|
|
43
|
+
*/
|
|
44
|
+
function strategiesToCallbackMode (strategies, callbackMode) {
|
|
45
|
+
return strategies.includes(oomExportStrategies.ASYNC_CALLBACK) ? callbackMode.Async : 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds the argv the near-OOM export subprocess ({@link ./exporter_cli.js}) is spawned with. Each
|
|
50
|
+
* exporter reports its own destination URL, so the command stays agnostic of the exporter types.
|
|
51
|
+
*
|
|
52
|
+
* @param {Array<{ getExportUrl(): URL | undefined }>} exporters
|
|
53
|
+
* @param {Record<string, string>} tags
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function buildExportCommand (exporters, tags) {
|
|
57
|
+
const tagString = [...Object.entries(tags),
|
|
58
|
+
['snapshot', snapshotKinds.ON_OUT_OF_MEMORY]].map(([key, value]) => `${key}:${value}`).join(',')
|
|
59
|
+
const urls = []
|
|
60
|
+
for (const exporter of exporters) {
|
|
61
|
+
const url = exporter.getExportUrl()
|
|
62
|
+
if (url !== undefined) {
|
|
63
|
+
urls.push(url.toString())
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [process.execPath,
|
|
67
|
+
path.join(__dirname, 'exporter_cli.js'),
|
|
68
|
+
urls.join(','), tagString, 'space']
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { oomExportStrategies, ensureOOMExportStrategies, strategiesToCallbackMode, buildExportCommand }
|
|
@@ -4,7 +4,7 @@ const { EventEmitter } = require('events')
|
|
|
4
4
|
const dc = require('dc-polyfill')
|
|
5
5
|
const crashtracker = require('../crashtracking')
|
|
6
6
|
const log = require('../log')
|
|
7
|
-
const {
|
|
7
|
+
const { buildProfilingRuntime } = require('./config')
|
|
8
8
|
const { snapshotKinds } = require('./constants')
|
|
9
9
|
const { threadNamePrefix } = require('./profilers/shared')
|
|
10
10
|
const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('./webspan-utils')
|
|
@@ -54,14 +54,19 @@ class Profiler extends EventEmitter {
|
|
|
54
54
|
#compressionFn
|
|
55
55
|
#compressionFnInitialized = false
|
|
56
56
|
#compressionOptions
|
|
57
|
-
#config
|
|
58
57
|
#customLabelKeys = new Set()
|
|
59
58
|
#enabled = false
|
|
60
59
|
#endpointCounts = new Map()
|
|
60
|
+
#exporters
|
|
61
|
+
#flushInterval
|
|
61
62
|
#lastStart
|
|
62
63
|
#profileSeq = 0
|
|
64
|
+
#profilers
|
|
63
65
|
#spanFinishListener
|
|
66
|
+
#systemInfoReport
|
|
67
|
+
#tags
|
|
64
68
|
#timer
|
|
69
|
+
#uploadCompression
|
|
65
70
|
|
|
66
71
|
constructor () {
|
|
67
72
|
super()
|
|
@@ -71,7 +76,7 @@ class Profiler extends EventEmitter {
|
|
|
71
76
|
get serverless () { return false }
|
|
72
77
|
|
|
73
78
|
get flushInterval () {
|
|
74
|
-
return this.#
|
|
79
|
+
return this.#flushInterval
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
get enabled () {
|
|
@@ -90,8 +95,8 @@ class Profiler extends EventEmitter {
|
|
|
90
95
|
for (const key of keys) {
|
|
91
96
|
this.#customLabelKeys.add(key)
|
|
92
97
|
}
|
|
93
|
-
if (this.#
|
|
94
|
-
for (const profiler of this.#
|
|
98
|
+
if (this.#profilers) {
|
|
99
|
+
for (const profiler of this.#profilers) {
|
|
95
100
|
profiler.setCustomLabelKeys?.(this.#customLabelKeys)
|
|
96
101
|
}
|
|
97
102
|
}
|
|
@@ -106,10 +111,10 @@ class Profiler extends EventEmitter {
|
|
|
106
111
|
* @template T
|
|
107
112
|
*/
|
|
108
113
|
runWithLabels (labels, fn) {
|
|
109
|
-
if (!this.#enabled || !this.#
|
|
114
|
+
if (!this.#enabled || !this.#profilers) {
|
|
110
115
|
return fn()
|
|
111
116
|
}
|
|
112
|
-
for (const profiler of this.#
|
|
117
|
+
for (const profiler of this.#profilers) {
|
|
113
118
|
if (profiler.runWithLabels) {
|
|
114
119
|
return profiler.runWithLabels(labels, fn)
|
|
115
120
|
}
|
|
@@ -123,7 +128,7 @@ class Profiler extends EventEmitter {
|
|
|
123
128
|
try {
|
|
124
129
|
const { promisify } = require('util')
|
|
125
130
|
const zlib = require('zlib')
|
|
126
|
-
const { method, level: clevel } = this.#
|
|
131
|
+
const { method, level: clevel } = this.#uploadCompression
|
|
127
132
|
switch (method) {
|
|
128
133
|
case 'gzip':
|
|
129
134
|
this.#compressionFn = promisify(zlib.gzip)
|
|
@@ -161,13 +166,20 @@ class Profiler extends EventEmitter {
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
/**
|
|
164
|
-
* @param {import('../config/config-base')}
|
|
169
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
165
170
|
*/
|
|
166
|
-
start (
|
|
171
|
+
start (config) {
|
|
167
172
|
if (this.enabled) return true
|
|
168
173
|
this.#enabled = true
|
|
169
174
|
|
|
170
|
-
const
|
|
175
|
+
const { tags, exporters, flushInterval, profilers, uploadCompression, systemInfoReport } =
|
|
176
|
+
buildProfilingRuntime(config)
|
|
177
|
+
this.#tags = tags
|
|
178
|
+
this.#exporters = exporters
|
|
179
|
+
this.#flushInterval = flushInterval
|
|
180
|
+
this.#profilers = profilers
|
|
181
|
+
this.#uploadCompression = uploadCompression
|
|
182
|
+
this.#systemInfoReport = systemInfoReport
|
|
171
183
|
|
|
172
184
|
this._setInterval()
|
|
173
185
|
// Log errors if the source map finder fails, but don't prevent the rest
|
|
@@ -176,11 +188,11 @@ class Profiler extends EventEmitter {
|
|
|
176
188
|
const { setLogger, SourceMapper } = require('@datadog/pprof')
|
|
177
189
|
setLogger(pprofLogger)
|
|
178
190
|
|
|
179
|
-
if (config.
|
|
180
|
-
mapper = new SourceMapper(config.
|
|
191
|
+
if (config.DD_PROFILING_SOURCE_MAP) {
|
|
192
|
+
mapper = new SourceMapper(config.DD_PROFILING_DEBUG_SOURCE_MAPS)
|
|
181
193
|
mapper.loadDirectory(process.cwd())
|
|
182
194
|
.then(() => {
|
|
183
|
-
if (config.
|
|
195
|
+
if (config.DD_PROFILING_DEBUG_SOURCE_MAPS) {
|
|
184
196
|
const count = mapper.infoMap.size
|
|
185
197
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
186
198
|
log.debug(() => {
|
|
@@ -198,7 +210,7 @@ class Profiler extends EventEmitter {
|
|
|
198
210
|
try {
|
|
199
211
|
const start = new Date()
|
|
200
212
|
const nearOOMCallback = this.#nearOOMExport.bind(this)
|
|
201
|
-
for (const profiler of
|
|
213
|
+
for (const profiler of profilers) {
|
|
202
214
|
// TODO: move this out of Profiler when restoring sourcemap support
|
|
203
215
|
profiler.start({
|
|
204
216
|
mapper,
|
|
@@ -207,7 +219,7 @@ class Profiler extends EventEmitter {
|
|
|
207
219
|
log.debug('Started %s profiler in %s thread', profiler.type, threadNamePrefix)
|
|
208
220
|
}
|
|
209
221
|
|
|
210
|
-
if (config.
|
|
222
|
+
if (config.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED) {
|
|
211
223
|
this.#spanFinishListener = this.#onSpanFinish.bind(this)
|
|
212
224
|
spanFinishedChannel.subscribe(this.#spanFinishListener)
|
|
213
225
|
}
|
|
@@ -233,7 +245,7 @@ class Profiler extends EventEmitter {
|
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
_setInterval () {
|
|
236
|
-
this._timeoutInterval = this.#
|
|
248
|
+
this._timeoutInterval = this.#flushInterval
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
stop () {
|
|
@@ -255,7 +267,7 @@ class Profiler extends EventEmitter {
|
|
|
255
267
|
this.#spanFinishListener = undefined
|
|
256
268
|
}
|
|
257
269
|
|
|
258
|
-
for (const profiler of this.#
|
|
270
|
+
for (const profiler of this.#profilers) {
|
|
259
271
|
profiler.stop()
|
|
260
272
|
log.debug('Stopped %s profiler in %s thread', profiler.type, threadNamePrefix)
|
|
261
273
|
}
|
|
@@ -298,7 +310,7 @@ class Profiler extends EventEmitter {
|
|
|
298
310
|
#createInitialInfos () {
|
|
299
311
|
return {
|
|
300
312
|
serverless: this.serverless,
|
|
301
|
-
settings: this.#
|
|
313
|
+
settings: this.#systemInfoReport,
|
|
302
314
|
hasMissingSourceMaps: false,
|
|
303
315
|
}
|
|
304
316
|
}
|
|
@@ -307,7 +319,7 @@ class Profiler extends EventEmitter {
|
|
|
307
319
|
if (!this.enabled) return
|
|
308
320
|
|
|
309
321
|
try {
|
|
310
|
-
if (this.#
|
|
322
|
+
if (this.#profilers.length === 0) {
|
|
311
323
|
throw new Error('No profile types configured.')
|
|
312
324
|
}
|
|
313
325
|
|
|
@@ -317,7 +329,7 @@ class Profiler extends EventEmitter {
|
|
|
317
329
|
|
|
318
330
|
crashtracker.withProfilerSerializing(() => {
|
|
319
331
|
// collect profiles synchronously so that profilers can be safely stopped asynchronously
|
|
320
|
-
for (const profiler of this.#
|
|
332
|
+
for (const profiler of this.#profilers) {
|
|
321
333
|
const info = profiler.getInfo()
|
|
322
334
|
const profile = profiler.profile(restart, startDate, endDate)
|
|
323
335
|
if (!restart) {
|
|
@@ -377,7 +389,7 @@ class Profiler extends EventEmitter {
|
|
|
377
389
|
}
|
|
378
390
|
|
|
379
391
|
#submit (profiles, infos, start, end, snapshotKind) {
|
|
380
|
-
const
|
|
392
|
+
const tags = this.#tags
|
|
381
393
|
|
|
382
394
|
// Flatten endpoint counts
|
|
383
395
|
const endpointCounts = {}
|
|
@@ -392,7 +404,7 @@ class Profiler extends EventEmitter {
|
|
|
392
404
|
? [...this.#customLabelKeys]
|
|
393
405
|
: undefined
|
|
394
406
|
const exportSpec = { profiles, infos, start, end, tags, endpointCounts, customAttributes }
|
|
395
|
-
const tasks = this.#
|
|
407
|
+
const tasks = this.#exporters.map(exporter =>
|
|
396
408
|
exporter.export(exportSpec).catch(error => {
|
|
397
409
|
log.warn(error)
|
|
398
410
|
})
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
ValueType,
|
|
13
13
|
} = require('../../../../../vendor/dist/pprof-format')
|
|
14
14
|
const { availableParallelism, effectiveLibuvThreadCount } = require('../libuv-size')
|
|
15
|
+
const { SAMPLING_INTERVAL } = require('../constants')
|
|
15
16
|
const { END_TIMESTAMP_LABEL, SPAN_ID_LABEL, LOCAL_ROOT_SPAN_ID_LABEL, encodeProfileAsync } = require('./shared')
|
|
16
17
|
const PoissonProcessSamplingFilter = require('./poisson')
|
|
17
18
|
// perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
|
|
@@ -19,6 +20,8 @@ const MS_TO_NS = 1_000_000
|
|
|
19
20
|
// The number of sampling intervals that need to pass before we reset the Poisson process sampling instant.
|
|
20
21
|
const POISSON_RESET_FACTOR = 2
|
|
21
22
|
|
|
23
|
+
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
24
|
+
|
|
22
25
|
// While this is an "events profiler", meaning it emits a pprof file based on events observed as
|
|
23
26
|
// perf_hooks events, the emitted pprof file uses the type "timeline".
|
|
24
27
|
const pprofValueType = 'timeline'
|
|
@@ -50,8 +53,8 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
|
|
|
50
53
|
return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
function getMaxSamples (
|
|
54
|
-
const maxCpuSamples =
|
|
56
|
+
function getMaxSamples (flushInterval, samplingInterval) {
|
|
57
|
+
const maxCpuSamples = flushInterval / samplingInterval
|
|
55
58
|
|
|
56
59
|
// The lesser of max parallelism and libuv thread pool size, plus one so we can detect
|
|
57
60
|
// oversubscription on libuv thread pool, plus another one for GC.
|
|
@@ -439,14 +442,18 @@ class EventsProfiler {
|
|
|
439
442
|
|
|
440
443
|
get type () { return 'events' }
|
|
441
444
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
+
/**
|
|
446
|
+
* @param {TracerConfig} config
|
|
447
|
+
* @param {{ flushInterval: number }} runtime
|
|
448
|
+
*/
|
|
449
|
+
constructor (config, { flushInterval }) {
|
|
450
|
+
this.#maxSamples = getMaxSamples(flushInterval, SAMPLING_INTERVAL)
|
|
451
|
+
this.#timelineSamplingEnabled = config.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED
|
|
445
452
|
this.#eventSerializer = new EventSerializer(this.#maxSamples)
|
|
446
453
|
|
|
447
454
|
const eventHandler = event => this.#eventSerializer.addEvent(event)
|
|
448
455
|
const eventFilter = this.#timelineSamplingEnabled
|
|
449
|
-
? createPoissonProcessSamplingFilter(
|
|
456
|
+
? createPoissonProcessSamplingFilter(SAMPLING_INTERVAL)
|
|
450
457
|
: () => true
|
|
451
458
|
const filteringEventHandler = event => {
|
|
452
459
|
if (eventFilter(event)) {
|
|
@@ -454,7 +461,7 @@ class EventsProfiler {
|
|
|
454
461
|
}
|
|
455
462
|
}
|
|
456
463
|
|
|
457
|
-
this.#eventSources =
|
|
464
|
+
this.#eventSources = config.DD_PROFILING_CODEHOTSPOTS_ENABLED
|
|
458
465
|
// Use Datadog instrumentation to collect events with span IDs. Still use
|
|
459
466
|
// Node API for GC events.
|
|
460
467
|
? [
|
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { oomExportStrategies } =
|
|
3
|
+
const { oomExportStrategies, ensureOOMExportStrategies, strategiesToCallbackMode, buildExportCommand } =
|
|
4
|
+
require('../oom')
|
|
4
5
|
const { encodeProfileAsync, getThreadLabels } = require('./shared')
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../exporters/agent').AgentExporter
|
|
10
|
+
* | import('../exporters/file').FileExporter} ProfilingExporter
|
|
11
|
+
*/
|
|
9
12
|
|
|
10
13
|
const STACK_DEPTH = 64
|
|
11
14
|
|
|
12
15
|
class NativeSpaceProfiler {
|
|
16
|
+
#config
|
|
17
|
+
#exporters
|
|
13
18
|
#mapper
|
|
14
|
-
#oomMonitoring
|
|
15
19
|
#pprof
|
|
16
|
-
#
|
|
17
|
-
#samplingInterval = 512 * 1024
|
|
20
|
+
#samplingInterval
|
|
18
21
|
#started = false
|
|
22
|
+
#tags
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @param {TracerConfig} config
|
|
26
|
+
* @param {{ tags: Record<string, string>, exporters: ProfilingExporter[] }} runtime
|
|
27
|
+
*/
|
|
28
|
+
constructor (config, { tags, exporters }) {
|
|
29
|
+
this.#config = config
|
|
30
|
+
this.#tags = tags
|
|
31
|
+
this.#exporters = exporters
|
|
32
|
+
this.#samplingInterval = config.DD_PROFILING_HEAP_SAMPLING_INTERVAL
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
get type () {
|
|
@@ -31,16 +39,17 @@ class NativeSpaceProfiler {
|
|
|
31
39
|
start ({ mapper, nearOOMCallback } = {}) {
|
|
32
40
|
if (this.#started) return
|
|
33
41
|
|
|
42
|
+
const config = this.#config
|
|
34
43
|
this.#mapper = mapper
|
|
35
44
|
this.#pprof = require('@datadog/pprof')
|
|
36
|
-
this.#pprof.heap.start(this.#samplingInterval, STACK_DEPTH,
|
|
37
|
-
if (
|
|
38
|
-
const strategies =
|
|
45
|
+
this.#pprof.heap.start(this.#samplingInterval, STACK_DEPTH, config.DD_PROFILING_ALLOCATION_ENABLED)
|
|
46
|
+
if (config.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED) {
|
|
47
|
+
const strategies = ensureOOMExportStrategies(config.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES)
|
|
39
48
|
this.#pprof.heap.monitorOutOfMemory(
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
config.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
50
|
+
config.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
|
|
42
51
|
strategies.includes(oomExportStrategies.LOGS),
|
|
43
|
-
strategies.includes(oomExportStrategies.PROCESS) ? this.#
|
|
52
|
+
strategies.includes(oomExportStrategies.PROCESS) ? buildExportCommand(this.#exporters, this.#tags) : [],
|
|
44
53
|
(profile) => nearOOMCallback(this.type, this.#pprof.encodeSync(profile), this.getInfo()),
|
|
45
54
|
strategiesToCallbackMode(strategies, this.#pprof.heap.CallbackMode)
|
|
46
55
|
)
|
|
@@ -7,6 +7,7 @@ const log = require('../../log')
|
|
|
7
7
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
8
8
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
9
|
const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('../webspan-utils')
|
|
10
|
+
const { SAMPLING_INTERVAL } = require('../constants')
|
|
10
11
|
|
|
11
12
|
const {
|
|
12
13
|
END_TIMESTAMP_LABEL,
|
|
@@ -18,6 +19,8 @@ const {
|
|
|
18
19
|
} = require('./shared')
|
|
19
20
|
const TRACE_ENDPOINT_LABEL = 'trace endpoint'
|
|
20
21
|
|
|
22
|
+
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
23
|
+
|
|
21
24
|
let beforeCh
|
|
22
25
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
23
26
|
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
@@ -108,13 +111,17 @@ class NativeWallProfiler {
|
|
|
108
111
|
#asyncContextFrameEnabled = false
|
|
109
112
|
#captureSpanData = false
|
|
110
113
|
#codeHotspotsEnabled = false
|
|
114
|
+
#contextCountGaugeUpdater
|
|
111
115
|
#cpuProfilingEnabled = false
|
|
116
|
+
#currentContext
|
|
112
117
|
#customLabelsActive = false
|
|
113
118
|
#customLabelKeys
|
|
114
119
|
#endpointCollectionEnabled = false
|
|
115
120
|
#flushIntervalMillis = 0
|
|
121
|
+
#lastSampleCount = 0
|
|
116
122
|
#mapper
|
|
117
123
|
#pprof
|
|
124
|
+
#profilerState
|
|
118
125
|
#samplingIntervalMicros = 0
|
|
119
126
|
#started = false
|
|
120
127
|
#telemetryHeartbeatIntervalMillis = 0
|
|
@@ -130,17 +137,20 @@ class NativeWallProfiler {
|
|
|
130
137
|
|
|
131
138
|
get type () { return 'wall' }
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.#
|
|
139
|
-
|
|
140
|
-
this.#
|
|
141
|
-
this.#
|
|
142
|
-
this.#
|
|
143
|
-
this.#
|
|
140
|
+
/**
|
|
141
|
+
* @param {TracerConfig} config
|
|
142
|
+
* @param {{ asyncContextFrameEnabled: boolean, flushInterval: number }} runtime
|
|
143
|
+
*/
|
|
144
|
+
constructor (config, { asyncContextFrameEnabled, flushInterval }) {
|
|
145
|
+
this.#asyncContextFrameEnabled = asyncContextFrameEnabled
|
|
146
|
+
this.#codeHotspotsEnabled = config.DD_PROFILING_CODEHOTSPOTS_ENABLED
|
|
147
|
+
this.#cpuProfilingEnabled = config.DD_PROFILING_CPU_ENABLED
|
|
148
|
+
this.#endpointCollectionEnabled = config.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED
|
|
149
|
+
this.#flushIntervalMillis = flushInterval
|
|
150
|
+
this.#samplingIntervalMicros = SAMPLING_INTERVAL * 1000
|
|
151
|
+
this.#telemetryHeartbeatIntervalMillis = config.telemetry.heartbeatInterval
|
|
152
|
+
this.#timelineEnabled = config.DD_PROFILING_TIMELINE_ENABLED
|
|
153
|
+
this.#v8ProfilerBugWorkaroundEnabled = config.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
|
|
144
154
|
|
|
145
155
|
// We need to capture span data into the sample context for either code hotspots
|
|
146
156
|
// or endpoint collection.
|
|
@@ -192,8 +202,8 @@ class NativeWallProfiler {
|
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
if (this.#captureSpanData) {
|
|
195
|
-
this
|
|
196
|
-
this
|
|
205
|
+
this.#profilerState = this.#pprof.time.getState()
|
|
206
|
+
this.#lastSampleCount = 0
|
|
197
207
|
|
|
198
208
|
ensureChannelsActivated(this.#asyncContextFrameEnabled)
|
|
199
209
|
|
|
@@ -217,12 +227,12 @@ class NativeWallProfiler {
|
|
|
217
227
|
const asyncContextsLiveGauge = profilerTelemetryMetrics.gauge('wall.async_contexts_live')
|
|
218
228
|
const asyncContextsUsedGauge = profilerTelemetryMetrics.gauge('wall.async_contexts_used')
|
|
219
229
|
|
|
220
|
-
this
|
|
230
|
+
this.#contextCountGaugeUpdater = setInterval(() => {
|
|
221
231
|
const { totalAsyncContextCount, usedAsyncContextCount } = this.#pprof.time.getMetrics()
|
|
222
232
|
asyncContextsLiveGauge.mark(totalAsyncContextCount)
|
|
223
233
|
asyncContextsUsedGauge.mark(usedAsyncContextCount)
|
|
224
234
|
}, this.#telemetryHeartbeatIntervalMillis)
|
|
225
|
-
this.
|
|
235
|
+
this.#contextCountGaugeUpdater.unref?.()
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
#enter () {
|
|
@@ -271,16 +281,16 @@ class NativeWallProfiler {
|
|
|
271
281
|
this.#pprof.time.setContext(sampleContext)
|
|
272
282
|
}
|
|
273
283
|
} else {
|
|
274
|
-
const sampleCount = this
|
|
275
|
-
if (sampleCount !== this
|
|
276
|
-
this
|
|
277
|
-
const context = this.
|
|
284
|
+
const sampleCount = this.#profilerState[kSampleCount]
|
|
285
|
+
if (sampleCount !== this.#lastSampleCount) {
|
|
286
|
+
this.#lastSampleCount = sampleCount
|
|
287
|
+
const context = this.#currentContext.ref
|
|
278
288
|
this.#setNewContext()
|
|
279
289
|
|
|
280
290
|
updateContext(context)
|
|
281
291
|
}
|
|
282
292
|
|
|
283
|
-
this.
|
|
293
|
+
this.#currentContext.ref = sampleContext
|
|
284
294
|
}
|
|
285
295
|
}
|
|
286
296
|
|
|
@@ -322,10 +332,10 @@ class NativeWallProfiler {
|
|
|
322
332
|
}
|
|
323
333
|
|
|
324
334
|
#setNewContext () {
|
|
325
|
-
this
|
|
335
|
+
this.#currentContext = {
|
|
326
336
|
ref: {},
|
|
327
337
|
}
|
|
328
|
-
this.#pprof.time.setContext(this
|
|
338
|
+
this.#pprof.time.setContext(this.#currentContext)
|
|
329
339
|
}
|
|
330
340
|
|
|
331
341
|
#spanFinished (span) {
|
|
@@ -360,7 +370,7 @@ class NativeWallProfiler {
|
|
|
360
370
|
if (this.#captureSpanData && !this.#asyncContextFrameEnabled) {
|
|
361
371
|
// update last sample context if needed
|
|
362
372
|
this.#enter()
|
|
363
|
-
this
|
|
373
|
+
this.#lastSampleCount = 0
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
// Mark thread labels and trace endpoint label as good deduplication candidates
|
|
@@ -382,7 +392,7 @@ class NativeWallProfiler {
|
|
|
382
392
|
this.#reportV8bug(v8BugDetected === 1)
|
|
383
393
|
}
|
|
384
394
|
} else {
|
|
385
|
-
clearInterval(this
|
|
395
|
+
clearInterval(this.#contextCountGaugeUpdater)
|
|
386
396
|
if (this.#captureSpanData) {
|
|
387
397
|
if (!this.#asyncContextFrameEnabled) {
|
|
388
398
|
beforeCh.unsubscribe(this.#boundEnter)
|
|
@@ -392,7 +402,7 @@ class NativeWallProfiler {
|
|
|
392
402
|
if (this.#endpointCollectionEnabled) {
|
|
393
403
|
tagsUpdateCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
394
404
|
}
|
|
395
|
-
this
|
|
405
|
+
this.#profilerState = undefined
|
|
396
406
|
}
|
|
397
407
|
this.#started = false
|
|
398
408
|
}
|