dd-trace 5.7.0 → 5.8.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/index.d.ts +35 -0
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/apollo.js +101 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/mongodb-core.js +1 -3
- package/packages/datadog-instrumentations/src/playwright.js +151 -6
- package/packages/datadog-plugin-apollo/src/gateway/execute.js +12 -0
- package/packages/datadog-plugin-apollo/src/gateway/fetch.js +36 -0
- package/packages/datadog-plugin-apollo/src/gateway/index.js +36 -0
- package/packages/datadog-plugin-apollo/src/gateway/plan.js +13 -0
- package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +12 -0
- package/packages/datadog-plugin-apollo/src/gateway/request.js +139 -0
- package/packages/datadog-plugin-apollo/src/gateway/validate.js +21 -0
- package/packages/datadog-plugin-apollo/src/index.js +15 -0
- package/packages/datadog-plugin-playwright/src/index.js +10 -2
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/apollo.js +50 -0
- package/packages/dd-trace/src/plugins/composite.js +1 -0
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/tracing.js +8 -5
- package/packages/dd-trace/src/profiling/profilers/events.js +79 -82
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +24 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +24 -0
package/index.d.ts
CHANGED
|
@@ -137,8 +137,10 @@ interface Tracer extends opentracing.Tracer {
|
|
|
137
137
|
// is doesn't need to be exported for Tracer
|
|
138
138
|
/** @hidden */
|
|
139
139
|
interface Plugins {
|
|
140
|
+
"aerospike": tracer.plugins.aerospike;
|
|
140
141
|
"amqp10": tracer.plugins.amqp10;
|
|
141
142
|
"amqplib": tracer.plugins.amqplib;
|
|
143
|
+
"apollo": tracer.plugins.apollo;
|
|
142
144
|
"aws-sdk": tracer.plugins.aws_sdk;
|
|
143
145
|
"bunyan": tracer.plugins.bunyan;
|
|
144
146
|
"cassandra-driver": tracer.plugins.cassandra_driver;
|
|
@@ -1098,6 +1100,12 @@ declare namespace tracer {
|
|
|
1098
1100
|
meta?: boolean;
|
|
1099
1101
|
}
|
|
1100
1102
|
|
|
1103
|
+
/**
|
|
1104
|
+
* This plugin automatically instruments the
|
|
1105
|
+
* [aerospike](https://github.com/aerospike/aerospike-client-nodejs) for module versions >= v3.16.2.
|
|
1106
|
+
*/
|
|
1107
|
+
interface aerospike extends Instrumentation {}
|
|
1108
|
+
|
|
1101
1109
|
/**
|
|
1102
1110
|
* This plugin automatically instruments the
|
|
1103
1111
|
* [amqp10](https://github.com/noodlefrenzy/node-amqp10) module.
|
|
@@ -1110,6 +1118,33 @@ declare namespace tracer {
|
|
|
1110
1118
|
*/
|
|
1111
1119
|
interface amqplib extends Instrumentation {}
|
|
1112
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* Currently this plugin automatically instruments
|
|
1123
|
+
* [@apollo/gateway](https://github.com/apollographql/federation) for module versions >= v2.3.0.
|
|
1124
|
+
* This module uses graphql operations to service requests & thus generates graphql spans.
|
|
1125
|
+
* We recommend disabling the graphql plugin if you only want to trace @apollo/gateway
|
|
1126
|
+
*/
|
|
1127
|
+
interface apollo extends Instrumentation {
|
|
1128
|
+
/**
|
|
1129
|
+
* Whether to include the source of the operation within the query as a tag
|
|
1130
|
+
* on every span. This may contain sensitive information and should only be
|
|
1131
|
+
* enabled if sensitive data is always sent as variables and not in the
|
|
1132
|
+
* query text.
|
|
1133
|
+
*
|
|
1134
|
+
* @default false
|
|
1135
|
+
*/
|
|
1136
|
+
source?: boolean;
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Whether to enable signature calculation for the resource name. This can
|
|
1140
|
+
* be disabled if your apollo/gateway operations always have a name. Note that when
|
|
1141
|
+
* disabled all queries will need to be named for this to work properly.
|
|
1142
|
+
*
|
|
1143
|
+
* @default true
|
|
1144
|
+
*/
|
|
1145
|
+
signature?: boolean;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1113
1148
|
/**
|
|
1114
1149
|
* This plugin automatically instruments the
|
|
1115
1150
|
* [aws-sdk](https://github.com/aws/aws-sdk-js) module.
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const {
|
|
2
|
+
addHook,
|
|
3
|
+
channel
|
|
4
|
+
} = require('./helpers/instrument')
|
|
5
|
+
const shimmer = require('../../datadog-shimmer')
|
|
6
|
+
const tracingChannel = require('dc-polyfill').tracingChannel
|
|
7
|
+
|
|
8
|
+
const CHANNELS = {
|
|
9
|
+
'gateway.request': tracingChannel('apm:apollo:gateway:request'),
|
|
10
|
+
'gateway.plan': tracingChannel('apm:apollo:gateway:plan'),
|
|
11
|
+
'gateway.validate': tracingChannel('apm:apollo:gateway:validate'),
|
|
12
|
+
'gateway.execute': tracingChannel('apm:apollo:gateway:execute'),
|
|
13
|
+
'gateway.fetch': tracingChannel('apm:apollo:gateway:fetch'),
|
|
14
|
+
'gateway.postprocessing': tracingChannel('apm:apollo:gateway:postprocessing')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const executorCh = channel('apm:apollo:gateway:request:executor')
|
|
18
|
+
const generalErrorCh = channel('apm:apollo:gateway:general:error')
|
|
19
|
+
|
|
20
|
+
function wrapExecutor (executor) {
|
|
21
|
+
return function (...args) {
|
|
22
|
+
const ctx = { requestContext: args[0], gateway: this }
|
|
23
|
+
executorCh.publish(ctx)
|
|
24
|
+
return executor.apply(this, args)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function wrapApolloGateway (ApolloGateway) {
|
|
29
|
+
class ApolloGatewayWrapper extends ApolloGateway {
|
|
30
|
+
constructor (...args) {
|
|
31
|
+
super(...args)
|
|
32
|
+
shimmer.wrap(this, 'executor', wrapExecutor)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return ApolloGatewayWrapper
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function wrapRecordExceptions (recordExceptions) {
|
|
39
|
+
return function wrappedRecordExceptions (...args) {
|
|
40
|
+
const errors = args[1]
|
|
41
|
+
// only the last exception in the array of exceptions will be reported on the span,
|
|
42
|
+
// this is mimicking apollo-gateways internal instrumentation
|
|
43
|
+
// TODO: should we consider a mechanism to report all exceptions? since this method aggregates all exceptions
|
|
44
|
+
// where as a span can only have one exception set on it at a time
|
|
45
|
+
generalErrorCh.publish({ error: errors[errors.length - 1] })
|
|
46
|
+
return recordExceptions.apply(this, args)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function wrapStartActiveSpan (startActiveSpan) {
|
|
51
|
+
return function (...args) {
|
|
52
|
+
const firstArg = args[0]
|
|
53
|
+
const cb = args[args.length - 1]
|
|
54
|
+
if (typeof firstArg !== 'string' || typeof cb !== 'function') return startActiveSpan.apply(this, args)
|
|
55
|
+
|
|
56
|
+
const method = CHANNELS[firstArg]
|
|
57
|
+
let ctx = {}
|
|
58
|
+
if (firstArg === 'gateway.fetch') {
|
|
59
|
+
ctx = { attributes: args[1].attributes }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
switch (firstArg) {
|
|
63
|
+
case 'gateway.plan' :
|
|
64
|
+
case 'gateway.validate': {
|
|
65
|
+
args[args.length - 1] = function (...callbackArgs) {
|
|
66
|
+
return method.traceSync(cb, ctx, this, ...callbackArgs)
|
|
67
|
+
}
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
case 'gateway.request':
|
|
71
|
+
case 'gateway.execute':
|
|
72
|
+
case 'gateway.postprocessing' :
|
|
73
|
+
case 'gateway.fetch': {
|
|
74
|
+
args[args.length - 1] = function (...callbackArgs) {
|
|
75
|
+
return method.tracePromise(cb, ctx, this, ...callbackArgs)
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return startActiveSpan.apply(this, args)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
addHook({ name: '@apollo/gateway', file: 'dist/utilities/opentelemetry.js', versions: ['>=2.3.0'] },
|
|
85
|
+
(obj) => {
|
|
86
|
+
const newTracerObj = Object.create(obj.tracer)
|
|
87
|
+
shimmer.wrap(newTracerObj, 'startActiveSpan', wrapStartActiveSpan)
|
|
88
|
+
obj.tracer = newTracerObj
|
|
89
|
+
return obj
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
addHook({ name: '@apollo/gateway', file: 'dist/utilities/opentelemetry.js', versions: ['>=2.6.0'] },
|
|
93
|
+
(obj) => {
|
|
94
|
+
shimmer.wrap(obj, 'recordExceptions', wrapRecordExceptions)
|
|
95
|
+
return obj
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
addHook({ name: '@apollo/gateway', versions: ['>=2.3.0'] }, (gateway) => {
|
|
99
|
+
shimmer.wrap(gateway, 'ApolloGateway', wrapApolloGateway)
|
|
100
|
+
return gateway
|
|
101
|
+
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
'@apollo/server': () => require('../apollo-server'),
|
|
5
|
+
'@apollo/gateway': () => require('../apollo'),
|
|
5
6
|
'apollo-server-core': () => require('../apollo-server-core'),
|
|
6
7
|
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
|
|
7
8
|
'@cucumber/cucumber': () => require('../cucumber'),
|
|
@@ -197,7 +197,7 @@ function instrumentPromise (operation, command, ctx, args, server, ns, ops, opti
|
|
|
197
197
|
|
|
198
198
|
const promise = command.apply(ctx, args)
|
|
199
199
|
|
|
200
|
-
promise.then(function (res) {
|
|
200
|
+
return promise.then(function (res) {
|
|
201
201
|
finishCh.publish()
|
|
202
202
|
return res
|
|
203
203
|
}, function (err) {
|
|
@@ -206,7 +206,5 @@ function instrumentPromise (operation, command, ctx, args, server, ns, ops, opti
|
|
|
206
206
|
|
|
207
207
|
return Promise.reject(err)
|
|
208
208
|
})
|
|
209
|
-
|
|
210
|
-
return promise
|
|
211
209
|
})
|
|
212
210
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
const semver = require('semver')
|
|
2
|
+
|
|
1
3
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
2
4
|
const shimmer = require('../../datadog-shimmer')
|
|
3
|
-
const { parseAnnotations } = require('../../dd-trace/src/plugins/util/test')
|
|
5
|
+
const { parseAnnotations, getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
|
|
6
|
+
const log = require('../../dd-trace/src/log')
|
|
4
7
|
|
|
5
8
|
const testStartCh = channel('ci:playwright:test:start')
|
|
6
9
|
const testFinishCh = channel('ci:playwright:test:finish')
|
|
@@ -8,6 +11,9 @@ const testFinishCh = channel('ci:playwright:test:finish')
|
|
|
8
11
|
const testSessionStartCh = channel('ci:playwright:session:start')
|
|
9
12
|
const testSessionFinishCh = channel('ci:playwright:session:finish')
|
|
10
13
|
|
|
14
|
+
const libraryConfigurationCh = channel('ci:playwright:library-configuration')
|
|
15
|
+
const knownTestsCh = channel('ci:playwright:known-tests')
|
|
16
|
+
|
|
11
17
|
const testSuiteStartCh = channel('ci:playwright:test-suite:start')
|
|
12
18
|
const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
|
|
13
19
|
|
|
@@ -16,6 +22,8 @@ const testSuiteToAr = new Map()
|
|
|
16
22
|
const testSuiteToTestStatuses = new Map()
|
|
17
23
|
const testSuiteToErrors = new Map()
|
|
18
24
|
|
|
25
|
+
let applyRepeatEachIndex = null
|
|
26
|
+
|
|
19
27
|
let startedSuites = []
|
|
20
28
|
|
|
21
29
|
const STATUS_TO_TEST_STATUS = {
|
|
@@ -26,6 +34,44 @@ const STATUS_TO_TEST_STATUS = {
|
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
let remainingTestsByFile = {}
|
|
37
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
38
|
+
let earlyFlakeDetectionNumRetries = 0
|
|
39
|
+
let knownTests = []
|
|
40
|
+
let rootDir = ''
|
|
41
|
+
const MINIMUM_SUPPORTED_VERSION_EFD = '1.38.0'
|
|
42
|
+
|
|
43
|
+
function isNewTest (test) {
|
|
44
|
+
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
45
|
+
const testsForSuite = knownTests?.playwright?.[testSuite] || []
|
|
46
|
+
|
|
47
|
+
return !testsForSuite.includes(test.title)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getSuiteType (test, type) {
|
|
51
|
+
let suite = test.parent
|
|
52
|
+
while (suite && suite._type !== type) {
|
|
53
|
+
suite = suite.parent
|
|
54
|
+
}
|
|
55
|
+
return suite
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Copy of Suite#_deepClone but with a function to filter tests
|
|
59
|
+
function deepCloneSuite (suite, filterTest) {
|
|
60
|
+
const copy = suite._clone()
|
|
61
|
+
for (const entry of suite._entries) {
|
|
62
|
+
if (entry.constructor.name === 'Suite') {
|
|
63
|
+
copy._addSuite(deepCloneSuite(entry, filterTest))
|
|
64
|
+
} else {
|
|
65
|
+
if (filterTest(entry)) {
|
|
66
|
+
const copiedTest = entry._clone()
|
|
67
|
+
copiedTest._ddIsNew = true
|
|
68
|
+
copiedTest._ddIsEfdRetry = true
|
|
69
|
+
copy._addTest(copiedTest)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return copy
|
|
74
|
+
}
|
|
29
75
|
|
|
30
76
|
function getTestsBySuiteFromTestGroups (testGroups) {
|
|
31
77
|
return testGroups.reduce((acc, { requireFile, tests }) => {
|
|
@@ -153,8 +199,11 @@ function getTestSuiteError (testSuiteAbsolutePath) {
|
|
|
153
199
|
function testBeginHandler (test, browserName) {
|
|
154
200
|
const {
|
|
155
201
|
_requireFile: testSuiteAbsolutePath,
|
|
156
|
-
title: testName,
|
|
157
|
-
|
|
202
|
+
title: testName,
|
|
203
|
+
_type,
|
|
204
|
+
location: {
|
|
205
|
+
line: testSourceLine
|
|
206
|
+
}
|
|
158
207
|
} = test
|
|
159
208
|
|
|
160
209
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
@@ -198,7 +247,14 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
198
247
|
const testResult = results[results.length - 1]
|
|
199
248
|
const testAsyncResource = testToAr.get(test)
|
|
200
249
|
testAsyncResource.runInAsyncScope(() => {
|
|
201
|
-
testFinishCh.publish({
|
|
250
|
+
testFinishCh.publish({
|
|
251
|
+
testStatus,
|
|
252
|
+
steps: testResult.steps,
|
|
253
|
+
error,
|
|
254
|
+
extraTags: annotationTags,
|
|
255
|
+
isNew: test._ddIsNew,
|
|
256
|
+
isEfdRetry: test._ddIsEfdRetry
|
|
257
|
+
})
|
|
202
258
|
})
|
|
203
259
|
|
|
204
260
|
if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
|
|
@@ -309,14 +365,54 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
309
365
|
|
|
310
366
|
function runnerHook (runnerExport, playwrightVersion) {
|
|
311
367
|
shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
|
|
368
|
+
let onDone
|
|
369
|
+
|
|
312
370
|
const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
313
|
-
|
|
371
|
+
|
|
372
|
+
rootDir = getRootDir(this)
|
|
314
373
|
|
|
315
374
|
const processArgv = process.argv.slice(2).join(' ')
|
|
316
375
|
const command = `playwright ${processArgv}`
|
|
317
376
|
testSessionAsyncResource.runInAsyncScope(() => {
|
|
318
377
|
testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
|
|
319
378
|
})
|
|
379
|
+
|
|
380
|
+
const configurationPromise = new Promise((resolve) => {
|
|
381
|
+
onDone = resolve
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
testSessionAsyncResource.runInAsyncScope(() => {
|
|
385
|
+
libraryConfigurationCh.publish({ onDone })
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const { err, libraryConfig } = await configurationPromise
|
|
390
|
+
if (!err) {
|
|
391
|
+
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
392
|
+
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
393
|
+
}
|
|
394
|
+
} catch (e) {
|
|
395
|
+
log.error(e)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isEarlyFlakeDetectionEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) {
|
|
399
|
+
const knownTestsPromise = new Promise((resolve) => {
|
|
400
|
+
onDone = resolve
|
|
401
|
+
})
|
|
402
|
+
testSessionAsyncResource.runInAsyncScope(() => {
|
|
403
|
+
knownTestsCh.publish({ onDone })
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const { err, knownTests: receivedKnownTests } = await knownTestsPromise
|
|
408
|
+
if (!err) {
|
|
409
|
+
knownTests = receivedKnownTests
|
|
410
|
+
}
|
|
411
|
+
} catch (err) {
|
|
412
|
+
log.error(err)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
320
416
|
const projects = getProjectsFromRunner(this)
|
|
321
417
|
|
|
322
418
|
const runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
@@ -334,7 +430,6 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
334
430
|
|
|
335
431
|
const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
|
|
336
432
|
|
|
337
|
-
let onDone
|
|
338
433
|
const flushWait = new Promise(resolve => {
|
|
339
434
|
onDone = resolve
|
|
340
435
|
})
|
|
@@ -394,3 +489,53 @@ addHook({
|
|
|
394
489
|
file: 'lib/runner/dispatcher.js',
|
|
395
490
|
versions: ['>=1.38.0']
|
|
396
491
|
}, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
|
|
492
|
+
|
|
493
|
+
// Hook used for early flake detection. EFD only works from >=1.38.0
|
|
494
|
+
addHook({
|
|
495
|
+
name: 'playwright',
|
|
496
|
+
file: 'lib/common/suiteUtils.js',
|
|
497
|
+
versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
|
|
498
|
+
}, suiteUtilsPackage => {
|
|
499
|
+
// We grab `applyRepeatEachIndex` to use it later
|
|
500
|
+
// `applyRepeatEachIndex` needs to be applied to a cloned suite
|
|
501
|
+
applyRepeatEachIndex = suiteUtilsPackage.applyRepeatEachIndex
|
|
502
|
+
return suiteUtilsPackage
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
// Hook used for early flake detection. EFD only works from >=1.38.0
|
|
506
|
+
addHook({
|
|
507
|
+
name: 'playwright',
|
|
508
|
+
file: 'lib/runner/loadUtils.js',
|
|
509
|
+
versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
|
|
510
|
+
}, (loadUtilsPackage) => {
|
|
511
|
+
const oldCreateRootSuite = loadUtilsPackage.createRootSuite
|
|
512
|
+
|
|
513
|
+
async function newCreateRootSuite () {
|
|
514
|
+
const rootSuite = await oldCreateRootSuite.apply(this, arguments)
|
|
515
|
+
if (!isEarlyFlakeDetectionEnabled) {
|
|
516
|
+
return rootSuite
|
|
517
|
+
}
|
|
518
|
+
const newTests = rootSuite
|
|
519
|
+
.allTests()
|
|
520
|
+
.filter(isNewTest)
|
|
521
|
+
|
|
522
|
+
newTests.forEach(newTest => {
|
|
523
|
+
newTest._ddIsNew = true
|
|
524
|
+
if (newTest.expectedStatus !== 'skipped') {
|
|
525
|
+
const fileSuite = getSuiteType(newTest, 'file')
|
|
526
|
+
const projectSuite = getSuiteType(newTest, 'project')
|
|
527
|
+
for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
|
|
528
|
+
const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
|
|
529
|
+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
530
|
+
projectSuite._addSuite(copyFileSuite)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
return rootSuite
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
loadUtilsPackage.createRootSuite = newCreateRootSuite
|
|
539
|
+
|
|
540
|
+
return loadUtilsPackage
|
|
541
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
4
|
+
|
|
5
|
+
class ApolloGatewayExecutePlugin extends ApolloBasePlugin {
|
|
6
|
+
static get operation () { return 'execute' }
|
|
7
|
+
static get prefix () {
|
|
8
|
+
return 'tracing:apm:apollo:gateway:execute'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = ApolloGatewayExecutePlugin
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../datadog-core')
|
|
4
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
5
|
+
|
|
6
|
+
class ApolloGatewayFetchPlugin extends ApolloBasePlugin {
|
|
7
|
+
static get operation () { return 'fetch' }
|
|
8
|
+
static get prefix () {
|
|
9
|
+
return 'tracing:apm:apollo:gateway:fetch'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
bindStart (ctx) {
|
|
13
|
+
const store = storage.getStore()
|
|
14
|
+
const childOf = store ? store.span : null
|
|
15
|
+
|
|
16
|
+
const spanData = {
|
|
17
|
+
childOf,
|
|
18
|
+
service: this.getServiceName(),
|
|
19
|
+
type: this.constructor.type,
|
|
20
|
+
meta: {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const serviceName = ctx?.attributes?.service
|
|
24
|
+
|
|
25
|
+
if (serviceName) { spanData.meta['serviceName'] = serviceName }
|
|
26
|
+
|
|
27
|
+
const span = this.startSpan(this.getOperationName(), spanData, false)
|
|
28
|
+
|
|
29
|
+
ctx.parentStore = store
|
|
30
|
+
ctx.currentStore = { ...store, span }
|
|
31
|
+
|
|
32
|
+
return ctx.currentStore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = ApolloGatewayFetchPlugin
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../datadog-core')
|
|
4
|
+
const CompositePlugin = require('../../../dd-trace/src/plugins/composite')
|
|
5
|
+
const ApolloGatewayExecutePlugin = require('./execute')
|
|
6
|
+
const ApolloGatewayPostProcessingPlugin = require('./postprocessing')
|
|
7
|
+
const ApolloGatewayRequestPlugin = require('./request')
|
|
8
|
+
const ApolloGatewayPlanPlugin = require('./plan')
|
|
9
|
+
const ApolloGatewayValidatePlugin = require('./validate')
|
|
10
|
+
const ApolloGatewayFetchPlugin = require('./fetch')
|
|
11
|
+
|
|
12
|
+
class ApolloGatewayPlugin extends CompositePlugin {
|
|
13
|
+
static get id () { return 'gateway' }
|
|
14
|
+
static get plugins () {
|
|
15
|
+
return {
|
|
16
|
+
execute: ApolloGatewayExecutePlugin,
|
|
17
|
+
postprocessing: ApolloGatewayPostProcessingPlugin,
|
|
18
|
+
request: ApolloGatewayRequestPlugin,
|
|
19
|
+
plan: ApolloGatewayPlanPlugin,
|
|
20
|
+
fetch: ApolloGatewayFetchPlugin,
|
|
21
|
+
validate: ApolloGatewayValidatePlugin
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor (...args) {
|
|
26
|
+
super(...args)
|
|
27
|
+
this.addSub('apm:apollo:gateway:general:error', (ctx) => {
|
|
28
|
+
const store = storage.getStore()
|
|
29
|
+
const span = store?.span
|
|
30
|
+
if (!span) return
|
|
31
|
+
span.setTag('error', ctx.error)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = ApolloGatewayPlugin
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
5
|
+
|
|
6
|
+
class ApolloGatewayPlanPlugin extends ApolloBasePlugin {
|
|
7
|
+
static get operation () { return 'plan' }
|
|
8
|
+
static get prefix () {
|
|
9
|
+
return 'tracing:apm:apollo:gateway:plan'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = ApolloGatewayPlanPlugin
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
4
|
+
|
|
5
|
+
class ApolloGatewayPostProcessingPlugin extends ApolloBasePlugin {
|
|
6
|
+
static get operation () { return 'postprocessing' }
|
|
7
|
+
static get prefix () {
|
|
8
|
+
return 'tracing:apm:apollo:gateway:postprocessing'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = ApolloGatewayPostProcessingPlugin
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../datadog-core')
|
|
4
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
5
|
+
|
|
6
|
+
let tools
|
|
7
|
+
|
|
8
|
+
const OPERATION_DEFINITION = 'OperationDefinition'
|
|
9
|
+
const FRAGMENT_DEFINITION = 'FragmentDefinition'
|
|
10
|
+
|
|
11
|
+
class ApolloGatewayRequestPlugin extends ApolloBasePlugin {
|
|
12
|
+
static get operation () { return 'request' }
|
|
13
|
+
static get prefix () {
|
|
14
|
+
return 'tracing:apm:apollo:gateway:request'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor (...args) {
|
|
18
|
+
super(...args)
|
|
19
|
+
this.addSub('apm:apollo:gateway:request:executor', (ctx) => {
|
|
20
|
+
if (ctx.requestContext || ctx.gateway) {
|
|
21
|
+
this.requestContext = ctx
|
|
22
|
+
} else {
|
|
23
|
+
this.requestContext = {}
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
bindStart (ctx) {
|
|
29
|
+
const store = storage.getStore()
|
|
30
|
+
const childOf = store ? store.span : null
|
|
31
|
+
const spanData = {
|
|
32
|
+
childOf,
|
|
33
|
+
service: this.serviceName(
|
|
34
|
+
{ id: `${this.constructor.id}.${this.constructor.operation}`, pluginConfig: this.config }),
|
|
35
|
+
type: this.constructor.type,
|
|
36
|
+
kind: this.constructor.kind,
|
|
37
|
+
meta: {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { requestContext, gateway } = this.requestContext
|
|
41
|
+
|
|
42
|
+
if (requestContext?.operationName) {
|
|
43
|
+
spanData.meta['graphql.operation.name'] = requestContext.operationName
|
|
44
|
+
}
|
|
45
|
+
if ((this.config.source || gateway?.config?.telemetry?.includeDocument) && requestContext?.source) {
|
|
46
|
+
spanData.meta['graphql.source'] = requestContext.source
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const operationContext =
|
|
50
|
+
buildOperationContext(gateway.schema, requestContext.document, requestContext.request.operationName)
|
|
51
|
+
|
|
52
|
+
if (operationContext?.operation?.operation) {
|
|
53
|
+
const document = requestContext?.document
|
|
54
|
+
const type = operationContext?.operation?.operation
|
|
55
|
+
const name = operationContext?.operation?.name && operationContext?.operation?.name?.value
|
|
56
|
+
|
|
57
|
+
spanData['resource'] = getSignature(document, name, type, this?.config?.signature)
|
|
58
|
+
spanData.meta['graphql.operation.type'] = type
|
|
59
|
+
}
|
|
60
|
+
const span = this.startSpan(this.operationName({ id: `${this.constructor.id}.${this.constructor.operation}` }),
|
|
61
|
+
spanData, false)
|
|
62
|
+
|
|
63
|
+
ctx.parentStore = store
|
|
64
|
+
ctx.currentStore = { ...store, span }
|
|
65
|
+
return ctx.currentStore
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
asyncStart (ctx) {
|
|
69
|
+
const errors = ctx?.result?.errors
|
|
70
|
+
// apollo gateway catches certain errors and returns them in the result object
|
|
71
|
+
// we want to capture these errors as spans
|
|
72
|
+
if (errors instanceof Array &&
|
|
73
|
+
errors[errors.length - 1] && errors[errors.length - 1].stack && errors[errors.length - 1].message) {
|
|
74
|
+
ctx.currentStore.span.setTag('error', errors[errors.length - 1])
|
|
75
|
+
}
|
|
76
|
+
ctx.currentStore.span.finish()
|
|
77
|
+
return ctx.parentStore
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
end () {
|
|
81
|
+
// do nothing to avoid ApolloBasePlugin's end method
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildOperationContext (schema, operationDocument, operationName) {
|
|
86
|
+
let operation
|
|
87
|
+
let operationCount = 0
|
|
88
|
+
const fragments = Object.create(null)
|
|
89
|
+
try {
|
|
90
|
+
operationDocument.definitions.forEach(definition => {
|
|
91
|
+
switch (definition.kind) {
|
|
92
|
+
case OPERATION_DEFINITION:
|
|
93
|
+
operationCount++
|
|
94
|
+
if (!operationName && operationCount > 1) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
if (
|
|
98
|
+
!operationName ||
|
|
99
|
+
(definition.name && definition.name.value === operationName)
|
|
100
|
+
) {
|
|
101
|
+
operation = definition
|
|
102
|
+
}
|
|
103
|
+
break
|
|
104
|
+
case FRAGMENT_DEFINITION:
|
|
105
|
+
fragments[definition.name.value] = definition
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// safety net
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
schema,
|
|
115
|
+
operation,
|
|
116
|
+
fragments
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getSignature (document, operationName, operationType, calculate) {
|
|
121
|
+
if (calculate !== false && tools !== false) {
|
|
122
|
+
try {
|
|
123
|
+
try {
|
|
124
|
+
tools = tools || require('../../../datadog-plugin-graphql/src/tools')
|
|
125
|
+
} catch (e) {
|
|
126
|
+
tools = false
|
|
127
|
+
throw e
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return tools.defaultEngineReportingSignature(document, operationName)
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// safety net
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [operationType, operationName].filter(val => val).join(' ')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = ApolloGatewayRequestPlugin
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
|
|
4
|
+
|
|
5
|
+
class ApolloGatewayValidatePlugin extends ApolloBasePlugin {
|
|
6
|
+
static get operation () { return 'validate' }
|
|
7
|
+
static get prefix () {
|
|
8
|
+
return 'tracing:apm:apollo:gateway:validate'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
end (ctx) {
|
|
12
|
+
const result = ctx.result
|
|
13
|
+
if (result instanceof Array &&
|
|
14
|
+
result[result.length - 1] && result[result.length - 1].stack && result[result.length - 1].message) {
|
|
15
|
+
ctx.currentStore.span.setTag('error', result[result.length - 1])
|
|
16
|
+
}
|
|
17
|
+
ctx.currentStore.span.finish()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = ApolloGatewayValidatePlugin
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
|
+
const ApolloGatewayPlugin = require('./gateway')
|
|
5
|
+
|
|
6
|
+
class ApolloPlugin extends CompositePlugin {
|
|
7
|
+
static get id () { return 'apollo' }
|
|
8
|
+
static get plugins () {
|
|
9
|
+
return {
|
|
10
|
+
gateway: ApolloGatewayPlugin
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = ApolloPlugin
|
|
@@ -11,7 +11,9 @@ const {
|
|
|
11
11
|
TEST_SOURCE_START,
|
|
12
12
|
TEST_CODE_OWNERS,
|
|
13
13
|
TEST_SOURCE_FILE,
|
|
14
|
-
TEST_CONFIGURATION_BROWSER_NAME
|
|
14
|
+
TEST_CONFIGURATION_BROWSER_NAME,
|
|
15
|
+
TEST_IS_NEW,
|
|
16
|
+
TEST_IS_RETRY
|
|
15
17
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
16
18
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
17
19
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -109,7 +111,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
109
111
|
|
|
110
112
|
this.enter(span, store)
|
|
111
113
|
})
|
|
112
|
-
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags }) => {
|
|
114
|
+
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry }) => {
|
|
113
115
|
const store = storage.getStore()
|
|
114
116
|
const span = store && store.span
|
|
115
117
|
if (!span) return
|
|
@@ -122,6 +124,12 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
122
124
|
if (extraTags) {
|
|
123
125
|
span.addTags(extraTags)
|
|
124
126
|
}
|
|
127
|
+
if (isNew) {
|
|
128
|
+
span.setTag(TEST_IS_NEW, 'true')
|
|
129
|
+
if (isEfdRetry) {
|
|
130
|
+
span.setTag(TEST_IS_RETRY, 'true')
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
|
|
126
134
|
steps.forEach(step => {
|
|
127
135
|
const stepStartTime = step.startTime.getTime()
|
|
@@ -4,7 +4,6 @@ const { channel } = require('dc-polyfill')
|
|
|
4
4
|
const { isFalse } = require('./util')
|
|
5
5
|
const plugins = require('./plugins')
|
|
6
6
|
const log = require('./log')
|
|
7
|
-
const Nomenclature = require('./service-naming')
|
|
8
7
|
|
|
9
8
|
const loadChannel = channel('dd-trace:instrumentation:load')
|
|
10
9
|
|
|
@@ -102,7 +101,7 @@ module.exports = class PluginManager {
|
|
|
102
101
|
// like instrumenter.enable()
|
|
103
102
|
configure (config = {}) {
|
|
104
103
|
this._tracerConfig = config
|
|
105
|
-
|
|
104
|
+
this._tracer._nomenclature.configure(config)
|
|
106
105
|
|
|
107
106
|
for (const name in pluginClasses) {
|
|
108
107
|
this.loadPlugin(name)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const TracingPlugin = require('./tracing')
|
|
2
|
+
const { storage } = require('../../../datadog-core')
|
|
3
|
+
|
|
4
|
+
class ApolloBasePlugin extends TracingPlugin {
|
|
5
|
+
static get id () { return 'apollo.gateway' }
|
|
6
|
+
static get type () { return 'web' }
|
|
7
|
+
static get kind () { return 'server' }
|
|
8
|
+
|
|
9
|
+
bindStart (ctx) {
|
|
10
|
+
const store = storage.getStore()
|
|
11
|
+
const childOf = store ? store.span : null
|
|
12
|
+
|
|
13
|
+
const span = this.startSpan(this.getOperationName(), {
|
|
14
|
+
childOf,
|
|
15
|
+
service: this.getServiceName(),
|
|
16
|
+
type: this.constructor.type,
|
|
17
|
+
kind: this.constructor.kind,
|
|
18
|
+
meta: {}
|
|
19
|
+
}, false)
|
|
20
|
+
|
|
21
|
+
ctx.parentStore = store
|
|
22
|
+
ctx.currentStore = { ...store, span }
|
|
23
|
+
|
|
24
|
+
return ctx.currentStore
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
end (ctx) {
|
|
28
|
+
ctx?.currentStore?.span.finish()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
asyncStart (ctx) {
|
|
32
|
+
ctx?.currentStore?.span.finish()
|
|
33
|
+
return ctx.parentStore
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getServiceName () {
|
|
37
|
+
return this.serviceName({
|
|
38
|
+
id: `${this.constructor.id}.${this.constructor.operation}`,
|
|
39
|
+
pluginConfig: this.config
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getOperationName () {
|
|
44
|
+
return this.operationName({
|
|
45
|
+
id: `${this.constructor.id}.${this.constructor.operation}`
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = ApolloBasePlugin
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
|
|
4
5
|
get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
5
6
|
get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
|
|
6
7
|
get '@playwright/test' () { return require('../../../datadog-plugin-playwright/src') },
|
|
@@ -4,7 +4,6 @@ const Plugin = require('./plugin')
|
|
|
4
4
|
const { storage } = require('../../../datadog-core')
|
|
5
5
|
const analyticsSampler = require('../analytics_sampler')
|
|
6
6
|
const { COMPONENT } = require('../constants')
|
|
7
|
-
const Nomenclature = require('../service-naming')
|
|
8
7
|
|
|
9
8
|
class TracingPlugin extends Plugin {
|
|
10
9
|
constructor (...args) {
|
|
@@ -29,7 +28,7 @@ class TracingPlugin extends Plugin {
|
|
|
29
28
|
kind = this.constructor.kind
|
|
30
29
|
} = opts
|
|
31
30
|
|
|
32
|
-
return
|
|
31
|
+
return this._tracer._nomenclature.serviceName(type, kind, id, opts)
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
operationName (opts = {}) {
|
|
@@ -39,7 +38,7 @@ class TracingPlugin extends Plugin {
|
|
|
39
38
|
kind = this.constructor.kind
|
|
40
39
|
} = opts
|
|
41
40
|
|
|
42
|
-
return
|
|
41
|
+
return this._tracer._nomenclature.opName(type, kind, id, opts)
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
configure (config) {
|
|
@@ -58,8 +57,12 @@ class TracingPlugin extends Plugin {
|
|
|
58
57
|
this.activeSpan?.finish()
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
error (
|
|
62
|
-
|
|
60
|
+
error (ctxOrError) {
|
|
61
|
+
if (ctxOrError?.currentStore) {
|
|
62
|
+
ctxOrError.currentStore?.span.setTag('error', ctxOrError?.error)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
this.addError(ctxOrError)
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
addTraceSubs () {
|
|
@@ -15,6 +15,8 @@ const MS_TO_NS = 1000000
|
|
|
15
15
|
const pprofValueType = 'timeline'
|
|
16
16
|
const pprofValueUnit = 'nanoseconds'
|
|
17
17
|
|
|
18
|
+
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
19
|
+
|
|
18
20
|
function labelFromStr (stringTable, key, valStr) {
|
|
19
21
|
return new Label({ key, str: stringTable.dedup(valStr) })
|
|
20
22
|
}
|
|
@@ -146,6 +148,76 @@ if (node16) {
|
|
|
146
148
|
decoratorTypes.net = NetDecorator
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
// Translates performance entries into pprof samples.
|
|
152
|
+
class EventSerializer {
|
|
153
|
+
constructor () {
|
|
154
|
+
this.stringTable = new StringTable()
|
|
155
|
+
this.samples = []
|
|
156
|
+
this.locations = []
|
|
157
|
+
this.functions = []
|
|
158
|
+
this.decorators = {}
|
|
159
|
+
|
|
160
|
+
// A synthetic single-frame location to serve as the location for timeline
|
|
161
|
+
// samples. We need these as the profiling backend (mimicking official pprof
|
|
162
|
+
// tool's behavior) ignores these.
|
|
163
|
+
const fn = new Function({ id: this.functions.length + 1, name: this.stringTable.dedup('') })
|
|
164
|
+
this.functions.push(fn)
|
|
165
|
+
const line = new Line({ functionId: fn.id })
|
|
166
|
+
const location = new Location({ id: this.locations.length + 1, line: [line] })
|
|
167
|
+
this.locations.push(location)
|
|
168
|
+
this.locationId = [location.id]
|
|
169
|
+
|
|
170
|
+
this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
addEvent (item) {
|
|
174
|
+
const { entryType, startTime, duration } = item
|
|
175
|
+
let decorator = this.decorators[entryType]
|
|
176
|
+
if (!decorator) {
|
|
177
|
+
const DecoratorCtor = decoratorTypes[entryType]
|
|
178
|
+
if (DecoratorCtor) {
|
|
179
|
+
decorator = new DecoratorCtor(this.stringTable)
|
|
180
|
+
decorator.eventTypeLabel = labelFromStrStr(this.stringTable, 'event', entryType)
|
|
181
|
+
this.decorators[entryType] = decorator
|
|
182
|
+
} else {
|
|
183
|
+
// Shouldn't happen but it's better to not rely on observer only getting
|
|
184
|
+
// requested event types.
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const endTime = startTime + duration
|
|
189
|
+
const sampleInput = {
|
|
190
|
+
value: [Math.round(duration * MS_TO_NS)],
|
|
191
|
+
locationId: this.locationId,
|
|
192
|
+
label: [
|
|
193
|
+
decorator.eventTypeLabel,
|
|
194
|
+
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
decorator.decorateSample(sampleInput, item)
|
|
198
|
+
this.samples.push(new Sample(sampleInput))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
createProfile (startDate, endDate) {
|
|
202
|
+
const timeValueType = new ValueType({
|
|
203
|
+
type: this.stringTable.dedup(pprofValueType),
|
|
204
|
+
unit: this.stringTable.dedup(pprofValueUnit)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return new Profile({
|
|
208
|
+
sampleType: [timeValueType],
|
|
209
|
+
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
210
|
+
periodType: timeValueType,
|
|
211
|
+
period: 1,
|
|
212
|
+
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
|
|
213
|
+
sample: this.samples,
|
|
214
|
+
location: this.locations,
|
|
215
|
+
function: this.functions,
|
|
216
|
+
stringTable: this.stringTable
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
149
221
|
/**
|
|
150
222
|
* This class generates pprof files with timeline events sourced from Node.js
|
|
151
223
|
* performance measurement APIs.
|
|
@@ -155,7 +227,7 @@ class EventsProfiler {
|
|
|
155
227
|
this.type = 'events'
|
|
156
228
|
this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
|
|
157
229
|
this._observer = undefined
|
|
158
|
-
this.
|
|
230
|
+
this.eventSerializer = new EventSerializer()
|
|
159
231
|
}
|
|
160
232
|
|
|
161
233
|
start () {
|
|
@@ -163,7 +235,9 @@ class EventsProfiler {
|
|
|
163
235
|
if (this._observer) return
|
|
164
236
|
|
|
165
237
|
function add (items) {
|
|
166
|
-
|
|
238
|
+
for (const item of items.getEntries()) {
|
|
239
|
+
this.eventSerializer.addEvent(item)
|
|
240
|
+
}
|
|
167
241
|
}
|
|
168
242
|
this._observer = new PerformanceObserver(add.bind(this))
|
|
169
243
|
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
|
|
@@ -177,89 +251,12 @@ class EventsProfiler {
|
|
|
177
251
|
}
|
|
178
252
|
|
|
179
253
|
profile (restart, startDate, endDate) {
|
|
180
|
-
if (this.entries.length === 0) {
|
|
181
|
-
// No events in the period; don't produce a profile
|
|
182
|
-
return null
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const stringTable = new StringTable()
|
|
186
|
-
const locations = []
|
|
187
|
-
const functions = []
|
|
188
|
-
|
|
189
|
-
// A synthetic single-frame location to serve as the location for timeline
|
|
190
|
-
// samples. We need these as the profiling backend (mimicking official pprof
|
|
191
|
-
// tool's behavior) ignores these.
|
|
192
|
-
const locationId = (() => {
|
|
193
|
-
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
|
|
194
|
-
functions.push(fn)
|
|
195
|
-
const line = new Line({ functionId: fn.id })
|
|
196
|
-
const location = new Location({ id: locations.length + 1, line: [line] })
|
|
197
|
-
locations.push(location)
|
|
198
|
-
return [location.id]
|
|
199
|
-
})()
|
|
200
|
-
|
|
201
|
-
const decorators = {}
|
|
202
|
-
for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
|
|
203
|
-
const decorator = new DecoratorCtor(stringTable)
|
|
204
|
-
decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
|
|
205
|
-
decorators[eventType] = decorator
|
|
206
|
-
}
|
|
207
|
-
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
208
|
-
|
|
209
|
-
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
210
|
-
const lateEntries = []
|
|
211
|
-
const perfEndDate = endDate.getTime() - performance.timeOrigin
|
|
212
|
-
const samples = this.entries.map((item) => {
|
|
213
|
-
const decorator = decorators[item.entryType]
|
|
214
|
-
if (!decorator) {
|
|
215
|
-
// Shouldn't happen but it's better to not rely on observer only getting
|
|
216
|
-
// requested event types.
|
|
217
|
-
return null
|
|
218
|
-
}
|
|
219
|
-
const { startTime, duration } = item
|
|
220
|
-
if (startTime >= perfEndDate) {
|
|
221
|
-
// An event past the current recording end date; save it for the next
|
|
222
|
-
// profile. Not supposed to happen as long as there's no async activity
|
|
223
|
-
// between capture of the endDate value in profiler.js _collect() and
|
|
224
|
-
// here, but better be safe than sorry.
|
|
225
|
-
lateEntries.push(item)
|
|
226
|
-
return null
|
|
227
|
-
}
|
|
228
|
-
const endTime = startTime + duration
|
|
229
|
-
const sampleInput = {
|
|
230
|
-
value: [Math.round(duration * MS_TO_NS)],
|
|
231
|
-
locationId,
|
|
232
|
-
label: [
|
|
233
|
-
decorator.eventTypeLabel,
|
|
234
|
-
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
235
|
-
]
|
|
236
|
-
}
|
|
237
|
-
decorator.decorateSample(sampleInput, item)
|
|
238
|
-
return new Sample(sampleInput)
|
|
239
|
-
}).filter(v => v)
|
|
240
|
-
|
|
241
|
-
this.entries = lateEntries
|
|
242
|
-
|
|
243
|
-
const timeValueType = new ValueType({
|
|
244
|
-
type: stringTable.dedup(pprofValueType),
|
|
245
|
-
unit: stringTable.dedup(pprofValueUnit)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
254
|
if (!restart) {
|
|
249
255
|
this.stop()
|
|
250
256
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
255
|
-
periodType: timeValueType,
|
|
256
|
-
period: 1,
|
|
257
|
-
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
|
|
258
|
-
sample: samples,
|
|
259
|
-
location: locations,
|
|
260
|
-
function: functions,
|
|
261
|
-
stringTable: stringTable
|
|
262
|
-
})
|
|
257
|
+
const profile = this.eventSerializer.createProfile(startDate, endDate)
|
|
258
|
+
this.eventSerializer = new EventSerializer()
|
|
259
|
+
return profile
|
|
263
260
|
}
|
|
264
261
|
|
|
265
262
|
encode (profile) {
|
|
@@ -6,6 +6,7 @@ const runtimeMetrics = require('./runtime_metrics')
|
|
|
6
6
|
const log = require('./log')
|
|
7
7
|
const { setStartupLogPluginManager } = require('./startup-log')
|
|
8
8
|
const telemetry = require('./telemetry')
|
|
9
|
+
const nomenclature = require('./service-naming')
|
|
9
10
|
const PluginManager = require('./plugin_manager')
|
|
10
11
|
const remoteConfig = require('./appsec/remote_config')
|
|
11
12
|
const AppsecSdk = require('./appsec/sdk')
|
|
@@ -17,6 +18,7 @@ class Tracer extends NoopProxy {
|
|
|
17
18
|
super()
|
|
18
19
|
|
|
19
20
|
this._initialized = false
|
|
21
|
+
this._nomenclature = nomenclature
|
|
20
22
|
this._pluginManager = new PluginManager(this)
|
|
21
23
|
this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
|
|
22
24
|
this._tracingInitialized = false
|
|
@@ -33,6 +33,30 @@ const web = {
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
server: {
|
|
36
|
+
'apollo.gateway.request': {
|
|
37
|
+
opName: () => 'apollo.gateway.request',
|
|
38
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
39
|
+
},
|
|
40
|
+
'apollo.gateway.plan': {
|
|
41
|
+
opName: () => 'apollo.gateway.plan',
|
|
42
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
43
|
+
},
|
|
44
|
+
'apollo.gateway.validate': {
|
|
45
|
+
opName: () => 'apollo.gateway.validate',
|
|
46
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
47
|
+
},
|
|
48
|
+
'apollo.gateway.execute': {
|
|
49
|
+
opName: () => 'apollo.gateway.execute',
|
|
50
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
51
|
+
},
|
|
52
|
+
'apollo.gateway.fetch': {
|
|
53
|
+
opName: () => 'apollo.gateway.fetch',
|
|
54
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
55
|
+
},
|
|
56
|
+
'apollo.gateway.postprocessing': {
|
|
57
|
+
opName: () => 'apollo.gateway.postprocessing',
|
|
58
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
59
|
+
},
|
|
36
60
|
grpc: {
|
|
37
61
|
opName: () => DD_MAJOR <= 2 ? 'grpc.request' : 'grpc.server',
|
|
38
62
|
serviceName: identityService
|
|
@@ -32,6 +32,30 @@ const web = {
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
server: {
|
|
35
|
+
'apollo.gateway.request': {
|
|
36
|
+
opName: () => 'apollo.gateway.request',
|
|
37
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
38
|
+
},
|
|
39
|
+
'apollo.gateway.plan': {
|
|
40
|
+
opName: () => 'apollo.gateway.plan',
|
|
41
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
42
|
+
},
|
|
43
|
+
'apollo.gateway.validate': {
|
|
44
|
+
opName: () => 'apollo.gateway.validate',
|
|
45
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
46
|
+
},
|
|
47
|
+
'apollo.gateway.execute': {
|
|
48
|
+
opName: () => 'apollo.gateway.execute',
|
|
49
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
50
|
+
},
|
|
51
|
+
'apollo.gateway.fetch': {
|
|
52
|
+
opName: () => 'apollo.gateway.fetch',
|
|
53
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
54
|
+
},
|
|
55
|
+
'apollo.gateway.postprocessing': {
|
|
56
|
+
opName: () => 'apollo.gateway.postprocessing',
|
|
57
|
+
serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
|
|
58
|
+
},
|
|
35
59
|
grpc: {
|
|
36
60
|
opName: () => 'grpc.server.request',
|
|
37
61
|
serviceName: identityService
|