dd-trace 2.11.0 → 2.12.1
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 +0 -1
- package/index.d.ts +3 -3
- package/package.json +4 -5
- package/packages/datadog-core/src/storage/async_hooks.js +4 -4
- package/packages/datadog-core/src/storage/async_resource.js +14 -4
- package/packages/datadog-instrumentations/src/connect.js +4 -4
- package/packages/datadog-instrumentations/src/couchbase.js +166 -61
- package/packages/datadog-instrumentations/src/fastify.js +12 -25
- package/packages/datadog-instrumentations/src/graphql.js +17 -5
- package/packages/datadog-instrumentations/src/koa.js +4 -4
- package/packages/datadog-instrumentations/src/mocha.js +88 -18
- package/packages/datadog-instrumentations/src/restify.js +4 -8
- package/packages/datadog-instrumentations/src/router.js +4 -4
- package/packages/datadog-plugin-couchbase/src/index.js +8 -10
- package/packages/datadog-plugin-graphql/src/resolve.js +2 -0
- package/packages/datadog-plugin-http/src/server.js +3 -8
- package/packages/datadog-plugin-mocha/src/index.js +80 -3
- package/packages/datadog-plugin-next/src/index.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +39 -10
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +111 -15
- package/packages/dd-trace/src/plugin_manager.js +49 -33
- package/packages/dd-trace/src/plugins/util/test.js +32 -1
- package/packages/dd-trace/src/plugins/util/web.js +25 -17
- package/packages/dd-trace/src/profiling/config.js +10 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -2
- package/packages/dd-trace/src/profiling/exporters/form-data.js +53 -0
- package/packages/dd-trace/src/profiling/index.js +2 -0
- package/packages/dd-trace/src/profiling/profiler.js +6 -1
- package/packages/dd-trace/src/profiling/profilers/cpu.js +126 -0
|
@@ -3,13 +3,25 @@ const { truncateSpan, normalizeSpan } = require('./tags-processors')
|
|
|
3
3
|
const Chunk = require('./chunk')
|
|
4
4
|
const { AgentEncoder } = require('./0.4')
|
|
5
5
|
const { version: ddTraceVersion } = require('../../../../package.json')
|
|
6
|
+
const id = require('../../../dd-trace/src/id')
|
|
6
7
|
|
|
7
8
|
const ENCODING_VERSION = 1
|
|
8
9
|
|
|
10
|
+
const ALLOWED_CONTENT_TYPES = ['test_session_end', 'test_suite_end', 'test']
|
|
11
|
+
|
|
12
|
+
const TEST_SUITE_KEYS_LENGTH = 11
|
|
13
|
+
const TEST_SESSION_KEYS_LENGTH = 10
|
|
14
|
+
|
|
15
|
+
const CHUNK_SIZE = 4 * 1024 * 1024 // 4MB
|
|
16
|
+
|
|
9
17
|
function formatSpan (span) {
|
|
18
|
+
let encodingVersion = ENCODING_VERSION
|
|
19
|
+
if (span.type === 'test' && span.meta && span.meta.test_session_id) {
|
|
20
|
+
encodingVersion = 2
|
|
21
|
+
}
|
|
10
22
|
return {
|
|
11
|
-
type: span.type
|
|
12
|
-
version:
|
|
23
|
+
type: ALLOWED_CONTENT_TYPES.includes(span.type) ? span.type : 'span',
|
|
24
|
+
version: encodingVersion,
|
|
13
25
|
content: normalizeSpan(truncateSpan(span))
|
|
14
26
|
}
|
|
15
27
|
}
|
|
@@ -21,8 +33,8 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
21
33
|
this.runtimeId = runtimeId
|
|
22
34
|
this.service = service
|
|
23
35
|
this.env = env
|
|
24
|
-
this._traceBytes = new Chunk()
|
|
25
|
-
this._stringBytes = new Chunk()
|
|
36
|
+
this._traceBytes = new Chunk(CHUNK_SIZE)
|
|
37
|
+
this._stringBytes = new Chunk(CHUNK_SIZE)
|
|
26
38
|
this._stringCount = 0
|
|
27
39
|
this._stringMap = {}
|
|
28
40
|
|
|
@@ -33,8 +45,69 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
33
45
|
this.reset()
|
|
34
46
|
}
|
|
35
47
|
|
|
48
|
+
_encodeTestSuite (bytes, content) {
|
|
49
|
+
this._encodeMapPrefix(bytes, TEST_SUITE_KEYS_LENGTH)
|
|
50
|
+
this._encodeString(bytes, 'type')
|
|
51
|
+
this._encodeString(bytes, content.type)
|
|
52
|
+
|
|
53
|
+
this._encodeString(bytes, 'test_session_id')
|
|
54
|
+
this._encodeId(bytes, content.trace_id)
|
|
55
|
+
|
|
56
|
+
this._encodeString(bytes, 'test_suite_id')
|
|
57
|
+
this._encodeId(bytes, content.span_id)
|
|
58
|
+
|
|
59
|
+
this._encodeString(bytes, 'error')
|
|
60
|
+
this._encodeNumber(bytes, content.error)
|
|
61
|
+
this._encodeString(bytes, 'name')
|
|
62
|
+
this._encodeString(bytes, content.name)
|
|
63
|
+
this._encodeString(bytes, 'service')
|
|
64
|
+
this._encodeString(bytes, content.service)
|
|
65
|
+
this._encodeString(bytes, 'resource')
|
|
66
|
+
this._encodeString(bytes, content.resource)
|
|
67
|
+
this._encodeString(bytes, 'start')
|
|
68
|
+
this._encodeNumber(bytes, content.start)
|
|
69
|
+
this._encodeString(bytes, 'duration')
|
|
70
|
+
this._encodeNumber(bytes, content.duration)
|
|
71
|
+
this._encodeString(bytes, 'meta')
|
|
72
|
+
this._encodeMap(bytes, content.meta)
|
|
73
|
+
this._encodeString(bytes, 'metrics')
|
|
74
|
+
this._encodeMap(bytes, content.metrics)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_encodeTestSession (bytes, content) {
|
|
78
|
+
this._encodeMapPrefix(bytes, TEST_SESSION_KEYS_LENGTH)
|
|
79
|
+
this._encodeString(bytes, 'type')
|
|
80
|
+
this._encodeString(bytes, content.type)
|
|
81
|
+
|
|
82
|
+
this._encodeString(bytes, 'test_session_id')
|
|
83
|
+
this._encodeId(bytes, content.trace_id)
|
|
84
|
+
|
|
85
|
+
this._encodeString(bytes, 'error')
|
|
86
|
+
this._encodeNumber(bytes, content.error)
|
|
87
|
+
this._encodeString(bytes, 'name')
|
|
88
|
+
this._encodeString(bytes, content.name)
|
|
89
|
+
this._encodeString(bytes, 'service')
|
|
90
|
+
this._encodeString(bytes, content.service)
|
|
91
|
+
this._encodeString(bytes, 'resource')
|
|
92
|
+
this._encodeString(bytes, content.resource)
|
|
93
|
+
this._encodeString(bytes, 'start')
|
|
94
|
+
this._encodeNumber(bytes, content.start)
|
|
95
|
+
this._encodeString(bytes, 'duration')
|
|
96
|
+
this._encodeNumber(bytes, content.duration)
|
|
97
|
+
this._encodeString(bytes, 'meta')
|
|
98
|
+
this._encodeMap(bytes, content.meta)
|
|
99
|
+
this._encodeString(bytes, 'metrics')
|
|
100
|
+
this._encodeMap(bytes, content.metrics)
|
|
101
|
+
}
|
|
102
|
+
|
|
36
103
|
_encodeEventContent (bytes, content) {
|
|
37
|
-
|
|
104
|
+
const keysLength = Object.keys(content).length
|
|
105
|
+
if (content.meta.test_session_id) {
|
|
106
|
+
this._encodeMapPrefix(bytes, keysLength + 2)
|
|
107
|
+
} else {
|
|
108
|
+
this._encodeMapPrefix(bytes, keysLength)
|
|
109
|
+
}
|
|
110
|
+
|
|
38
111
|
if (content.type) {
|
|
39
112
|
this._encodeString(bytes, 'type')
|
|
40
113
|
this._encodeString(bytes, content.type)
|
|
@@ -57,6 +130,24 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
57
130
|
this._encodeNumber(bytes, content.start)
|
|
58
131
|
this._encodeString(bytes, 'duration')
|
|
59
132
|
this._encodeNumber(bytes, content.duration)
|
|
133
|
+
/**
|
|
134
|
+
* We include `test_session_id` and `test_suite_id`
|
|
135
|
+
* in the root of the event by passing them via the `meta` dict.
|
|
136
|
+
* This is to avoid changing the span format in packages/dd-trace/src/format.js,
|
|
137
|
+
* which can have undesired side effects in other products.
|
|
138
|
+
* But `test_session_id` and `test_suite_id` are *not* supposed to be in `meta`,
|
|
139
|
+
* so we delete them before enconding the dictionary.
|
|
140
|
+
* TODO: find a better way to do this.
|
|
141
|
+
*/
|
|
142
|
+
if (content.meta.test_session_id) {
|
|
143
|
+
this._encodeString(bytes, 'test_session_id')
|
|
144
|
+
this._encodeId(bytes, id(content.meta.test_session_id))
|
|
145
|
+
delete content.meta.test_session_id
|
|
146
|
+
|
|
147
|
+
this._encodeString(bytes, 'test_suite_id')
|
|
148
|
+
this._encodeId(bytes, id(content.meta.test_suite_id))
|
|
149
|
+
delete content.meta.test_suite_id
|
|
150
|
+
}
|
|
60
151
|
this._encodeString(bytes, 'meta')
|
|
61
152
|
this._encodeMap(bytes, content.meta)
|
|
62
153
|
this._encodeString(bytes, 'metrics')
|
|
@@ -64,7 +155,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
64
155
|
}
|
|
65
156
|
|
|
66
157
|
_encodeEvent (bytes, event) {
|
|
67
|
-
this._encodeMapPrefix(bytes, event)
|
|
158
|
+
this._encodeMapPrefix(bytes, Object.keys(event).length)
|
|
68
159
|
this._encodeString(bytes, 'type')
|
|
69
160
|
this._encodeString(bytes, event.type)
|
|
70
161
|
|
|
@@ -72,7 +163,13 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
72
163
|
this._encodeNumber(bytes, event.version)
|
|
73
164
|
|
|
74
165
|
this._encodeString(bytes, 'content')
|
|
75
|
-
|
|
166
|
+
if (event.type === 'span' || event.type === 'test') {
|
|
167
|
+
this._encodeEventContent(bytes, event.content)
|
|
168
|
+
} else if (event.type === 'test_suite_end') {
|
|
169
|
+
this._encodeTestSuite(bytes, event.content)
|
|
170
|
+
} else if (event.type === 'test_session_end') {
|
|
171
|
+
this._encodeTestSession(bytes, event.content)
|
|
172
|
+
}
|
|
76
173
|
}
|
|
77
174
|
|
|
78
175
|
_encodeNumber (bytes, value) {
|
|
@@ -107,18 +204,17 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
107
204
|
buffer[offset + 8] = lo
|
|
108
205
|
}
|
|
109
206
|
|
|
110
|
-
_encodeMapPrefix (bytes,
|
|
111
|
-
const keys = Object.keys(map)
|
|
207
|
+
_encodeMapPrefix (bytes, keysLength) {
|
|
112
208
|
const buffer = bytes.buffer
|
|
113
209
|
const offset = bytes.length
|
|
114
210
|
|
|
115
211
|
bytes.reserve(5)
|
|
116
212
|
bytes.length += 5
|
|
117
213
|
buffer[offset] = 0xdf
|
|
118
|
-
buffer[offset + 1] =
|
|
119
|
-
buffer[offset + 2] =
|
|
120
|
-
buffer[offset + 3] =
|
|
121
|
-
buffer[offset + 4] =
|
|
214
|
+
buffer[offset + 1] = keysLength >> 24
|
|
215
|
+
buffer[offset + 2] = keysLength >> 16
|
|
216
|
+
buffer[offset + 3] = keysLength >> 8
|
|
217
|
+
buffer[offset + 4] = keysLength
|
|
122
218
|
}
|
|
123
219
|
|
|
124
220
|
_encode (bytes, trace) {
|
|
@@ -171,11 +267,11 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
171
267
|
payload.metadata['*']['runtime-id'] = this.runtimeId
|
|
172
268
|
}
|
|
173
269
|
|
|
174
|
-
this._encodeMapPrefix(bytes, payload)
|
|
270
|
+
this._encodeMapPrefix(bytes, Object.keys(payload).length)
|
|
175
271
|
this._encodeString(bytes, 'version')
|
|
176
272
|
this._encodeNumber(bytes, payload.version)
|
|
177
273
|
this._encodeString(bytes, 'metadata')
|
|
178
|
-
this._encodeMapPrefix(bytes, payload.metadata)
|
|
274
|
+
this._encodeMapPrefix(bytes, Object.keys(payload.metadata).length)
|
|
179
275
|
this._encodeString(bytes, '*')
|
|
180
276
|
this._encodeMap(bytes, payload.metadata['*'])
|
|
181
277
|
this._encodeString(bytes, 'events')
|
|
@@ -36,61 +36,77 @@ const collectDisabledPlugins = () => {
|
|
|
36
36
|
// TODO this must always be a singleton.
|
|
37
37
|
module.exports = class PluginManager {
|
|
38
38
|
constructor (tracer) {
|
|
39
|
+
this._tracer = tracer
|
|
39
40
|
this._pluginsByName = {}
|
|
40
41
|
this._configsByName = {}
|
|
41
|
-
|
|
42
|
-
const _disabledPlugins = collectDisabledPlugins()
|
|
43
|
-
|
|
44
|
-
for (const PluginClass of Object.values(plugins)) {
|
|
45
|
-
/**
|
|
46
|
-
* disabling the plugin here instead of in `configure` so we don't waste subscriber
|
|
47
|
-
* resources on a plugin that will eventually be disabled anyways
|
|
48
|
-
*/
|
|
49
|
-
if (_disabledPlugins.has(PluginClass.name)) {
|
|
50
|
-
log.debug(`Plugin "${PluginClass.name}" was disabled via configuration option.`)
|
|
51
|
-
continue
|
|
52
|
-
}
|
|
53
|
-
if (typeof PluginClass !== 'function') continue
|
|
54
|
-
this._pluginsByName[PluginClass.name] = new PluginClass(tracer)
|
|
55
|
-
this._configsByName[PluginClass.name] = {}
|
|
56
|
-
}
|
|
42
|
+
this._disabledPlugins = collectDisabledPlugins()
|
|
57
43
|
}
|
|
58
44
|
|
|
59
45
|
// like instrumenter.use()
|
|
60
46
|
configurePlugin (name, pluginConfig) {
|
|
61
|
-
if (!(name in this._pluginsByName)) return
|
|
62
47
|
if (typeof pluginConfig === 'boolean') {
|
|
63
48
|
pluginConfig = { enabled: pluginConfig }
|
|
64
49
|
}
|
|
50
|
+
if (!pluginConfig) {
|
|
51
|
+
pluginConfig = { enabled: true }
|
|
52
|
+
}
|
|
65
53
|
|
|
66
|
-
|
|
54
|
+
this._configsByName[name] = {
|
|
67
55
|
...this._configsByName[name],
|
|
68
56
|
...pluginConfig
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
this._pluginsByName[name]
|
|
59
|
+
if (this._pluginsByName[name]) {
|
|
60
|
+
this._pluginsByName[name].configure(getConfig(name, this._configsByName[name]))
|
|
61
|
+
}
|
|
72
62
|
}
|
|
73
63
|
|
|
74
64
|
// like instrumenter.enable()
|
|
75
|
-
configure (config) {
|
|
76
|
-
const { logInjection, serviceMapping } = config
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
65
|
+
configure (config = {}) {
|
|
66
|
+
const { logInjection, serviceMapping, experimental } = config
|
|
67
|
+
|
|
68
|
+
for (const PluginClass of Object.values(plugins)) {
|
|
69
|
+
const name = PluginClass.name
|
|
70
|
+
|
|
71
|
+
if (this._disabledPlugins.has(name)) {
|
|
72
|
+
log.debug(`Plugin "${name}" was disabled via configuration option.`)
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof PluginClass !== 'function') continue
|
|
77
|
+
|
|
78
|
+
this._pluginsByName[name] = new PluginClass(this._tracer)
|
|
79
|
+
|
|
80
|
+
if (config.plugins === false) continue
|
|
81
|
+
|
|
82
|
+
const pluginConfig = {
|
|
83
|
+
...this._configsByName[name]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (logInjection !== undefined) {
|
|
87
|
+
pluginConfig.logInjection = logInjection
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// TODO: update so that it's available for every CI Visibility's plugin
|
|
91
|
+
if (name === 'mocha') {
|
|
92
|
+
pluginConfig.isAgentlessEnabled = experimental && experimental.exporter === 'datadog'
|
|
88
93
|
}
|
|
94
|
+
|
|
95
|
+
if (serviceMapping && serviceMapping[name]) {
|
|
96
|
+
pluginConfig.service = serviceMapping[name]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.configurePlugin(name, pluginConfig)
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
// This is basically just for testing. like intrumenter.disable()
|
|
93
104
|
destroy () {
|
|
94
|
-
for (const name in this._pluginsByName)
|
|
105
|
+
for (const name in this._pluginsByName) {
|
|
106
|
+
this._pluginsByName[name].configure({ enabled: false })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this._pluginsByName = {}
|
|
110
|
+
this._configsByName = {}
|
|
95
111
|
}
|
|
96
112
|
}
|
|
@@ -36,6 +36,9 @@ const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
|
|
|
36
36
|
const TEST_CODE_OWNERS = 'test.codeowners'
|
|
37
37
|
const TEST_SOURCE_FILE = 'test.source.file'
|
|
38
38
|
const LIBRARY_VERSION = 'library_version'
|
|
39
|
+
const TEST_COMMAND = 'test.command'
|
|
40
|
+
const TEST_SESSION_ID = 'test_session_id'
|
|
41
|
+
const TEST_SUITE_ID = 'test_suite_id'
|
|
39
42
|
|
|
40
43
|
const ERROR_TYPE = 'error.type'
|
|
41
44
|
const ERROR_MESSAGE = 'error.msg'
|
|
@@ -70,7 +73,12 @@ module.exports = {
|
|
|
70
73
|
getTestSuitePath,
|
|
71
74
|
getCodeOwnersFileEntries,
|
|
72
75
|
getCodeOwnersForFilename,
|
|
73
|
-
getTestCommonTags
|
|
76
|
+
getTestCommonTags,
|
|
77
|
+
getTestSessionCommonTags,
|
|
78
|
+
getTestSuiteCommonTags,
|
|
79
|
+
TEST_COMMAND,
|
|
80
|
+
TEST_SESSION_ID,
|
|
81
|
+
TEST_SUITE_ID
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function getTestEnvironmentMetadata (testFramework, config) {
|
|
@@ -225,3 +233,26 @@ function getCodeOwnersForFilename (filename, entries) {
|
|
|
225
233
|
}
|
|
226
234
|
return null
|
|
227
235
|
}
|
|
236
|
+
|
|
237
|
+
function getTestSessionCommonTags (command, version) {
|
|
238
|
+
return {
|
|
239
|
+
[SPAN_TYPE]: 'test_session_end',
|
|
240
|
+
[TEST_TYPE]: 'test',
|
|
241
|
+
[RESOURCE_NAME]: `test_session.${command}`,
|
|
242
|
+
[TEST_FRAMEWORK_VERSION]: version,
|
|
243
|
+
[LIBRARY_VERSION]: ddTraceVersion,
|
|
244
|
+
[TEST_COMMAND]: command
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getTestSuiteCommonTags (command, version, testSuite) {
|
|
249
|
+
return {
|
|
250
|
+
[SPAN_TYPE]: 'test_suite_end',
|
|
251
|
+
[TEST_TYPE]: 'test',
|
|
252
|
+
[RESOURCE_NAME]: `test_suite.${testSuite}`,
|
|
253
|
+
[TEST_FRAMEWORK_VERSION]: version,
|
|
254
|
+
[LIBRARY_VERSION]: ddTraceVersion,
|
|
255
|
+
[TEST_SUITE]: testSuite,
|
|
256
|
+
[TEST_COMMAND]: command
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -259,9 +259,9 @@ const web = {
|
|
|
259
259
|
const context = contexts.get(req)
|
|
260
260
|
const span = context.span
|
|
261
261
|
const error = context.error
|
|
262
|
-
const
|
|
262
|
+
const hasExistingError = span.context()._tags['error'] || span.context()._tags['error.msg']
|
|
263
263
|
|
|
264
|
-
if (!
|
|
264
|
+
if (!hasExistingError && !context.config.validateStatus(statusCode)) {
|
|
265
265
|
span.setTag(ERROR, error || true)
|
|
266
266
|
}
|
|
267
267
|
},
|
|
@@ -270,7 +270,10 @@ const web = {
|
|
|
270
270
|
addError (req, error) {
|
|
271
271
|
if (error instanceof Error) {
|
|
272
272
|
const context = contexts.get(req)
|
|
273
|
-
|
|
273
|
+
|
|
274
|
+
if (context) {
|
|
275
|
+
context.error = error
|
|
276
|
+
}
|
|
274
277
|
}
|
|
275
278
|
},
|
|
276
279
|
|
|
@@ -298,6 +301,23 @@ const web = {
|
|
|
298
301
|
context.span.finish()
|
|
299
302
|
context.finished = true
|
|
300
303
|
},
|
|
304
|
+
|
|
305
|
+
finishAll (context) {
|
|
306
|
+
const { req, res } = context
|
|
307
|
+
|
|
308
|
+
for (const beforeEnd of context.beforeEnd) {
|
|
309
|
+
beforeEnd()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
web.finishMiddleware(context)
|
|
313
|
+
|
|
314
|
+
if (incomingHttpRequestEnd.hasSubscribers) {
|
|
315
|
+
incomingHttpRequestEnd.publish({ req, res })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
web.finishSpan(context)
|
|
319
|
+
},
|
|
320
|
+
|
|
301
321
|
wrapWriteHead (context) {
|
|
302
322
|
const { req, res } = context
|
|
303
323
|
const writeHead = res.writeHead
|
|
@@ -318,21 +338,9 @@ const web = {
|
|
|
318
338
|
},
|
|
319
339
|
wrapRes (context, req, res, end) {
|
|
320
340
|
return function () {
|
|
321
|
-
|
|
322
|
-
beforeEnd()
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
web.finishMiddleware(context)
|
|
326
|
-
|
|
327
|
-
if (incomingHttpRequestEnd.hasSubscribers) {
|
|
328
|
-
incomingHttpRequestEnd.publish({ req, res })
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const returnValue = end.apply(res, arguments)
|
|
332
|
-
|
|
333
|
-
web.finishSpan(context)
|
|
341
|
+
web.finishAll(context)
|
|
334
342
|
|
|
335
|
-
return
|
|
343
|
+
return end.apply(res, arguments)
|
|
336
344
|
}
|
|
337
345
|
},
|
|
338
346
|
wrapEnd (context) {
|
|
@@ -6,6 +6,7 @@ const { URL } = require('url')
|
|
|
6
6
|
const { AgentExporter } = require('./exporters/agent')
|
|
7
7
|
const { FileExporter } = require('./exporters/file')
|
|
8
8
|
const { ConsoleLogger } = require('./loggers/console')
|
|
9
|
+
const CpuProfiler = require('./profilers/cpu')
|
|
9
10
|
const WallProfiler = require('./profilers/wall')
|
|
10
11
|
const SpaceProfiler = require('./profilers/space')
|
|
11
12
|
const { tagger } = require('./tagger')
|
|
@@ -13,6 +14,7 @@ const { tagger } = require('./tagger')
|
|
|
13
14
|
const {
|
|
14
15
|
DD_PROFILING_ENABLED,
|
|
15
16
|
DD_PROFILING_PROFILERS,
|
|
17
|
+
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
16
18
|
DD_ENV,
|
|
17
19
|
DD_TAGS,
|
|
18
20
|
DD_SERVICE,
|
|
@@ -37,6 +39,8 @@ class Config {
|
|
|
37
39
|
DD_PROFILING_UPLOAD_TIMEOUT, 60 * 1000)
|
|
38
40
|
const sourceMap = coalesce(options.sourceMap,
|
|
39
41
|
DD_PROFILING_SOURCE_MAP, true)
|
|
42
|
+
const endpointCollection = coalesce(options.endpointCollection,
|
|
43
|
+
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, false)
|
|
40
44
|
|
|
41
45
|
this.enabled = String(enabled) !== 'false'
|
|
42
46
|
this.service = service
|
|
@@ -53,6 +57,7 @@ class Config {
|
|
|
53
57
|
this.flushInterval = flushInterval
|
|
54
58
|
this.uploadTimeout = uploadTimeout
|
|
55
59
|
this.sourceMap = sourceMap
|
|
60
|
+
this.endpointCollection = endpointCollection
|
|
56
61
|
|
|
57
62
|
const hostname = coalesce(options.hostname, DD_AGENT_HOST, 'localhost')
|
|
58
63
|
const port = coalesce(options.port, DD_TRACE_AGENT_PORT, 8126)
|
|
@@ -64,8 +69,8 @@ class Config {
|
|
|
64
69
|
], this)
|
|
65
70
|
|
|
66
71
|
const profilers = coalesce(options.profilers, DD_PROFILING_PROFILERS, [
|
|
67
|
-
new WallProfiler(),
|
|
68
|
-
new SpaceProfiler()
|
|
72
|
+
new WallProfiler(this),
|
|
73
|
+
new SpaceProfiler(this)
|
|
69
74
|
])
|
|
70
75
|
|
|
71
76
|
this.profilers = ensureProfilers(profilers, this)
|
|
@@ -100,10 +105,13 @@ function ensureExporters (exporters, options) {
|
|
|
100
105
|
|
|
101
106
|
function getProfiler (name, options) {
|
|
102
107
|
switch (name) {
|
|
108
|
+
case 'cpu':
|
|
103
109
|
case 'wall':
|
|
104
110
|
return new WallProfiler(options)
|
|
105
111
|
case 'space':
|
|
106
112
|
return new SpaceProfiler(options)
|
|
113
|
+
case 'cpu-experimental':
|
|
114
|
+
return new CpuProfiler(options)
|
|
107
115
|
default:
|
|
108
116
|
options.logger.error(`Unknown profiler "${name}"`)
|
|
109
117
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const retry = require('retry')
|
|
4
4
|
const { request } = require('http')
|
|
5
|
-
const FormData = require('form-data')
|
|
5
|
+
const FormData = require('./form-data')
|
|
6
6
|
|
|
7
7
|
// TODO: avoid using dd-trace internals. Make this a separate module?
|
|
8
8
|
const docker = require('../../exporters/common/docker')
|
|
@@ -22,7 +22,6 @@ function sendRequest (options, form, callback) {
|
|
|
22
22
|
})
|
|
23
23
|
req.on('error', callback)
|
|
24
24
|
if (form) form.pipe(req)
|
|
25
|
-
req.end()
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function getBody (stream, callback) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Readable } = require('stream')
|
|
4
|
+
const id = require('../../id')
|
|
5
|
+
|
|
6
|
+
class FormData extends Readable {
|
|
7
|
+
constructor () {
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
this._boundary = id().toString()
|
|
11
|
+
this._data = []
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
append (key, value, options = {}) {
|
|
15
|
+
this._appendBoundary()
|
|
16
|
+
|
|
17
|
+
if (options.filename) {
|
|
18
|
+
this._appendFile(key, value, options)
|
|
19
|
+
} else {
|
|
20
|
+
this._appendMetadata(key, value, options)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getHeaders () {
|
|
25
|
+
return { 'Content-Type': 'multipart/form-data; boundary=' + this._boundary }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_appendBoundary () {
|
|
29
|
+
this._data.push(`--${this._boundary}\r\n`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_appendMetadata (key, value) {
|
|
33
|
+
this._data.push(`Content-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_appendFile (key, value, { filename, contentType = 'application/octet-stream' }) {
|
|
37
|
+
this._data.push(`Content-Disposition: form-data; name="${key}"; filename="${filename}"\r\n`)
|
|
38
|
+
this._data.push(`Content-Type: ${contentType}\r\n\r\n`)
|
|
39
|
+
this._data.push(value)
|
|
40
|
+
this._data.push('\r\n')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_read () {
|
|
44
|
+
this.push(this._data.shift())
|
|
45
|
+
|
|
46
|
+
if (this._data.length === 0) {
|
|
47
|
+
this.push(`--${this._boundary}--\r\n`)
|
|
48
|
+
this.push(null)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = FormData
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Profiler } = require('./profiler')
|
|
4
|
+
const CpuProfiler = require('./profilers/cpu')
|
|
4
5
|
const WallProfiler = require('./profilers/wall')
|
|
5
6
|
const SpaceProfiler = require('./profilers/space')
|
|
6
7
|
const { AgentExporter } = require('./exporters/agent')
|
|
@@ -13,6 +14,7 @@ module.exports = {
|
|
|
13
14
|
profiler,
|
|
14
15
|
AgentExporter,
|
|
15
16
|
FileExporter,
|
|
17
|
+
CpuProfiler,
|
|
16
18
|
WallProfiler,
|
|
17
19
|
SpaceProfiler,
|
|
18
20
|
ConsoleLogger
|
|
@@ -91,7 +91,12 @@ class Profiler extends EventEmitter {
|
|
|
91
91
|
if (!profile) continue
|
|
92
92
|
|
|
93
93
|
profiles[profiler.type] = await profiler.encode(profile)
|
|
94
|
-
this._logger.debug(
|
|
94
|
+
this._logger.debug(() => {
|
|
95
|
+
const profileJson = JSON.stringify(profile, (key, value) => {
|
|
96
|
+
return typeof value === 'bigint' ? value.toString() : value
|
|
97
|
+
})
|
|
98
|
+
return `Collected ${profiler.type} profile: ` + profileJson
|
|
99
|
+
})
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
this._capture(this._config.flushInterval)
|