dd-trace 4.22.0 → 4.24.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 (52) hide show
  1. package/package.json +4 -4
  2. package/packages/datadog-instrumentations/src/child-process.js +4 -5
  3. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  4. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  5. package/packages/datadog-instrumentations/src/dns.js +2 -1
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +7 -2
  7. package/packages/datadog-instrumentations/src/helpers/instrument.js +8 -3
  8. package/packages/datadog-instrumentations/src/helpers/register.js +18 -2
  9. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  10. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  11. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  12. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  13. package/packages/datadog-instrumentations/src/jest.js +3 -1
  14. package/packages/datadog-instrumentations/src/net.js +10 -2
  15. package/packages/datadog-plugin-cucumber/src/index.js +34 -2
  16. package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
  17. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
  18. package/packages/datadog-plugin-jest/src/index.js +60 -6
  19. package/packages/datadog-plugin-jest/src/util.js +38 -16
  20. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  21. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  22. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
  23. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  24. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  25. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  26. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +36 -4
  27. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +18 -1
  28. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  29. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  30. package/packages/dd-trace/src/config.js +100 -59
  31. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  32. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  33. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  34. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  35. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  36. package/packages/dd-trace/src/plugins/ci_plugin.js +44 -8
  37. package/packages/dd-trace/src/plugins/index.js +5 -0
  38. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  39. package/packages/dd-trace/src/plugins/util/git.js +94 -19
  40. package/packages/dd-trace/src/priority_sampler.js +30 -38
  41. package/packages/dd-trace/src/profiling/config.js +17 -2
  42. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  43. package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
  44. package/packages/dd-trace/src/profiling/profiler.js +18 -14
  45. package/packages/dd-trace/src/profiling/profilers/events.js +11 -5
  46. package/packages/dd-trace/src/profiling/profilers/shared.js +7 -1
  47. package/packages/dd-trace/src/profiling/profilers/space.js +17 -2
  48. package/packages/dd-trace/src/profiling/profilers/wall.js +34 -21
  49. package/packages/dd-trace/src/sampling_rule.js +130 -0
  50. package/packages/dd-trace/src/span_sampler.js +6 -64
  51. package/packages/dd-trace/src/telemetry/index.js +43 -5
  52. package/packages/dd-trace/src/telemetry/send-data.js +35 -16
@@ -7,7 +7,7 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
7
7
  const { WEB } = require('../../../../../ext/types')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const telemetryMetrics = require('../../telemetry/metrics')
10
- const { END_TIMESTAMP_LABEL, getThreadLabels } = require('./shared')
10
+ const { END_TIMESTAMP_LABEL, getNonJSThreadsLabels, getThreadLabels } = require('./shared')
11
11
 
12
12
  const beforeCh = dc.channel('dd-trace:storage:before')
13
13
  const enterCh = dc.channel('dd-trace:storage:enter')
@@ -78,13 +78,15 @@ class NativeWallProfiler {
78
78
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
79
79
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
80
80
  this._timelineEnabled = !!options.timelineEnabled
81
+ this._cpuProfilingEnabled = !!options.cpuProfilingEnabled
81
82
  // We need to capture span data into the sample context for either code hotspots
82
83
  // or endpoint collection.
83
84
  this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
84
85
  // We need to run the pprof wall profiler with sample contexts if we're either
85
86
  // capturing span data or timeline is enabled (so we need sample timestamps, and for now
86
- // timestamps require the sample contexts feature in the pprof wall profiler.)
87
- this._withContexts = this._captureSpanData || this._timelineEnabled
87
+ // timestamps require the sample contexts feature in the pprof wall profiler), or
88
+ // cpu profiling is enabled.
89
+ this._withContexts = this._captureSpanData || this._timelineEnabled || this._cpuProfilingEnabled
88
90
  this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
89
91
  this._mapper = undefined
90
92
  this._pprof = undefined
@@ -131,7 +133,8 @@ class NativeWallProfiler {
131
133
  sourceMapper: this._mapper,
132
134
  withContexts: this._withContexts,
133
135
  lineNumbers: false,
134
- workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
136
+ workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled,
137
+ collectCpuTime: this._cpuProfilingEnabled
135
138
  })
136
139
 
137
140
  if (this._withContexts) {
@@ -220,22 +223,42 @@ class NativeWallProfiler {
220
223
 
221
224
  _stop (restart) {
222
225
  if (!this._started) return
226
+
223
227
  if (this._captureSpanData) {
224
228
  // update last sample context if needed
225
229
  this._enter()
226
230
  this._lastSampleCount = 0
227
231
  }
228
232
  const profile = this._pprof.time.stop(restart, this._generateLabels)
233
+
229
234
  if (restart) {
230
235
  const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
231
236
  if (v8BugDetected !== 0) {
232
237
  this._reportV8bug(v8BugDetected === 1)
233
238
  }
239
+ } else {
240
+ if (this._captureSpanData) {
241
+ beforeCh.unsubscribe(this._enter)
242
+ enterCh.unsubscribe(this._enter)
243
+ spanFinishCh.unsubscribe(this._spanFinished)
244
+ this._profilerState = undefined
245
+ this._lastSpan = undefined
246
+ this._lastStartedSpans = undefined
247
+ this._lastWebTags = undefined
248
+ }
249
+ this._started = false
234
250
  }
251
+
235
252
  return profile
236
253
  }
237
254
 
238
- _generateLabels (context) {
255
+ _generateLabels ({ node, context }) {
256
+ // check for special node that represents CPU time all non-JS threads.
257
+ // In that case only return a special thread name label since we cannot associate any timestamp/span/endpoint to it.
258
+ if (node.name === this._pprof.time.constants.NON_JS_THREADS_FUNCTION_NAME) {
259
+ return getNonJSThreadsLabels()
260
+ }
261
+
239
262
  if (context == null) {
240
263
  // generateLabels is also called for samples without context.
241
264
  // In that case just return thread labels.
@@ -267,8 +290,8 @@ class NativeWallProfiler {
267
290
  return labels
268
291
  }
269
292
 
270
- profile () {
271
- return this._stop(true)
293
+ profile (restart) {
294
+ return this._stop(restart)
272
295
  }
273
296
 
274
297
  encode (profile) {
@@ -276,21 +299,11 @@ class NativeWallProfiler {
276
299
  }
277
300
 
278
301
  stop () {
279
- if (!this._started) return
280
-
281
- const profile = this._stop(false)
282
- if (this._captureSpanData) {
283
- beforeCh.unsubscribe(this._enter)
284
- enterCh.unsubscribe(this._enter)
285
- spanFinishCh.unsubscribe(this._spanFinished)
286
- this._profilerState = undefined
287
- this._lastSpan = undefined
288
- this._lastStartedSpans = undefined
289
- this._lastWebTags = undefined
290
- }
302
+ this._stop(false)
303
+ }
291
304
 
292
- this._started = false
293
- return profile
305
+ isStarted () {
306
+ return this._started
294
307
  }
295
308
  }
296
309
 
@@ -0,0 +1,130 @@
1
+ 'use strict'
2
+
3
+ const { globMatch } = require('../src/util')
4
+ const RateLimiter = require('./rate_limiter')
5
+ const Sampler = require('./sampler')
6
+
7
+ class AlwaysMatcher {
8
+ match () {
9
+ return true
10
+ }
11
+ }
12
+
13
+ class GlobMatcher {
14
+ constructor (pattern, locator) {
15
+ this.pattern = pattern
16
+ this.locator = locator
17
+ }
18
+
19
+ match (span) {
20
+ const subject = this.locator(span)
21
+ if (!subject) return false
22
+ return globMatch(this.pattern, subject)
23
+ }
24
+ }
25
+
26
+ class RegExpMatcher {
27
+ constructor (pattern, locator) {
28
+ this.pattern = pattern
29
+ this.locator = locator
30
+ }
31
+
32
+ match (span) {
33
+ const subject = this.locator(span)
34
+ if (!subject) return false
35
+ return this.pattern.test(subject)
36
+ }
37
+ }
38
+
39
+ function matcher (pattern, locator) {
40
+ if (pattern instanceof RegExp) {
41
+ return new RegExpMatcher(pattern, locator)
42
+ }
43
+
44
+ if (typeof pattern === 'string' && pattern !== '*') {
45
+ return new GlobMatcher(pattern, locator)
46
+ }
47
+
48
+ return new AlwaysMatcher()
49
+ }
50
+
51
+ function makeTagLocator (tag) {
52
+ return (span) => span.context()._tags[tag]
53
+ }
54
+
55
+ function nameLocator (span) {
56
+ return span.context()._name
57
+ }
58
+
59
+ function serviceLocator (span) {
60
+ const { _tags: tags } = span.context()
61
+ return tags.service ||
62
+ tags['service.name'] ||
63
+ span.tracer()._service
64
+ }
65
+
66
+ class SamplingRule {
67
+ constructor ({ name, service, resource, tags, sampleRate = 1.0, maxPerSecond } = {}) {
68
+ this.matchers = []
69
+
70
+ if (name) {
71
+ this.matchers.push(matcher(name, nameLocator))
72
+ }
73
+ if (service) {
74
+ this.matchers.push(matcher(service, serviceLocator))
75
+ }
76
+ if (resource) {
77
+ this.matchers.push(matcher(resource, makeTagLocator('resource.name')))
78
+ }
79
+ for (const [key, value] of Object.entries(tags || {})) {
80
+ this.matchers.push(matcher(value, makeTagLocator(key)))
81
+ }
82
+
83
+ this._sampler = new Sampler(sampleRate)
84
+ this._limiter = undefined
85
+
86
+ if (Number.isFinite(maxPerSecond)) {
87
+ this._limiter = new RateLimiter(maxPerSecond)
88
+ }
89
+ }
90
+
91
+ static from (config) {
92
+ return new SamplingRule(config)
93
+ }
94
+
95
+ get sampleRate () {
96
+ return this._sampler.rate()
97
+ }
98
+
99
+ get effectiveRate () {
100
+ return this._limiter && this._limiter.effectiveRate()
101
+ }
102
+
103
+ get maxPerSecond () {
104
+ return this._limiter && this._limiter._rateLimit
105
+ }
106
+
107
+ match (span) {
108
+ for (const matcher of this.matchers) {
109
+ if (!matcher.match(span)) {
110
+ return false
111
+ }
112
+ }
113
+
114
+ return true
115
+ }
116
+
117
+ sample () {
118
+ if (!this._sampler.isSampled()) {
119
+ return false
120
+ }
121
+
122
+ if (this._limiter) {
123
+ return this._limiter.isAllowed()
124
+ }
125
+
126
+ return true
127
+ }
128
+ }
129
+
130
+ module.exports = SamplingRule
@@ -1,67 +1,16 @@
1
1
  'use strict'
2
- const { globMatch } = require('../src/util')
3
- const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
4
- const RateLimiter = require('./rate_limiter')
5
- const Sampler = require('./sampler')
6
-
7
- class SpanSamplingRule {
8
- constructor ({ service, name, sampleRate = 1.0, maxPerSecond } = {}) {
9
- this.service = service
10
- this.name = name
11
-
12
- this._sampler = new Sampler(sampleRate)
13
- this._limiter = undefined
14
-
15
- if (Number.isFinite(maxPerSecond)) {
16
- this._limiter = new RateLimiter(maxPerSecond)
17
- }
18
- }
19
-
20
- get sampleRate () {
21
- return this._sampler.rate()
22
- }
23
-
24
- get maxPerSecond () {
25
- return this._limiter && this._limiter._rateLimit
26
- }
27
-
28
- static from (config) {
29
- return new SpanSamplingRule(config)
30
- }
31
-
32
- match (service, name) {
33
- if (this.service && !globMatch(this.service, service)) {
34
- return false
35
- }
36
-
37
- if (this.name && !globMatch(this.name, name)) {
38
- return false
39
- }
40
-
41
- return true
42
- }
43
-
44
- sample () {
45
- if (!this._sampler.isSampled()) {
46
- return false
47
- }
48
2
 
49
- if (this._limiter) {
50
- return this._limiter.isAllowed()
51
- }
52
-
53
- return true
54
- }
55
- }
3
+ const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
4
+ const SamplingRule = require('./sampling_rule')
56
5
 
57
6
  class SpanSampler {
58
7
  constructor ({ spanSamplingRules = [] } = {}) {
59
- this._rules = spanSamplingRules.map(SpanSamplingRule.from)
8
+ this._rules = spanSamplingRules.map(SamplingRule.from)
60
9
  }
61
10
 
62
- findRule (service, name) {
11
+ findRule (context) {
63
12
  for (const rule of this._rules) {
64
- if (rule.match(service, name)) {
13
+ if (rule.match(context)) {
65
14
  return rule
66
15
  }
67
16
  }
@@ -73,14 +22,7 @@ class SpanSampler {
73
22
 
74
23
  const { started } = spanContext._trace
75
24
  for (const span of started) {
76
- const context = span.context()
77
- const tags = context._tags || {}
78
- const name = context._name
79
- const service = tags.service ||
80
- tags['service.name'] ||
81
- span.tracer()._service
82
-
83
- const rule = this.findRule(service, name)
25
+ const rule = this.findRule(span)
84
26
  if (rule && rule.sample()) {
85
27
  span.context()._spanSampling = {
86
28
  sampleRate: rule.sampleRate,
@@ -112,11 +112,26 @@ function flatten (input, result = [], prefix = [], traversedObjects = null) {
112
112
  return result
113
113
  }
114
114
 
115
+ function getInstallSignature (config) {
116
+ const { installSignature: sig } = config
117
+ if (sig && (sig.id || sig.time || sig.type)) {
118
+ return {
119
+ install_id: sig.id,
120
+ install_time: sig.time,
121
+ install_type: sig.type
122
+ }
123
+ }
124
+ }
125
+
115
126
  function appStarted (config) {
116
127
  const app = {
117
128
  products: getProducts(config),
118
129
  configuration: flatten(config)
119
130
  }
131
+ const installSignature = getInstallSignature(config)
132
+ if (installSignature) {
133
+ app.install_signature = installSignature
134
+ }
120
135
  // TODO: add app.error with correct error codes
121
136
  // if (errors.agentError) {
122
137
  // app.error = errors.agentError
@@ -129,6 +144,10 @@ function onBeforeExit () {
129
144
  process.removeListener('beforeExit', onBeforeExit)
130
145
  const { reqType, payload } = createPayload('app-closing')
131
146
  sendData(config, application, host, reqType, payload)
147
+ // we flush before shutting down. Only in CI Visibility
148
+ if (config.isCiVisibility) {
149
+ metricsManager.send(config, application, host)
150
+ }
132
151
  }
133
152
 
134
153
  function createAppObject (config) {
@@ -286,11 +305,30 @@ function updateConfig (changes, config) {
286
305
  const application = createAppObject(config)
287
306
  const host = createHostObject()
288
307
 
289
- const configuration = changes.map(change => ({
290
- name: change.name,
291
- value: Array.isArray(change.value) ? change.value.join(',') : change.value,
292
- origin: change.origin
293
- }))
308
+ const names = {
309
+ sampleRate: 'DD_TRACE_SAMPLE_RATE',
310
+ logInjection: 'DD_LOG_INJECTION',
311
+ headerTags: 'DD_TRACE_HEADER_TAGS',
312
+ tags: 'DD_TAGS'
313
+ }
314
+
315
+ const configuration = []
316
+
317
+ for (const change of changes) {
318
+ if (!names.hasOwnProperty(change.name)) continue
319
+
320
+ const name = names[change.name]
321
+ const { origin, value } = change
322
+ const entry = { name, origin, value }
323
+
324
+ if (Array.isArray(value)) {
325
+ entry.value = value.join(',')
326
+ } else if (name === 'DD_TAGS') {
327
+ entry.value = Object.entries(value).map(([key, value]) => `${key}:${value}`)
328
+ }
329
+
330
+ configuration.push(entry)
331
+ }
294
332
 
295
333
  const { reqType, payload } = createPayload('app-client-configuration-change', { configuration })
296
334
 
@@ -1,6 +1,7 @@
1
1
 
2
2
  const request = require('../exporters/common/request')
3
3
  const log = require('../log')
4
+
4
5
  let agentTelemetry = true
5
6
 
6
7
  function getHeaders (config, application, reqType) {
@@ -15,9 +16,22 @@ function getHeaders (config, application, reqType) {
15
16
  if (debug) {
16
17
  headers['dd-telemetry-debug-enabled'] = 'true'
17
18
  }
19
+ if (config.apiKey) {
20
+ headers['dd-api-key'] = config.apiKey
21
+ }
18
22
  return headers
19
23
  }
20
24
 
25
+ function getAgentlessTelemetryEndpoint (site) {
26
+ if (site === 'datad0g.com') { // staging
27
+ return 'https://all-http-intake.logs.datad0g.com'
28
+ }
29
+ if (site === 'datadoghq.eu') {
30
+ return 'https://instrumentation-telemetry-intake.eu1.datadoghq.com'
31
+ }
32
+ return `https://instrumentation-telemetry-intake.${site}`
33
+ }
34
+
21
35
  let seqId = 0
22
36
 
23
37
  function getPayload (payload) {
@@ -35,17 +49,33 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
35
49
  const {
36
50
  hostname,
37
51
  port,
38
- url
52
+ experimental,
53
+ isCiVisibility
39
54
  } = config
40
55
 
56
+ let url = config.url
57
+
58
+ const isCiVisibilityAgentlessMode = isCiVisibility && experimental?.exporter === 'datadog'
59
+
60
+ if (isCiVisibilityAgentlessMode) {
61
+ try {
62
+ url = url || new URL(getAgentlessTelemetryEndpoint(config.site))
63
+ } catch (err) {
64
+ log.error(err)
65
+ // No point to do the request if the URL is invalid
66
+ return cb(err, { payload, reqType })
67
+ }
68
+ }
69
+
41
70
  const options = {
42
71
  url,
43
72
  hostname,
44
73
  port,
45
74
  method: 'POST',
46
- path: '/telemetry/proxy/api/v2/apmtelemetry',
75
+ path: isCiVisibilityAgentlessMode ? '/api/v2/apmtelemetry' : '/telemetry/proxy/api/v2/apmtelemetry',
47
76
  headers: getHeaders(config, application, reqType)
48
77
  }
78
+
49
79
  const data = JSON.stringify({
50
80
  api_version: 'v2',
51
81
  naming_schema_version: config.spanAttributeSchema ? config.spanAttributeSchema : '',
@@ -65,24 +95,13 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
65
95
  agentTelemetry = false
66
96
  }
67
97
  // figure out which data center to send to
68
- let backendUrl
69
- const dataCenters = [
70
- 'datadoghq.com',
71
- 'us3.datadoghq.com',
72
- 'us5.datadoghq.com',
73
- 'ap1.datadoghq.com',
74
- 'eu1.datadoghq.com'
75
- ]
76
- if (config.site === 'datad0g.com') { // staging
77
- backendUrl = 'https://all-http-intake.logs.datad0g.com/api/v2/apmtelemetry'
78
- } else if (dataCenters.includes(config.site)) {
79
- backendUrl = 'https://instrumentation-telemetry-intake.' + config.site + '/api/v2/apmtelemetry'
80
- }
98
+ const backendUrl = getAgentlessTelemetryEndpoint(config.site)
81
99
  const backendHeader = { ...options.headers, 'DD-API-KEY': process.env.DD_API_KEY }
82
100
  const backendOptions = {
83
101
  ...options,
84
102
  url: backendUrl,
85
- headers: backendHeader
103
+ headers: backendHeader,
104
+ path: '/api/v2/apmtelemetry'
86
105
  }
87
106
  if (backendUrl) {
88
107
  request(data, backendOptions, (error) => { log.error(error) })