dd-trace 4.45.0 → 4.47.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/LICENSE-3rdparty.csv +2 -0
- package/index.d.ts +20 -8
- package/package.json +11 -5
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/body-parser.js +4 -4
- package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
- package/packages/datadog-instrumentations/src/child_process.js +2 -2
- package/packages/datadog-instrumentations/src/connect.js +4 -4
- package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
- package/packages/datadog-instrumentations/src/couchbase.js +12 -12
- package/packages/datadog-instrumentations/src/cucumber.js +294 -56
- package/packages/datadog-instrumentations/src/dns.js +10 -10
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
- package/packages/datadog-instrumentations/src/express.js +4 -4
- package/packages/datadog-instrumentations/src/fastify.js +6 -6
- package/packages/datadog-instrumentations/src/fetch.js +1 -1
- package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
- package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
- package/packages/datadog-instrumentations/src/hapi.js +10 -13
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/http/client.js +3 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/knex.js +2 -2
- package/packages/datadog-instrumentations/src/koa.js +5 -5
- package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
- package/packages/datadog-instrumentations/src/mariadb.js +8 -8
- package/packages/datadog-instrumentations/src/memcached.js +2 -2
- package/packages/datadog-instrumentations/src/microgateway-core.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -53
- package/packages/datadog-instrumentations/src/mocha/utils.js +37 -18
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/mocha.js +4 -0
- package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
- package/packages/datadog-instrumentations/src/mongoose.js +5 -6
- package/packages/datadog-instrumentations/src/mysql.js +3 -3
- package/packages/datadog-instrumentations/src/mysql2.js +6 -6
- package/packages/datadog-instrumentations/src/net.js +2 -2
- package/packages/datadog-instrumentations/src/next.js +5 -5
- package/packages/datadog-instrumentations/src/openai.js +62 -71
- package/packages/datadog-instrumentations/src/oracledb.js +8 -8
- package/packages/datadog-instrumentations/src/passport-http.js +1 -1
- package/packages/datadog-instrumentations/src/passport-local.js +1 -1
- package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +60 -5
- package/packages/datadog-instrumentations/src/pino.js +4 -4
- package/packages/datadog-instrumentations/src/playwright.js +6 -4
- package/packages/datadog-instrumentations/src/redis.js +2 -2
- package/packages/datadog-instrumentations/src/restify.js +4 -4
- package/packages/datadog-instrumentations/src/rhea.js +4 -4
- package/packages/datadog-instrumentations/src/router.js +5 -5
- package/packages/datadog-instrumentations/src/sharedb.js +2 -2
- package/packages/datadog-instrumentations/src/vitest.js +188 -12
- package/packages/datadog-instrumentations/src/winston.js +2 -3
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +39 -10
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-hapi/src/index.js +2 -2
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +18 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +85 -65
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/datadog-shimmer/src/shimmer.js +144 -10
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/blocking.js +23 -17
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/graphql.js +3 -1
- package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +93 -52
- package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
- package/packages/dd-trace/src/appsec/telemetry.js +3 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -1
- package/packages/dd-trace/src/config.js +100 -40
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/lambda/handler.js +1 -0
- package/packages/dd-trace/src/lambda/index.js +12 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -6
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +24 -4
- package/packages/dd-trace/src/profiler.js +15 -5
- package/packages/dd-trace/src/profiling/config.js +7 -4
- package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +8 -2
- package/packages/dd-trace/src/profiling/profiler.js +0 -9
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +59 -60
- package/packages/dd-trace/src/proxy.js +31 -24
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/telemetry/index.js +23 -6
- package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { AsyncLocalStorage } = require('async_hooks')
|
|
2
|
+
const TracingPlugin = require('../../../plugins/tracing')
|
|
3
|
+
const { performance } = require('perf_hooks')
|
|
4
|
+
|
|
5
|
+
// We are leveraging the TracingPlugin class for its functionality to bind
|
|
6
|
+
// start/error/finish methods to the appropriate diagnostic channels.
|
|
7
|
+
class EventPlugin extends TracingPlugin {
|
|
8
|
+
constructor (eventHandler) {
|
|
9
|
+
super()
|
|
10
|
+
this.eventHandler = eventHandler
|
|
11
|
+
this.store = new AsyncLocalStorage()
|
|
12
|
+
this.entryType = this.constructor.entryType
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start (startEvent) {
|
|
16
|
+
this.store.enterWith({
|
|
17
|
+
startEvent,
|
|
18
|
+
startTime: performance.now()
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error () {
|
|
23
|
+
this.store.getStore().error = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
finish () {
|
|
27
|
+
const { startEvent, startTime, error } = this.store.getStore()
|
|
28
|
+
if (error) {
|
|
29
|
+
return // don't emit perf events for failed operations
|
|
30
|
+
}
|
|
31
|
+
const duration = performance.now() - startTime
|
|
32
|
+
|
|
33
|
+
const context = this.activeSpan?.context()
|
|
34
|
+
const _ddSpanId = context?.toSpanId()
|
|
35
|
+
const _ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || _ddSpanId
|
|
36
|
+
|
|
37
|
+
const event = {
|
|
38
|
+
entryType: this.entryType,
|
|
39
|
+
startTime,
|
|
40
|
+
duration,
|
|
41
|
+
_ddSpanId,
|
|
42
|
+
_ddRootSpanId
|
|
43
|
+
}
|
|
44
|
+
this.eventHandler(this.extendEvent(event, startEvent))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = EventPlugin
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const EventPlugin = require('./event')
|
|
2
|
+
|
|
3
|
+
class NetPlugin extends EventPlugin {
|
|
4
|
+
static get id () {
|
|
5
|
+
return 'net'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static get operation () {
|
|
9
|
+
return 'tcp'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get entryType () {
|
|
13
|
+
return 'net'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
extendEvent (event, { options }) {
|
|
17
|
+
event.name = 'connect'
|
|
18
|
+
event.detail = options
|
|
19
|
+
|
|
20
|
+
return event
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = NetPlugin
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('perf_hooks')
|
|
2
|
-
const { END_TIMESTAMP_LABEL } = require('./shared')
|
|
3
|
-
const semver = require('semver')
|
|
2
|
+
const { END_TIMESTAMP_LABEL, SPAN_ID_LABEL, LOCAL_ROOT_SPAN_ID_LABEL } = require('./shared')
|
|
4
3
|
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
4
|
const pprof = require('@datadog/pprof/')
|
|
6
5
|
|
|
7
|
-
// Format of perf_hooks events changed with Node 16, we need to be mindful of it.
|
|
8
|
-
const node16 = semver.gte(process.version, '16.0.0')
|
|
9
|
-
|
|
10
6
|
// perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
|
|
11
7
|
const MS_TO_NS = 1000000
|
|
12
8
|
|
|
@@ -48,7 +44,7 @@ class GCDecorator {
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
decorateSample (sampleInput, item) {
|
|
51
|
-
const { kind, flags } =
|
|
47
|
+
const { kind, flags } = item.detail
|
|
52
48
|
sampleInput.label.push(this.kindLabels[kind])
|
|
53
49
|
const reasonLabel = this.getReasonLabel(flags)
|
|
54
50
|
if (reasonLabel) {
|
|
@@ -140,12 +136,9 @@ class NetDecorator {
|
|
|
140
136
|
// Keys correspond to PerformanceEntry.entryType, values are constructor
|
|
141
137
|
// functions for type-specific decorators.
|
|
142
138
|
const decoratorTypes = {
|
|
143
|
-
gc: GCDecorator
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (node16) {
|
|
147
|
-
decoratorTypes.dns = DNSDecorator
|
|
148
|
-
decoratorTypes.net = NetDecorator
|
|
139
|
+
gc: GCDecorator,
|
|
140
|
+
dns: DNSDecorator,
|
|
141
|
+
net: NetDecorator
|
|
149
142
|
}
|
|
150
143
|
|
|
151
144
|
// Translates performance entries into pprof samples.
|
|
@@ -168,10 +161,12 @@ class EventSerializer {
|
|
|
168
161
|
this.locationId = [location.id]
|
|
169
162
|
|
|
170
163
|
this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
164
|
+
this.spanIdKey = this.stringTable.dedup(SPAN_ID_LABEL)
|
|
165
|
+
this.rootSpanIdKey = this.stringTable.dedup(LOCAL_ROOT_SPAN_ID_LABEL)
|
|
171
166
|
}
|
|
172
167
|
|
|
173
168
|
addEvent (item) {
|
|
174
|
-
const { entryType, startTime, duration } = item
|
|
169
|
+
const { entryType, startTime, duration, _ddSpanId, _ddRootSpanId } = item
|
|
175
170
|
let decorator = this.decorators[entryType]
|
|
176
171
|
if (!decorator) {
|
|
177
172
|
const DecoratorCtor = decoratorTypes[entryType]
|
|
@@ -186,13 +181,21 @@ class EventSerializer {
|
|
|
186
181
|
}
|
|
187
182
|
}
|
|
188
183
|
const endTime = startTime + duration
|
|
184
|
+
const label = [
|
|
185
|
+
decorator.eventTypeLabel,
|
|
186
|
+
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
187
|
+
]
|
|
188
|
+
if (_ddSpanId) {
|
|
189
|
+
label.push(labelFromStr(this.stringTable, this.spanIdKey, _ddSpanId))
|
|
190
|
+
}
|
|
191
|
+
if (_ddRootSpanId) {
|
|
192
|
+
label.push(labelFromStr(this.stringTable, this.rootSpanIdKey, _ddRootSpanId))
|
|
193
|
+
}
|
|
194
|
+
|
|
189
195
|
const sampleInput = {
|
|
190
196
|
value: [Math.round(duration * MS_TO_NS)],
|
|
191
197
|
locationId: this.locationId,
|
|
192
|
-
label
|
|
193
|
-
decorator.eventTypeLabel,
|
|
194
|
-
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
195
|
-
]
|
|
198
|
+
label
|
|
196
199
|
}
|
|
197
200
|
decorator.decorateSample(sampleInput, item)
|
|
198
201
|
this.samples.push(new Sample(sampleInput))
|
|
@@ -219,36 +222,109 @@ class EventSerializer {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
/**
|
|
222
|
-
*
|
|
223
|
-
* performance measurement APIs.
|
|
225
|
+
* Class that sources timeline events through Node.js performance measurement APIs.
|
|
224
226
|
*/
|
|
225
|
-
class
|
|
226
|
-
constructor (
|
|
227
|
-
this.
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
230
|
-
this.eventSerializer = new EventSerializer()
|
|
227
|
+
class NodeApiEventSource {
|
|
228
|
+
constructor (eventHandler, entryTypes) {
|
|
229
|
+
this.eventHandler = eventHandler
|
|
230
|
+
this.observer = undefined
|
|
231
|
+
this.entryTypes = entryTypes || Object.keys(decoratorTypes)
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
start () {
|
|
234
235
|
// if already started, do nothing
|
|
235
|
-
if (this.
|
|
236
|
+
if (this.observer) return
|
|
236
237
|
|
|
237
238
|
function add (items) {
|
|
238
239
|
for (const item of items.getEntries()) {
|
|
239
|
-
this.
|
|
240
|
+
this.eventHandler(item)
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
|
-
|
|
243
|
-
this.
|
|
243
|
+
|
|
244
|
+
this.observer = new PerformanceObserver(add.bind(this))
|
|
245
|
+
this.observer.observe({ entryTypes: this.entryTypes })
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
stop () {
|
|
249
|
+
if (this.observer) {
|
|
250
|
+
this.observer.disconnect()
|
|
251
|
+
this.observer = undefined
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class DatadogInstrumentationEventSource {
|
|
257
|
+
constructor (eventHandler) {
|
|
258
|
+
this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'net'].map(m => {
|
|
259
|
+
const Plugin = require(`./event_plugins/${m}`)
|
|
260
|
+
return new Plugin(eventHandler)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
this.started = false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
start () {
|
|
267
|
+
if (!this.started) {
|
|
268
|
+
this.plugins.forEach(p => p.configure({ enabled: true }))
|
|
269
|
+
this.started = true
|
|
270
|
+
}
|
|
244
271
|
}
|
|
245
272
|
|
|
246
273
|
stop () {
|
|
247
|
-
if (this.
|
|
248
|
-
this.
|
|
249
|
-
this.
|
|
274
|
+
if (this.started) {
|
|
275
|
+
this.plugins.forEach(p => p.configure({ enabled: false }))
|
|
276
|
+
this.started = false
|
|
250
277
|
}
|
|
251
278
|
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class CompositeEventSource {
|
|
282
|
+
constructor (sources) {
|
|
283
|
+
this.sources = sources
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
start () {
|
|
287
|
+
this.sources.forEach(s => s.start())
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
stop () {
|
|
291
|
+
this.sources.forEach(s => s.stop())
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* This class generates pprof files with timeline events. It combines an event
|
|
297
|
+
* source with an event serializer.
|
|
298
|
+
*/
|
|
299
|
+
class EventsProfiler {
|
|
300
|
+
constructor (options = {}) {
|
|
301
|
+
this.type = 'events'
|
|
302
|
+
this.eventSerializer = new EventSerializer()
|
|
303
|
+
|
|
304
|
+
const eventHandler = event => {
|
|
305
|
+
this.eventSerializer.addEvent(event)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (options.codeHotspotsEnabled) {
|
|
309
|
+
// Use Datadog instrumentation to collect events with span IDs. Still use
|
|
310
|
+
// Node API for GC events.
|
|
311
|
+
this.eventSource = new CompositeEventSource([
|
|
312
|
+
new DatadogInstrumentationEventSource(eventHandler),
|
|
313
|
+
new NodeApiEventSource(eventHandler, ['gc'])
|
|
314
|
+
])
|
|
315
|
+
} else {
|
|
316
|
+
// Use Node API instrumentation to collect events without span IDs
|
|
317
|
+
this.eventSource = new NodeApiEventSource(eventHandler)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
start () {
|
|
322
|
+
this.eventSource.start()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
stop () {
|
|
326
|
+
this.eventSource.stop()
|
|
327
|
+
}
|
|
252
328
|
|
|
253
329
|
profile (restart, startDate, endDate) {
|
|
254
330
|
if (!restart) {
|
|
@@ -6,6 +6,9 @@ const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
|
|
|
6
6
|
const THREAD_NAME_LABEL = 'thread name'
|
|
7
7
|
const OS_THREAD_ID_LABEL = 'os thread id'
|
|
8
8
|
const THREAD_ID_LABEL = 'thread id'
|
|
9
|
+
const SPAN_ID_LABEL = 'span id'
|
|
10
|
+
const LOCAL_ROOT_SPAN_ID_LABEL = 'local root span id'
|
|
11
|
+
|
|
9
12
|
const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
|
|
10
13
|
const eventLoopThreadName = `${threadNamePrefix} Event Loop`
|
|
11
14
|
|
|
@@ -38,6 +41,8 @@ module.exports = {
|
|
|
38
41
|
THREAD_NAME_LABEL,
|
|
39
42
|
THREAD_ID_LABEL,
|
|
40
43
|
OS_THREAD_ID_LABEL,
|
|
44
|
+
SPAN_ID_LABEL,
|
|
45
|
+
LOCAL_ROOT_SPAN_ID_LABEL,
|
|
41
46
|
threadNamePrefix,
|
|
42
47
|
eventLoopThreadName,
|
|
43
48
|
getNonJSThreadsLabels,
|
|
@@ -7,7 +7,13 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
|
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
9
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
END_TIMESTAMP_LABEL,
|
|
12
|
+
SPAN_ID_LABEL,
|
|
13
|
+
LOCAL_ROOT_SPAN_ID_LABEL,
|
|
14
|
+
getNonJSThreadsLabels,
|
|
15
|
+
getThreadLabels
|
|
16
|
+
} = require('./shared')
|
|
11
17
|
|
|
12
18
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
13
19
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
@@ -275,10 +281,10 @@ class NativeWallProfiler {
|
|
|
275
281
|
}
|
|
276
282
|
|
|
277
283
|
if (spanId) {
|
|
278
|
-
labels[
|
|
284
|
+
labels[SPAN_ID_LABEL] = spanId
|
|
279
285
|
}
|
|
280
286
|
if (rootSpanId) {
|
|
281
|
-
labels[
|
|
287
|
+
labels[LOCAL_ROOT_SPAN_ID_LABEL] = rootSpanId
|
|
282
288
|
}
|
|
283
289
|
if (webTags && Object.keys(webTags).length !== 0) {
|
|
284
290
|
labels['trace endpoint'] = endpointNameFromTags(webTags)
|
|
@@ -3,44 +3,11 @@
|
|
|
3
3
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
4
4
|
const profilersNamespace = telemetryMetrics.manager.namespace('profilers')
|
|
5
5
|
const dc = require('dc-polyfill')
|
|
6
|
+
const log = require('../log')
|
|
6
7
|
|
|
7
8
|
// If the process lives for at least 30 seconds, it's considered long-lived
|
|
8
9
|
const DEFAULT_LONG_LIVED_THRESHOLD = 30000
|
|
9
10
|
|
|
10
|
-
const EnablementChoice = {
|
|
11
|
-
MANUALLY_ENABLED: Symbol('SSITelemetry.EnablementChoice.MANUALLY_ENABLED'),
|
|
12
|
-
SSI_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_ENABLED'),
|
|
13
|
-
SSI_NOT_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_NOT_ENABLED'),
|
|
14
|
-
DISABLED: Symbol('SSITelemetry.EnablementChoice.DISABLED')
|
|
15
|
-
}
|
|
16
|
-
Object.freeze(EnablementChoice)
|
|
17
|
-
|
|
18
|
-
function getEnablementChoiceFromConfig (config) {
|
|
19
|
-
if (config.ssi === false || config.enabled === false) {
|
|
20
|
-
return EnablementChoice.DISABLED
|
|
21
|
-
} else if (config.heuristicsEnabled === true) {
|
|
22
|
-
return EnablementChoice.SSI_ENABLED
|
|
23
|
-
} else if (config.enabled === true) {
|
|
24
|
-
return EnablementChoice.MANUALLY_ENABLED
|
|
25
|
-
} else {
|
|
26
|
-
return EnablementChoice.SSI_NOT_ENABLED
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function enablementChoiceToTagValue (enablementChoice) {
|
|
31
|
-
switch (enablementChoice) {
|
|
32
|
-
case EnablementChoice.MANUALLY_ENABLED:
|
|
33
|
-
return 'manually_enabled'
|
|
34
|
-
case EnablementChoice.SSI_ENABLED:
|
|
35
|
-
return 'ssi_enabled'
|
|
36
|
-
case EnablementChoice.SSI_NOT_ENABLED:
|
|
37
|
-
return 'not_enabled'
|
|
38
|
-
case EnablementChoice.DISABLED:
|
|
39
|
-
// Can't emit this one as a tag
|
|
40
|
-
throw new Error('Invalid enablement choice')
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
11
|
/**
|
|
45
12
|
* This class embodies the SSI profiler-triggering heuristics and also emits telemetry metrics about
|
|
46
13
|
* the profiler behavior under SSI. It emits the following metrics:
|
|
@@ -56,25 +23,48 @@ function enablementChoiceToTagValue (enablementChoice) {
|
|
|
56
23
|
*/
|
|
57
24
|
class SSIHeuristics {
|
|
58
25
|
constructor (config) {
|
|
59
|
-
|
|
26
|
+
const injectionIncludesProfiler = config.injectionEnabled.includes('profiler')
|
|
27
|
+
this._heuristicsActive = injectionIncludesProfiler || config.profiling.enabled === 'auto'
|
|
28
|
+
this._emitsTelemetry = config.injectionEnabled.length > 0 && config.profiling.enabled !== 'false'
|
|
29
|
+
|
|
30
|
+
if (this._emitsTelemetry) {
|
|
31
|
+
if (config.profiling.enabled === 'true') {
|
|
32
|
+
this.enablementChoice = 'manually_enabled'
|
|
33
|
+
} else if (injectionIncludesProfiler) {
|
|
34
|
+
this.enablementChoice = 'ssi_enabled'
|
|
35
|
+
} else if (config.profiling.enabled === 'auto') {
|
|
36
|
+
this.enablementChoice = 'auto_enabled'
|
|
37
|
+
} else {
|
|
38
|
+
this.enablementChoice = 'ssi_not_enabled'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
60
41
|
|
|
61
|
-
const longLivedThreshold = config.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
|
|
42
|
+
const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
|
|
62
43
|
if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
|
|
63
|
-
|
|
44
|
+
this.longLivedThreshold = DEFAULT_LONG_LIVED_THRESHOLD
|
|
45
|
+
log.warn(
|
|
46
|
+
`Invalid SSIHeuristics.longLivedThreshold value: ${config.profiling.longLivedThreshold}. ` +
|
|
47
|
+
`Using default value: ${DEFAULT_LONG_LIVED_THRESHOLD}`
|
|
48
|
+
)
|
|
49
|
+
} else {
|
|
50
|
+
this.longLivedThreshold = longLivedThreshold
|
|
64
51
|
}
|
|
65
|
-
this.longLivedThreshold = longLivedThreshold
|
|
66
52
|
|
|
67
53
|
this.hasSentProfiles = false
|
|
68
54
|
this.noSpan = true
|
|
69
55
|
this.shortLived = true
|
|
70
56
|
}
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
return this.
|
|
58
|
+
get emitsTelemetry () {
|
|
59
|
+
return this._emitsTelemetry
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get heuristicsActive () {
|
|
63
|
+
return this._heuristicsActive
|
|
74
64
|
}
|
|
75
65
|
|
|
76
66
|
start () {
|
|
77
|
-
if (this.
|
|
67
|
+
if (this.heuristicsActive || this.emitsTelemetry) {
|
|
78
68
|
// Used to determine short-livedness of the process. We could use the process start time as the
|
|
79
69
|
// reference point, but the tracer initialization point is more relevant, as we couldn't be
|
|
80
70
|
// collecting profiles earlier anyway. The difference is not particularly significant if the
|
|
@@ -85,13 +75,17 @@ class SSIHeuristics {
|
|
|
85
75
|
}, this.longLivedThreshold).unref()
|
|
86
76
|
|
|
87
77
|
this._onSpanCreated = this._onSpanCreated.bind(this)
|
|
88
|
-
this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
|
|
89
|
-
this._onMockProfileSubmitted = this._onMockProfileSubmitted.bind(this)
|
|
90
|
-
this._onAppClosing = this._onAppClosing.bind(this)
|
|
91
|
-
|
|
92
78
|
dc.subscribe('dd-trace:span:start', this._onSpanCreated)
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
|
|
80
|
+
if (this.emitsTelemetry) {
|
|
81
|
+
this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
|
|
82
|
+
this._onMockProfileSubmitted = this._onMockProfileSubmitted.bind(this)
|
|
83
|
+
|
|
84
|
+
dc.subscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
|
|
85
|
+
dc.subscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._onAppClosing = this._onAppClosing.bind(this)
|
|
95
89
|
dc.subscribe('datadog:telemetry:app-closing', this._onAppClosing)
|
|
96
90
|
}
|
|
97
91
|
}
|
|
@@ -106,6 +100,8 @@ class SSIHeuristics {
|
|
|
106
100
|
})
|
|
107
101
|
break
|
|
108
102
|
default:
|
|
103
|
+
// injection hardening: only usage is internal, one call site with
|
|
104
|
+
// a function and another with undefined, so we can throw here.
|
|
109
105
|
throw new TypeError('callback must be a function or undefined')
|
|
110
106
|
}
|
|
111
107
|
}
|
|
@@ -152,7 +148,7 @@ class SSIHeuristics {
|
|
|
152
148
|
|
|
153
149
|
const tags = [
|
|
154
150
|
'installation:ssi',
|
|
155
|
-
`enablement_choice:${
|
|
151
|
+
`enablement_choice:${this.enablementChoice}`,
|
|
156
152
|
`has_sent_profiles:${this.hasSentProfiles}`,
|
|
157
153
|
`heuristic_hypothetical_decision:${decision.join('_')}`
|
|
158
154
|
]
|
|
@@ -163,9 +159,9 @@ class SSIHeuristics {
|
|
|
163
159
|
if (
|
|
164
160
|
!this._emittedRuntimeId &&
|
|
165
161
|
decision[0] === 'triggered' &&
|
|
166
|
-
// When
|
|
162
|
+
// When heuristics are active, hasSentProfiles can transition from false to true when the
|
|
167
163
|
// profiler gets started and the first profile is submitted, so we have to wait for it.
|
|
168
|
-
(this.
|
|
164
|
+
(!this.heuristicsActive || this.hasSentProfiles)
|
|
169
165
|
) {
|
|
170
166
|
// Tags won't change anymore, so we can emit the runtime ID metric now.
|
|
171
167
|
this._emittedRuntimeId = true
|
|
@@ -174,17 +170,20 @@ class SSIHeuristics {
|
|
|
174
170
|
}
|
|
175
171
|
|
|
176
172
|
_onAppClosing () {
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this._emittedRuntimeId
|
|
181
|
-
|
|
173
|
+
if (this.emitsTelemetry) {
|
|
174
|
+
this._ensureProfileMetrics()
|
|
175
|
+
// Last ditch effort to emit a runtime ID count metric
|
|
176
|
+
if (!this._emittedRuntimeId) {
|
|
177
|
+
this._emittedRuntimeId = true
|
|
178
|
+
this._runtimeIdCount.inc()
|
|
179
|
+
}
|
|
180
|
+
// So we have the metrics in the final state
|
|
181
|
+
this._profileCount.inc(0)
|
|
182
|
+
|
|
183
|
+
dc.unsubscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
|
|
184
|
+
dc.unsubscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
|
|
182
185
|
}
|
|
183
|
-
// So we have the metrics in the final state
|
|
184
|
-
this._profileCount.inc(0)
|
|
185
186
|
|
|
186
|
-
dc.unsubscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
|
|
187
|
-
dc.unsubscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
|
|
188
187
|
dc.unsubscribe('datadog:telemetry:app-closing', this._onAppClosing)
|
|
189
188
|
if (this.noSpan) {
|
|
190
189
|
dc.unsubscribe('dd-trace:span:start', this._onSpanCreated)
|
|
@@ -192,4 +191,4 @@ class SSIHeuristics {
|
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
module.exports = { SSIHeuristics
|
|
194
|
+
module.exports = { SSIHeuristics }
|
|
@@ -5,6 +5,7 @@ const Config = require('./config')
|
|
|
5
5
|
const runtimeMetrics = require('./runtime_metrics')
|
|
6
6
|
const log = require('./log')
|
|
7
7
|
const { setStartupLogPluginManager } = require('./startup-log')
|
|
8
|
+
const DynamicInstrumentation = require('./debugger')
|
|
8
9
|
const telemetry = require('./telemetry')
|
|
9
10
|
const nomenclature = require('./service-naming')
|
|
10
11
|
const PluginManager = require('./plugin_manager')
|
|
@@ -14,7 +15,6 @@ const dogstatsd = require('./dogstatsd')
|
|
|
14
15
|
const NoopDogStatsDClient = require('./noop/dogstatsd')
|
|
15
16
|
const spanleak = require('./spanleak')
|
|
16
17
|
const { SSIHeuristics } = require('./profiling/ssi-heuristics')
|
|
17
|
-
const telemetryLog = require('dc-polyfill').channel('datadog:telemetry:log')
|
|
18
18
|
const appsecStandalone = require('./appsec/standalone')
|
|
19
19
|
|
|
20
20
|
class LazyModule {
|
|
@@ -84,7 +84,7 @@ class Tracer extends NoopProxy {
|
|
|
84
84
|
if (config.remoteConfig.enabled && !config.isCiVisibility) {
|
|
85
85
|
const rc = remoteConfig.enable(config, this._modules.appsec)
|
|
86
86
|
|
|
87
|
-
rc.
|
|
87
|
+
rc.setProductHandler('APM_TRACING', (action, conf) => {
|
|
88
88
|
if (action === 'unapply') {
|
|
89
89
|
config.configure({}, true)
|
|
90
90
|
} else {
|
|
@@ -93,7 +93,7 @@ class Tracer extends NoopProxy {
|
|
|
93
93
|
this._enableOrDisableTracing(config)
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
rc.
|
|
96
|
+
rc.setProductHandler('AGENT_CONFIG', (action, conf) => {
|
|
97
97
|
if (!conf?.name?.startsWith('flare-log-level.')) return
|
|
98
98
|
|
|
99
99
|
if (action === 'unapply') {
|
|
@@ -104,37 +104,49 @@ class Tracer extends NoopProxy {
|
|
|
104
104
|
}
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
rc.
|
|
107
|
+
rc.setProductHandler('AGENT_TASK', (action, conf) => {
|
|
108
108
|
if (action === 'unapply' || !conf) return
|
|
109
109
|
if (conf.task_type !== 'tracer_flare' || !conf.args) return
|
|
110
110
|
|
|
111
111
|
this._flare.enable(config)
|
|
112
112
|
this._flare.module.send(conf.args)
|
|
113
113
|
})
|
|
114
|
+
|
|
115
|
+
if (config.dynamicInstrumentationEnabled) {
|
|
116
|
+
DynamicInstrumentation.start(config, rc)
|
|
117
|
+
}
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
if (config.isGCPFunction || config.isAzureFunction) {
|
|
117
121
|
require('./serverless').maybeStartServerlessMiniAgent(config)
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if (config.profiling.enabled !== 'false') {
|
|
125
|
+
const ssiHeuristics = new SSIHeuristics(config)
|
|
126
|
+
ssiHeuristics.start()
|
|
127
|
+
let mockProfiler = null
|
|
128
|
+
if (config.profiling.enabled === 'true') {
|
|
129
|
+
this._profilerStarted = this._startProfiler(config)
|
|
130
|
+
} else if (ssiHeuristics.emitsTelemetry) {
|
|
131
|
+
// Start a mock profiler that emits mock profile-submitted events for the telemetry.
|
|
132
|
+
// It will be stopped if the real profiler is started by the heuristics.
|
|
133
|
+
mockProfiler = require('./profiling/ssi-telemetry-mock-profiler')
|
|
134
|
+
mockProfiler.start(config)
|
|
135
|
+
}
|
|
127
136
|
|
|
128
|
-
if (
|
|
137
|
+
if (ssiHeuristics.heuristicsActive) {
|
|
129
138
|
ssiHeuristics.onTriggered(() => {
|
|
130
|
-
mockProfiler
|
|
139
|
+
if (mockProfiler) {
|
|
140
|
+
mockProfiler.stop()
|
|
141
|
+
}
|
|
131
142
|
this._startProfiler(config)
|
|
132
|
-
ssiHeuristics.onTriggered()
|
|
143
|
+
ssiHeuristics.onTriggered() // deregister this callback
|
|
133
144
|
})
|
|
134
145
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
|
|
147
|
+
if (!this._profilerStarted) {
|
|
148
|
+
this._profilerStarted = Promise.resolve(false)
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
if (config.runtimeMetrics) {
|
|
@@ -163,13 +175,6 @@ class Tracer extends NoopProxy {
|
|
|
163
175
|
return require('./profiler').start(config)
|
|
164
176
|
} catch (e) {
|
|
165
177
|
log.error(e)
|
|
166
|
-
if (telemetryLog.hasSubscribers) {
|
|
167
|
-
telemetryLog.publish({
|
|
168
|
-
message: e.message,
|
|
169
|
-
level: 'ERROR',
|
|
170
|
-
stack_trace: e.stack
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
|
|
@@ -196,12 +201,14 @@ class Tracer extends NoopProxy {
|
|
|
196
201
|
if (this._tracingInitialized) {
|
|
197
202
|
this._tracer.configure(config)
|
|
198
203
|
this._pluginManager.configure(config)
|
|
204
|
+
DynamicInstrumentation.configure(config)
|
|
199
205
|
setStartupLogPluginManager(this._pluginManager)
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
profilerStarted () {
|
|
204
210
|
if (!this._profilerStarted) {
|
|
211
|
+
// injection hardening: this is only ever invoked from tests.
|
|
205
212
|
throw new Error('profilerStarted() must be called after init()')
|
|
206
213
|
}
|
|
207
214
|
return this._profilerStarted
|
|
@@ -127,7 +127,8 @@ class SpanStatsProcessor {
|
|
|
127
127
|
url,
|
|
128
128
|
env,
|
|
129
129
|
tags,
|
|
130
|
-
appsec
|
|
130
|
+
appsec,
|
|
131
|
+
version
|
|
131
132
|
} = {}) {
|
|
132
133
|
this.exporter = new SpanStatsExporter({
|
|
133
134
|
hostname,
|
|
@@ -143,6 +144,7 @@ class SpanStatsProcessor {
|
|
|
143
144
|
this.env = env
|
|
144
145
|
this.tags = tags || {}
|
|
145
146
|
this.sequence = 0
|
|
147
|
+
this.version = version
|
|
146
148
|
|
|
147
149
|
if (this.enabled) {
|
|
148
150
|
this.timer = setInterval(this.onInterval.bind(this), interval * 1e3)
|
|
@@ -157,7 +159,7 @@ class SpanStatsProcessor {
|
|
|
157
159
|
this.exporter.export({
|
|
158
160
|
Hostname: this.hostname,
|
|
159
161
|
Env: this.env,
|
|
160
|
-
Version: version,
|
|
162
|
+
Version: this.version || version,
|
|
161
163
|
Stats: serialized,
|
|
162
164
|
Lang: 'javascript',
|
|
163
165
|
TracerVersion: pkg.version,
|