dd-trace 1.5.0 → 1.7.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.
Files changed (36) hide show
  1. package/ci/cypress/plugin.js +3 -0
  2. package/ci/cypress/support.js +1 -0
  3. package/ci/init.js +13 -0
  4. package/ci/jest/env.js +14 -0
  5. package/index.d.ts +5 -5
  6. package/package.json +6 -5
  7. package/packages/datadog-plugin-cucumber/src/index.js +6 -4
  8. package/packages/datadog-plugin-cypress/src/plugin.js +17 -5
  9. package/packages/datadog-plugin-cypress/src/support.js +21 -6
  10. package/packages/datadog-plugin-graphql/src/index.js +16 -10
  11. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  12. package/packages/datadog-plugin-jest/src/jest-environment.js +9 -5
  13. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +13 -4
  14. package/packages/datadog-plugin-mocha/src/index.js +12 -3
  15. package/packages/datadog-plugin-redis/src/index.js +31 -1
  16. package/packages/dd-trace/lib/version.js +1 -1
  17. package/packages/dd-trace/src/constants.js +6 -1
  18. package/packages/dd-trace/src/encode/0.4.js +84 -23
  19. package/packages/dd-trace/src/encode/chunk.js +12 -14
  20. package/packages/dd-trace/src/format.js +12 -0
  21. package/packages/dd-trace/src/id.js +22 -18
  22. package/packages/dd-trace/src/instrumenter.js +2 -2
  23. package/packages/dd-trace/src/opentracing/propagation/text_map.js +34 -0
  24. package/packages/dd-trace/src/opentracing/span_context.js +2 -1
  25. package/packages/dd-trace/src/{.DS_Store → plugins/.DS_Store} +0 -0
  26. package/packages/dd-trace/src/plugins/util/ci.js +22 -9
  27. package/packages/dd-trace/src/plugins/util/git.js +11 -25
  28. package/packages/dd-trace/src/plugins/util/redis.js +0 -2
  29. package/packages/dd-trace/src/plugins/util/test.js +22 -4
  30. package/packages/dd-trace/src/plugins/util/user-provided-git.js +72 -0
  31. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  32. package/packages/dd-trace/src/priority_sampler.js +71 -19
  33. package/packages/dd-trace/src/profiling/exporters/agent.js +41 -33
  34. package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -3
  35. package/packages/dd-trace/src/proxy.js +1 -1
  36. package/packages/dd-trace/src/plugins/util/ci-app-spec.json +0 -35
@@ -0,0 +1,72 @@
1
+ const {
2
+ GIT_COMMIT_SHA,
3
+ GIT_BRANCH,
4
+ GIT_REPOSITORY_URL,
5
+ GIT_TAG,
6
+ GIT_COMMIT_MESSAGE,
7
+ GIT_COMMIT_COMMITTER_DATE,
8
+ GIT_COMMIT_COMMITTER_EMAIL,
9
+ GIT_COMMIT_COMMITTER_NAME,
10
+ GIT_COMMIT_AUTHOR_DATE,
11
+ GIT_COMMIT_AUTHOR_EMAIL,
12
+ GIT_COMMIT_AUTHOR_NAME
13
+ } = require('./tags')
14
+
15
+ const { normalizeRef } = require('./ci')
16
+
17
+ function removeEmptyValues (tags) {
18
+ return Object.keys(tags).reduce((filteredTags, tag) => {
19
+ if (!tags[tag]) {
20
+ return filteredTags
21
+ }
22
+ return {
23
+ ...filteredTags,
24
+ [tag]: tags[tag]
25
+ }
26
+ }, {})
27
+ }
28
+
29
+ function getUserProviderGitMetadata () {
30
+ const {
31
+ DD_GIT_COMMIT_SHA,
32
+ DD_GIT_BRANCH,
33
+ DD_GIT_REPOSITORY_URL,
34
+ DD_GIT_TAG,
35
+ DD_GIT_COMMIT_MESSAGE,
36
+ DD_GIT_COMMIT_COMMITTER_NAME,
37
+ DD_GIT_COMMIT_COMMITTER_EMAIL,
38
+ DD_GIT_COMMIT_COMMITTER_DATE,
39
+ DD_GIT_COMMIT_AUTHOR_NAME,
40
+ DD_GIT_COMMIT_AUTHOR_EMAIL,
41
+ DD_GIT_COMMIT_AUTHOR_DATE
42
+ } = process.env
43
+
44
+ let branch = normalizeRef(DD_GIT_BRANCH)
45
+ let tag = normalizeRef(DD_GIT_TAG)
46
+
47
+ if (DD_GIT_TAG) {
48
+ branch = undefined
49
+ }
50
+
51
+ // if DD_GIT_BRANCH is a tag, we associate its value to TAG instead of BRANCH
52
+ if ((DD_GIT_BRANCH || '').includes('origin/tags') || (DD_GIT_BRANCH || '').includes('refs/heads/tags')) {
53
+ branch = undefined
54
+ tag = normalizeRef(DD_GIT_BRANCH)
55
+ }
56
+
57
+ return removeEmptyValues({
58
+ [GIT_COMMIT_SHA]: DD_GIT_COMMIT_SHA,
59
+ [GIT_BRANCH]: branch,
60
+ [GIT_REPOSITORY_URL]: DD_GIT_REPOSITORY_URL,
61
+ [GIT_TAG]: tag,
62
+ [GIT_COMMIT_MESSAGE]: DD_GIT_COMMIT_MESSAGE,
63
+ [GIT_COMMIT_COMMITTER_NAME]: DD_GIT_COMMIT_COMMITTER_NAME,
64
+ [GIT_COMMIT_COMMITTER_DATE]: DD_GIT_COMMIT_COMMITTER_DATE,
65
+ [GIT_COMMIT_COMMITTER_EMAIL]: DD_GIT_COMMIT_COMMITTER_EMAIL,
66
+ [GIT_COMMIT_AUTHOR_NAME]: DD_GIT_COMMIT_AUTHOR_NAME,
67
+ [GIT_COMMIT_AUTHOR_EMAIL]: DD_GIT_COMMIT_AUTHOR_EMAIL,
68
+ [GIT_COMMIT_AUTHOR_DATE]: DD_GIT_COMMIT_AUTHOR_DATE
69
+ })
70
+ }
71
+
72
+ module.exports = { getUserProviderGitMetadata }
@@ -181,7 +181,7 @@ const web = {
181
181
 
182
182
  // Extract the parent span from the headers and start a new span as its child
183
183
  startChildSpan (tracer, name, headers) {
184
- const childOf = tracer.extract(FORMAT_HTTP_HEADERS, headers)
184
+ const childOf = tracer.scope().active() || tracer.extract(FORMAT_HTTP_HEADERS, headers)
185
185
  const span = tracer.startSpan(name, { childOf })
186
186
 
187
187
  return span
@@ -1,14 +1,20 @@
1
1
  'use strict'
2
2
 
3
+ const coalesce = require('koalas')
3
4
  const RateLimiter = require('./rate_limiter')
4
5
  const Sampler = require('./sampler')
5
6
  const ext = require('../../../ext')
6
7
  const { setSamplingRules } = require('./startup-log')
7
8
 
8
9
  const {
10
+ SAMPLING_MECHANISM_DEFAULT,
11
+ SAMPLING_MECHANISM_AGENT,
12
+ SAMPLING_MECHANISM_RULE,
13
+ SAMPLING_MECHANISM_MANUAL,
9
14
  SAMPLING_RULE_DECISION,
10
15
  SAMPLING_LIMIT_DECISION,
11
- SAMPLING_AGENT_DECISION
16
+ SAMPLING_AGENT_DECISION,
17
+ UPSTREAM_SERVICES_KEY
12
18
  } = require('./constants')
13
19
 
14
20
  const SERVICE_NAME = ext.tags.SERVICE_NAME
@@ -21,6 +27,9 @@ const AUTO_KEEP = ext.priority.AUTO_KEEP
21
27
  const USER_KEEP = ext.priority.USER_KEEP
22
28
  const DEFAULT_KEY = 'service:,env:'
23
29
 
30
+ const defaultSampler = new Sampler(AUTO_KEEP)
31
+ const serviceNames = new Map()
32
+
24
33
  class PrioritySampler {
25
34
  constructor (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
26
35
  this._env = env
@@ -33,12 +42,8 @@ class PrioritySampler {
33
42
  }
34
43
 
35
44
  isSampled (span) {
36
- const context = this._getContext(span)
37
- const rule = this._findRule(context)
38
-
39
- return rule
40
- ? this._isSampledByRule(context, rule) && this._isSampledByRateLimit(context)
41
- : this._isSampledByAgent(context)
45
+ const priority = this._getPriorityFromAuto(span)
46
+ return priority === USER_KEEP || priority === AUTO_KEEP
42
47
  }
43
48
 
44
49
  sample (span, auto = true) {
@@ -50,16 +55,18 @@ class PrioritySampler {
50
55
  if (context._sampling.priority !== undefined) return
51
56
  if (!root) return // noop span
52
57
 
53
- const tag = this._getPriority(context._tags)
58
+ const tag = this._getPriorityFromTags(context._tags)
54
59
 
55
60
  if (this.validate(tag)) {
56
61
  context._sampling.priority = tag
62
+ context._sampling.mechanism = SAMPLING_MECHANISM_MANUAL
63
+ } else if (auto) {
64
+ context._sampling.priority = this._getPriorityFromAuto(root)
65
+ } else {
57
66
  return
58
67
  }
59
68
 
60
- if (auto) {
61
- context._sampling.priority = this.isSampled(root) ? AUTO_KEEP : AUTO_REJECT
62
- }
69
+ this._addUpstreamService(root)
63
70
  }
64
71
 
65
72
  update (rates) {
@@ -72,7 +79,7 @@ class PrioritySampler {
72
79
  samplers[key] = sampler
73
80
  }
74
81
 
75
- samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || new Sampler(AUTO_KEEP)
82
+ samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler
76
83
 
77
84
  this._samplers = samplers
78
85
  }
@@ -93,7 +100,16 @@ class PrioritySampler {
93
100
  return typeof span.context === 'function' ? span.context() : span
94
101
  }
95
102
 
96
- _getPriority (tags) {
103
+ _getPriorityFromAuto (span) {
104
+ const context = this._getContext(span)
105
+ const rule = this._findRule(context)
106
+
107
+ return rule
108
+ ? this._getPriorityByRule(context, rule)
109
+ : this._getPriorityByAgent(context)
110
+ }
111
+
112
+ _getPriorityFromTags (tags) {
97
113
  if (tags.hasOwnProperty(MANUAL_KEEP) && tags[MANUAL_KEEP] !== false) {
98
114
  return USER_KEEP
99
115
  } else if (tags.hasOwnProperty(MANUAL_DROP) && tags[MANUAL_DROP] !== false) {
@@ -109,10 +125,11 @@ class PrioritySampler {
109
125
  }
110
126
  }
111
127
 
112
- _isSampledByRule (context, rule) {
128
+ _getPriorityByRule (context, rule) {
113
129
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
130
+ context._sampling.mechanism = SAMPLING_MECHANISM_RULE
114
131
 
115
- return rule.sampler.isSampled(context)
132
+ return rule.sampler.isSampled(context) && this._isSampledByRateLimit(context) ? USER_KEEP : USER_REJECT
116
133
  }
117
134
 
118
135
  _isSampledByRateLimit (context) {
@@ -123,13 +140,48 @@ class PrioritySampler {
123
140
  return allowed
124
141
  }
125
142
 
126
- _isSampledByAgent (context) {
143
+ _getPriorityByAgent (context) {
127
144
  const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}`
128
145
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
129
146
 
130
147
  context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
131
148
 
132
- return sampler.isSampled(context)
149
+ if (sampler === defaultSampler) {
150
+ context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
151
+ } else {
152
+ context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
153
+ }
154
+
155
+ return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
156
+ }
157
+
158
+ _addUpstreamService (span) {
159
+ const context = span.context()
160
+ const trace = context._trace
161
+ const service = this._toBase64(context._tags['service.name'])
162
+ const priority = context._sampling.priority
163
+ const mechanism = context._sampling.mechanism
164
+ const rate = Math.ceil(coalesce(
165
+ context._trace[SAMPLING_RULE_DECISION],
166
+ context._trace[SAMPLING_AGENT_DECISION]
167
+ ) * 10000) / 10000
168
+ const group = `${service}|${priority}|${mechanism}|${rate}`
169
+ const groups = trace.tags[UPSTREAM_SERVICES_KEY]
170
+ ? `${trace.tags[UPSTREAM_SERVICES_KEY]};${group}`
171
+ : group
172
+
173
+ trace.tags[UPSTREAM_SERVICES_KEY] = groups
174
+ }
175
+
176
+ _toBase64 (serviceName) {
177
+ let encoded = serviceNames.get(serviceName)
178
+
179
+ if (!encoded) {
180
+ encoded = Buffer.from(serviceName).toString('base64')
181
+ serviceNames.set(serviceName, encoded)
182
+ }
183
+
184
+ return encoded
133
185
  }
134
186
 
135
187
  _normalizeRules (rules, sampleRate) {
@@ -151,9 +203,9 @@ class PrioritySampler {
151
203
  const service = context._tags['service.name']
152
204
 
153
205
  if (rule.name instanceof RegExp && !rule.name.test(name)) return false
154
- if (rule.name && rule.name !== name) return false
206
+ if (typeof rule.name === 'string' && rule.name !== name) return false
155
207
  if (rule.service instanceof RegExp && !rule.service.test(service)) return false
156
- if (rule.service && rule.service !== service) return false
208
+ if (typeof rule.service === 'string' && rule.service !== service) return false
157
209
 
158
210
  return true
159
211
  }
@@ -4,9 +4,13 @@ const retry = require('retry')
4
4
  const { request } = require('http')
5
5
  const FormData = require('form-data')
6
6
 
7
+ // TODO: avoid using dd-trace internals. Make this a separate module?
8
+ const docker = require('../../exporters/agent/docker')
7
9
  const version = require('../../../lib/version')
8
10
 
9
- function sendRequest (options, body, callback) {
11
+ const containerId = docker.id()
12
+
13
+ function sendRequest (options, form, callback) {
10
14
  const req = request(options, res => {
11
15
  if (res.statusCode >= 400) {
12
16
  const error = new Error(`HTTP Error ${res.statusCode}`)
@@ -17,7 +21,7 @@ function sendRequest (options, body, callback) {
17
21
  }
18
22
  })
19
23
  req.on('error', callback)
20
- if (body) req.write(body)
24
+ if (form) form.pipe(req)
21
25
  req.end()
22
26
  }
23
27
 
@@ -36,7 +40,7 @@ function computeRetries (uploadTimeout) {
36
40
  tries++
37
41
  uploadTimeout /= 2
38
42
  }
39
- return [tries, uploadTimeout]
43
+ return [tries, Math.floor(uploadTimeout)]
40
44
  }
41
45
 
42
46
  class AgentExporter {
@@ -51,7 +55,6 @@ class AgentExporter {
51
55
  }
52
56
 
53
57
  export ({ profiles, start, end, tags }) {
54
- const form = new FormData()
55
58
  const types = Object.keys(profiles)
56
59
 
57
60
  const fields = [
@@ -71,10 +74,6 @@ class AgentExporter {
71
74
  ...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])
72
75
  ]
73
76
 
74
- for (const [key, value] of fields) {
75
- form.append(key, value)
76
- }
77
-
78
77
  this._logger.debug(() => {
79
78
  const body = fields.map(([key, value]) => ` ${key}: ${value}`).join('\n')
80
79
  return `Building agent export report: ${'\n' + body}`
@@ -89,33 +88,14 @@ class AgentExporter {
89
88
  return `Adding ${type} profile to agent export: ` + bytes
90
89
  })
91
90
 
92
- form.append(`types[${index}]`, type)
93
- form.append(`data[${index}]`, buffer, {
91
+ fields.push([`types[${index}]`, type])
92
+ fields.push([`data[${index}]`, buffer, {
94
93
  filename: `${type}.pb.gz`,
95
94
  contentType: 'application/octet-stream',
96
95
  knownLength: buffer.length
97
- })
98
- }
99
-
100
- const body = form.getBuffer()
101
- const options = {
102
- method: 'POST',
103
- path: '/profiling/v1/input',
104
- headers: form.getHeaders()
105
- }
106
-
107
- if (this._url.protocol === 'unix:') {
108
- options.socketPath = this._url.pathname
109
- } else {
110
- options.protocol = this._url.protocol
111
- options.hostname = this._url.hostname
112
- options.port = this._url.port
96
+ }])
113
97
  }
114
98
 
115
- this._logger.debug(() => {
116
- return `Submitting agent report to: ${JSON.stringify(options)}`
117
- })
118
-
119
99
  return new Promise((resolve, reject) => {
120
100
  const operation = retry.operation({
121
101
  randomize: true,
@@ -124,8 +104,36 @@ class AgentExporter {
124
104
  })
125
105
 
126
106
  operation.attempt((attempt) => {
127
- const timeout = Math.pow(this._backoffTime, attempt)
128
- sendRequest({ ...options, timeout }, body, (err, response) => {
107
+ const form = new FormData()
108
+
109
+ for (const [key, value, options] of fields) {
110
+ form.append(key, value, options)
111
+ }
112
+
113
+ const options = {
114
+ method: 'POST',
115
+ path: '/profiling/v1/input',
116
+ headers: form.getHeaders(),
117
+ timeout: this._backoffTime * Math.pow(2, attempt)
118
+ }
119
+
120
+ if (containerId) {
121
+ options.headers['Datadog-Container-ID'] = containerId
122
+ }
123
+
124
+ if (this._url.protocol === 'unix:') {
125
+ options.socketPath = this._url.pathname
126
+ } else {
127
+ options.protocol = this._url.protocol
128
+ options.hostname = this._url.hostname
129
+ options.port = this._url.port
130
+ }
131
+
132
+ this._logger.debug(() => {
133
+ return `Submitting profiler agent report attempt #${attempt} to: ${JSON.stringify(options)}`
134
+ })
135
+
136
+ sendRequest(options, form, (err, response) => {
129
137
  if (operation.retry(err)) {
130
138
  this._logger.error(`Error from the agent: ${err.message}`)
131
139
  return
@@ -152,4 +160,4 @@ class AgentExporter {
152
160
  }
153
161
  }
154
162
 
155
- module.exports = { AgentExporter }
163
+ module.exports = { AgentExporter, computeRetries }
@@ -25,9 +25,7 @@ class NativeCpuProfiler {
25
25
 
26
26
  profile () {
27
27
  if (!this._stop) return
28
- const profile = this._stop()
29
- this._record()
30
- return profile
28
+ return this._stop(true)
31
29
  }
32
30
 
33
31
  encode (profile) {
@@ -46,7 +46,7 @@ class Tracer extends BaseTracer {
46
46
  metrics.start(config)
47
47
  }
48
48
 
49
- // dirty require for now so zero appsec code is executed unless explicitely enabled
49
+ // dirty require for now so zero appsec code is executed unless explicitly enabled
50
50
  if (config.appsec.enabled) {
51
51
  require('./appsec').enable(config)
52
52
  }
@@ -1,35 +0,0 @@
1
- [
2
- "test.type",
3
- "test.status",
4
- "test.framework",
5
- "test.suite",
6
- "test.name",
7
- "test.skip_reason",
8
- "ci.pipeline.id",
9
- "ci.pipeline.name",
10
- "ci.pipeline.number",
11
- "ci.pipeline.url",
12
- "ci.provider.name",
13
- "ci.workspace_path",
14
- "git.repository_url",
15
- "ci.job.url",
16
- "ci.job.name",
17
- "ci.stage.name",
18
- "git.commit.sha",
19
- "git.branch",
20
- "git.tag",
21
- "git.commit.message",
22
- "git.commit.committer.date",
23
- "git.commit.committer.email",
24
- "git.commit.committer.name",
25
- "git.commit.author.date",
26
- "git.commit.author.email",
27
- "git.commit.author.name",
28
- "os.platform",
29
- "os.version",
30
- "os.architecture",
31
- "runtime.name",
32
- "runtime.version",
33
- "test.parameters",
34
- "_dd.origin"
35
- ]