dd-trace 4.20.0 → 4.21.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 +24 -0
- package/package.json +6 -5
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/client.js +22 -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 +3 -1
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- 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 +7 -7
- package/packages/dd-trace/src/appsec/addresses.js +2 -1
- 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 +13 -1
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -3
- 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 +31 -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/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +8 -0
- package/packages/dd-trace/src/profiling/profiler.js +10 -4
- package/packages/dd-trace/src/profiling/profilers/events.js +166 -73
- package/packages/dd-trace/src/proxy.js +21 -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
|
@@ -397,8 +397,9 @@ function addIntelligentTestRunnerSpanTags (
|
|
|
397
397
|
testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
//
|
|
401
|
-
|
|
400
|
+
// This will not be reported unless the user has manually added code coverage.
|
|
401
|
+
// This is always the case for Mocha and Cucumber, but not for Jest.
|
|
402
|
+
if (testCodeCoverageLinesTotal !== undefined) {
|
|
402
403
|
testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
403
404
|
testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
404
405
|
}
|
|
@@ -8,7 +8,7 @@ process.once('beforeExit', () => { profiler.stop() })
|
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
start: config => {
|
|
11
|
-
const { service, version, env, url, hostname, port, tags } = config
|
|
11
|
+
const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
|
|
12
12
|
const { enabled, sourceMap, exporters } = config.profiling
|
|
13
13
|
const logger = {
|
|
14
14
|
debug: (message) => log.debug(message),
|
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
error: (message) => log.error(message)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
profiler.start({
|
|
20
|
+
return profiler.start({
|
|
21
21
|
enabled,
|
|
22
22
|
service,
|
|
23
23
|
version,
|
|
@@ -28,7 +28,9 @@ module.exports = {
|
|
|
28
28
|
url,
|
|
29
29
|
hostname,
|
|
30
30
|
port,
|
|
31
|
-
tags
|
|
31
|
+
tags,
|
|
32
|
+
repositoryUrl,
|
|
33
|
+
commitSHA
|
|
32
34
|
})
|
|
33
35
|
},
|
|
34
36
|
|
|
@@ -11,6 +11,7 @@ const WallProfiler = require('./profilers/wall')
|
|
|
11
11
|
const SpaceProfiler = require('./profilers/space')
|
|
12
12
|
const EventsProfiler = require('./profilers/events')
|
|
13
13
|
const { oomExportStrategies, snapshotKinds } = require('./constants')
|
|
14
|
+
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
14
15
|
const { tagger } = require('./tagger')
|
|
15
16
|
const { isFalse, isTrue } = require('../util')
|
|
16
17
|
|
|
@@ -72,6 +73,13 @@ class Config {
|
|
|
72
73
|
tagger.parse(options.tags),
|
|
73
74
|
tagger.parse({ env, host, service, version, functionname })
|
|
74
75
|
)
|
|
76
|
+
|
|
77
|
+
// Add source code integration tags if available
|
|
78
|
+
if (options.repositoryUrl && options.commitSHA) {
|
|
79
|
+
this.tags[GIT_REPOSITORY_URL] = options.repositoryUrl
|
|
80
|
+
this.tags[GIT_COMMIT_SHA] = options.commitSHA
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
this.logger = ensureLogger(options.logger)
|
|
76
84
|
const logger = this.logger
|
|
77
85
|
function logExperimentalVarDeprecation (shortVarName) {
|
|
@@ -23,15 +23,19 @@ class Profiler extends EventEmitter {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
start (options) {
|
|
26
|
-
this._start(options).catch((err) => {
|
|
27
|
-
|
|
26
|
+
return this._start(options).catch((err) => {
|
|
27
|
+
if (options.logger) {
|
|
28
|
+
options.logger.error(err)
|
|
29
|
+
}
|
|
30
|
+
return false
|
|
31
|
+
})
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
async _start (options) {
|
|
31
|
-
if (this._enabled) return
|
|
35
|
+
if (this._enabled) return true
|
|
32
36
|
|
|
33
37
|
const config = this._config = new Config(options)
|
|
34
|
-
if (!config.enabled) return
|
|
38
|
+
if (!config.enabled) return false
|
|
35
39
|
|
|
36
40
|
this._logger = config.logger
|
|
37
41
|
this._enabled = true
|
|
@@ -67,9 +71,11 @@ class Profiler extends EventEmitter {
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
this._capture(this._timeoutInterval)
|
|
74
|
+
return true
|
|
70
75
|
} catch (e) {
|
|
71
76
|
this._logger.error(e)
|
|
72
77
|
this._stop()
|
|
78
|
+
return false
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
|
|
2
|
-
const { END_TIMESTAMP
|
|
2
|
+
const { END_TIMESTAMP } = 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 () {
|
|
@@ -52,91 +181,55 @@ class EventsProfiler {
|
|
|
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
186
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
})()
|
|
68
198
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
91
|
-
const gcEventLabel = labelFromStrStr('event', 'gc')
|
|
92
|
-
const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
|
|
93
|
-
|
|
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)
|
|
112
206
|
|
|
113
207
|
let durationFrom = Number.POSITIVE_INFINITY
|
|
114
208
|
let durationTo = 0
|
|
115
209
|
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
116
210
|
|
|
117
211
|
const samples = this.entries.map((item) => {
|
|
212
|
+
const decorator = decorators[item.entryType]
|
|
213
|
+
if (!decorator) {
|
|
214
|
+
// Shouldn't happen but it's better to not rely on observer only getting
|
|
215
|
+
// requested event types.
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
118
218
|
const { startTime, duration } = item
|
|
119
|
-
const { kind, flags } = node16 ? item.detail : item
|
|
120
219
|
const endTime = startTime + duration
|
|
121
220
|
if (durationFrom > startTime) durationFrom = startTime
|
|
122
221
|
if (durationTo < endTime) durationTo = endTime
|
|
123
|
-
const
|
|
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)
|
|
132
|
-
}
|
|
133
|
-
const sample = new Sample({
|
|
222
|
+
const sampleInput = {
|
|
134
223
|
value: [Math.round(duration * MS_TO_NS)],
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
224
|
+
locationId,
|
|
225
|
+
label: [
|
|
226
|
+
decorator.eventTypeLabel,
|
|
227
|
+
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
decorator.decorateSample(sampleInput, item)
|
|
231
|
+
return new Sample(sampleInput)
|
|
232
|
+
}).filter(v => v)
|
|
140
233
|
|
|
141
234
|
this.entries = []
|
|
142
235
|
|
|
@@ -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 () {
|
|
@@ -37,6 +38,15 @@ class Tracer extends NoopProxy {
|
|
|
37
38
|
}, 10 * 1000).unref()
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
if (config.spanLeakDebug > 0) {
|
|
42
|
+
if (config.spanLeakDebug === spanleak.MODES.LOG) {
|
|
43
|
+
spanleak.enableLogging()
|
|
44
|
+
} else if (config.spanLeakDebug === spanleak.MODES.GC_AND_LOG) {
|
|
45
|
+
spanleak.enableGarbageCollection()
|
|
46
|
+
}
|
|
47
|
+
spanleak.startScrubber()
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
if (config.remoteConfig.enabled && !config.isCiVisibility) {
|
|
41
51
|
const rc = remoteConfig.enable(config)
|
|
42
52
|
|
|
@@ -62,11 +72,14 @@ class Tracer extends NoopProxy {
|
|
|
62
72
|
// do not stop tracer initialization if the profiler fails to be imported
|
|
63
73
|
try {
|
|
64
74
|
const profiler = require('./profiler')
|
|
65
|
-
profiler.start(config)
|
|
75
|
+
this._profilerStarted = profiler.start(config)
|
|
66
76
|
} catch (e) {
|
|
67
77
|
log.error(e)
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
if (!this._profilerStarted) {
|
|
81
|
+
this._profilerStarted = Promise.resolve(false)
|
|
82
|
+
}
|
|
70
83
|
|
|
71
84
|
if (config.runtimeMetrics) {
|
|
72
85
|
runtimeMetrics.start(config)
|
|
@@ -104,6 +117,13 @@ class Tracer extends NoopProxy {
|
|
|
104
117
|
return this
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
profilerStarted () {
|
|
121
|
+
if (!this._profilerStarted) {
|
|
122
|
+
throw new Error('profilerStarted() must be called after init()')
|
|
123
|
+
}
|
|
124
|
+
return this._profilerStarted
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
use () {
|
|
108
128
|
this._pluginManager.configurePlugin(...arguments)
|
|
109
129
|
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
|
}
|
|
@@ -6,6 +6,7 @@ const requirePackageJson = require('../require-package-json')
|
|
|
6
6
|
const { sendData } = require('./send-data')
|
|
7
7
|
const dc = require('dc-polyfill')
|
|
8
8
|
const { fileURLToPath } = require('url')
|
|
9
|
+
const { isTrue } = require('../../src/util')
|
|
9
10
|
|
|
10
11
|
const savedDependenciesToSend = new Set()
|
|
11
12
|
const detectedDependencyKeys = new Set()
|
|
@@ -14,20 +15,57 @@ const detectedDependencyVersions = new Set()
|
|
|
14
15
|
const FILE_URI_START = `file://`
|
|
15
16
|
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
16
17
|
|
|
17
|
-
let immediate, config, application, host
|
|
18
|
+
let immediate, config, application, host, initialLoad
|
|
18
19
|
let isFirstModule = true
|
|
20
|
+
let getRetryData
|
|
21
|
+
let updateRetryData
|
|
19
22
|
|
|
23
|
+
function createBatchPayload (payload) {
|
|
24
|
+
const batchPayload = []
|
|
25
|
+
payload.map(item => {
|
|
26
|
+
batchPayload.push({
|
|
27
|
+
request_type: item.reqType,
|
|
28
|
+
payload: item.payload
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return batchPayload
|
|
33
|
+
}
|
|
20
34
|
function waitAndSend (config, application, host) {
|
|
21
35
|
if (!immediate) {
|
|
22
36
|
immediate = setImmediate(() => {
|
|
23
37
|
immediate = null
|
|
24
38
|
if (savedDependenciesToSend.size > 0) {
|
|
25
|
-
const dependencies = Array.from(savedDependenciesToSend.values())
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
const dependencies = Array.from(savedDependenciesToSend.values())
|
|
40
|
+
// if a depencdency is from the initial load, *always* send the event
|
|
41
|
+
// Otherwise, only send if dependencyCollection is enabled
|
|
42
|
+
.filter(dep => {
|
|
43
|
+
const initialLoadModule = isTrue(dep.split(' ')[2])
|
|
44
|
+
const sendModule = initialLoadModule || (config.telemetry?.dependencyCollection)
|
|
45
|
+
|
|
46
|
+
if (!sendModule) savedDependenciesToSend.delete(dep) // we'll never send it
|
|
47
|
+
return sendModule
|
|
48
|
+
})
|
|
49
|
+
.splice(0, 2000) // v2 documentation specifies up to 2000 dependencies can be sent at once
|
|
50
|
+
.map(pair => {
|
|
51
|
+
savedDependenciesToSend.delete(pair)
|
|
52
|
+
const [name, version] = pair.split(' ')
|
|
53
|
+
return { name, version }
|
|
54
|
+
})
|
|
55
|
+
let currPayload
|
|
56
|
+
const retryData = getRetryData()
|
|
57
|
+
if (retryData) {
|
|
58
|
+
currPayload = { reqType: 'app-dependencies-loaded', payload: { dependencies } }
|
|
59
|
+
} else {
|
|
60
|
+
if (!dependencies.length) return // no retry data and no dependencies, nothing to send
|
|
61
|
+
currPayload = { dependencies }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const payload = retryData ? createBatchPayload([currPayload, retryData]) : currPayload
|
|
65
|
+
const reqType = retryData ? 'message-batch' : 'app-dependencies-loaded'
|
|
66
|
+
|
|
67
|
+
sendData(config, application, host, reqType, payload, updateRetryData)
|
|
68
|
+
|
|
31
69
|
if (savedDependenciesToSend.size > 0) {
|
|
32
70
|
waitAndSend(config, application, host)
|
|
33
71
|
}
|
|
@@ -76,7 +114,7 @@ function onModuleLoad (data) {
|
|
|
76
114
|
const dependencyAndVersion = `${name} ${version}`
|
|
77
115
|
|
|
78
116
|
if (!detectedDependencyVersions.has(dependencyAndVersion)) {
|
|
79
|
-
savedDependenciesToSend.add(dependencyAndVersion)
|
|
117
|
+
savedDependenciesToSend.add(`${dependencyAndVersion} ${initialLoad}`)
|
|
80
118
|
detectedDependencyVersions.add(dependencyAndVersion)
|
|
81
119
|
|
|
82
120
|
waitAndSend(config, application, host)
|
|
@@ -89,11 +127,19 @@ function onModuleLoad (data) {
|
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
129
|
}
|
|
92
|
-
function start (_config, _application, _host) {
|
|
130
|
+
function start (_config = {}, _application, _host, getRetryDataFunction, updateRetryDatafunction) {
|
|
93
131
|
config = _config
|
|
94
132
|
application = _application
|
|
95
133
|
host = _host
|
|
134
|
+
initialLoad = true
|
|
135
|
+
getRetryData = getRetryDataFunction
|
|
136
|
+
updateRetryData = updateRetryDatafunction
|
|
96
137
|
moduleLoadStartChannel.subscribe(onModuleLoad)
|
|
138
|
+
|
|
139
|
+
// try and capture intially loaded modules in the first tick
|
|
140
|
+
// since, ideally, the tracer (and this module) should be loaded first,
|
|
141
|
+
// this should capture any first-tick dependencies
|
|
142
|
+
queueMicrotask(() => { initialLoad = false })
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
function isDependency (filename, request) {
|