dd-trace 4.22.0 → 4.23.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 (44) hide show
  1. package/package.json +2 -2
  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 +1 -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-jest/src/index.js +38 -4
  18. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  20. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
  21. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  22. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  23. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  24. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +36 -4
  25. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +18 -1
  26. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  27. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  28. package/packages/dd-trace/src/config.js +99 -58
  29. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  30. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  31. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  32. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  33. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  34. package/packages/dd-trace/src/plugins/ci_plugin.js +44 -8
  35. package/packages/dd-trace/src/plugins/index.js +5 -0
  36. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  37. package/packages/dd-trace/src/plugins/util/git.js +94 -19
  38. package/packages/dd-trace/src/priority_sampler.js +30 -38
  39. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  40. package/packages/dd-trace/src/profiling/profilers/events.js +1 -1
  41. package/packages/dd-trace/src/profiling/profilers/shared.js +1 -1
  42. package/packages/dd-trace/src/sampling_rule.js +130 -0
  43. package/packages/dd-trace/src/span_sampler.js +6 -64
  44. package/packages/dd-trace/src/telemetry/index.js +43 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.22.0",
3
+ "version": "4.23.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -79,7 +79,7 @@
79
79
  "crypto-randomuuid": "^1.0.0",
80
80
  "dc-polyfill": "^0.1.2",
81
81
  "ignore": "^5.2.4",
82
- "import-in-the-middle": "^1.4.2",
82
+ "import-in-the-middle": "^1.7.1",
83
83
  "int64-buffer": "^0.1.9",
84
84
  "ipaddr.js": "^2.1.0",
85
85
  "istanbul-lib-coverage": "3.2.0",
@@ -9,11 +9,10 @@ const shimmer = require('../../datadog-shimmer')
9
9
  const childProcessChannel = channel('datadog:child_process:execution:start')
10
10
  const execMethods = ['exec', 'execFile', 'fork', 'spawn', 'execFileSync', 'execSync', 'spawnSync']
11
11
  const names = ['child_process', 'node:child_process']
12
- names.forEach(name => {
13
- addHook({ name }, childProcess => {
14
- shimmer.massWrap(childProcess, execMethods, wrapChildProcessMethod())
15
- return childProcess
16
- })
12
+
13
+ addHook({ name: names }, childProcess => {
14
+ shimmer.massWrap(childProcess, execMethods, wrapChildProcessMethod())
15
+ return childProcess
17
16
  })
18
17
 
19
18
  function wrapChildProcessMethod () {
@@ -252,9 +252,10 @@ addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['^3.0.7', '^3.1.
252
252
  return Cluster
253
253
  })
254
254
 
255
- // semver >=3.2.0
255
+ // semver >=3.2.2
256
+ // NOTE: <3.2.2 segfaults on cluster.close() https://issues.couchbase.com/browse/JSCBC-936
256
257
 
257
- addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.0'] }, collection => {
258
+ addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.2'] }, collection => {
258
259
  const Collection = collection.Collection
259
260
 
260
261
  wrapAllNames(['upsert', 'insert', 'replace'], name => {
@@ -264,7 +265,7 @@ addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.0'] }
264
265
  return collection
265
266
  })
266
267
 
267
- addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.0'] }, bucket => {
268
+ addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.2'] }, bucket => {
268
269
  const Bucket = bucket.Bucket
269
270
  shimmer.wrap(Bucket.prototype, 'collection', getCollection => {
270
271
  return function () {
@@ -278,7 +279,7 @@ addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.0'] }, bu
278
279
  return bucket
279
280
  })
280
281
 
281
- addHook({ name: 'couchbase', file: 'dist/cluster.js', versions: ['3.2.0 - 3.2.1', '>=3.2.2'] }, (cluster) => {
282
+ addHook({ name: 'couchbase', file: 'dist/cluster.js', versions: ['>=3.2.2'] }, (cluster) => {
282
283
  const Cluster = cluster.Cluster
283
284
 
284
285
  shimmer.wrap(Cluster.prototype, 'query', wrapV3Query)
@@ -11,8 +11,9 @@ const cryptoCipherCh = channel('datadog:crypto:cipher:start')
11
11
 
12
12
  const hashMethods = ['createHash', 'createHmac', 'createSign', 'createVerify', 'sign', 'verify']
13
13
  const cipherMethods = ['createCipheriv', 'createDecipheriv']
14
+ const names = ['crypto', 'node:crypto']
14
15
 
15
- addHook({ name: 'crypto' }, crypto => {
16
+ addHook({ name: names }, crypto => {
16
17
  shimmer.massWrap(crypto, hashMethods, wrapCryptoMethod(cryptoHashCh))
17
18
  shimmer.massWrap(crypto, cipherMethods, wrapCryptoMethod(cryptoCipherCh))
18
19
  return crypto
@@ -18,8 +18,9 @@ const rrtypes = {
18
18
  }
19
19
 
20
20
  const rrtypeMap = new WeakMap()
21
+ const names = ['dns', 'node:dns']
21
22
 
22
- addHook({ name: 'dns' }, dns => {
23
+ addHook({ name: names }, dns => {
23
24
  dns.lookup = wrap('apm:dns:lookup', dns.lookup, 2)
24
25
  dns.lookupService = wrap('apm:dns:lookup_service', dns.lookupService, 3)
25
26
  dns.resolve = wrap('apm:dns:resolve', dns.resolve, 2)
@@ -31,7 +31,6 @@ module.exports = {
31
31
  'bunyan': () => require('../bunyan'),
32
32
  'cassandra-driver': () => require('../cassandra-driver'),
33
33
  'child_process': () => require('../child-process'),
34
- 'node:child_process': () => require('../child-process'),
35
34
  'connect': () => require('../connect'),
36
35
  'cookie': () => require('../cookie'),
37
36
  'cookie-parser': () => require('../cookie-parser'),
@@ -45,7 +44,6 @@ module.exports = {
45
44
  'fastify': () => require('../fastify'),
46
45
  'find-my-way': () => require('../find-my-way'),
47
46
  'fs': () => require('../fs'),
48
- 'node:fs': () => require('../fs'),
49
47
  'generic-pool': () => require('../generic-pool'),
50
48
  'graphql': () => require('../graphql'),
51
49
  'grpc': () => require('../grpc'),
@@ -79,6 +77,13 @@ module.exports = {
79
77
  'mysql2': () => require('../mysql2'),
80
78
  'net': () => require('../net'),
81
79
  'next': () => require('../next'),
80
+ 'node:child_process': () => require('../child-process'),
81
+ 'node:crypto': () => require('../crypto'),
82
+ 'node:dns': () => require('../dns'),
83
+ 'node:http': () => require('../http'),
84
+ 'node:http2': () => require('../http2'),
85
+ 'node:https': () => require('../http'),
86
+ 'node:net': () => require('../net'),
82
87
  'oracledb': () => require('../oracledb'),
83
88
  'openai': () => require('../openai'),
84
89
  'paperplane': () => require('../paperplane'),
@@ -21,11 +21,16 @@ exports.channel = function (name) {
21
21
  * @param Function hook
22
22
  */
23
23
  exports.addHook = function addHook ({ name, versions, file }, hook) {
24
- if (!instrumentations[name]) {
25
- instrumentations[name] = []
24
+ if (typeof name === 'string') {
25
+ name = [name]
26
26
  }
27
27
 
28
- instrumentations[name].push({ name, versions, file, hook })
28
+ for (const val of name) {
29
+ if (!instrumentations[val]) {
30
+ instrumentations[val] = []
31
+ }
32
+ instrumentations[val].push({ name: val, versions, file, hook })
33
+ }
29
34
  }
30
35
 
31
36
  // AsyncResource.bind exists and binds `this` properly only from 17.8.0 and up.
@@ -24,6 +24,7 @@ if (!disabledInstrumentations.has('fetch')) {
24
24
  require('../fetch')
25
25
  }
26
26
 
27
+ const HOOK_SYMBOL = Symbol('hookExportsMap')
27
28
  // TODO: make this more efficient
28
29
 
29
30
  for (const packageName of names) {
@@ -42,14 +43,29 @@ for (const packageName of names) {
42
43
  for (const { name, file, versions, hook } of instrumentations[packageName]) {
43
44
  const fullFilename = filename(name, file)
44
45
 
46
+ // Create a WeakMap associated with the hook function so that patches on the same moduleExport only happens once
47
+ // for example by instrumenting both dns and node:dns double the spans would be created
48
+ // since they both patch the same moduleExport, this WeakMap is used to mitigate that
49
+ if (!hook[HOOK_SYMBOL]) {
50
+ hook[HOOK_SYMBOL] = new WeakMap()
51
+ }
52
+
45
53
  if (moduleName === fullFilename) {
46
54
  const version = moduleVersion || getVersion(moduleBaseDir)
47
55
 
48
56
  if (matchVersion(version, versions)) {
57
+ // Check if the hook already has a set moduleExport
58
+ if (hook[HOOK_SYMBOL].has(moduleExports)) {
59
+ return moduleExports
60
+ }
61
+
49
62
  try {
50
63
  loadChannel.publish({ name, version, file })
51
-
52
- moduleExports = hook(moduleExports, version)
64
+ // Send the name and version of the module back to the callback because now addHook
65
+ // takes in an array of names so by passing the name the callback will know which module name is being used
66
+ moduleExports = hook(moduleExports, version, name)
67
+ // Set the moduleExports in the hooks weakmap
68
+ hook[HOOK_SYMBOL].set(moduleExports, name)
53
69
  } catch (e) {
54
70
  log.error(e)
55
71
  }
@@ -14,9 +14,9 @@ const endChannel = channel('apm:http:client:request:end')
14
14
  const asyncStartChannel = channel('apm:http:client:request:asyncStart')
15
15
  const errorChannel = channel('apm:http:client:request:error')
16
16
 
17
- addHook({ name: 'https' }, hookFn)
17
+ const names = ['http', 'https', 'node:http', 'node:https']
18
18
 
19
- addHook({ name: 'http' }, hookFn)
19
+ addHook({ name: names }, hookFn)
20
20
 
21
21
  function hookFn (http) {
22
22
  patch(http, 'request')
@@ -15,14 +15,17 @@ const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finis
15
15
 
16
16
  const requestFinishedSet = new WeakSet()
17
17
 
18
- addHook({ name: 'https' }, http => {
19
- // http.ServerResponse not present on https
18
+ const httpNames = ['http', 'node:http']
19
+ const httpsNames = ['https', 'node:https']
20
+
21
+ addHook({ name: httpNames }, http => {
22
+ shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
20
23
  shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
21
24
  return http
22
25
  })
23
26
 
24
- addHook({ name: 'http' }, http => {
25
- shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
27
+ addHook({ name: httpsNames }, http => {
28
+ // http.ServerResponse not present on https
26
29
  shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
27
30
  return http
28
31
  })
@@ -10,6 +10,8 @@ const asyncStartChannel = channel('apm:http2:client:request:asyncStart')
10
10
  const asyncEndChannel = channel('apm:http2:client:request:asyncEnd')
11
11
  const errorChannel = channel('apm:http2:client:request:error')
12
12
 
13
+ const names = ['http2', 'node:http2']
14
+
13
15
  function createWrapEmit (ctx) {
14
16
  return function wrapEmit (emit) {
15
17
  return function (event, arg1) {
@@ -66,7 +68,7 @@ function wrapConnect (connect) {
66
68
  }
67
69
  }
68
70
 
69
- addHook({ name: 'http2' }, http2 => {
71
+ addHook({ name: names }, http2 => {
70
72
  shimmer.wrap(http2, 'connect', wrapConnect)
71
73
 
72
74
  return http2
@@ -14,7 +14,9 @@ const startServerCh = channel('apm:http2:server:request:start')
14
14
  const errorServerCh = channel('apm:http2:server:request:error')
15
15
  const finishServerCh = channel('apm:http2:server:request:finish')
16
16
 
17
- addHook({ name: 'http2' }, http2 => {
17
+ const names = ['http2', 'node:http2']
18
+
19
+ addHook({ name: names }, http2 => {
18
20
  shimmer.wrap(http2, 'createSecureServer', wrapCreateServer)
19
21
  shimmer.wrap(http2, 'createServer', wrapCreateServer)
20
22
  return http2
@@ -403,7 +403,7 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
403
403
  const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
404
404
  .map(filename => getTestSuitePath(filename, environment.rootDir))
405
405
  asyncResource.runInAsyncScope(() => {
406
- testSuiteCodeCoverageCh.publish([...coverageFiles, environment.testSuite])
406
+ testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSuite })
407
407
  })
408
408
  }
409
409
  testSuiteFinishCh.publish({ status, errorMessage })
@@ -17,8 +17,16 @@ const errorTCPCh = channel('apm:net:tcp:error')
17
17
 
18
18
  const connectionCh = channel(`apm:net:tcp:connection`)
19
19
 
20
- addHook({ name: 'net' }, net => {
21
- require('dns')
20
+ const names = ['net', 'node:net']
21
+
22
+ addHook({ name: names }, (net, version, name) => {
23
+ // explicitly require dns so that net gets an instrumented instance
24
+ // so that we don't miss the dns calls
25
+ if (name === 'net') {
26
+ require('dns')
27
+ } else {
28
+ require('node:dns')
29
+ }
22
30
 
23
31
  shimmer.wrap(net.Socket.prototype, 'connect', connect => function () {
24
32
  if (!startICPCh.hasSubscribers || !startTCPCh.hasSubscribers) {
@@ -12,10 +12,21 @@ const {
12
12
  getTestSuiteCommonTags,
13
13
  addIntelligentTestRunnerSpanTags,
14
14
  TEST_ITR_UNSKIPPABLE,
15
- TEST_ITR_FORCED_RUN
15
+ TEST_ITR_FORCED_RUN,
16
+ TEST_CODE_OWNERS
16
17
  } = require('../../dd-trace/src/plugins/util/test')
17
18
  const { RESOURCE_NAME } = require('../../../ext/tags')
18
19
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
20
+ const {
21
+ TELEMETRY_EVENT_CREATED,
22
+ TELEMETRY_EVENT_FINISHED,
23
+ TELEMETRY_CODE_COVERAGE_STARTED,
24
+ TELEMETRY_CODE_COVERAGE_FINISHED,
25
+ TELEMETRY_ITR_FORCED_TO_RUN,
26
+ TELEMETRY_CODE_COVERAGE_EMPTY,
27
+ TELEMETRY_ITR_UNSKIPPABLE,
28
+ TELEMETRY_CODE_COVERAGE_NUM_FILES
29
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
19
30
 
20
31
  class CucumberPlugin extends CiPlugin {
21
32
  static get id () {
@@ -54,7 +65,9 @@ class CucumberPlugin extends CiPlugin {
54
65
  this.testSessionSpan.setTag(TEST_STATUS, status)
55
66
  this.testModuleSpan.setTag(TEST_STATUS, status)
56
67
  this.testModuleSpan.finish()
68
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
57
69
  this.testSessionSpan.finish()
70
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
58
71
  finishAllTraceSpans(this.testSessionSpan)
59
72
 
60
73
  this.itrConfig = null
@@ -69,9 +82,11 @@ class CucumberPlugin extends CiPlugin {
69
82
  'cucumber'
70
83
  )
71
84
  if (isUnskippable) {
85
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
72
86
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
73
87
  }
74
88
  if (isForcedToRun) {
89
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
75
90
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
76
91
  }
77
92
  this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
@@ -82,20 +97,31 @@ class CucumberPlugin extends CiPlugin {
82
97
  ...testSuiteMetadata
83
98
  }
84
99
  })
100
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
101
+ if (this.itrConfig?.isCodeCoverageEnabled) {
102
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
103
+ }
85
104
  })
86
105
 
87
106
  this.addSub('ci:cucumber:test-suite:finish', status => {
88
107
  this.testSuiteSpan.setTag(TEST_STATUS, status)
89
108
  this.testSuiteSpan.finish()
109
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
90
110
  })
91
111
 
92
112
  this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
93
- if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
113
+ if (!this.itrConfig?.isCodeCoverageEnabled) {
94
114
  return
95
115
  }
116
+ if (!coverageFiles.length) {
117
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
118
+ }
119
+
96
120
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
97
121
  .map(filename => getTestSuitePath(filename, this.sourceRoot))
98
122
 
123
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
124
+
99
125
  const formattedCoverage = {
100
126
  sessionId: this.testSuiteSpan.context()._traceId,
101
127
  suiteId: this.testSuiteSpan.context()._spanId,
@@ -103,6 +129,7 @@ class CucumberPlugin extends CiPlugin {
103
129
  }
104
130
 
105
131
  this.tracer._exporter.exportCoverage(formattedCoverage)
132
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
106
133
  })
107
134
 
108
135
  this.addSub('ci:cucumber:test:start', ({ testName, fullTestSuite, testSourceLine }) => {
@@ -142,6 +169,11 @@ class CucumberPlugin extends CiPlugin {
142
169
  }
143
170
 
144
171
  span.finish()
172
+ this.telemetry.ciVisEvent(
173
+ TELEMETRY_EVENT_FINISHED,
174
+ 'test',
175
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
176
+ )
145
177
  if (!isStep) {
146
178
  finishAllTraceSpans(span)
147
179
  }
@@ -29,6 +29,29 @@ const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
29
29
  const log = require('../../dd-trace/src/log')
30
30
  const NoopTracer = require('../../dd-trace/src/noop/tracer')
31
31
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
32
+ const {
33
+ TELEMETRY_EVENT_CREATED,
34
+ TELEMETRY_EVENT_FINISHED,
35
+ TELEMETRY_ITR_FORCED_TO_RUN,
36
+ TELEMETRY_CODE_COVERAGE_EMPTY,
37
+ TELEMETRY_ITR_UNSKIPPABLE,
38
+ TELEMETRY_CODE_COVERAGE_NUM_FILES,
39
+ incrementCountMetric,
40
+ distributionMetric
41
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
42
+ const {
43
+ GIT_REPOSITORY_URL,
44
+ GIT_COMMIT_SHA,
45
+ GIT_BRANCH,
46
+ CI_PROVIDER_NAME
47
+ } = require('../../dd-trace/src/plugins/util/tags')
48
+ const {
49
+ OS_VERSION,
50
+ OS_PLATFORM,
51
+ OS_ARCHITECTURE,
52
+ RUNTIME_NAME,
53
+ RUNTIME_VERSION
54
+ } = require('../../dd-trace/src/plugins/util/env')
32
55
 
33
56
  const TEST_FRAMEWORK_NAME = 'cypress'
34
57
 
@@ -152,16 +175,19 @@ module.exports = (on, config) => {
152
175
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
153
176
 
154
177
  const {
155
- 'git.repository_url': repositoryUrl,
156
- 'git.commit.sha': sha,
157
- 'os.version': osVersion,
158
- 'os.platform': osPlatform,
159
- 'os.architecture': osArchitecture,
160
- 'runtime.name': runtimeName,
161
- 'runtime.version': runtimeVersion,
162
- 'git.branch': branch
178
+ [GIT_REPOSITORY_URL]: repositoryUrl,
179
+ [GIT_COMMIT_SHA]: sha,
180
+ [OS_VERSION]: osVersion,
181
+ [OS_PLATFORM]: osPlatform,
182
+ [OS_ARCHITECTURE]: osArchitecture,
183
+ [RUNTIME_NAME]: runtimeName,
184
+ [RUNTIME_VERSION]: runtimeVersion,
185
+ [GIT_BRANCH]: branch,
186
+ [CI_PROVIDER_NAME]: ciProviderName
163
187
  } = testEnvironmentMetadata
164
188
 
189
+ const isUnsupportedCIProvider = !ciProviderName
190
+
165
191
  const finishedTestsByFile = {}
166
192
 
167
193
  const testConfiguration = {
@@ -192,6 +218,15 @@ module.exports = (on, config) => {
192
218
  let hasForcedToRunSuites = false
193
219
  let hasUnskippableSuites = false
194
220
 
221
+ function ciVisEvent (name, testLevel, tags = {}) {
222
+ incrementCountMetric(name, {
223
+ testLevel,
224
+ testFramework: 'cypress',
225
+ isUnsupportedCIProvider,
226
+ ...tags
227
+ })
228
+ }
229
+
195
230
  function getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
196
231
  const testSuiteTags = {
197
232
  [TEST_COMMAND]: command,
@@ -220,14 +255,18 @@ module.exports = (on, config) => {
220
255
 
221
256
  if (isUnskippable) {
222
257
  hasUnskippableSuites = true
258
+ incrementCountMetric(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
223
259
  testSpanMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
224
260
  }
225
261
 
226
262
  if (isForcedToRun) {
227
263
  hasForcedToRunSuites = true
264
+ incrementCountMetric(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
228
265
  testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
229
266
  }
230
267
 
268
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'test', { hasCodeOwners: !!codeOwners })
269
+
231
270
  return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
232
271
  childOf,
233
272
  tags: {
@@ -281,6 +320,8 @@ module.exports = (on, config) => {
281
320
  ...testSessionSpanMetadata
282
321
  }
283
322
  })
323
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
324
+
284
325
  testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
285
326
  childOf: testSessionSpan,
286
327
  tags: {
@@ -289,6 +330,8 @@ module.exports = (on, config) => {
289
330
  ...testModuleSpanMetadata
290
331
  }
291
332
  })
333
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
334
+
292
335
  return details
293
336
  })
294
337
  })
@@ -347,6 +390,7 @@ module.exports = (on, config) => {
347
390
  }
348
391
  testSuiteSpan.finish()
349
392
  testSuiteSpan = null
393
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
350
394
  }
351
395
  })
352
396
 
@@ -371,7 +415,9 @@ module.exports = (on, config) => {
371
415
  )
372
416
 
373
417
  testModuleSpan.finish()
418
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
374
419
  testSessionSpan.finish()
420
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
375
421
 
376
422
  finishAllTraceSpans(testSessionSpan)
377
423
  }
@@ -406,6 +452,7 @@ module.exports = (on, config) => {
406
452
  ...testSuiteSpanMetadata
407
453
  }
408
454
  })
455
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
409
456
  return null
410
457
  },
411
458
  'dd:beforeEach': (test) => {
@@ -435,6 +482,10 @@ module.exports = (on, config) => {
435
482
  if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
436
483
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
437
484
  const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
485
+ if (!relativeCoverageFiles.length) {
486
+ incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
487
+ }
488
+ distributionMetric(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
438
489
  const { _traceId, _spanId } = testSuiteSpan.context()
439
490
  const formattedCoverage = {
440
491
  sessionId: _traceId,
@@ -470,6 +521,7 @@ module.exports = (on, config) => {
470
521
  // test spans are finished at after:spec
471
522
  }
472
523
  activeSpan = null
524
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test')
473
525
  return null
474
526
  },
475
527
  'dd:addTags': (tags) => {
@@ -12,10 +12,21 @@ const {
12
12
  TEST_FRAMEWORK_VERSION,
13
13
  TEST_SOURCE_START,
14
14
  TEST_ITR_UNSKIPPABLE,
15
- TEST_ITR_FORCED_RUN
15
+ TEST_ITR_FORCED_RUN,
16
+ TEST_CODE_OWNERS
16
17
  } = require('../../dd-trace/src/plugins/util/test')
17
18
  const { COMPONENT } = require('../../dd-trace/src/constants')
18
19
  const id = require('../../dd-trace/src/id')
20
+ const {
21
+ TELEMETRY_EVENT_CREATED,
22
+ TELEMETRY_EVENT_FINISHED,
23
+ TELEMETRY_CODE_COVERAGE_STARTED,
24
+ TELEMETRY_CODE_COVERAGE_FINISHED,
25
+ TELEMETRY_ITR_FORCED_TO_RUN,
26
+ TELEMETRY_CODE_COVERAGE_EMPTY,
27
+ TELEMETRY_ITR_UNSKIPPABLE,
28
+ TELEMETRY_CODE_COVERAGE_NUM_FILES
29
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
19
30
 
20
31
  const isJestWorker = !!process.env.JEST_WORKER_ID
21
32
 
@@ -81,7 +92,9 @@ class JestPlugin extends CiPlugin {
81
92
  )
82
93
 
83
94
  this.testModuleSpan.finish()
95
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
84
96
  this.testSessionSpan.finish()
97
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
85
98
  finishAllTraceSpans(this.testSessionSpan)
86
99
  this.tracer._exporter.flush()
87
100
  })
@@ -103,7 +116,8 @@ class JestPlugin extends CiPlugin {
103
116
  _ddTestCommand: testCommand,
104
117
  _ddTestModuleId: testModuleId,
105
118
  _ddForcedToRun,
106
- _ddUnskippable
119
+ _ddUnskippable,
120
+ _ddTestCodeCoverageEnabled
107
121
  } = testEnvironmentOptions
108
122
 
109
123
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -114,8 +128,10 @@ class JestPlugin extends CiPlugin {
114
128
  const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
115
129
 
116
130
  if (_ddUnskippable) {
131
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
117
132
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
118
133
  if (_ddForcedToRun) {
134
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
119
135
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
120
136
  }
121
137
  }
@@ -128,6 +144,10 @@ class JestPlugin extends CiPlugin {
128
144
  ...testSuiteMetadata
129
145
  }
130
146
  })
147
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
148
+ if (_ddTestCodeCoverageEnabled) {
149
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
150
+ }
131
151
  })
132
152
 
133
153
  this.addSub('ci:jest:worker-report:trace', traces => {
@@ -164,6 +184,7 @@ class JestPlugin extends CiPlugin {
164
184
  this.testSuiteSpan.setTag('error', new Error(errorMessage))
165
185
  }
166
186
  this.testSuiteSpan.finish()
187
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
167
188
  // Suites potentially run in a different process than the session,
168
189
  // so calling finishAllTraceSpans on the session span is not enough
169
190
  finishAllTraceSpans(this.testSuiteSpan)
@@ -180,14 +201,22 @@ class JestPlugin extends CiPlugin {
180
201
  * because this subscription happens in a different process from the one
181
202
  * fetching the ITR config.
182
203
  */
183
- this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
204
+ this.addSub('ci:jest:test-suite:code-coverage', ({ coverageFiles, testSuite }) => {
205
+ if (!coverageFiles.length) {
206
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
207
+ }
208
+ const files = [...coverageFiles, testSuite]
209
+
184
210
  const { _traceId, _spanId } = this.testSuiteSpan.context()
185
211
  const formattedCoverage = {
186
212
  sessionId: _traceId,
187
213
  suiteId: _spanId,
188
- files: coverageFiles
214
+ files
189
215
  }
216
+
190
217
  this.tracer._exporter.exportCoverage(formattedCoverage)
218
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
219
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, files.length)
191
220
  })
192
221
 
193
222
  this.addSub('ci:jest:test:start', (test) => {
@@ -204,6 +233,11 @@ class JestPlugin extends CiPlugin {
204
233
  span.setTag(TEST_SOURCE_START, testStartLine)
205
234
  }
206
235
  span.finish()
236
+ this.telemetry.ciVisEvent(
237
+ TELEMETRY_EVENT_FINISHED,
238
+ 'test',
239
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
240
+ )
207
241
  finishAllTraceSpans(span)
208
242
  })
209
243