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.
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/child-process.js +4 -5
- package/packages/datadog-instrumentations/src/couchbase.js +5 -4
- package/packages/datadog-instrumentations/src/crypto.js +2 -1
- package/packages/datadog-instrumentations/src/dns.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +7 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +8 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +18 -2
- package/packages/datadog-instrumentations/src/http/client.js +2 -2
- package/packages/datadog-instrumentations/src/http/server.js +7 -4
- package/packages/datadog-instrumentations/src/http2/client.js +3 -1
- package/packages/datadog-instrumentations/src/http2/server.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +1 -1
- package/packages/datadog-instrumentations/src/net.js +10 -2
- package/packages/datadog-plugin-cucumber/src/index.js +34 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
- package/packages/datadog-plugin-jest/src/index.js +38 -4
- package/packages/datadog-plugin-mocha/src/index.js +32 -1
- package/packages/datadog-plugin-playwright/src/index.js +17 -1
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +36 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +18 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
- package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
- package/packages/dd-trace/src/config.js +99 -58
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
- package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +44 -8
- package/packages/dd-trace/src/plugins/index.js +5 -0
- package/packages/dd-trace/src/plugins/util/exec.js +23 -2
- package/packages/dd-trace/src/plugins/util/git.js +94 -19
- package/packages/dd-trace/src/priority_sampler.js +30 -38
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +1 -1
- package/packages/dd-trace/src/sampling_rule.js +130 -0
- package/packages/dd-trace/src/span_sampler.js +6 -64
- 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.
|
|
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.
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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: ['
|
|
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:
|
|
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:
|
|
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 (
|
|
25
|
-
|
|
24
|
+
if (typeof name === 'string') {
|
|
25
|
+
name = [name]
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
+
const names = ['http', 'https', 'node:http', 'node:https']
|
|
18
18
|
|
|
19
|
-
addHook({ name:
|
|
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
|
-
|
|
19
|
-
|
|
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:
|
|
25
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
|