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.
- package/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +29 -0
- package/package.json +8 -7
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/client.js +10 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/next.js +18 -6
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
- package/packages/datadog-plugin-next/src/index.js +40 -14
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +32 -31
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
- package/packages/dd-trace/src/config.js +36 -2
- package/packages/dd-trace/src/datastreams/processor.js +107 -12
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +8 -0
- package/packages/dd-trace/src/profiling/profiler.js +17 -10
- package/packages/dd-trace/src/profiling/profilers/events.js +181 -83
- package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
- package/packages/dd-trace/src/proxy.js +25 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
- package/packages/dd-trace/src/telemetry/index.js +135 -43
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
|
|
2
|
-
const {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
226
|
+
const endTime = startTime + duration
|
|
227
|
+
const sampleInput = {
|
|
134
228
|
value: [Math.round(duration * MS_TO_NS)],
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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:
|
|
248
|
+
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
151
249
|
periodType: timeValueType,
|
|
152
|
-
period:
|
|
153
|
-
durationNanos:
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 {
|
|
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 (
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
[
|
|
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
|
}
|