dd-trace 5.96.0 → 5.98.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/index.d.ts +60 -2
- package/package.json +9 -7
- package/packages/datadog-esbuild/index.js +20 -9
- package/packages/datadog-instrumentations/src/child_process.js +7 -17
- package/packages/datadog-instrumentations/src/crypto.js +1 -2
- package/packages/datadog-instrumentations/src/cucumber.js +69 -4
- package/packages/datadog-instrumentations/src/cypress-config.js +318 -0
- package/packages/datadog-instrumentations/src/cypress.js +86 -4
- package/packages/datadog-instrumentations/src/dns.js +1 -2
- package/packages/datadog-instrumentations/src/express.js +4 -4
- package/packages/datadog-instrumentations/src/fs.js +27 -29
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
- package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
- package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
- package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
- package/packages/datadog-instrumentations/src/http/client.js +2 -3
- package/packages/datadog-instrumentations/src/http/server.js +2 -5
- package/packages/datadog-instrumentations/src/http2/client.js +1 -3
- package/packages/datadog-instrumentations/src/http2/server.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +117 -16
- package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +12 -1
- package/packages/datadog-instrumentations/src/net.js +2 -8
- package/packages/datadog-instrumentations/src/pino.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +4 -1
- package/packages/datadog-instrumentations/src/prisma.js +1 -2
- package/packages/datadog-instrumentations/src/redis.js +12 -6
- package/packages/datadog-instrumentations/src/selenium.js +4 -1
- package/packages/datadog-instrumentations/src/sequelize.js +1 -1
- package/packages/datadog-instrumentations/src/url.js +1 -3
- package/packages/datadog-instrumentations/src/vitest.js +5 -1
- package/packages/datadog-instrumentations/src/vm.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -4
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +13 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +166 -6
- package/packages/datadog-plugin-cypress/src/index.js +59 -2
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
- package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +10 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/server.js +10 -2
- package/packages/datadog-plugin-jest/src/index.js +4 -2
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
- package/packages/datadog-plugin-mocha/src/index.js +5 -2
- package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
- package/packages/datadog-plugin-mysql/src/index.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +10 -16
- package/packages/datadog-plugin-openai/src/services.js +1 -0
- package/packages/datadog-plugin-pg/src/index.js +1 -1
- package/packages/datadog-plugin-tedious/src/index.js +1 -1
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/datadog-plugin-ws/src/receiver.js +1 -1
- package/packages/datadog-webpack/index.js +3 -3
- package/packages/dd-trace/index.js +12 -10
- package/packages/dd-trace/src/agent/url.js +2 -2
- package/packages/dd-trace/src/aiguard/sdk.js +26 -22
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
- package/packages/dd-trace/src/appsec/blocking.js +64 -33
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/remote_config.js +1 -0
- package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
- package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/config/defaults.js +315 -146
- package/packages/dd-trace/src/config/generated-config-types.d.ts +9 -1
- package/packages/dd-trace/src/config/helper.js +59 -10
- package/packages/dd-trace/src/config/index.js +587 -1496
- package/packages/dd-trace/src/config/parsers.js +256 -0
- package/packages/dd-trace/src/config/remote_config.js +59 -2
- package/packages/dd-trace/src/config/supported-configurations.json +406 -432
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
- package/packages/dd-trace/src/crashtracking/index.js +1 -7
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/dogstatsd.js +12 -9
- package/packages/dd-trace/src/encode/0.4.js +8 -7
- package/packages/dd-trace/src/encode/span-stats.js +4 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
- package/packages/dd-trace/src/exporters/common/request.js +9 -0
- package/packages/dd-trace/src/exporters/common/writer.js +12 -2
- package/packages/dd-trace/src/heap_snapshots.js +3 -0
- package/packages/dd-trace/src/index.js +5 -2
- package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
- package/packages/dd-trace/src/llmobs/index.js +4 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
- package/packages/dd-trace/src/llmobs/sdk.js +12 -8
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +9 -6
- package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
- package/packages/dd-trace/src/log/index.js +20 -59
- package/packages/dd-trace/src/log/writer.js +7 -19
- package/packages/dd-trace/src/noop/proxy.js +8 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
- package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +17 -2
- package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
- package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
- package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +75 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +66 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +332 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
- package/packages/dd-trace/src/opentracing/tracer.js +9 -4
- package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
- package/packages/dd-trace/src/plugin_manager.js +8 -6
- package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
- package/packages/dd-trace/src/plugins/plugin.js +11 -13
- package/packages/dd-trace/src/plugins/storage.js +2 -2
- package/packages/dd-trace/src/plugins/tracing.js +22 -5
- package/packages/dd-trace/src/plugins/util/test.js +2 -0
- package/packages/dd-trace/src/plugins/util/web.js +6 -88
- package/packages/dd-trace/src/process-tags/index.js +3 -0
- package/packages/dd-trace/src/profiler.js +27 -2
- package/packages/dd-trace/src/profiling/config.js +73 -241
- package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
- package/packages/dd-trace/src/profiling/profiler.js +78 -109
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
- package/packages/dd-trace/src/propagation-hash/index.js +2 -1
- package/packages/dd-trace/src/proxy.js +40 -6
- package/packages/dd-trace/src/remote_config/index.js +3 -0
- package/packages/dd-trace/src/require-package-json.js +8 -4
- package/packages/dd-trace/src/ritm.js +58 -26
- package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +18 -11
- package/packages/dd-trace/src/sampler.js +1 -1
- package/packages/dd-trace/src/service-naming/index.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
- package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +17 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +11 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
- package/packages/dd-trace/src/span_stats.js +5 -1
- package/packages/dd-trace/src/standalone/index.js +3 -0
- package/packages/dd-trace/src/telemetry/index.js +2 -3
- package/packages/dd-trace/src/telemetry/send-data.js +5 -19
- package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
- package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
- package/packages/dd-trace/src/tracer.js +2 -2
- package/packages/dd-trace/src/util.js +0 -9
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
- package/vendor/dist/protobufjs/index.js +1 -1
- package/packages/dd-trace/src/log/utils.js +0 -16
|
@@ -14,7 +14,7 @@ class EventSerializer {
|
|
|
14
14
|
this._host = host
|
|
15
15
|
this._service = service
|
|
16
16
|
this._appVersion = version
|
|
17
|
-
this._libraryInjected =
|
|
17
|
+
this._libraryInjected = libraryInjected
|
|
18
18
|
this._activation = activation || 'unknown'
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ class EventSerializer {
|
|
|
22
22
|
return `${type}.pprof`
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
getEventJSON ({ profiles, infos, start, end, tags = {}, endpointCounts }) {
|
|
25
|
+
getEventJSON ({ profiles, infos, start, end, tags = {}, endpointCounts, customAttributes }) {
|
|
26
26
|
const event = {
|
|
27
27
|
attachments: Object.keys(profiles).map(t => this.typeToFile(t)),
|
|
28
28
|
start: start.toISOString(),
|
|
@@ -80,6 +80,10 @@ class EventSerializer {
|
|
|
80
80
|
},
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
if (customAttributes) {
|
|
84
|
+
event.custom_attributes = customAttributes
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
if (processTags.serialized) {
|
|
84
88
|
event[processTags.PROFILING_FIELD_NAME] = processTags.serialized
|
|
85
89
|
}
|
|
@@ -12,12 +12,6 @@ const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('./we
|
|
|
12
12
|
const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
|
|
13
13
|
const spanFinishedChannel = dc.channel('dd-trace:span:finish')
|
|
14
14
|
|
|
15
|
-
function logError (logger, ...args) {
|
|
16
|
-
if (logger) {
|
|
17
|
-
logger.error(...args)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
function findWebSpan (startedSpans, spanId) {
|
|
22
16
|
for (let i = startedSpans.length; --i >= 0;) {
|
|
23
17
|
const ispan = startedSpans[i]
|
|
@@ -51,6 +45,7 @@ class Profiler extends EventEmitter {
|
|
|
51
45
|
#compressionFnInitialized = false
|
|
52
46
|
#compressionOptions
|
|
53
47
|
#config
|
|
48
|
+
#customLabelKeys = new Set()
|
|
54
49
|
#enabled = false
|
|
55
50
|
#endpointCounts = new Map()
|
|
56
51
|
#lastStart
|
|
@@ -70,73 +65,47 @@ class Profiler extends EventEmitter {
|
|
|
70
65
|
return this.#config?.flushInterval
|
|
71
66
|
}
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
version,
|
|
77
|
-
env,
|
|
78
|
-
url,
|
|
79
|
-
hostname,
|
|
80
|
-
port,
|
|
81
|
-
tags,
|
|
82
|
-
repositoryUrl,
|
|
83
|
-
commitSHA,
|
|
84
|
-
injectionEnabled,
|
|
85
|
-
reportHostname,
|
|
86
|
-
} = config
|
|
87
|
-
const { enabled, sourceMap, exporters } = config.profiling
|
|
88
|
-
const { heartbeatInterval } = config.telemetry
|
|
89
|
-
|
|
90
|
-
// TODO: Unify with main logger and rewrite template strings to use printf formatting.
|
|
91
|
-
const logger = {
|
|
92
|
-
debug (message) { log.debug(message) },
|
|
93
|
-
info (message) { log.info(message) },
|
|
94
|
-
warn (message) { log.warn(message) },
|
|
95
|
-
error (...args) { log.error(...args) },
|
|
96
|
-
}
|
|
68
|
+
get enabled () {
|
|
69
|
+
return this.#enabled
|
|
70
|
+
}
|
|
97
71
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
env,
|
|
110
|
-
logger,
|
|
111
|
-
sourceMap,
|
|
112
|
-
exporters,
|
|
113
|
-
url,
|
|
114
|
-
hostname,
|
|
115
|
-
port,
|
|
116
|
-
tags,
|
|
117
|
-
repositoryUrl,
|
|
118
|
-
commitSHA,
|
|
119
|
-
libraryInjected,
|
|
120
|
-
activation,
|
|
121
|
-
heartbeatInterval,
|
|
122
|
-
reportHostname,
|
|
72
|
+
/**
|
|
73
|
+
* Declares the set of custom label keys that will be used with
|
|
74
|
+
* {@link runWithLabels}. This is used for profile upload metadata and
|
|
75
|
+
* for pprof serialization optimization (low-cardinality deduplication).
|
|
76
|
+
*
|
|
77
|
+
* @param {Iterable<string>} keys - Custom label key names
|
|
78
|
+
*/
|
|
79
|
+
setCustomLabelKeys (keys) {
|
|
80
|
+
this.#customLabelKeys.clear()
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
this.#customLabelKeys.add(key)
|
|
123
83
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
logError(logger, 'Error starting profiler. For troubleshooting tips, see ' +
|
|
129
|
-
'<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
|
|
130
|
-
return false
|
|
84
|
+
if (this.#config) {
|
|
85
|
+
for (const profiler of this.#config.profilers) {
|
|
86
|
+
profiler.setCustomLabelKeys?.(this.#customLabelKeys)
|
|
87
|
+
}
|
|
131
88
|
}
|
|
132
89
|
}
|
|
133
90
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Runs a function with custom profiling labels attached to wall profiler samples.
|
|
93
|
+
*
|
|
94
|
+
* @param {Record<string, string | number>} labels - Custom labels to attach
|
|
95
|
+
* @param {function(): T} fn - Function to execute with the labels
|
|
96
|
+
* @returns {T} The return value of fn
|
|
97
|
+
* @template T
|
|
98
|
+
*/
|
|
99
|
+
runWithLabels (labels, fn) {
|
|
100
|
+
if (!this.#enabled || !this.#config) {
|
|
101
|
+
return fn()
|
|
102
|
+
}
|
|
103
|
+
for (const profiler of this.#config.profilers) {
|
|
104
|
+
if (profiler.runWithLabels) {
|
|
105
|
+
return profiler.runWithLabels(labels, fn)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return fn()
|
|
140
109
|
}
|
|
141
110
|
|
|
142
111
|
#getCompressionFn () {
|
|
@@ -175,48 +144,46 @@ class Profiler extends EventEmitter {
|
|
|
175
144
|
}
|
|
176
145
|
break
|
|
177
146
|
}
|
|
178
|
-
} catch (
|
|
179
|
-
|
|
147
|
+
} catch (error) {
|
|
148
|
+
log.error(error)
|
|
180
149
|
}
|
|
181
150
|
}
|
|
182
151
|
return this.#compressionFn
|
|
183
152
|
}
|
|
184
153
|
|
|
185
|
-
|
|
154
|
+
/**
|
|
155
|
+
* @param {import('../config/config-base')} options - Tracer configuration
|
|
156
|
+
*/
|
|
157
|
+
start (options) {
|
|
186
158
|
if (this.enabled) return true
|
|
159
|
+
this.#enabled = true
|
|
187
160
|
|
|
188
161
|
const config = this.#config = new Config(options)
|
|
189
|
-
|
|
190
162
|
this.#logger = config.logger
|
|
191
|
-
this.#enabled = true
|
|
192
|
-
this._setInterval()
|
|
193
163
|
|
|
164
|
+
this._setInterval()
|
|
194
165
|
// Log errors if the source map finder fails, but don't prevent the rest
|
|
195
166
|
// of the profiler from running without source maps.
|
|
196
167
|
let mapper
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
.
|
|
215
|
-
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
} catch (err) {
|
|
219
|
-
this.#logError(err)
|
|
168
|
+
const { setLogger, SourceMapper } = require('@datadog/pprof')
|
|
169
|
+
setLogger(config.logger)
|
|
170
|
+
|
|
171
|
+
if (config.sourceMap) {
|
|
172
|
+
mapper = new SourceMapper(config.debugSourceMaps)
|
|
173
|
+
mapper.loadDirectory(process.cwd())
|
|
174
|
+
.then(() => {
|
|
175
|
+
if (config.debugSourceMaps) {
|
|
176
|
+
const count = mapper.infoMap.size
|
|
177
|
+
this.#logger.debug(() => {
|
|
178
|
+
return count === 0
|
|
179
|
+
? 'Found no source maps'
|
|
180
|
+
: `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.catch((error) => {
|
|
185
|
+
log.error(error)
|
|
186
|
+
})
|
|
220
187
|
}
|
|
221
188
|
|
|
222
189
|
try {
|
|
@@ -237,12 +204,13 @@ class Profiler extends EventEmitter {
|
|
|
237
204
|
}
|
|
238
205
|
|
|
239
206
|
this._capture(this._timeoutInterval, start)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.#logError(e)
|
|
207
|
+
} catch (error) {
|
|
208
|
+
log.error(error)
|
|
243
209
|
this.#stop()
|
|
244
210
|
return false
|
|
245
211
|
}
|
|
212
|
+
|
|
213
|
+
return true
|
|
246
214
|
}
|
|
247
215
|
|
|
248
216
|
#nearOOMExport (profileType, encodedProfile, info) {
|
|
@@ -380,10 +348,10 @@ class Profiler extends EventEmitter {
|
|
|
380
348
|
return `Collected ${profiler.type} profile: ` + profileJson
|
|
381
349
|
})
|
|
382
350
|
hasEncoded = true
|
|
383
|
-
} catch (
|
|
351
|
+
} catch (error) {
|
|
384
352
|
// If encoding one of the profile types fails, we should still try to
|
|
385
353
|
// encode and submit the other profile types.
|
|
386
|
-
|
|
354
|
+
log.error(error)
|
|
387
355
|
}
|
|
388
356
|
}))
|
|
389
357
|
|
|
@@ -392,8 +360,8 @@ class Profiler extends EventEmitter {
|
|
|
392
360
|
profileSubmittedChannel.publish()
|
|
393
361
|
this.#logger.debug('Submitted profiles')
|
|
394
362
|
}
|
|
395
|
-
} catch (
|
|
396
|
-
|
|
363
|
+
} catch (error) {
|
|
364
|
+
log.error(error)
|
|
397
365
|
this.#stop()
|
|
398
366
|
}
|
|
399
367
|
}
|
|
@@ -410,12 +378,13 @@ class Profiler extends EventEmitter {
|
|
|
410
378
|
|
|
411
379
|
tags.snapshot = snapshotKind
|
|
412
380
|
tags.profile_seq = this.#profileSeq++
|
|
413
|
-
const
|
|
381
|
+
const customAttributes = this.#customLabelKeys.size > 0
|
|
382
|
+
? [...this.#customLabelKeys]
|
|
383
|
+
: undefined
|
|
384
|
+
const exportSpec = { profiles, infos, start, end, tags, endpointCounts, customAttributes }
|
|
414
385
|
const tasks = this.#config.exporters.map(exporter =>
|
|
415
|
-
exporter.export(exportSpec).catch(
|
|
416
|
-
|
|
417
|
-
this.#logger.warn(err)
|
|
418
|
-
}
|
|
386
|
+
exporter.export(exportSpec).catch(error => {
|
|
387
|
+
log.warn(error)
|
|
419
388
|
})
|
|
420
389
|
)
|
|
421
390
|
|
|
@@ -51,8 +51,7 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function getMaxSamples (options) {
|
|
54
|
-
const
|
|
55
|
-
const maxCpuSamples = flushInterval / options.samplingInterval
|
|
54
|
+
const maxCpuSamples = options.flushInterval / options.samplingInterval
|
|
56
55
|
|
|
57
56
|
// The lesser of max parallelism and libuv thread pool size, plus one so we can detect
|
|
58
57
|
// oversubscription on libuv thread pool, plus another one for GC.
|
|
@@ -403,7 +402,7 @@ class EventsProfiler {
|
|
|
403
402
|
|
|
404
403
|
get type () { return 'events' }
|
|
405
404
|
|
|
406
|
-
constructor (options
|
|
405
|
+
constructor (options) {
|
|
407
406
|
this.#maxSamples = getMaxSamples(options)
|
|
408
407
|
this.#timelineSamplingEnabled = !!options.timelineSamplingEnabled
|
|
409
408
|
this.#eventSerializer = new EventSerializer(this.#maxSamples)
|
|
@@ -108,6 +108,8 @@ class NativeWallProfiler {
|
|
|
108
108
|
#captureSpanData = false
|
|
109
109
|
#codeHotspotsEnabled = false
|
|
110
110
|
#cpuProfilingEnabled = false
|
|
111
|
+
#customLabelsActive = false
|
|
112
|
+
#customLabelKeys
|
|
111
113
|
#endpointCollectionEnabled = false
|
|
112
114
|
#flushIntervalMillis = 0
|
|
113
115
|
#logger
|
|
@@ -245,7 +247,24 @@ class NativeWallProfiler {
|
|
|
245
247
|
// context -- we simply can't tell which one it might've been across all
|
|
246
248
|
// possible async context frames.
|
|
247
249
|
if (this.#asyncContextFrameEnabled) {
|
|
248
|
-
this.#
|
|
250
|
+
if (this.#customLabelsActive) {
|
|
251
|
+
// Custom labels may be active in this async context. The current CPED
|
|
252
|
+
// context could be a 2-element array [profilingContext, customLabels].
|
|
253
|
+
// Replace the profiling context while preserving the custom labels.
|
|
254
|
+
// This flag is monotonic (once set, stays true) because async
|
|
255
|
+
// continuations from runWithLabels can fire at any time after the
|
|
256
|
+
// synchronous runWithLabels call has returned.
|
|
257
|
+
const current = this.#pprof.time.getContext()
|
|
258
|
+
if (Array.isArray(current)) {
|
|
259
|
+
if (current[0] !== sampleContext) {
|
|
260
|
+
this.#pprof.time.setContext([sampleContext, current[1]])
|
|
261
|
+
}
|
|
262
|
+
} else if (current !== sampleContext) {
|
|
263
|
+
this.#pprof.time.setContext(sampleContext)
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
this.#pprof.time.setContext(sampleContext)
|
|
267
|
+
}
|
|
249
268
|
} else {
|
|
250
269
|
const sampleCount = this._profilerState[kSampleCount]
|
|
251
270
|
if (sampleCount !== this._lastSampleCount) {
|
|
@@ -344,6 +363,13 @@ class NativeWallProfiler {
|
|
|
344
363
|
const lowCardinalityLabels = Object.keys(getThreadLabels())
|
|
345
364
|
lowCardinalityLabels.push(TRACE_ENDPOINT_LABEL)
|
|
346
365
|
|
|
366
|
+
// Custom labels are expected to be low-cardinality (e.g. customer tier, region)
|
|
367
|
+
if (this.#customLabelKeys) {
|
|
368
|
+
for (const key of this.#customLabelKeys) {
|
|
369
|
+
lowCardinalityLabels.push(key)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
347
373
|
const profile = this.#pprof.time.stop(restart, this.#boundGenerateLabels, lowCardinalityLabels)
|
|
348
374
|
|
|
349
375
|
if (restart) {
|
|
@@ -383,7 +409,29 @@ class NativeWallProfiler {
|
|
|
383
409
|
return getThreadLabels()
|
|
384
410
|
}
|
|
385
411
|
|
|
386
|
-
|
|
412
|
+
// Native profiler doesn't set context.context for some samples, such as idle samples or when
|
|
413
|
+
// the context was otherwise unavailable when the sample was taken. Note that with ACF, we don't
|
|
414
|
+
// use the "ref" indirection.
|
|
415
|
+
let ref
|
|
416
|
+
let customLabels
|
|
417
|
+
const cctx = context.context
|
|
418
|
+
if (this.#asyncContextFrameEnabled) {
|
|
419
|
+
// When custom labels are active with ACF, context.context is a 2-element array:
|
|
420
|
+
// [profilingContext, customLabels]. Otherwise it's a plain object.
|
|
421
|
+
if (Array.isArray(cctx)) {
|
|
422
|
+
[ref, customLabels] = cctx
|
|
423
|
+
} else {
|
|
424
|
+
ref = cctx
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
ref = cctx?.ref
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Custom labels are spread first so that internal labels always take
|
|
431
|
+
// precedence and overwrite them.
|
|
432
|
+
const labels = customLabels === undefined
|
|
433
|
+
? { ...getThreadLabels() }
|
|
434
|
+
: { ...customLabels, ...getThreadLabels() }
|
|
387
435
|
|
|
388
436
|
if (this.#timelineEnabled) {
|
|
389
437
|
// Incoming timestamps are in microseconds, we emit nanos.
|
|
@@ -395,10 +443,6 @@ class NativeWallProfiler {
|
|
|
395
443
|
labels['async id'] = asyncId
|
|
396
444
|
}
|
|
397
445
|
|
|
398
|
-
// Native profiler doesn't set context.context for some samples, such as idle samples or when
|
|
399
|
-
// the context was otherwise unavailable when the sample was taken. Note that with async context
|
|
400
|
-
// frame, we don't use the "ref" indirection.
|
|
401
|
-
const ref = this.#asyncContextFrameEnabled ? context.context : context.context?.ref
|
|
402
446
|
if (typeof ref !== 'object') {
|
|
403
447
|
return labels
|
|
404
448
|
}
|
|
@@ -421,6 +465,45 @@ class NativeWallProfiler {
|
|
|
421
465
|
return labels
|
|
422
466
|
}
|
|
423
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Sets the custom label keys used for pprof low-cardinality deduplication.
|
|
470
|
+
* Called once by the top-level Profiler when keys are declared.
|
|
471
|
+
*
|
|
472
|
+
* @param {Iterable<string>} keys
|
|
473
|
+
*/
|
|
474
|
+
setCustomLabelKeys (keys) {
|
|
475
|
+
this.#customLabelKeys = keys
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Runs a function with custom profiling labels attached to all wall profiler
|
|
480
|
+
* samples taken during its execution. Labels are key-value pairs that appear
|
|
481
|
+
* in the pprof output and can be used to filter flame graphs in the Datadog UI.
|
|
482
|
+
*
|
|
483
|
+
* Requires AsyncContextFrame (ACF) to be enabled. Supports nesting: inner
|
|
484
|
+
* calls merge labels with outer calls, with inner values taking precedence.
|
|
485
|
+
*
|
|
486
|
+
* @param {Record<string, string | number>} labels - Custom labels to attach
|
|
487
|
+
* @param {function(): T} fn - Function to execute with the labels
|
|
488
|
+
* @returns {T} The return value of fn
|
|
489
|
+
* @template T
|
|
490
|
+
*/
|
|
491
|
+
runWithLabels (labels, fn) {
|
|
492
|
+
if (!this.#asyncContextFrameEnabled || !this.#withContexts) {
|
|
493
|
+
return fn()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Read current context; merge custom labels if already in a runWithLabels scope
|
|
497
|
+
const current = this.#pprof.time.getContext()
|
|
498
|
+
const isCurrentArray = Array.isArray(current)
|
|
499
|
+
const customLabels = isCurrentArray ? { ...current[1], ...labels } : labels
|
|
500
|
+
|
|
501
|
+
const profilingContext = (isCurrentArray ? current[0] : current) ?? {}
|
|
502
|
+
|
|
503
|
+
this.#customLabelsActive = true
|
|
504
|
+
return this.#pprof.time.runWithContext([profilingContext, customLabels], fn)
|
|
505
|
+
}
|
|
506
|
+
|
|
424
507
|
profile (restart) {
|
|
425
508
|
return this.#stop(restart)
|
|
426
509
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dc = require('dc-polyfill')
|
|
3
|
+
const dc = /** @type {typeof import('diagnostics_channel')} */ (require('dc-polyfill'))
|
|
4
4
|
const log = require('../log')
|
|
5
5
|
|
|
6
6
|
// If the process lives for at least 30 seconds, it's considered long-lived
|
|
@@ -10,6 +10,9 @@ const DEFAULT_LONG_LIVED_THRESHOLD = 30_000
|
|
|
10
10
|
* This class embodies the SSI profiler-triggering heuristics under SSI.
|
|
11
11
|
*/
|
|
12
12
|
class SSIHeuristics {
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
15
|
+
*/
|
|
13
16
|
constructor (config) {
|
|
14
17
|
const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
|
|
15
18
|
if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
|
|
@@ -17,11 +17,12 @@ class PropagationHashManager {
|
|
|
17
17
|
_cachedHash = null
|
|
18
18
|
_cachedHashString = null
|
|
19
19
|
_cachedHashBase64 = null
|
|
20
|
+
/** @type {import('../config/config-base') | null} */
|
|
20
21
|
_config = null
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Configure the propagation hash manager with tracer config
|
|
24
|
-
* @param {
|
|
25
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
25
26
|
*/
|
|
26
27
|
configure (config) {
|
|
27
28
|
this._config = config
|
|
@@ -27,9 +27,12 @@ class LazyModule {
|
|
|
27
27
|
this.provider = provider
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @param {import('./config/config-base')} config - Tracer configuration
|
|
32
|
+
*/
|
|
33
|
+
enable (config, ...args) {
|
|
31
34
|
this.module = this.provider()
|
|
32
|
-
this.module.enable(...args)
|
|
35
|
+
this.module.enable(config, ...args)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
disable () {
|
|
@@ -238,25 +241,32 @@ class Tracer extends NoopProxy {
|
|
|
238
241
|
getDynamicInstrumentationClient(config)
|
|
239
242
|
}
|
|
240
243
|
} catch (e) {
|
|
241
|
-
log.error('Error
|
|
244
|
+
log.error('Error initializing tracer', e)
|
|
245
|
+
// TODO: Should we stop everything started so far?
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
return this
|
|
245
249
|
}
|
|
246
250
|
|
|
251
|
+
/**
|
|
252
|
+
* @param {import('./config/config-base')} config - Tracer configuration
|
|
253
|
+
*/
|
|
247
254
|
_startProfiler (config) {
|
|
248
255
|
// do not stop tracer initialization if the profiler fails to be imported
|
|
249
256
|
try {
|
|
250
257
|
return require('./profiler').start(config)
|
|
251
|
-
} catch (
|
|
258
|
+
} catch (error) {
|
|
252
259
|
log.error(
|
|
253
260
|
'Error starting profiler. For troubleshooting tips, see <https://dtdg.co/nodejs-profiler-troubleshooting>',
|
|
254
|
-
|
|
261
|
+
error
|
|
255
262
|
)
|
|
256
263
|
return false
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @param {import('./config/config-base')} config - Tracer configuration
|
|
269
|
+
*/
|
|
260
270
|
#updateTracing (config) {
|
|
261
271
|
if (config.tracing !== false) {
|
|
262
272
|
if (config.appsec.enabled) {
|
|
@@ -269,7 +279,12 @@ class Tracer extends NoopProxy {
|
|
|
269
279
|
const prioritySampler = config.apmTracingEnabled === false
|
|
270
280
|
? require('./standalone').configure(config)
|
|
271
281
|
: undefined
|
|
272
|
-
|
|
282
|
+
let otlpExporter
|
|
283
|
+
if (config.otelTracesEnabled) {
|
|
284
|
+
const { buildResourceAttributes, createOtlpTraceExporter } = require('./opentelemetry/trace')
|
|
285
|
+
otlpExporter = createOtlpTraceExporter(config, buildResourceAttributes(config))
|
|
286
|
+
}
|
|
287
|
+
this._tracer = new DatadogTracer(config, prioritySampler, otlpExporter)
|
|
273
288
|
this.dataStreamsCheckpointer = this._tracer.dataStreamsCheckpointer
|
|
274
289
|
lazyProxy(this, 'appsec', () => require('./appsec/sdk'), this._tracer, config)
|
|
275
290
|
lazyProxy(this, 'llmobs', () => require('./llmobs/sdk'), this._tracer, this._modules.llmobs, config)
|
|
@@ -330,6 +345,25 @@ class Tracer extends NoopProxy {
|
|
|
330
345
|
}
|
|
331
346
|
}
|
|
332
347
|
|
|
348
|
+
/**
|
|
349
|
+
* @override
|
|
350
|
+
*/
|
|
351
|
+
get profiling () {
|
|
352
|
+
// Lazily require the profiler module and cache the result. If profiling
|
|
353
|
+
// is not enabled, runWithLabels still works as a passthrough (just calls fn()).
|
|
354
|
+
const profilerModule = require('./profiler')
|
|
355
|
+
const profiling = {
|
|
356
|
+
setCustomLabelKeys (keys) {
|
|
357
|
+
profilerModule.setCustomLabelKeys(keys)
|
|
358
|
+
},
|
|
359
|
+
runWithLabels (labels, fn) {
|
|
360
|
+
return profilerModule.runWithLabels(labels, fn)
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
Reflect.defineProperty(this, 'profiling', { value: profiling, configurable: true, enumerable: true })
|
|
364
|
+
return profiling
|
|
365
|
+
}
|
|
366
|
+
|
|
333
367
|
/**
|
|
334
368
|
* @override
|
|
335
369
|
*/
|
|
@@ -25,6 +25,9 @@ class RemoteConfig {
|
|
|
25
25
|
#products = new Set()
|
|
26
26
|
#batchHandlers = new Map()
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
30
|
+
*/
|
|
28
31
|
constructor (config) {
|
|
29
32
|
const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
|
|
30
33
|
|
|
@@ -21,10 +21,14 @@ function requirePackageJson (name, module) {
|
|
|
21
21
|
}
|
|
22
22
|
for (const modulePath of module.paths) {
|
|
23
23
|
const candidate = path.join(modulePath, name, 'package.json')
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// fs.existsSync is faster than fs.readFileSync due to not throwing an error if the file does not exist.
|
|
25
|
+
// The race condition should also not matter here as the time window is very small.
|
|
26
|
+
if (fs.existsSync(candidate)) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(candidate, 'utf8'))
|
|
29
|
+
} catch {
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
throw new Error(`could not find ${name}/package.json`)
|