dd-trace 4.46.0 → 4.47.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 +2 -0
- package/index.d.ts +20 -8
- package/package.json +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +290 -53
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/openai.js +4 -2
- package/packages/datadog-instrumentations/src/pg.js +59 -4
- package/packages/datadog-instrumentations/src/vitest.js +184 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +17 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +27 -18
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
- package/packages/dd-trace/src/config.js +61 -10
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +26 -2
- package/packages/dd-trace/src/profiling/config.js +5 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
- package/packages/dd-trace/src/proxy.js +10 -3
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const { PAYLOAD_TAGGING_MAX_TAGS } = require('../constants')
|
|
2
|
+
|
|
3
|
+
const redactedKeys = [
|
|
4
|
+
'authorization', 'x-authorization', 'password', 'token'
|
|
5
|
+
]
|
|
6
|
+
const truncated = 'truncated'
|
|
7
|
+
const redacted = 'redacted'
|
|
8
|
+
|
|
9
|
+
function escapeKey (key) {
|
|
10
|
+
return key.replaceAll('.', '\\.')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Compute normalized payload tags from any given object.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} object
|
|
17
|
+
* @param {import('./mask').Mask} mask
|
|
18
|
+
* @param {number} maxDepth
|
|
19
|
+
* @param {string} prefix
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
function tagsFromObject (object, opts) {
|
|
23
|
+
const { maxDepth, prefix } = opts
|
|
24
|
+
|
|
25
|
+
let tagCount = 0
|
|
26
|
+
let abort = false
|
|
27
|
+
const result = {}
|
|
28
|
+
|
|
29
|
+
function tagRec (prefix, object, depth = 0) {
|
|
30
|
+
// Off by one: _dd.payload_tags_trimmed counts as 1 tag
|
|
31
|
+
if (abort) { return }
|
|
32
|
+
|
|
33
|
+
if (tagCount >= PAYLOAD_TAGGING_MAX_TAGS - 1) {
|
|
34
|
+
abort = true
|
|
35
|
+
result['_dd.payload_tags_incomplete'] = true
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (depth >= maxDepth && typeof object === 'object') {
|
|
40
|
+
tagCount += 1
|
|
41
|
+
result[prefix] = truncated
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (object === undefined) {
|
|
46
|
+
tagCount += 1
|
|
47
|
+
result[prefix] = 'undefined'
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (object === null) {
|
|
52
|
+
tagCount += 1
|
|
53
|
+
result[prefix] = 'null'
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (['number', 'boolean'].includes(typeof object) || Buffer.isBuffer(object)) {
|
|
58
|
+
tagCount += 1
|
|
59
|
+
result[prefix] = object.toString().substring(0, 5000)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof object === 'string') {
|
|
64
|
+
tagCount += 1
|
|
65
|
+
result[prefix] = object.substring(0, 5000)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof object === 'object') {
|
|
69
|
+
for (const [key, value] of Object.entries(object)) {
|
|
70
|
+
if (redactedKeys.includes(key.toLowerCase())) {
|
|
71
|
+
tagCount += 1
|
|
72
|
+
result[`${prefix}.${escapeKey(key)}`] = redacted
|
|
73
|
+
} else {
|
|
74
|
+
tagRec(`${prefix}.${escapeKey(key)}`, value, depth + 1)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
tagRec(prefix, object)
|
|
80
|
+
return result
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { tagsFromObject }
|
|
@@ -136,10 +136,19 @@ module.exports = class PluginManager {
|
|
|
136
136
|
dbmPropagationMode,
|
|
137
137
|
dsmEnabled,
|
|
138
138
|
clientIpEnabled,
|
|
139
|
-
memcachedCommandEnabled
|
|
139
|
+
memcachedCommandEnabled,
|
|
140
|
+
ciVisibilityTestSessionName
|
|
140
141
|
} = this._tracerConfig
|
|
141
142
|
|
|
142
|
-
const sharedConfig = {
|
|
143
|
+
const sharedConfig = {
|
|
144
|
+
dbmPropagationMode,
|
|
145
|
+
dsmEnabled,
|
|
146
|
+
memcachedCommandEnabled,
|
|
147
|
+
site,
|
|
148
|
+
url,
|
|
149
|
+
headers: headerTags || [],
|
|
150
|
+
ciVisibilityTestSessionName
|
|
151
|
+
}
|
|
143
152
|
|
|
144
153
|
if (logInjection !== undefined) {
|
|
145
154
|
sharedConfig.logInjection = logInjection
|
|
@@ -149,10 +158,6 @@ module.exports = class PluginManager {
|
|
|
149
158
|
sharedConfig.queryStringObfuscation = queryStringObfuscation
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
sharedConfig.dbmPropagationMode = dbmPropagationMode
|
|
153
|
-
sharedConfig.dsmEnabled = dsmEnabled
|
|
154
|
-
sharedConfig.memcachedCommandEnabled = memcachedCommandEnabled
|
|
155
|
-
|
|
156
161
|
if (serviceMapping && serviceMapping[name]) {
|
|
157
162
|
sharedConfig.service = serviceMapping[name]
|
|
158
163
|
}
|
|
@@ -161,10 +166,6 @@ module.exports = class PluginManager {
|
|
|
161
166
|
sharedConfig.clientIpEnabled = clientIpEnabled
|
|
162
167
|
}
|
|
163
168
|
|
|
164
|
-
sharedConfig.site = site
|
|
165
|
-
sharedConfig.url = url
|
|
166
|
-
sharedConfig.headers = headerTags || []
|
|
167
|
-
|
|
168
169
|
return sharedConfig
|
|
169
170
|
}
|
|
170
171
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const {
|
|
2
2
|
getTestEnvironmentMetadata,
|
|
3
|
+
getTestSessionName,
|
|
3
4
|
getCodeOwnersFileEntries,
|
|
4
5
|
getTestParentSpan,
|
|
5
6
|
getTestCommonTags,
|
|
@@ -13,11 +14,14 @@ const {
|
|
|
13
14
|
TEST_SESSION_ID,
|
|
14
15
|
TEST_COMMAND,
|
|
15
16
|
TEST_MODULE,
|
|
17
|
+
TEST_SESSION_NAME,
|
|
16
18
|
getTestSuiteCommonTags,
|
|
17
19
|
TEST_STATUS,
|
|
18
20
|
TEST_SKIPPED_BY_ITR,
|
|
19
21
|
ITR_CORRELATION_ID,
|
|
20
|
-
TEST_SOURCE_FILE
|
|
22
|
+
TEST_SOURCE_FILE,
|
|
23
|
+
TEST_LEVEL_EVENT_TYPES,
|
|
24
|
+
TEST_SUITE
|
|
21
25
|
} = require('./util/test')
|
|
22
26
|
const Plugin = require('./plugin')
|
|
23
27
|
const { COMPONENT } = require('../constants')
|
|
@@ -75,6 +79,19 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
75
79
|
// only for playwright
|
|
76
80
|
this.rootDir = rootDir
|
|
77
81
|
|
|
82
|
+
const testSessionName = getTestSessionName(this.config, this.command, this.testEnvironmentMetadata)
|
|
83
|
+
|
|
84
|
+
const metadataTags = {}
|
|
85
|
+
for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
|
|
86
|
+
metadataTags[testLevel] = {
|
|
87
|
+
[TEST_SESSION_NAME]: testSessionName
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// tracer might not be initialized correctly
|
|
91
|
+
if (this.tracer._exporter.setMetadataTags) {
|
|
92
|
+
this.tracer._exporter.setMetadataTags(metadataTags)
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
this.testSessionSpan = this.tracer.startSpan(`${this.constructor.id}.test_session`, {
|
|
79
96
|
childOf,
|
|
80
97
|
tags: {
|
|
@@ -97,6 +114,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
97
114
|
if (this.constructor.id === 'vitest') {
|
|
98
115
|
process.env.DD_CIVISIBILITY_TEST_SESSION_ID = this.testSessionSpan.context().toTraceId()
|
|
99
116
|
process.env.DD_CIVISIBILITY_TEST_MODULE_ID = this.testModuleSpan.context().toSpanId()
|
|
117
|
+
process.env.DD_CIVISIBILITY_TEST_COMMAND = this.command
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
|
|
@@ -194,6 +212,19 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
214
|
|
|
215
|
+
getCodeOwners (tags) {
|
|
216
|
+
const {
|
|
217
|
+
[TEST_SOURCE_FILE]: testSourceFile,
|
|
218
|
+
[TEST_SUITE]: testSuite
|
|
219
|
+
} = tags
|
|
220
|
+
// We'll try with the test source file if available (it could be different from the test suite)
|
|
221
|
+
let codeOwners = getCodeOwnersForFilename(testSourceFile, this.codeOwnersEntries)
|
|
222
|
+
if (!codeOwners) {
|
|
223
|
+
codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries)
|
|
224
|
+
}
|
|
225
|
+
return codeOwners
|
|
226
|
+
}
|
|
227
|
+
|
|
197
228
|
startTestSpan (testName, testSuite, testSuiteSpan, extraTags = {}) {
|
|
198
229
|
const childOf = getTestParentSpan(this.tracer)
|
|
199
230
|
|
|
@@ -208,13 +239,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
208
239
|
...extraTags
|
|
209
240
|
}
|
|
210
241
|
|
|
211
|
-
const
|
|
212
|
-
// We'll try with the test source file if available (it could be different from the test suite)
|
|
213
|
-
let codeOwners = getCodeOwnersForFilename(testSourceFile, this.codeOwnersEntries)
|
|
214
|
-
if (!codeOwners) {
|
|
215
|
-
codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries)
|
|
216
|
-
}
|
|
217
|
-
|
|
242
|
+
const codeOwners = this.getCodeOwners(testTags)
|
|
218
243
|
if (codeOwners) {
|
|
219
244
|
testTags[TEST_CODE_OWNERS] = codeOwners
|
|
220
245
|
}
|
|
@@ -5,6 +5,7 @@ const OS_VERSION = 'os.version'
|
|
|
5
5
|
const OS_ARCHITECTURE = 'os.architecture'
|
|
6
6
|
const RUNTIME_NAME = 'runtime.name'
|
|
7
7
|
const RUNTIME_VERSION = 'runtime.version'
|
|
8
|
+
const DD_HOST_CPU_COUNT = '_dd.host.vcpu_count'
|
|
8
9
|
|
|
9
10
|
function getRuntimeAndOSMetadata () {
|
|
10
11
|
return {
|
|
@@ -12,7 +13,8 @@ function getRuntimeAndOSMetadata () {
|
|
|
12
13
|
[OS_ARCHITECTURE]: process.arch,
|
|
13
14
|
[OS_PLATFORM]: process.platform,
|
|
14
15
|
[RUNTIME_NAME]: 'node',
|
|
15
|
-
[OS_VERSION]: os.release()
|
|
16
|
+
[OS_VERSION]: os.release(),
|
|
17
|
+
[DD_HOST_CPU_COUNT]: os.cpus().length
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -22,5 +24,6 @@ module.exports = {
|
|
|
22
24
|
OS_VERSION,
|
|
23
25
|
OS_ARCHITECTURE,
|
|
24
26
|
RUNTIME_NAME,
|
|
25
|
-
RUNTIME_VERSION
|
|
27
|
+
RUNTIME_VERSION,
|
|
28
|
+
DD_HOST_CPU_COUNT
|
|
26
29
|
}
|
|
@@ -19,7 +19,8 @@ const {
|
|
|
19
19
|
GIT_COMMIT_AUTHOR_NAME,
|
|
20
20
|
GIT_COMMIT_MESSAGE,
|
|
21
21
|
CI_WORKSPACE_PATH,
|
|
22
|
-
CI_PIPELINE_URL
|
|
22
|
+
CI_PIPELINE_URL,
|
|
23
|
+
CI_JOB_NAME
|
|
23
24
|
} = require('./tags')
|
|
24
25
|
const id = require('../../id')
|
|
25
26
|
|
|
@@ -28,6 +29,9 @@ const { SAMPLING_RULE_DECISION } = require('../../constants')
|
|
|
28
29
|
const { AUTO_KEEP } = require('../../../../../ext/priority')
|
|
29
30
|
const { version: ddTraceVersion } = require('../../../../../package.json')
|
|
30
31
|
|
|
32
|
+
// session tags
|
|
33
|
+
const TEST_SESSION_NAME = 'test_session.name'
|
|
34
|
+
|
|
31
35
|
const TEST_FRAMEWORK = 'test.framework'
|
|
32
36
|
const TEST_FRAMEWORK_VERSION = 'test.framework_version'
|
|
33
37
|
const TEST_TYPE = 'test.type'
|
|
@@ -95,8 +99,16 @@ const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
|
|
|
95
99
|
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
96
100
|
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
97
101
|
|
|
102
|
+
const TEST_LEVEL_EVENT_TYPES = [
|
|
103
|
+
'test',
|
|
104
|
+
'test_suite_end',
|
|
105
|
+
'test_module_end',
|
|
106
|
+
'test_session_end'
|
|
107
|
+
]
|
|
108
|
+
|
|
98
109
|
module.exports = {
|
|
99
110
|
TEST_CODE_OWNERS,
|
|
111
|
+
TEST_SESSION_NAME,
|
|
100
112
|
TEST_FRAMEWORK,
|
|
101
113
|
TEST_FRAMEWORK_VERSION,
|
|
102
114
|
JEST_TEST_RUNNER,
|
|
@@ -167,7 +179,9 @@ module.exports = {
|
|
|
167
179
|
TEST_BROWSER_DRIVER,
|
|
168
180
|
TEST_BROWSER_DRIVER_VERSION,
|
|
169
181
|
TEST_BROWSER_NAME,
|
|
170
|
-
TEST_BROWSER_VERSION
|
|
182
|
+
TEST_BROWSER_VERSION,
|
|
183
|
+
getTestSessionName,
|
|
184
|
+
TEST_LEVEL_EVENT_TYPES
|
|
171
185
|
}
|
|
172
186
|
|
|
173
187
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -615,3 +629,13 @@ function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faulty
|
|
|
615
629
|
newSuitesPercentage > faultyThresholdPercentage
|
|
616
630
|
)
|
|
617
631
|
}
|
|
632
|
+
|
|
633
|
+
function getTestSessionName (config, testCommand, envTags) {
|
|
634
|
+
if (config.ciVisibilityTestSessionName) {
|
|
635
|
+
return config.ciVisibilityTestSessionName
|
|
636
|
+
}
|
|
637
|
+
if (envTags[CI_JOB_NAME]) {
|
|
638
|
+
return `${envTags[CI_JOB_NAME]}-${testCommand}`
|
|
639
|
+
}
|
|
640
|
+
return testCommand
|
|
641
|
+
}
|
|
@@ -97,6 +97,11 @@ class Config {
|
|
|
97
97
|
const samplingContextsAvailable = process.platform !== 'win32'
|
|
98
98
|
function checkOptionAllowed (option, description, condition) {
|
|
99
99
|
if (option && !condition) {
|
|
100
|
+
// injection hardening: all of these can only happen if user explicitly
|
|
101
|
+
// sets an environment variable to its non-default value on the platform.
|
|
102
|
+
// In practical terms, it'd require someone explicitly turning on OOM
|
|
103
|
+
// monitoring, code hotspots, endpoint profiling, or CPU profiling on
|
|
104
|
+
// Windows, where it is not supported.
|
|
100
105
|
throw new Error(`${description} not supported on ${process.platform}.`)
|
|
101
106
|
}
|
|
102
107
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const DNSPlugin = require('./dns')
|
|
2
|
+
|
|
3
|
+
class DNSLookupPlugin extends DNSPlugin {
|
|
4
|
+
static get operation () {
|
|
5
|
+
return 'lookup'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
extendEvent (event, startEvent) {
|
|
9
|
+
event.name = 'lookup'
|
|
10
|
+
event.detail = { hostname: startEvent[0] }
|
|
11
|
+
|
|
12
|
+
return event
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = DNSLookupPlugin
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const DNSPlugin = require('./dns')
|
|
2
|
+
|
|
3
|
+
class DNSLookupServicePlugin extends DNSPlugin {
|
|
4
|
+
static get operation () {
|
|
5
|
+
return 'lookup_service'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
extendEvent (event, startEvent) {
|
|
9
|
+
event.name = 'lookupService'
|
|
10
|
+
event.detail = { host: startEvent[0], port: startEvent[1] }
|
|
11
|
+
|
|
12
|
+
return event
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = DNSLookupServicePlugin
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const DNSPlugin = require('./dns')
|
|
2
|
+
|
|
3
|
+
const queryNames = new Map()
|
|
4
|
+
|
|
5
|
+
class DNSResolvePlugin extends DNSPlugin {
|
|
6
|
+
static get operation () {
|
|
7
|
+
return 'resolve'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
extendEvent (event, startEvent) {
|
|
11
|
+
const rrtype = startEvent[1]
|
|
12
|
+
let name = queryNames.get(rrtype)
|
|
13
|
+
if (!name) {
|
|
14
|
+
name = `query${rrtype}`
|
|
15
|
+
queryNames.set(rrtype, name)
|
|
16
|
+
}
|
|
17
|
+
event.name = name
|
|
18
|
+
event.detail = { host: startEvent[0] }
|
|
19
|
+
|
|
20
|
+
return event
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = DNSResolvePlugin
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const DNSPlugin = require('./dns')
|
|
2
|
+
|
|
3
|
+
class DNSReversePlugin extends DNSPlugin {
|
|
4
|
+
static get operation () {
|
|
5
|
+
return 'reverse'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
extendEvent (event, startEvent) {
|
|
9
|
+
event.name = 'getHostByAddr'
|
|
10
|
+
event.detail = { host: startEvent[0] }
|
|
11
|
+
|
|
12
|
+
return event
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = DNSReversePlugin
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { AsyncLocalStorage } = require('async_hooks')
|
|
2
|
+
const TracingPlugin = require('../../../plugins/tracing')
|
|
3
|
+
const { performance } = require('perf_hooks')
|
|
4
|
+
|
|
5
|
+
// We are leveraging the TracingPlugin class for its functionality to bind
|
|
6
|
+
// start/error/finish methods to the appropriate diagnostic channels.
|
|
7
|
+
class EventPlugin extends TracingPlugin {
|
|
8
|
+
constructor (eventHandler) {
|
|
9
|
+
super()
|
|
10
|
+
this.eventHandler = eventHandler
|
|
11
|
+
this.store = new AsyncLocalStorage()
|
|
12
|
+
this.entryType = this.constructor.entryType
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start (startEvent) {
|
|
16
|
+
this.store.enterWith({
|
|
17
|
+
startEvent,
|
|
18
|
+
startTime: performance.now()
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error () {
|
|
23
|
+
this.store.getStore().error = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
finish () {
|
|
27
|
+
const { startEvent, startTime, error } = this.store.getStore()
|
|
28
|
+
if (error) {
|
|
29
|
+
return // don't emit perf events for failed operations
|
|
30
|
+
}
|
|
31
|
+
const duration = performance.now() - startTime
|
|
32
|
+
|
|
33
|
+
const context = this.activeSpan?.context()
|
|
34
|
+
const _ddSpanId = context?.toSpanId()
|
|
35
|
+
const _ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || _ddSpanId
|
|
36
|
+
|
|
37
|
+
const event = {
|
|
38
|
+
entryType: this.entryType,
|
|
39
|
+
startTime,
|
|
40
|
+
duration,
|
|
41
|
+
_ddSpanId,
|
|
42
|
+
_ddRootSpanId
|
|
43
|
+
}
|
|
44
|
+
this.eventHandler(this.extendEvent(event, startEvent))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = EventPlugin
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const EventPlugin = require('./event')
|
|
2
|
+
|
|
3
|
+
class NetPlugin extends EventPlugin {
|
|
4
|
+
static get id () {
|
|
5
|
+
return 'net'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static get operation () {
|
|
9
|
+
return 'tcp'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get entryType () {
|
|
13
|
+
return 'net'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
extendEvent (event, { options }) {
|
|
17
|
+
event.name = 'connect'
|
|
18
|
+
event.detail = options
|
|
19
|
+
|
|
20
|
+
return event
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = NetPlugin
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('perf_hooks')
|
|
2
|
-
const { END_TIMESTAMP_LABEL } = require('./shared')
|
|
3
|
-
const semver = require('semver')
|
|
2
|
+
const { END_TIMESTAMP_LABEL, SPAN_ID_LABEL, LOCAL_ROOT_SPAN_ID_LABEL } = require('./shared')
|
|
4
3
|
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
4
|
const pprof = require('@datadog/pprof/')
|
|
6
5
|
|
|
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
6
|
// perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
|
|
11
7
|
const MS_TO_NS = 1000000
|
|
12
8
|
|
|
@@ -48,7 +44,7 @@ class GCDecorator {
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
decorateSample (sampleInput, item) {
|
|
51
|
-
const { kind, flags } =
|
|
47
|
+
const { kind, flags } = item.detail
|
|
52
48
|
sampleInput.label.push(this.kindLabels[kind])
|
|
53
49
|
const reasonLabel = this.getReasonLabel(flags)
|
|
54
50
|
if (reasonLabel) {
|
|
@@ -140,12 +136,9 @@ class NetDecorator {
|
|
|
140
136
|
// Keys correspond to PerformanceEntry.entryType, values are constructor
|
|
141
137
|
// functions for type-specific decorators.
|
|
142
138
|
const decoratorTypes = {
|
|
143
|
-
gc: GCDecorator
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (node16) {
|
|
147
|
-
decoratorTypes.dns = DNSDecorator
|
|
148
|
-
decoratorTypes.net = NetDecorator
|
|
139
|
+
gc: GCDecorator,
|
|
140
|
+
dns: DNSDecorator,
|
|
141
|
+
net: NetDecorator
|
|
149
142
|
}
|
|
150
143
|
|
|
151
144
|
// Translates performance entries into pprof samples.
|
|
@@ -168,10 +161,12 @@ class EventSerializer {
|
|
|
168
161
|
this.locationId = [location.id]
|
|
169
162
|
|
|
170
163
|
this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
164
|
+
this.spanIdKey = this.stringTable.dedup(SPAN_ID_LABEL)
|
|
165
|
+
this.rootSpanIdKey = this.stringTable.dedup(LOCAL_ROOT_SPAN_ID_LABEL)
|
|
171
166
|
}
|
|
172
167
|
|
|
173
168
|
addEvent (item) {
|
|
174
|
-
const { entryType, startTime, duration } = item
|
|
169
|
+
const { entryType, startTime, duration, _ddSpanId, _ddRootSpanId } = item
|
|
175
170
|
let decorator = this.decorators[entryType]
|
|
176
171
|
if (!decorator) {
|
|
177
172
|
const DecoratorCtor = decoratorTypes[entryType]
|
|
@@ -186,13 +181,21 @@ class EventSerializer {
|
|
|
186
181
|
}
|
|
187
182
|
}
|
|
188
183
|
const endTime = startTime + duration
|
|
184
|
+
const label = [
|
|
185
|
+
decorator.eventTypeLabel,
|
|
186
|
+
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
187
|
+
]
|
|
188
|
+
if (_ddSpanId) {
|
|
189
|
+
label.push(labelFromStr(this.stringTable, this.spanIdKey, _ddSpanId))
|
|
190
|
+
}
|
|
191
|
+
if (_ddRootSpanId) {
|
|
192
|
+
label.push(labelFromStr(this.stringTable, this.rootSpanIdKey, _ddRootSpanId))
|
|
193
|
+
}
|
|
194
|
+
|
|
189
195
|
const sampleInput = {
|
|
190
196
|
value: [Math.round(duration * MS_TO_NS)],
|
|
191
197
|
locationId: this.locationId,
|
|
192
|
-
label
|
|
193
|
-
decorator.eventTypeLabel,
|
|
194
|
-
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
195
|
-
]
|
|
198
|
+
label
|
|
196
199
|
}
|
|
197
200
|
decorator.decorateSample(sampleInput, item)
|
|
198
201
|
this.samples.push(new Sample(sampleInput))
|
|
@@ -219,36 +222,109 @@ class EventSerializer {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
/**
|
|
222
|
-
*
|
|
223
|
-
* performance measurement APIs.
|
|
225
|
+
* Class that sources timeline events through Node.js performance measurement APIs.
|
|
224
226
|
*/
|
|
225
|
-
class
|
|
226
|
-
constructor (
|
|
227
|
-
this.
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
230
|
-
this.eventSerializer = new EventSerializer()
|
|
227
|
+
class NodeApiEventSource {
|
|
228
|
+
constructor (eventHandler, entryTypes) {
|
|
229
|
+
this.eventHandler = eventHandler
|
|
230
|
+
this.observer = undefined
|
|
231
|
+
this.entryTypes = entryTypes || Object.keys(decoratorTypes)
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
start () {
|
|
234
235
|
// if already started, do nothing
|
|
235
|
-
if (this.
|
|
236
|
+
if (this.observer) return
|
|
236
237
|
|
|
237
238
|
function add (items) {
|
|
238
239
|
for (const item of items.getEntries()) {
|
|
239
|
-
this.
|
|
240
|
+
this.eventHandler(item)
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
|
-
|
|
243
|
-
this.
|
|
243
|
+
|
|
244
|
+
this.observer = new PerformanceObserver(add.bind(this))
|
|
245
|
+
this.observer.observe({ entryTypes: this.entryTypes })
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
stop () {
|
|
249
|
+
if (this.observer) {
|
|
250
|
+
this.observer.disconnect()
|
|
251
|
+
this.observer = undefined
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class DatadogInstrumentationEventSource {
|
|
257
|
+
constructor (eventHandler) {
|
|
258
|
+
this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'net'].map(m => {
|
|
259
|
+
const Plugin = require(`./event_plugins/${m}`)
|
|
260
|
+
return new Plugin(eventHandler)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
this.started = false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
start () {
|
|
267
|
+
if (!this.started) {
|
|
268
|
+
this.plugins.forEach(p => p.configure({ enabled: true }))
|
|
269
|
+
this.started = true
|
|
270
|
+
}
|
|
244
271
|
}
|
|
245
272
|
|
|
246
273
|
stop () {
|
|
247
|
-
if (this.
|
|
248
|
-
this.
|
|
249
|
-
this.
|
|
274
|
+
if (this.started) {
|
|
275
|
+
this.plugins.forEach(p => p.configure({ enabled: false }))
|
|
276
|
+
this.started = false
|
|
250
277
|
}
|
|
251
278
|
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class CompositeEventSource {
|
|
282
|
+
constructor (sources) {
|
|
283
|
+
this.sources = sources
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
start () {
|
|
287
|
+
this.sources.forEach(s => s.start())
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
stop () {
|
|
291
|
+
this.sources.forEach(s => s.stop())
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* This class generates pprof files with timeline events. It combines an event
|
|
297
|
+
* source with an event serializer.
|
|
298
|
+
*/
|
|
299
|
+
class EventsProfiler {
|
|
300
|
+
constructor (options = {}) {
|
|
301
|
+
this.type = 'events'
|
|
302
|
+
this.eventSerializer = new EventSerializer()
|
|
303
|
+
|
|
304
|
+
const eventHandler = event => {
|
|
305
|
+
this.eventSerializer.addEvent(event)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (options.codeHotspotsEnabled) {
|
|
309
|
+
// Use Datadog instrumentation to collect events with span IDs. Still use
|
|
310
|
+
// Node API for GC events.
|
|
311
|
+
this.eventSource = new CompositeEventSource([
|
|
312
|
+
new DatadogInstrumentationEventSource(eventHandler),
|
|
313
|
+
new NodeApiEventSource(eventHandler, ['gc'])
|
|
314
|
+
])
|
|
315
|
+
} else {
|
|
316
|
+
// Use Node API instrumentation to collect events without span IDs
|
|
317
|
+
this.eventSource = new NodeApiEventSource(eventHandler)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
start () {
|
|
322
|
+
this.eventSource.start()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
stop () {
|
|
326
|
+
this.eventSource.stop()
|
|
327
|
+
}
|
|
252
328
|
|
|
253
329
|
profile (restart, startDate, endDate) {
|
|
254
330
|
if (!restart) {
|