dd-trace 4.20.0 → 4.22.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 (76) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +29 -0
  3. package/package.json +8 -7
  4. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  5. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  7. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  9. package/packages/datadog-instrumentations/src/http/client.js +10 -0
  10. package/packages/datadog-instrumentations/src/jest.js +11 -5
  11. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  12. package/packages/datadog-instrumentations/src/next.js +18 -6
  13. package/packages/datadog-instrumentations/src/restify.js +1 -1
  14. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  15. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  16. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  17. package/packages/datadog-plugin-http/src/client.js +19 -2
  18. package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
  19. package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
  20. package/packages/datadog-plugin-next/src/index.js +40 -14
  21. package/packages/dd-trace/src/appsec/activation.js +29 -0
  22. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  23. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  24. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  25. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  26. package/packages/dd-trace/src/appsec/channels.js +4 -1
  27. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  29. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  31. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  38. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  39. package/packages/dd-trace/src/appsec/index.js +32 -31
  40. package/packages/dd-trace/src/appsec/recommended.json +1395 -2
  41. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  43. package/packages/dd-trace/src/appsec/reporter.js +19 -0
  44. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  45. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  46. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  48. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  49. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
  50. package/packages/dd-trace/src/config.js +36 -2
  51. package/packages/dd-trace/src/datastreams/processor.js +107 -12
  52. package/packages/dd-trace/src/noop/proxy.js +4 -0
  53. package/packages/dd-trace/src/opentracing/span.js +2 -0
  54. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  55. package/packages/dd-trace/src/plugins/index.js +1 -0
  56. package/packages/dd-trace/src/plugins/util/git.js +2 -2
  57. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  58. package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
  59. package/packages/dd-trace/src/profiler.js +5 -3
  60. package/packages/dd-trace/src/profiling/config.js +8 -0
  61. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  62. package/packages/dd-trace/src/profiling/profilers/events.js +181 -83
  63. package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
  64. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  65. package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
  66. package/packages/dd-trace/src/proxy.js +25 -1
  67. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  69. package/packages/dd-trace/src/spanleak.js +98 -0
  70. package/packages/dd-trace/src/startup-log.js +7 -1
  71. package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
  72. package/packages/dd-trace/src/telemetry/index.js +135 -43
  73. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  74. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  75. package/packages/dd-trace/src/tracer.js +4 -0
  76. package/scripts/install_plugin_modules.js +11 -3
@@ -1,5 +1,5 @@
1
1
  const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
2
- const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
2
+ const { END_TIMESTAMP_LABEL } = require('./shared')
3
3
  const semver = require('semver')
4
4
  const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
5
5
  const pprof = require('@datadog/pprof/')
@@ -14,7 +14,137 @@ const MS_TO_NS = 1000000
14
14
  // perf_hooks events, the emitted pprof file uses the type "timeline".
15
15
  const pprofValueType = 'timeline'
16
16
  const pprofValueUnit = 'nanoseconds'
17
- const threadName = `${threadNamePrefix} GC`
17
+
18
+ function labelFromStr (stringTable, key, valStr) {
19
+ return new Label({ key, str: stringTable.dedup(valStr) })
20
+ }
21
+
22
+ function labelFromStrStr (stringTable, keyStr, valStr) {
23
+ return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
24
+ }
25
+
26
+ class GCDecorator {
27
+ constructor (stringTable) {
28
+ this.stringTable = stringTable
29
+ this.reasonLabelKey = stringTable.dedup('gc reason')
30
+ this.kindLabels = []
31
+ this.reasonLabels = []
32
+ this.flagObj = {}
33
+
34
+ const kindLabelKey = stringTable.dedup('gc type')
35
+
36
+ // Create labels for all GC performance flags and kinds of GC
37
+ for (const [key, value] of Object.entries(constants)) {
38
+ if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
39
+ this.flagObj[key.substring(26).toLowerCase()] = value
40
+ } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
41
+ // It's a constant for a kind of GC
42
+ const kind = key.substring(20).toLowerCase()
43
+ this.kindLabels[value] = labelFromStr(stringTable, kindLabelKey, kind)
44
+ }
45
+ }
46
+ }
47
+
48
+ decorateSample (sampleInput, item) {
49
+ const { kind, flags } = node16 ? item.detail : item
50
+ sampleInput.label.push(this.kindLabels[kind])
51
+ const reasonLabel = this.getReasonLabel(flags)
52
+ if (reasonLabel) {
53
+ sampleInput.label.push(reasonLabel)
54
+ }
55
+ }
56
+
57
+ getReasonLabel (flags) {
58
+ if (flags === 0) {
59
+ return null
60
+ }
61
+ let reasonLabel = this.reasonLabels[flags]
62
+ if (!reasonLabel) {
63
+ const reasons = []
64
+ for (const [key, value] of Object.entries(this.flagObj)) {
65
+ if (value & flags) {
66
+ reasons.push(key)
67
+ }
68
+ }
69
+ const reasonStr = reasons.join(',')
70
+ reasonLabel = labelFromStr(this.stringTable, this.reasonLabelKey, reasonStr)
71
+ this.reasonLabels[flags] = reasonLabel
72
+ }
73
+ return reasonLabel
74
+ }
75
+ }
76
+
77
+ class DNSDecorator {
78
+ constructor (stringTable) {
79
+ this.stringTable = stringTable
80
+ this.operationNameLabelKey = stringTable.dedup('operation')
81
+ this.hostLabelKey = stringTable.dedup('host')
82
+ this.addressLabelKey = stringTable.dedup('address')
83
+ this.portLabelKey = stringTable.dedup('port')
84
+ }
85
+
86
+ decorateSample (sampleInput, item) {
87
+ const labels = sampleInput.label
88
+ const stringTable = this.stringTable
89
+ function addLabel (labelNameKey, labelValue) {
90
+ labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
91
+ }
92
+ const op = item.name
93
+ addLabel(this.operationNameLabelKey, item.name)
94
+ const detail = item.detail
95
+ switch (op) {
96
+ case 'lookup':
97
+ addLabel(this.hostLabelKey, detail.hostname)
98
+ break
99
+ case 'lookupService':
100
+ addLabel(this.addressLabelKey, detail.host)
101
+ labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
102
+ break
103
+ case 'getHostByAddr':
104
+ addLabel(this.addressLabelKey, detail.host)
105
+ break
106
+ default:
107
+ if (op.startsWith('query')) {
108
+ addLabel(this.hostLabelKey, detail.host)
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ class NetDecorator {
115
+ constructor (stringTable) {
116
+ this.stringTable = stringTable
117
+ this.operationNameLabelKey = stringTable.dedup('operation')
118
+ this.hostLabelKey = stringTable.dedup('host')
119
+ this.portLabelKey = stringTable.dedup('port')
120
+ }
121
+
122
+ decorateSample (sampleInput, item) {
123
+ const labels = sampleInput.label
124
+ const stringTable = this.stringTable
125
+ function addLabel (labelNameKey, labelValue) {
126
+ labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
127
+ }
128
+ const op = item.name
129
+ addLabel(this.operationNameLabelKey, op)
130
+ if (op === 'connect') {
131
+ const detail = item.detail
132
+ addLabel(this.hostLabelKey, detail.host)
133
+ labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
134
+ }
135
+ }
136
+ }
137
+
138
+ // Keys correspond to PerformanceEntry.entryType, values are constructor
139
+ // functions for type-specific decorators.
140
+ const decoratorTypes = {
141
+ gc: GCDecorator
142
+ }
143
+ // Needs at least node 16 for DNS and Net
144
+ if (node16) {
145
+ decoratorTypes.dns = DNSDecorator
146
+ decoratorTypes.net = NetDecorator
147
+ }
18
148
 
19
149
  /**
20
150
  * This class generates pprof files with timeline events sourced from Node.js
@@ -35,8 +165,7 @@ class EventsProfiler {
35
165
  if (!this._observer) {
36
166
  this._observer = new PerformanceObserver(add.bind(this))
37
167
  }
38
- // Currently only support GC
39
- this._observer.observe({ entryTypes: ['gc'] })
168
+ this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
40
169
  }
41
170
 
42
171
  stop () {
@@ -45,100 +174,69 @@ class EventsProfiler {
45
174
  }
46
175
  }
47
176
 
48
- profile () {
177
+ profile (startDate, endDate) {
49
178
  if (this.entries.length === 0) {
50
179
  // No events in the period; don't produce a profile
51
180
  return null
52
181
  }
53
182
 
54
183
  const stringTable = new StringTable()
55
- const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
56
- const kindLabelKey = stringTable.dedup('gc type')
57
- const reasonLabelKey = stringTable.dedup('gc reason')
58
- const kindLabels = []
59
- const reasonLabels = []
60
184
  const locations = []
61
185
  const functions = []
62
- const locationsPerKind = []
63
- const flagObj = {}
64
-
65
- function labelFromStr (key, valStr) {
66
- return new Label({ key, str: stringTable.dedup(valStr) })
67
- }
68
-
69
- function labelFromStrStr (keyStr, valStr) {
70
- return labelFromStr(stringTable.dedup(keyStr), valStr)
71
- }
72
-
73
- // Create labels for all GC performance flags and kinds of GC
74
- for (const [key, value] of Object.entries(constants)) {
75
- if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
76
- flagObj[key.substring(26).toLowerCase()] = value
77
- } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
78
- // It's a constant for a kind of GC
79
- const kind = key.substring(20).toLowerCase()
80
- kindLabels[value] = labelFromStr(kindLabelKey, kind)
81
- // Construct a single-frame "location" too
82
- const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
83
- functions.push(fn)
84
- const line = new Line({ functionId: fn.id })
85
- const location = new Location({ id: locations.length + 1, line: [line] })
86
- locations.push(location)
87
- locationsPerKind[value] = [location.id]
88
- }
89
- }
90
186
 
91
- const gcEventLabel = labelFromStrStr('event', 'gc')
92
- const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
187
+ // A synthetic single-frame location to serve as the location for timeline
188
+ // samples. We need these as the profiling backend (mimicking official pprof
189
+ // tool's behavior) ignores these.
190
+ const locationId = (() => {
191
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
192
+ functions.push(fn)
193
+ const line = new Line({ functionId: fn.id })
194
+ const location = new Location({ id: locations.length + 1, line: [line] })
195
+ locations.push(location)
196
+ return [location.id]
197
+ })()
93
198
 
94
- function getReasonLabel (flags) {
95
- if (flags === 0) {
96
- return null
97
- }
98
- let reasonLabel = reasonLabels[flags]
99
- if (!reasonLabel) {
100
- const reasons = []
101
- for (const [key, value] of Object.entries(flagObj)) {
102
- if (value & flags) {
103
- reasons.push(key)
104
- }
105
- }
106
- const reasonStr = reasons.join(',')
107
- reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
108
- reasonLabels[flags] = reasonLabel
109
- }
110
- return reasonLabel
199
+ const decorators = {}
200
+ for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
201
+ const decorator = new DecoratorCtor(stringTable)
202
+ decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
203
+ decorators[eventType] = decorator
111
204
  }
205
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
112
206
 
113
- let durationFrom = Number.POSITIVE_INFINITY
114
- let durationTo = 0
115
207
  const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
116
-
208
+ const lateEntries = []
209
+ const perfEndDate = endDate.getTime() - performance.timeOrigin
117
210
  const samples = this.entries.map((item) => {
211
+ const decorator = decorators[item.entryType]
212
+ if (!decorator) {
213
+ // Shouldn't happen but it's better to not rely on observer only getting
214
+ // requested event types.
215
+ return null
216
+ }
118
217
  const { startTime, duration } = item
119
- const { kind, flags } = node16 ? item.detail : item
120
- const endTime = startTime + duration
121
- if (durationFrom > startTime) durationFrom = startTime
122
- if (durationTo < endTime) durationTo = endTime
123
- const labels = [
124
- gcEventLabel,
125
- threadLabel,
126
- new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
127
- kindLabels[kind]
128
- ]
129
- const reasonLabel = getReasonLabel(flags)
130
- if (reasonLabel) {
131
- labels.push(reasonLabel)
218
+ if (startTime >= perfEndDate) {
219
+ // An event past the current recording end date; save it for the next
220
+ // profile. Not supposed to happen as long as there's no async activity
221
+ // between capture of the endDate value in profiler.js _collect() and
222
+ // here, but better be safe than sorry.
223
+ lateEntries.push(item)
224
+ return null
132
225
  }
133
- const sample = new Sample({
226
+ const endTime = startTime + duration
227
+ const sampleInput = {
134
228
  value: [Math.round(duration * MS_TO_NS)],
135
- label: labels,
136
- locationId: locationsPerKind[kind]
137
- })
138
- return sample
139
- })
229
+ locationId,
230
+ label: [
231
+ decorator.eventTypeLabel,
232
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
233
+ ]
234
+ }
235
+ decorator.decorateSample(sampleInput, item)
236
+ return new Sample(sampleInput)
237
+ }).filter(v => v)
140
238
 
141
- this.entries = []
239
+ this.entries = lateEntries
142
240
 
143
241
  const timeValueType = new ValueType({
144
242
  type: stringTable.dedup(pprofValueType),
@@ -147,10 +245,10 @@ class EventsProfiler {
147
245
 
148
246
  return new Profile({
149
247
  sampleType: [timeValueType],
150
- timeNanos: dateOffset + BigInt(Math.round(durationFrom * MS_TO_NS)),
248
+ timeNanos: endDate.getTime() * MS_TO_NS,
151
249
  periodType: timeValueType,
152
- period: this._flushIntervalNanos,
153
- durationNanos: Math.max(0, Math.round((durationTo - durationFrom) * MS_TO_NS)),
250
+ period: 1,
251
+ durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
154
252
  sample: samples,
155
253
  location: locations,
156
254
  function: functions,
@@ -2,8 +2,38 @@
2
2
 
3
3
  const { isMainThread, threadId } = require('node:worker_threads')
4
4
 
5
+ const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
6
+ const THREAD_NAME_LABEL = 'thread name'
7
+ const OS_THREAD_ID_LABEL = 'os thread id'
8
+ const THREAD_ID_LABEL = 'thread id'
9
+ const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
10
+ const eventLoopThreadName = `${threadNamePrefix} Event Loop`
11
+
12
+ function getThreadLabels () {
13
+ const pprof = require('@datadog/pprof')
14
+ const nativeThreadId = pprof.getNativeThreadId()
15
+ return {
16
+ [THREAD_NAME_LABEL]: eventLoopThreadName,
17
+ [THREAD_ID_LABEL]: `${threadId}`,
18
+ [OS_THREAD_ID_LABEL]: `${nativeThreadId}`
19
+ }
20
+ }
21
+
22
+ function cacheThreadLabels () {
23
+ let labels
24
+ return () => {
25
+ if (!labels) {
26
+ labels = getThreadLabels()
27
+ }
28
+ return labels
29
+ }
30
+ }
31
+
5
32
  module.exports = {
6
- END_TIMESTAMP: 'end_timestamp_ns',
7
- THREAD_NAME: 'thread name',
8
- threadNamePrefix: isMainThread ? 'Main' : `Worker #${threadId}`
33
+ END_TIMESTAMP_LABEL,
34
+ THREAD_NAME_LABEL,
35
+ THREAD_ID_LABEL,
36
+ threadNamePrefix,
37
+ eventLoopThreadName,
38
+ getThreadLabels: cacheThreadLabels()
9
39
  }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { oomExportStrategies } = require('../constants')
4
+ const { getThreadLabels } = require('./shared')
4
5
 
5
6
  function strategiesToCallbackMode (strategies, callbackMode) {
6
7
  return strategies.includes(oomExportStrategies.ASYNC_CALLBACK) ? callbackMode.Async : 0
@@ -33,7 +34,7 @@ class NativeSpaceProfiler {
33
34
  }
34
35
 
35
36
  profile () {
36
- return this._pprof.heap.profile(undefined, this._mapper)
37
+ return this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
37
38
  }
38
39
 
39
40
  encode (profile) {
@@ -7,13 +7,12 @@ 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 { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
10
+ const { END_TIMESTAMP_LABEL, getThreadLabels } = require('./shared')
11
11
 
12
12
  const beforeCh = dc.channel('dd-trace:storage:before')
13
13
  const enterCh = dc.channel('dd-trace:storage:enter')
14
14
  const spanFinishCh = dc.channel('dd-trace:span:finish')
15
15
  const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
16
- const threadName = `${threadNamePrefix} Event Loop`
17
16
 
18
17
  const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
19
18
 
@@ -96,12 +95,9 @@ class NativeWallProfiler {
96
95
  this._enter = this._enter.bind(this)
97
96
  this._spanFinished = this._spanFinished.bind(this)
98
97
  }
99
- this._generateLabels = this._generateLabels.bind(this)
100
- } else {
101
- // Explicitly assigning, to express the intent that this is meant to be
102
- // undefined when passed to pprof.time.stop() when not using sample contexts.
103
- this._generateLabels = undefined
104
98
  }
99
+ this._generateLabels = this._generateLabels.bind(this)
100
+
105
101
  this._logger = options.logger
106
102
  this._started = false
107
103
  }
@@ -239,12 +235,21 @@ class NativeWallProfiler {
239
235
  return profile
240
236
  }
241
237
 
242
- _generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
243
- const labels = this._timelineEnabled ? {
244
- [THREAD_NAME]: threadName,
238
+ _generateLabels (context) {
239
+ if (context == null) {
240
+ // generateLabels is also called for samples without context.
241
+ // In that case just return thread labels.
242
+ return getThreadLabels()
243
+ }
244
+
245
+ const labels = { ...getThreadLabels() }
246
+
247
+ const { context: { spanId, rootSpanId, webTags, endpoint }, timestamp } = context
248
+
249
+ if (this._timelineEnabled) {
245
250
  // Incoming timestamps are in microseconds, we emit nanos.
246
- [END_TIMESTAMP]: timestamp * 1000n
247
- } : {}
251
+ labels[END_TIMESTAMP_LABEL] = timestamp * 1000n
252
+ }
248
253
 
249
254
  if (spanId) {
250
255
  labels['span id'] = spanId
@@ -10,6 +10,7 @@ const PluginManager = require('./plugin_manager')
10
10
  const remoteConfig = require('./appsec/remote_config')
11
11
  const AppsecSdk = require('./appsec/sdk')
12
12
  const dogstatsd = require('./dogstatsd')
13
+ const spanleak = require('./spanleak')
13
14
 
14
15
  class Tracer extends NoopProxy {
15
16
  constructor () {
@@ -35,6 +36,19 @@ class Tracer extends NoopProxy {
35
36
  setInterval(() => {
36
37
  this.dogstatsd.flush()
37
38
  }, 10 * 1000).unref()
39
+
40
+ process.once('beforeExit', () => {
41
+ this.dogstatsd.flush()
42
+ })
43
+ }
44
+
45
+ if (config.spanLeakDebug > 0) {
46
+ if (config.spanLeakDebug === spanleak.MODES.LOG) {
47
+ spanleak.enableLogging()
48
+ } else if (config.spanLeakDebug === spanleak.MODES.GC_AND_LOG) {
49
+ spanleak.enableGarbageCollection()
50
+ }
51
+ spanleak.startScrubber()
38
52
  }
39
53
 
40
54
  if (config.remoteConfig.enabled && !config.isCiVisibility) {
@@ -62,11 +76,14 @@ class Tracer extends NoopProxy {
62
76
  // do not stop tracer initialization if the profiler fails to be imported
63
77
  try {
64
78
  const profiler = require('./profiler')
65
- profiler.start(config)
79
+ this._profilerStarted = profiler.start(config)
66
80
  } catch (e) {
67
81
  log.error(e)
68
82
  }
69
83
  }
84
+ if (!this._profilerStarted) {
85
+ this._profilerStarted = Promise.resolve(false)
86
+ }
70
87
 
71
88
  if (config.runtimeMetrics) {
72
89
  runtimeMetrics.start(config)
@@ -104,6 +121,13 @@ class Tracer extends NoopProxy {
104
121
  return this
105
122
  }
106
123
 
124
+ profilerStarted () {
125
+ if (!this._profilerStarted) {
126
+ throw new Error('profilerStarted() must be called after init()')
127
+ }
128
+ return this._profilerStarted
129
+ }
130
+
107
131
  use () {
108
132
  this._pluginManager.configurePlugin(...arguments)
109
133
  return this
@@ -37,6 +37,11 @@ const redisConfig = {
37
37
 
38
38
  const storage = {
39
39
  client: {
40
+ aerospike: {
41
+ opName: () => 'aerospike.command',
42
+ serviceName: ({ tracerService, pluginConfig }) =>
43
+ pluginConfig.service || `${tracerService}-aerospike`
44
+ },
40
45
  'cassandra-driver': {
41
46
  opName: () => 'cassandra.query',
42
47
  serviceName: ({ tracerService, pluginConfig, system }) =>
@@ -22,6 +22,10 @@ function withFunction ({ tracerService, pluginConfig, params }) {
22
22
 
23
23
  const storage = {
24
24
  client: {
25
+ aerospike: {
26
+ opName: () => 'aerospike.command',
27
+ serviceName: configWithFallback
28
+ },
25
29
  'cassandra-driver': {
26
30
  opName: () => 'cassandra.query',
27
31
  serviceName: configWithFallback
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ const SortedSet = require('tlhunter-sorted-set')
6
+
7
+ const INTERVAL = 1000 // look for expired spans every 1s
8
+ const LIFETIME = 60 * 1000 // all spans have a max lifetime of 1m
9
+
10
+ const MODES = {
11
+ DISABLED: 0,
12
+ // METRICS_ONLY
13
+ LOG: 1,
14
+ GC_AND_LOG: 2
15
+ // GC
16
+ }
17
+
18
+ module.exports.MODES = MODES
19
+
20
+ const spans = new SortedSet()
21
+
22
+ // TODO: should these also be delivered as runtime metrics?
23
+
24
+ // const registry = new FinalizationRegistry(name => {
25
+ // spans.del(span) // there is no span
26
+ // })
27
+
28
+ let interval
29
+ let mode = MODES.DISABLED
30
+
31
+ module.exports.disable = function () {
32
+ mode = MODES.DISABLED
33
+ }
34
+
35
+ module.exports.enableLogging = function () {
36
+ mode = MODES.LOG
37
+ }
38
+
39
+ module.exports.enableGarbageCollection = function () {
40
+ mode = MODES.GC_AND_LOG
41
+ }
42
+
43
+ module.exports.startScrubber = function () {
44
+ if (!isEnabled()) return
45
+
46
+ interval = setInterval(() => {
47
+ const now = Date.now()
48
+ const expired = spans.rangeByScore(0, now)
49
+
50
+ if (!expired.length) return
51
+
52
+ const gc = isGarbageCollecting()
53
+
54
+ const expirationsByType = Object.create(null) // { [spanType]: count }
55
+
56
+ for (const wrapped of expired) {
57
+ spans.del(wrapped)
58
+ const span = wrapped.deref()
59
+
60
+ if (!span) continue // span has already been garbage collected
61
+
62
+ // TODO: Should we also do things like record the route to help users debug leaks?
63
+ if (!expirationsByType[span._name]) expirationsByType[span._name] = 0
64
+ expirationsByType[span._name]++
65
+
66
+ if (!gc) continue // everything after this point is related to manual GC
67
+
68
+ // TODO: what else can we do to alleviate memory usage
69
+ span.context()._tags = Object.create(null)
70
+ }
71
+
72
+ console.log('expired spans:' +
73
+ Object.keys(expirationsByType).reduce((a, c) => `${a} ${c}: ${expirationsByType[c]}`, ''))
74
+ }, INTERVAL)
75
+ }
76
+
77
+ module.exports.stopScrubber = function () {
78
+ clearInterval(interval)
79
+ }
80
+
81
+ module.exports.addSpan = function (span) {
82
+ if (!isEnabled()) return
83
+
84
+ const now = Date.now()
85
+ const expiration = now + LIFETIME
86
+ // eslint-disable-next-line no-undef
87
+ const wrapped = new WeakRef(span)
88
+ spans.add(wrapped, expiration)
89
+ // registry.register(span, span._name)
90
+ }
91
+
92
+ function isEnabled () {
93
+ return mode > MODES.DISABLED
94
+ }
95
+
96
+ function isGarbageCollecting () {
97
+ return mode >= MODES.GC_AND_LOG
98
+ }
@@ -6,6 +6,7 @@ const os = require('os')
6
6
  const { inspect } = require('util')
7
7
  const tracerVersion = require('../../../package.json').version
8
8
 
9
+ const errors = {}
9
10
  let config
10
11
  let pluginManager
11
12
  let samplingRules = []
@@ -89,6 +90,10 @@ function startupLog ({ agentError } = {}) {
89
90
  info('DATADOG TRACER CONFIGURATION - ' + out)
90
91
  if (agentError) {
91
92
  warn('DATADOG TRACER DIAGNOSTIC - Agent Error: ' + agentError.message)
93
+ errors.agentError = {
94
+ code: agentError.code ? agentError.code : '',
95
+ message: `Agent Error:${agentError.message}`
96
+ }
92
97
  }
93
98
 
94
99
  config = undefined
@@ -112,5 +117,6 @@ module.exports = {
112
117
  startupLog,
113
118
  setStartupLogConfig,
114
119
  setStartupLogPluginManager,
115
- setSamplingRules
120
+ setSamplingRules,
121
+ errors
116
122
  }