dd-trace 2.11.0 → 2.12.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.
@@ -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(`Collected ${profiler.type} profile: ` + JSON.stringify(profile))
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)
@@ -0,0 +1,126 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../../../datadog-core')
4
+
5
+ const dc = require('diagnostics_channel')
6
+
7
+ const beforeCh = dc.channel('dd-trace:storage:before')
8
+ const afterCh = dc.channel('dd-trace:storage:after')
9
+
10
+ function getActiveSpan () {
11
+ const store = storage.getStore()
12
+ if (!store) return
13
+ return store.span
14
+ }
15
+
16
+ function getStartedSpans (activeSpan) {
17
+ const context = activeSpan.context()
18
+ if (!context) return
19
+ return context._trace.started
20
+ }
21
+
22
+ function getSpanContextTags (span) {
23
+ return span._context()._tags
24
+ }
25
+
26
+ function isWebServerSpan (tags) {
27
+ return tags['span.type'] === 'web'
28
+ }
29
+
30
+ function endpointNameFromTags (tags) {
31
+ return tags['resource.name'] || [
32
+ tags['http.method'],
33
+ tags['http.route']
34
+ ].filter(v => v).join(' ')
35
+ }
36
+
37
+ class NativeCpuProfiler {
38
+ constructor (options = {}) {
39
+ this.type = 'cpu'
40
+ this._frequency = options.frequency || 99
41
+ this._mapper = undefined
42
+ this._pprof = undefined
43
+ this._started = false
44
+ this._cpuProfiler = undefined
45
+ this._endpointCollection = options.endpointCollection
46
+
47
+ // Bind to this so the same value can be used to unsubscribe later
48
+ this._enter = this._enter.bind(this)
49
+ this._exit = this._exit.bind(this)
50
+ }
51
+
52
+ _enter () {
53
+ if (!this._cpuProfiler) return
54
+
55
+ const active = getActiveSpan()
56
+ if (!active) return
57
+
58
+ const activeCtx = active._context()
59
+ if (!activeCtx) return
60
+
61
+ const spans = getStartedSpans(active)
62
+ if (!spans || !spans.length) return
63
+
64
+ const firstCtx = spans[0]._context()
65
+ if (!firstCtx) return
66
+
67
+ const labels = {
68
+ 'local root span id': firstCtx.toSpanId(),
69
+ 'span id': activeCtx.toSpanId()
70
+ }
71
+
72
+ if (this._endpointCollection) {
73
+ const webServerTags = spans
74
+ .map(getSpanContextTags)
75
+ .filter(isWebServerSpan)[0]
76
+
77
+ if (webServerTags) {
78
+ labels['trace endpoint'] = endpointNameFromTags(webServerTags)
79
+ }
80
+ }
81
+
82
+ this._cpuProfiler.labels = labels
83
+ }
84
+
85
+ _exit () {
86
+ if (!this._cpuProfiler) return
87
+ this._cpuProfiler.labels = {}
88
+ }
89
+
90
+ start ({ mapper } = {}) {
91
+ if (this._started) return
92
+ this._started = true
93
+
94
+ this._mapper = mapper
95
+ if (!this._pprof) {
96
+ this._pprof = require('@datadog/pprof')
97
+ this._cpuProfiler = new this._pprof.CpuProfiler()
98
+ }
99
+
100
+ this._cpuProfiler.start(this._frequency)
101
+
102
+ this._enter()
103
+ beforeCh.subscribe(this._enter)
104
+ afterCh.subscribe(this._exit)
105
+ }
106
+
107
+ profile () {
108
+ if (!this._started) return
109
+ return this._cpuProfiler.profile()
110
+ }
111
+
112
+ encode (profile) {
113
+ return this._pprof.encode(profile)
114
+ }
115
+
116
+ stop () {
117
+ if (!this._started) return
118
+ this._started = false
119
+
120
+ this._cpuProfiler.stop()
121
+ beforeCh.unsubscribe(this._enter)
122
+ afterCh.unsubscribe(this._exit)
123
+ }
124
+ }
125
+
126
+ module.exports = NativeCpuProfiler