dd-trace 4.18.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +3 -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/index.d.ts +29 -0
  8. package/package.json +11 -10
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  13. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  14. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  15. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  18. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  19. package/packages/datadog-instrumentations/src/http/client.js +10 -0
  20. package/packages/datadog-instrumentations/src/jest.js +11 -5
  21. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  22. package/packages/datadog-instrumentations/src/next.js +18 -6
  23. package/packages/datadog-instrumentations/src/restify.js +14 -1
  24. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  25. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  26. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  27. package/packages/datadog-plugin-http/src/client.js +19 -2
  28. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  30. package/packages/datadog-plugin-next/src/index.js +40 -14
  31. package/packages/dd-trace/src/appsec/activation.js +29 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  34. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  35. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  36. package/packages/dd-trace/src/appsec/channels.js +5 -2
  37. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  38. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  39. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  42. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  53. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  54. package/packages/dd-trace/src/appsec/index.js +33 -32
  55. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  56. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  57. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  58. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  59. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  60. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  61. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  62. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  63. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  64. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  65. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +22 -6
  66. package/packages/dd-trace/src/config.js +48 -7
  67. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  68. package/packages/dd-trace/src/format.js +6 -1
  69. package/packages/dd-trace/src/id.js +12 -0
  70. package/packages/dd-trace/src/iitm.js +1 -1
  71. package/packages/dd-trace/src/log/channels.js +1 -1
  72. package/packages/dd-trace/src/noop/proxy.js +4 -0
  73. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  74. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  75. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  76. package/packages/dd-trace/src/opentracing/span.js +6 -0
  77. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  78. package/packages/dd-trace/src/plugin_manager.js +1 -1
  79. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  80. package/packages/dd-trace/src/plugins/database.js +1 -1
  81. package/packages/dd-trace/src/plugins/index.js +1 -0
  82. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  83. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  84. package/packages/dd-trace/src/plugins/util/git.js +4 -3
  85. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  86. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  87. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  88. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  89. package/packages/dd-trace/src/profiler.js +5 -3
  90. package/packages/dd-trace/src/profiling/config.js +26 -2
  91. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  92. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  93. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  94. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  96. package/packages/dd-trace/src/proxy.js +25 -1
  97. package/packages/dd-trace/src/ritm.js +1 -1
  98. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  99. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  100. package/packages/dd-trace/src/span_processor.js +4 -0
  101. package/packages/dd-trace/src/spanleak.js +98 -0
  102. package/packages/dd-trace/src/startup-log.js +7 -1
  103. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  104. package/packages/dd-trace/src/telemetry/index.js +136 -44
  105. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  106. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  107. package/packages/dd-trace/src/tracer.js +8 -2
  108. package/scripts/install_plugin_modules.js +11 -3
  109. package/packages/diagnostics_channel/index.js +0 -3
  110. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -0,0 +1,264 @@
1
+ const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
2
+ const { END_TIMESTAMP_LABEL } = 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
+
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
+ }
148
+
149
+ /**
150
+ * This class generates pprof files with timeline events sourced from Node.js
151
+ * performance measurement APIs.
152
+ */
153
+ class EventsProfiler {
154
+ constructor (options = {}) {
155
+ this.type = 'events'
156
+ this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
157
+ this._observer = undefined
158
+ this.entries = []
159
+ }
160
+
161
+ start () {
162
+ function add (items) {
163
+ this.entries.push(...items.getEntries())
164
+ }
165
+ if (!this._observer) {
166
+ this._observer = new PerformanceObserver(add.bind(this))
167
+ }
168
+ this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
169
+ }
170
+
171
+ stop () {
172
+ if (this._observer) {
173
+ this._observer.disconnect()
174
+ }
175
+ }
176
+
177
+ profile (startDate, endDate) {
178
+ if (this.entries.length === 0) {
179
+ // No events in the period; don't produce a profile
180
+ return null
181
+ }
182
+
183
+ const stringTable = new StringTable()
184
+ const locations = []
185
+ const functions = []
186
+
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
+ })()
198
+
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
204
+ }
205
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
206
+
207
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
208
+ const lateEntries = []
209
+ const perfEndDate = endDate.getTime() - performance.timeOrigin
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
+ }
217
+ const { startTime, duration } = item
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
225
+ }
226
+ const endTime = startTime + duration
227
+ const sampleInput = {
228
+ value: [Math.round(duration * MS_TO_NS)],
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)
238
+
239
+ this.entries = lateEntries
240
+
241
+ const timeValueType = new ValueType({
242
+ type: stringTable.dedup(pprofValueType),
243
+ unit: stringTable.dedup(pprofValueUnit)
244
+ })
245
+
246
+ return new Profile({
247
+ sampleType: [timeValueType],
248
+ timeNanos: endDate.getTime() * MS_TO_NS,
249
+ periodType: timeValueType,
250
+ period: 1,
251
+ durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
252
+ sample: samples,
253
+ location: locations,
254
+ function: functions,
255
+ stringTable: stringTable
256
+ })
257
+ }
258
+
259
+ encode (profile) {
260
+ return pprof.encode(profile)
261
+ }
262
+ }
263
+
264
+ module.exports = EventsProfiler
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread, threadId } = require('node:worker_threads')
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
+
32
+ module.exports = {
33
+ END_TIMESTAMP_LABEL,
34
+ THREAD_NAME_LABEL,
35
+ THREAD_ID_LABEL,
36
+ threadNamePrefix,
37
+ eventLoopThreadName,
38
+ getThreadLabels: cacheThreadLabels()
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) {
@@ -2,21 +2,19 @@
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_LABEL, getThreadLabels } = 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')
14
16
 
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
- })()
17
+ const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
20
18
 
21
19
  let kSampleCount
22
20
 
@@ -29,30 +27,6 @@ function getStartedSpans (context) {
29
27
  return context._trace.started
30
28
  }
31
29
 
32
- function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
33
- const labels = { 'thread name': threadName }
34
- if (spanId) {
35
- labels['span id'] = spanId
36
- }
37
- if (rootSpanId) {
38
- labels['local root span id'] = rootSpanId
39
- }
40
- if (webTags && Object.keys(webTags).length !== 0) {
41
- labels['trace endpoint'] = endpointNameFromTags(webTags)
42
- } else if (endpoint) {
43
- // fallback to endpoint computed when sample was taken
44
- labels['trace endpoint'] = endpoint
45
- }
46
- // Incoming timestamps are in microseconds, we emit nanos.
47
- labels['end_timestamp_ns'] = timestamp * 1000n
48
-
49
- return labels
50
- }
51
-
52
- function getSpanContextTags (span) {
53
- return span.context()._tags
54
- }
55
-
56
30
  function isWebServerSpan (tags) {
57
31
  return tags[SPAN_TYPE] === WEB
58
32
  }
@@ -64,6 +38,38 @@ function endpointNameFromTags (tags) {
64
38
  ].filter(v => v).join(' ')
65
39
  }
66
40
 
41
+ function getWebTags (startedSpans, i, span) {
42
+ // Are web tags for this span already memoized?
43
+ const memoizedWebTags = span[MemoizedWebTags]
44
+ if (memoizedWebTags !== undefined) {
45
+ return memoizedWebTags
46
+ }
47
+ // No, we'll have to memoize a new value
48
+ function memoize (tags) {
49
+ span[MemoizedWebTags] = tags
50
+ return tags
51
+ }
52
+ // Is this span itself a web span?
53
+ const context = span.context()
54
+ const tags = context._tags
55
+ if (isWebServerSpan(tags)) {
56
+ return memoize(tags)
57
+ }
58
+ // It isn't. Get parent's web tags (memoize them too recursively.)
59
+ // There might be several webspans, for example with next.js, http plugin creates the first span
60
+ // and then next.js plugin creates a child span, and this child span has the correct endpoint
61
+ // information. That's why we always use the tags of the closest ancestor web span.
62
+ const parentId = context._parentId
63
+ while (--i >= 0) {
64
+ const ispan = startedSpans[i]
65
+ if (ispan.context()._spanId === parentId) {
66
+ return memoize(getWebTags(startedSpans, i, ispan))
67
+ }
68
+ }
69
+ // Local root span with no web span
70
+ return memoize(null)
71
+ }
72
+
67
73
  class NativeWallProfiler {
68
74
  constructor (options = {}) {
69
75
  this.type = 'wall'
@@ -71,13 +77,27 @@ class NativeWallProfiler {
71
77
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
72
78
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
73
79
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
74
- this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
80
+ this._timelineEnabled = !!options.timelineEnabled
81
+ // We need to capture span data into the sample context for either code hotspots
82
+ // or endpoint collection.
83
+ this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
84
+ // We need to run the pprof wall profiler with sample contexts if we're either
85
+ // capturing span data or timeline is enabled (so we need sample timestamps, and for now
86
+ // timestamps require the sample contexts feature in the pprof wall profiler.)
87
+ this._withContexts = this._captureSpanData || this._timelineEnabled
75
88
  this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
76
89
  this._mapper = undefined
77
90
  this._pprof = undefined
78
91
 
79
- // Bind to this so the same value can be used to unsubscribe later
80
- this._enter = this._enter.bind(this)
92
+ // Bind these to this so they can be used as callbacks
93
+ if (this._withContexts) {
94
+ if (this._captureSpanData) {
95
+ this._enter = this._enter.bind(this)
96
+ this._spanFinished = this._spanFinished.bind(this)
97
+ }
98
+ }
99
+ this._generateLabels = this._generateLabels.bind(this)
100
+
81
101
  this._logger = options.logger
82
102
  this._started = false
83
103
  }
@@ -115,15 +135,20 @@ class NativeWallProfiler {
115
135
  })
116
136
 
117
137
  if (this._withContexts) {
118
- this._profilerState = this._pprof.time.getState()
119
138
  this._currentContext = {}
120
139
  this._pprof.time.setContext(this._currentContext)
121
- this._lastSpan = undefined
122
- this._lastStartedSpans = undefined
123
- this._lastSampleCount = 0
124
140
 
125
- beforeCh.subscribe(this._enter)
126
- enterCh.subscribe(this._enter)
141
+ if (this._captureSpanData) {
142
+ this._profilerState = this._pprof.time.getState()
143
+ this._lastSpan = undefined
144
+ this._lastStartedSpans = undefined
145
+ this._lastWebTags = undefined
146
+ this._lastSampleCount = 0
147
+
148
+ beforeCh.subscribe(this._enter)
149
+ enterCh.subscribe(this._enter)
150
+ spanFinishCh.subscribe(this._spanFinished)
151
+ }
127
152
  }
128
153
 
129
154
  this._started = true
@@ -144,11 +169,17 @@ class NativeWallProfiler {
144
169
 
145
170
  const span = getActiveSpan()
146
171
  if (span) {
172
+ const context = span.context()
147
173
  this._lastSpan = span
148
- this._lastStartedSpans = getStartedSpans(span.context())
174
+ const startedSpans = getStartedSpans(context)
175
+ this._lastStartedSpans = startedSpans
176
+ if (this._endpointCollectionEnabled) {
177
+ this._lastWebTags = getWebTags(startedSpans, startedSpans.length, span)
178
+ }
149
179
  } else {
150
180
  this._lastStartedSpans = undefined
151
181
  this._lastSpan = undefined
182
+ this._lastWebTags = undefined
152
183
  }
153
184
  }
154
185
 
@@ -163,21 +194,17 @@ class NativeWallProfiler {
163
194
  context.rootSpanId = rootSpan.context().toSpanId()
164
195
  }
165
196
  }
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
- }
197
+ if (this._lastWebTags) {
198
+ context.webTags = this._lastWebTags
199
+ // endpoint may not be determined yet, but keep it as fallback
200
+ // if tags are not available anymore during serialization
201
+ context.endpoint = endpointNameFromTags(this._lastWebTags)
202
+ }
203
+ }
204
+
205
+ _spanFinished (span) {
206
+ if (span[MemoizedWebTags]) {
207
+ span[MemoizedWebTags] = undefined
181
208
  }
182
209
  }
183
210
 
@@ -193,12 +220,12 @@ class NativeWallProfiler {
193
220
 
194
221
  _stop (restart) {
195
222
  if (!this._started) return
196
- if (this._withContexts) {
223
+ if (this._captureSpanData) {
197
224
  // update last sample context if needed
198
225
  this._enter()
199
226
  this._lastSampleCount = 0
200
227
  }
201
- const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
228
+ const profile = this._pprof.time.stop(restart, this._generateLabels)
202
229
  if (restart) {
203
230
  const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
204
231
  if (v8BugDetected !== 0) {
@@ -208,6 +235,38 @@ class NativeWallProfiler {
208
235
  return profile
209
236
  }
210
237
 
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) {
250
+ // Incoming timestamps are in microseconds, we emit nanos.
251
+ labels[END_TIMESTAMP_LABEL] = timestamp * 1000n
252
+ }
253
+
254
+ if (spanId) {
255
+ labels['span id'] = spanId
256
+ }
257
+ if (rootSpanId) {
258
+ labels['local root span id'] = rootSpanId
259
+ }
260
+ if (webTags && Object.keys(webTags).length !== 0) {
261
+ labels['trace endpoint'] = endpointNameFromTags(webTags)
262
+ } else if (endpoint) {
263
+ // fallback to endpoint computed when sample was taken
264
+ labels['trace endpoint'] = endpoint
265
+ }
266
+
267
+ return labels
268
+ }
269
+
211
270
  profile () {
212
271
  return this._stop(true)
213
272
  }
@@ -220,10 +279,14 @@ class NativeWallProfiler {
220
279
  if (!this._started) return
221
280
 
222
281
  const profile = this._stop(false)
223
- if (this._withContexts) {
282
+ if (this._captureSpanData) {
224
283
  beforeCh.unsubscribe(this._enter)
225
284
  enterCh.unsubscribe(this._enter)
285
+ spanFinishCh.unsubscribe(this._spanFinished)
226
286
  this._profilerState = undefined
287
+ this._lastSpan = undefined
288
+ this._lastStartedSpans = undefined
289
+ this._lastWebTags = undefined
227
290
  }
228
291
 
229
292
  this._started = false
@@ -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
@@ -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
 
@@ -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
@@ -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
  }