dd-trace 4.18.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE-3rdparty.csv +2 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/package.json +6 -6
  8. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  9. package/packages/datadog-esbuild/index.js +1 -20
  10. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  12. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  13. package/packages/datadog-instrumentations/src/restify.js +14 -1
  14. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
  15. package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
  16. package/packages/dd-trace/src/appsec/channels.js +1 -1
  17. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  18. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  19. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  20. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  21. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  22. package/packages/dd-trace/src/appsec/recommended.json +272 -48
  23. package/packages/dd-trace/src/appsec/reporter.js +31 -34
  24. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +17 -7
  25. package/packages/dd-trace/src/config.js +6 -4
  26. package/packages/dd-trace/src/datastreams/processor.js +60 -15
  27. package/packages/dd-trace/src/format.js +6 -1
  28. package/packages/dd-trace/src/iitm.js +1 -1
  29. package/packages/dd-trace/src/log/channels.js +1 -1
  30. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  31. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  32. package/packages/dd-trace/src/opentracing/span.js +4 -0
  33. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  34. package/packages/dd-trace/src/plugin_manager.js +1 -1
  35. package/packages/dd-trace/src/plugins/database.js +1 -1
  36. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  37. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  38. package/packages/dd-trace/src/plugins/util/git.js +2 -1
  39. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  40. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  41. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
  42. package/packages/dd-trace/src/profiling/config.js +19 -2
  43. package/packages/dd-trace/src/profiling/profilers/events.js +161 -0
  44. package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
  45. package/packages/dd-trace/src/profiling/profilers/wall.js +61 -29
  46. package/packages/dd-trace/src/ritm.js +1 -1
  47. package/packages/dd-trace/src/span_processor.js +4 -0
  48. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  49. package/packages/dd-trace/src/telemetry/index.js +1 -1
  50. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  51. package/packages/dd-trace/src/tracer.js +4 -2
  52. package/packages/diagnostics_channel/index.js +0 -3
  53. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -0,0 +1,161 @@
1
+ const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
2
+ const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
3
+ const semver = require('semver')
4
+ const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
5
+ const pprof = require('@datadog/pprof/')
6
+
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
+ // perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
11
+ const MS_TO_NS = 1000000
12
+
13
+ // While this is an "events profiler", meaning it emits a pprof file based on events observed as
14
+ // perf_hooks events, the emitted pprof file uses the type "timeline".
15
+ const pprofValueType = 'timeline'
16
+ const pprofValueUnit = 'nanoseconds'
17
+ const threadName = `${threadNamePrefix} GC`
18
+
19
+ /**
20
+ * This class generates pprof files with timeline events sourced from Node.js
21
+ * performance measurement APIs.
22
+ */
23
+ class EventsProfiler {
24
+ constructor (options = {}) {
25
+ this.type = 'events'
26
+ this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
27
+ this._observer = undefined
28
+ this.entries = []
29
+ }
30
+
31
+ start () {
32
+ function add (items) {
33
+ this.entries.push(...items.getEntries())
34
+ }
35
+ if (!this._observer) {
36
+ this._observer = new PerformanceObserver(add.bind(this))
37
+ }
38
+ // Currently only support GC
39
+ this._observer.observe({ type: 'gc' })
40
+ }
41
+
42
+ stop () {
43
+ if (this._observer) {
44
+ this._observer.disconnect()
45
+ }
46
+ }
47
+
48
+ profile () {
49
+ const stringTable = new StringTable()
50
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
51
+ const kindLabelKey = stringTable.dedup('gc type')
52
+ const reasonLabelKey = stringTable.dedup('gc reason')
53
+ const kindLabels = []
54
+ const reasonLabels = []
55
+ const locations = []
56
+ const functions = []
57
+ const locationsPerKind = []
58
+ const flagObj = {}
59
+
60
+ function labelFromStr (key, valStr) {
61
+ return new Label({ key, str: stringTable.dedup(valStr) })
62
+ }
63
+
64
+ function labelFromStrStr (keyStr, valStr) {
65
+ return labelFromStr(stringTable.dedup(keyStr), valStr)
66
+ }
67
+
68
+ // Create labels for all GC performance flags and kinds of GC
69
+ for (const [key, value] of Object.entries(constants)) {
70
+ if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
71
+ flagObj[key.substring(26).toLowerCase()] = value
72
+ } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
73
+ // It's a constant for a kind of GC
74
+ const kind = key.substring(20).toLowerCase()
75
+ kindLabels[value] = labelFromStr(kindLabelKey, kind)
76
+ // Construct a single-frame "location" too
77
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
78
+ functions.push(fn)
79
+ const line = new Line({ functionId: fn.id })
80
+ const location = new Location({ id: locations.length + 1, line: [line] })
81
+ locations.push(location)
82
+ locationsPerKind[value] = [location.id]
83
+ }
84
+ }
85
+
86
+ const gcEventLabel = labelFromStrStr('event', 'gc')
87
+ const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
88
+
89
+ function getReasonLabel (flags) {
90
+ if (flags === 0) {
91
+ return null
92
+ }
93
+ let reasonLabel = reasonLabels[flags]
94
+ if (!reasonLabel) {
95
+ const reasons = []
96
+ for (const [key, value] of Object.entries(flagObj)) {
97
+ if (value & flags) {
98
+ reasons.push(key)
99
+ }
100
+ }
101
+ const reasonStr = reasons.join(',')
102
+ reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
103
+ reasonLabels[flags] = reasonLabel
104
+ }
105
+ return reasonLabel
106
+ }
107
+
108
+ let durationFrom = Number.POSITIVE_INFINITY
109
+ let durationTo = 0
110
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
111
+
112
+ const samples = this.entries.map((item) => {
113
+ const { startTime, duration } = item
114
+ const { kind, flags } = node16 ? item.detail : item
115
+ const endTime = startTime + duration
116
+ if (durationFrom > startTime) durationFrom = startTime
117
+ if (durationTo < endTime) durationTo = endTime
118
+ const labels = [
119
+ gcEventLabel,
120
+ threadLabel,
121
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
122
+ kindLabels[kind]
123
+ ]
124
+ const reasonLabel = getReasonLabel(flags)
125
+ if (reasonLabel) {
126
+ labels.push(reasonLabel)
127
+ }
128
+ const sample = new Sample({
129
+ value: [Math.round(duration * MS_TO_NS)],
130
+ label: labels,
131
+ locationId: locationsPerKind[kind]
132
+ })
133
+ return sample
134
+ })
135
+
136
+ this.entries = []
137
+
138
+ const timeValueType = new ValueType({
139
+ type: stringTable.dedup(pprofValueType),
140
+ unit: stringTable.dedup(pprofValueUnit)
141
+ })
142
+
143
+ return new Profile({
144
+ sampleType: [timeValueType],
145
+ timeNanos: dateOffset + BigInt(Math.round(durationFrom * MS_TO_NS)),
146
+ periodType: timeValueType,
147
+ period: this._flushIntervalNanos,
148
+ durationNanos: Math.max(0, Math.round((durationTo - durationFrom) * MS_TO_NS)),
149
+ sample: samples,
150
+ location: locations,
151
+ function: functions,
152
+ stringTable: stringTable
153
+ })
154
+ }
155
+
156
+ encode (profile) {
157
+ return pprof.encode(profile)
158
+ }
159
+ }
160
+
161
+ module.exports = EventsProfiler
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread, threadId } = require('node:worker_threads')
4
+
5
+ module.exports = {
6
+ END_TIMESTAMP: 'end_timestamp_ns',
7
+ THREAD_NAME: 'thread name',
8
+ threadNamePrefix: isMainThread ? 'Main' : `Worker #${threadId}`
9
+ }
@@ -2,21 +2,20 @@
2
2
 
3
3
  const { storage } = require('../../../../datadog-core')
4
4
 
5
- const dc = require('../../../../diagnostics_channel')
5
+ const dc = require('dc-polyfill')
6
6
  const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
7
7
  const { WEB } = require('../../../../../ext/types')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const telemetryMetrics = require('../../telemetry/metrics')
10
+ const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
10
11
 
11
12
  const beforeCh = dc.channel('dd-trace:storage:before')
12
13
  const enterCh = dc.channel('dd-trace:storage:enter')
14
+ const spanFinishCh = dc.channel('dd-trace:span:finish')
13
15
  const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
16
+ const threadName = `${threadNamePrefix} Event Loop`
14
17
 
15
- const threadName = (function () {
16
- const { isMainThread, threadId } = require('node:worker_threads')
17
- const name = isMainThread ? 'Main' : `Worker #${threadId}`
18
- return `${name} Event Loop`
19
- })()
18
+ const CachedWebTags = Symbol('NativeWallProfiler.CachedWebTags')
20
19
 
21
20
  let kSampleCount
22
21
 
@@ -30,7 +29,11 @@ function getStartedSpans (context) {
30
29
  }
31
30
 
32
31
  function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
33
- const labels = { 'thread name': threadName }
32
+ const labels = {
33
+ [THREAD_NAME]: threadName,
34
+ // Incoming timestamps are in microseconds, we emit nanos.
35
+ [END_TIMESTAMP]: timestamp * 1000n
36
+ }
34
37
  if (spanId) {
35
38
  labels['span id'] = spanId
36
39
  }
@@ -43,16 +46,10 @@ function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, t
43
46
  // fallback to endpoint computed when sample was taken
44
47
  labels['trace endpoint'] = endpoint
45
48
  }
46
- // Incoming timestamps are in microseconds, we emit nanos.
47
- labels['end_timestamp_ns'] = timestamp * 1000n
48
49
 
49
50
  return labels
50
51
  }
51
52
 
52
- function getSpanContextTags (span) {
53
- return span.context()._tags
54
- }
55
-
56
53
  function isWebServerSpan (tags) {
57
54
  return tags[SPAN_TYPE] === WEB
58
55
  }
@@ -78,6 +75,7 @@ class NativeWallProfiler {
78
75
 
79
76
  // Bind to this so the same value can be used to unsubscribe later
80
77
  this._enter = this._enter.bind(this)
78
+ this._spanFinished = this._spanFinished.bind(this)
81
79
  this._logger = options.logger
82
80
  this._started = false
83
81
  }
@@ -120,10 +118,12 @@ class NativeWallProfiler {
120
118
  this._pprof.time.setContext(this._currentContext)
121
119
  this._lastSpan = undefined
122
120
  this._lastStartedSpans = undefined
121
+ this._lastWebTags = undefined
123
122
  this._lastSampleCount = 0
124
123
 
125
124
  beforeCh.subscribe(this._enter)
126
125
  enterCh.subscribe(this._enter)
126
+ spanFinishCh.subscribe(this._spanFinished)
127
127
  }
128
128
 
129
129
  this._started = true
@@ -144,11 +144,43 @@ class NativeWallProfiler {
144
144
 
145
145
  const span = getActiveSpan()
146
146
  if (span) {
147
+ const context = span.context()
147
148
  this._lastSpan = span
148
- this._lastStartedSpans = getStartedSpans(span.context())
149
+ const startedSpans = getStartedSpans(context)
150
+ this._lastStartedSpans = startedSpans
151
+ if (this._endpointCollectionEnabled) {
152
+ const cachedWebTags = span[CachedWebTags]
153
+ if (cachedWebTags === undefined) {
154
+ let found = false
155
+ // Find the first webspan starting from the end:
156
+ // There might be several webspans, for example with next.js, http plugin creates a first span
157
+ // and then next.js plugin creates a child span, and this child span has the correct endpoint information.
158
+ let nextSpanId = context._spanId
159
+ for (let i = startedSpans.length - 1; i >= 0; i--) {
160
+ const nextContext = startedSpans[i].context()
161
+ if (nextContext._spanId === nextSpanId) {
162
+ const tags = nextContext._tags
163
+ if (isWebServerSpan(tags)) {
164
+ this._lastWebTags = tags
165
+ span[CachedWebTags] = tags
166
+ found = true
167
+ break
168
+ }
169
+ nextSpanId = nextContext._parentId
170
+ }
171
+ }
172
+ if (!found) {
173
+ this._lastWebTags = undefined
174
+ span[CachedWebTags] = null // cache negative lookup result
175
+ }
176
+ } else {
177
+ this._lastWebTags = cachedWebTags
178
+ }
179
+ }
149
180
  } else {
150
181
  this._lastStartedSpans = undefined
151
182
  this._lastSpan = undefined
183
+ this._lastWebTags = undefined
152
184
  }
153
185
  }
154
186
 
@@ -163,21 +195,17 @@ class NativeWallProfiler {
163
195
  context.rootSpanId = rootSpan.context().toSpanId()
164
196
  }
165
197
  }
166
- if (this._endpointCollectionEnabled) {
167
- const startedSpans = this._lastStartedSpans
168
- // Find the first webspan starting from the end:
169
- // There might be several webspans, for example with next.js, http plugin creates a first span
170
- // and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
171
- for (let i = startedSpans.length - 1; i >= 0; i--) {
172
- const tags = getSpanContextTags(startedSpans[i])
173
- if (isWebServerSpan(tags)) {
174
- context.webTags = tags
175
- // endpoint may not be determined yet, but keep it as fallback
176
- // if tags are not available anymore during serialization
177
- context.endpoint = endpointNameFromTags(tags)
178
- break
179
- }
180
- }
198
+ if (this._lastWebTags) {
199
+ context.webTags = this._lastWebTags
200
+ // endpoint may not be determined yet, but keep it as fallback
201
+ // if tags are not available anymore during serialization
202
+ context.endpoint = endpointNameFromTags(this._lastWebTags)
203
+ }
204
+ }
205
+
206
+ _spanFinished (span) {
207
+ if (span[CachedWebTags]) {
208
+ span[CachedWebTags] = undefined
181
209
  }
182
210
  }
183
211
 
@@ -223,7 +251,11 @@ class NativeWallProfiler {
223
251
  if (this._withContexts) {
224
252
  beforeCh.unsubscribe(this._enter)
225
253
  enterCh.unsubscribe(this._enter)
254
+ spanFinishCh.unsubscribe(this._spanFinished)
226
255
  this._profilerState = undefined
256
+ this._lastSpan = undefined
257
+ this._lastStartedSpans = undefined
258
+ this._lastWebTags = undefined
227
259
  }
228
260
 
229
261
  this._started = false
@@ -3,7 +3,7 @@
3
3
  const path = require('path')
4
4
  const Module = require('module')
5
5
  const parse = require('module-details-from-path')
6
- const dc = require('../../diagnostics_channel')
6
+ const dc = require('dc-polyfill')
7
7
 
8
8
  const origRequire = Module.prototype.require
9
9
 
@@ -138,6 +138,10 @@ class SpanProcessor {
138
138
  }
139
139
  }
140
140
 
141
+ for (const span of trace.finished) {
142
+ span.context()._tags = {}
143
+ }
144
+
141
145
  trace.started = active
142
146
  trace.finished = []
143
147
  }
@@ -4,7 +4,7 @@ const path = require('path')
4
4
  const parse = require('module-details-from-path')
5
5
  const requirePackageJson = require('../require-package-json')
6
6
  const { sendData } = require('./send-data')
7
- const dc = require('../../../diagnostics_channel')
7
+ const dc = require('dc-polyfill')
8
8
  const { fileURLToPath } = require('url')
9
9
 
10
10
  const savedDependenciesToSend = new Set()
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const tracerVersion = require('../../../../package.json').version
4
- const dc = require('../../../diagnostics_channel')
4
+ const dc = require('dc-polyfill')
5
5
  const os = require('os')
6
6
  const dependencies = require('./dependencies')
7
7
  const { sendData } = require('./send-data')
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
  const logCollector = require('./log-collector')
5
5
  const { sendData } = require('../send-data')
6
6
 
@@ -31,8 +31,10 @@ class DatadogTracer extends Tracer {
31
31
 
32
32
  // todo[piochelepiotr] These two methods are not related to the tracer, but to data streams monitoring.
33
33
  // They should be moved outside of the tracer in the future.
34
- setCheckpoint (edgeTags) {
35
- const ctx = this._dataStreamsProcessor.setCheckpoint(edgeTags, DataStreamsContext.getDataStreamsContext())
34
+ setCheckpoint (edgeTags, span, payloadSize = 0) {
35
+ const ctx = this._dataStreamsProcessor.setCheckpoint(
36
+ edgeTags, span, DataStreamsContext.getDataStreamsContext(), payloadSize
37
+ )
36
38
  DataStreamsContext.setDataStreamsContext(ctx)
37
39
  return ctx
38
40
  }
@@ -1,3 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = require('./src')
@@ -1,121 +0,0 @@
1
- 'use strict'
2
-
3
- const {
4
- Channel,
5
- channel
6
- } = require('diagnostics_channel') // eslint-disable-line n/no-restricted-require
7
-
8
- const [major, minor] = process.versions.node.split('.')
9
- const channels = new WeakSet()
10
-
11
- // Our own DC with a limited subset of functionality stable across Node versions.
12
- // TODO: Move the rest of the polyfill here.
13
- // TODO: Switch to using global subscribe/unsubscribe/hasSubscribers.
14
- const dc = { channel }
15
-
16
- // Prevent going to 0 subscribers to avoid bug in Node.
17
- // See https://github.com/nodejs/node/pull/47520
18
- if (major === '19' && minor === '9') {
19
- dc.channel = function () {
20
- const ch = channel.apply(this, arguments)
21
-
22
- if (!channels.has(ch)) {
23
- const subscribe = ch.subscribe
24
- const unsubscribe = ch.unsubscribe
25
-
26
- ch.subscribe = function () {
27
- delete ch.subscribe
28
- delete ch.unsubscribe
29
-
30
- const result = subscribe.apply(this, arguments)
31
-
32
- this.subscribe(() => {}) // Keep it active forever.
33
-
34
- return result
35
- }
36
-
37
- if (ch.unsubscribe === Channel.prototype.unsubscribe) {
38
- // Needed because another subscriber could have subscribed to something
39
- // that we unsubscribe to before the library is loaded.
40
- ch.unsubscribe = function () {
41
- delete ch.subscribe
42
- delete ch.unsubscribe
43
-
44
- this.subscribe(() => {}) // Keep it active forever.
45
-
46
- return unsubscribe.apply(this, arguments)
47
- }
48
- }
49
-
50
- channels.add(ch)
51
- }
52
-
53
- return ch
54
- }
55
- }
56
-
57
- if (!Channel.prototype.runStores) {
58
- const ActiveChannelPrototype = getActiveChannelPrototype()
59
-
60
- Channel.prototype.bindStore = ActiveChannelPrototype.bindStore = function (store, transform) {
61
- if (!this._stores) {
62
- this._stores = new Map()
63
- }
64
- this._stores.set(store, transform)
65
- }
66
-
67
- Channel.prototype.unbindStore = ActiveChannelPrototype.unbindStore = function (store) {
68
- if (!this._stores) return
69
- this._stores.delete(store)
70
- }
71
-
72
- Channel.prototype.runStores = ActiveChannelPrototype.runStores = function (data, fn, thisArg, ...args) {
73
- if (!this._stores) return Reflect.apply(fn, thisArg, args)
74
-
75
- let run = () => {
76
- this.publish(data)
77
- return Reflect.apply(fn, thisArg, args)
78
- }
79
-
80
- for (const entry of this._stores.entries()) {
81
- const store = entry[0]
82
- const transform = entry[1]
83
- run = wrapStoreRun(store, data, run, transform)
84
- }
85
-
86
- return run()
87
- }
88
- }
89
-
90
- function defaultTransform (data) {
91
- return data
92
- }
93
-
94
- function wrapStoreRun (store, data, next, transform = defaultTransform) {
95
- return () => {
96
- let context
97
- try {
98
- context = transform(data)
99
- } catch (err) {
100
- process.nextTick(() => {
101
- throw err
102
- })
103
- return next()
104
- }
105
-
106
- return store.run(context, next)
107
- }
108
- }
109
-
110
- function getActiveChannelPrototype () {
111
- const dummyChannel = channel('foo')
112
- const listener = () => {}
113
-
114
- dummyChannel.subscribe(listener)
115
- const ActiveChannelPrototype = Object.getPrototypeOf(dummyChannel)
116
- dummyChannel.unsubscribe(listener)
117
-
118
- return ActiveChannelPrototype
119
- }
120
-
121
- module.exports = dc