dd-trace 2.0.0-appsec-beta.4 → 2.0.0-appsec-beta.5

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/ci/init.js CHANGED
@@ -1,7 +1,11 @@
1
1
  const tracer = require('../packages/dd-trace')
2
+ const { ORIGIN_KEY } = require('../packages/dd-trace/src/constants')
2
3
 
3
4
  tracer.init({
4
- startupLogs: false
5
+ startupLogs: false,
6
+ tags: {
7
+ [ORIGIN_KEY]: 'ciapp-test'
8
+ }
5
9
  })
6
10
 
7
11
  tracer.use('fs', false)
package/ci/jest/env.js CHANGED
@@ -1,8 +1,12 @@
1
1
  const tracer = require('../../packages/dd-trace')
2
+ const { ORIGIN_KEY } = require('../../packages/dd-trace/src/constants')
2
3
 
3
4
  tracer.init({
4
5
  startupLogs: false,
5
- flushInterval: 400000
6
+ flushInterval: 400000,
7
+ tags: {
8
+ [ORIGIN_KEY]: 'ciapp-test'
9
+ }
6
10
  })
7
11
 
8
12
  tracer.use('fs', false)
package/index.d.ts CHANGED
@@ -786,7 +786,7 @@ declare namespace plugins {
786
786
  * This plugin automatically instruments the
787
787
  * [cucumber](https://www.npmjs.com/package/@cucumber/cucumber) module.
788
788
  */
789
- interface cucumber extends Instrumentation {}
789
+ interface cucumber extends Integration {}
790
790
 
791
791
  /**
792
792
  * This plugin automatically instruments the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.0.0-appsec-beta.4",
3
+ "version": "2.0.0-appsec-beta.5",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -10,6 +10,7 @@
10
10
  "bench": "node benchmark",
11
11
  "bench:profiler": "node benchmark/profiler",
12
12
  "bench:e2e": "SERVICES=mongo yarn services && cd benchmark/e2e && node benchmark-run.js --duration=30",
13
+ "bench:e2e:ci-visibility": "node benchmark/e2e-ci/benchmark-run.js",
13
14
  "type:doc": "cd docs && yarn && yarn build",
14
15
  "type:test": "cd docs && yarn && yarn test",
15
16
  "lint": "node scripts/check_licenses.js && eslint . && yarn audit --groups dependencies",
@@ -2,3 +2,4 @@
2
2
 
3
3
  require('./src/dns')
4
4
  require('./src/memcached')
5
+ require('./src/mysql')
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+
3
+ const { AsyncResource } = require('async_hooks')
4
+ const {
5
+ channel,
6
+ addHook,
7
+ bind,
8
+ bindAsyncResource
9
+ } = require('./helpers/instrument')
10
+ const shimmer = require('../../datadog-shimmer')
11
+
12
+ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connection => {
13
+ const startCh = channel('apm:mysql:query:start')
14
+ const asyncEndCh = channel('apm:mysql:query:async-end')
15
+ const endCh = channel('apm:mysql:query:end')
16
+ const errorCh = channel('apm:mysql:query:error')
17
+
18
+ shimmer.wrap(Connection.prototype, 'query', query => function () {
19
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
20
+ if (!startCh.hasSubscribers) {
21
+ return query.apply(this, arguments)
22
+ }
23
+
24
+ const sql = arguments[0].sql ? arguments[0].sql : arguments[0]
25
+ const startArgs = [sql, this.config]
26
+
27
+ startCh.publish(startArgs)
28
+
29
+ try {
30
+ const res = query.apply(this, arguments)
31
+
32
+ if (res._callback) {
33
+ const cb = bindAsyncResource.call(asyncResource, res._callback)
34
+ res._callback = bind(function (error, result) {
35
+ if (error) {
36
+ errorCh.publish(error)
37
+ }
38
+ asyncEndCh.publish(result)
39
+
40
+ return cb.apply(this, arguments)
41
+ })
42
+ } else {
43
+ const cb = bind(function () {
44
+ asyncEndCh.publish(undefined)
45
+ })
46
+ res.on('end', cb)
47
+ }
48
+
49
+ return res
50
+ } catch (err) {
51
+ err.stack // trigger getting the stack at the original throwing point
52
+ errorCh.publish(err)
53
+
54
+ throw err
55
+ } finally {
56
+ endCh.publish(undefined)
57
+ }
58
+ })
59
+
60
+ return Connection
61
+ })
62
+
63
+ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
64
+ shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
65
+ arguments[0] = bind(cb)
66
+ return getConnection.apply(this, arguments)
67
+ })
68
+ return Pool
69
+ })
@@ -103,8 +103,8 @@ module.exports = [
103
103
  name: '@cucumber/cucumber',
104
104
  versions: ['7.0.0 - 7.2.1'],
105
105
  file: 'lib/runtime/pickle_runner.js',
106
- patch (PickleRunner, tracer) {
107
- const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber')
106
+ patch (PickleRunner, tracer, config) {
107
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', config)
108
108
  const sourceRoot = process.cwd()
109
109
  const pl = PickleRunner.default
110
110
  this.wrap(
@@ -127,8 +127,8 @@ module.exports = [
127
127
  name: '@cucumber/cucumber',
128
128
  versions: ['>=7.3.0'],
129
129
  file: 'lib/runtime/test_case_runner.js',
130
- patch (TestCaseRunner, tracer) {
131
- const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber')
130
+ patch (TestCaseRunner, tracer, config) {
131
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', config)
132
132
  const sourceRoot = process.cwd()
133
133
  const pl = TestCaseRunner.default
134
134
  this.wrap(
@@ -4,6 +4,7 @@ const {
4
4
  TEST_SUITE,
5
5
  TEST_STATUS,
6
6
  TEST_FRAMEWORK_VERSION,
7
+ TEST_IS_RUM_ACTIVE,
7
8
  getTestEnvironmentMetadata,
8
9
  CI_APP_ORIGIN,
9
10
  getTestParentSpan
@@ -66,15 +67,18 @@ module.exports = (on, config) => {
66
67
  }
67
68
  })
68
69
  }
69
- return null
70
+ return activeSpan ? activeSpan._spanContext._traceId.toString(10) : null
70
71
  },
71
72
  'dd:afterEach': (test) => {
72
- const { state, error } = test
73
+ const { state, error, isRUMActive } = test
73
74
  if (activeSpan) {
74
75
  activeSpan.setTag(TEST_STATUS, CYPRESS_STATUS_TO_TEST_STATUS[state])
75
76
  if (error) {
76
77
  activeSpan.setTag('error', error)
77
78
  }
79
+ if (isRUMActive) {
80
+ activeSpan.setTag(TEST_IS_RUM_ACTIVE, true)
81
+ }
78
82
  activeSpan.finish()
79
83
  }
80
84
  activeSpan = null
@@ -3,15 +3,30 @@ beforeEach(() => {
3
3
  cy.task('dd:beforeEach', {
4
4
  testName: Cypress.mocha.getRunner().suite.ctx.currentTest.fullTitle(),
5
5
  testSuite: Cypress.mocha.getRootSuite().file
6
+ }).then(traceId => {
7
+ Cypress.env('traceId', traceId)
6
8
  })
7
9
  })
8
10
 
11
+ after(() => {
12
+ cy.window().then(win => {
13
+ win.dispatchEvent(new Event('beforeunload'))
14
+ })
15
+ })
16
+
17
+
9
18
  afterEach(() => {
10
- const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
11
- cy.task('dd:afterEach', {
12
- testName: currentTest.fullTitle(),
13
- testSuite: Cypress.mocha.getRootSuite().file,
14
- state: currentTest.state,
15
- error: currentTest.err
19
+ cy.window().then(win => {
20
+ const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
21
+ const testInfo = {
22
+ testName: currentTest.fullTitle(),
23
+ testSuite: Cypress.mocha.getRootSuite().file,
24
+ state: currentTest.state,
25
+ error: currentTest.err,
26
+ }
27
+ if (win.DD_RUM) {
28
+ testInfo.isRUMActive = true
29
+ }
30
+ cy.task('dd:afterEach', testInfo)
16
31
  })
17
32
  })
@@ -82,7 +82,7 @@ function defaultAsyncEnd () {
82
82
  }
83
83
 
84
84
  function errorHandler (error) {
85
- storage.getStore().addError(error)
85
+ storage.getStore().span.setTag('error', error)
86
86
  }
87
87
 
88
88
  module.exports = DNSPlugin
@@ -238,8 +238,8 @@ module.exports = [
238
238
  {
239
239
  name: 'jest-environment-node',
240
240
  versions: ['>=24.8.0'],
241
- patch: function (NodeEnvironment, tracer) {
242
- const testEnvironmentMetadata = getTestEnvironmentMetadata('jest')
241
+ patch: function (NodeEnvironment, tracer, config) {
242
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('jest', config)
243
243
 
244
244
  this.wrap(NodeEnvironment.prototype, 'teardown', createWrapTeardown(tracer, this))
245
245
 
@@ -257,8 +257,8 @@ module.exports = [
257
257
  {
258
258
  name: 'jest-environment-jsdom',
259
259
  versions: ['>=24.8.0'],
260
- patch: function (JsdomEnvironment, tracer) {
261
- const testEnvironmentMetadata = getTestEnvironmentMetadata('jest')
260
+ patch: function (JsdomEnvironment, tracer, config) {
261
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('jest', config)
262
262
 
263
263
  this.wrap(JsdomEnvironment.prototype, 'teardown', createWrapTeardown(tracer, this))
264
264
 
@@ -169,8 +169,8 @@ module.exports = [
169
169
  name: 'jest-jasmine2',
170
170
  versions: ['>=24.8.0'],
171
171
  file: 'build/jasmineAsyncInstall.js',
172
- patch: function (jasmineAsyncInstallExport, tracer) {
173
- const testEnvironmentMetadata = getTestEnvironmentMetadata('jest')
172
+ patch: function (jasmineAsyncInstallExport, tracer, config) {
173
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('jest', config)
174
174
  return this.wrapExport(
175
175
  jasmineAsyncInstallExport.default,
176
176
  createWrapJasmineAsyncInstall(tracer, this, testEnvironmentMetadata)(jasmineAsyncInstallExport.default)
@@ -239,8 +239,8 @@ module.exports = [
239
239
  name: 'mocha',
240
240
  versions: ['>=5.2.0'],
241
241
  file: 'lib/runner.js',
242
- patch (Runner, tracer) {
243
- const testEnvironmentMetadata = getTestEnvironmentMetadata('mocha')
242
+ patch (Runner, tracer, config) {
243
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('mocha', config)
244
244
  const sourceRoot = process.cwd()
245
245
  this.wrap(Runner.prototype, 'runTests', createWrapRunTests(tracer, testEnvironmentMetadata, sourceRoot))
246
246
  this.wrap(Runner.prototype, 'runTest', createWrapRunTest(tracer, testEnvironmentMetadata, sourceRoot))
@@ -1,110 +1,58 @@
1
1
  'use strict'
2
2
 
3
- const Tags = require('opentracing').Tags
3
+ const Plugin = require('../../dd-trace/src/plugins/plugin')
4
+ const { storage } = require('../../datadog-core')
4
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
5
6
 
6
- function createWrapQuery (tracer, config) {
7
- return function wrapQuery (query) {
8
- return function queryWithTrace (sql, values, cb) {
9
- const scope = tracer.scope()
10
- const childOf = scope.active()
11
- const span = tracer.startSpan('mysql.query', {
7
+ class MySQLPlugin extends Plugin {
8
+ static get name () {
9
+ return 'mysql'
10
+ }
11
+
12
+ constructor (...args) {
13
+ super(...args)
14
+
15
+ this.addSub('apm:mysql:query:start', ([sql, conf]) => {
16
+ const store = storage.getStore()
17
+ const childOf = store ? store.span : store
18
+ const span = this.tracer.startSpan('mysql.query', {
12
19
  childOf,
13
20
  tags: {
14
- [Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_CLIENT,
15
- 'service.name': config.service || `${tracer._service}-mysql`,
21
+ 'service.name': this.config.service || `${this.tracer._service}-mysql`,
16
22
  'span.type': 'sql',
17
23
  'span.kind': 'client',
18
24
  'db.type': 'mysql',
19
- 'db.user': this.config.user,
20
- 'out.host': this.config.host,
21
- 'out.port': this.config.port
25
+ 'db.user': conf.user,
26
+ 'out.host': conf.host,
27
+ 'out.port': conf.port,
28
+ 'resource.name': sql
22
29
  }
23
30
  })
24
31
 
25
- if (this.config.database) {
26
- span.setTag('db.name', this.config.database)
32
+ if (conf.database) {
33
+ span.setTag('db.name', conf.database)
27
34
  }
28
35
 
29
- analyticsSampler.sample(span, config.measured)
36
+ analyticsSampler.sample(span, this.config.measured)
37
+ this.enter(span, store)
38
+ })
30
39
 
31
- const sequence = scope.bind(query, span).apply(this, arguments)
40
+ this.addSub('apm:mysql:query:end', () => {
41
+ this.exit()
42
+ })
32
43
 
33
- scope.bind(sequence)
34
-
35
- span.setTag('resource.name', sequence.sql)
36
-
37
- if (sequence._callback) {
38
- sequence._callback = wrapCallback(tracer, span, childOf, sequence._callback)
39
- } else {
40
- sequence.on('end', () => {
41
- span.finish()
42
- })
44
+ this.addSub('apm:mysql:query:error', err => {
45
+ if (err) {
46
+ const span = storage.getStore().span
47
+ span.setTag('error', err)
43
48
  }
49
+ })
44
50
 
45
- return sequence
46
- }
47
- }
48
- }
49
-
50
- function createWrapGetConnection (tracer, config) {
51
- return function wrapGetConnection (getConnection) {
52
- return function getConnectionWithTrace (cb) {
53
- const scope = tracer.scope()
54
-
55
- arguments[0] = scope.bind(cb)
56
-
57
- return scope.bind(getConnection).apply(this, arguments)
58
- }
51
+ this.addSub('apm:mysql:query:async-end', () => {
52
+ const span = storage.getStore().span
53
+ span.finish()
54
+ })
59
55
  }
60
56
  }
61
57
 
62
- function wrapCallback (tracer, span, parent, done) {
63
- return tracer.scope().bind((...args) => {
64
- const err = args[0]
65
- if (err) {
66
- span.addTags({
67
- 'error.type': err.name,
68
- 'error.msg': err.message,
69
- 'error.stack': err.stack
70
- })
71
- }
72
-
73
- span.finish()
74
-
75
- done(...args)
76
- }, parent)
77
- }
78
-
79
- function patchConnection (Connection, tracer, config) {
80
- this.wrap(Connection.prototype, 'query', createWrapQuery(tracer, config))
81
- }
82
-
83
- function unpatchConnection (Connection) {
84
- this.unwrap(Connection.prototype, 'query')
85
- }
86
-
87
- function patchPool (Pool, tracer, config) {
88
- this.wrap(Pool.prototype, 'getConnection', createWrapGetConnection(tracer, config))
89
- }
90
-
91
- function unpatchPool (Pool) {
92
- this.unwrap(Pool.prototype, 'getConnection')
93
- }
94
-
95
- module.exports = [
96
- {
97
- name: 'mysql',
98
- file: 'lib/Connection.js',
99
- versions: ['>=2'],
100
- patch: patchConnection,
101
- unpatch: unpatchConnection
102
- },
103
- {
104
- name: 'mysql',
105
- file: 'lib/Pool.js',
106
- versions: ['>=2'],
107
- patch: patchPool,
108
- unpatch: unpatchPool
109
- }
110
- ]
58
+ module.exports = MySQLPlugin
@@ -1 +1 @@
1
- module.exports = '2.0.0-pre'
1
+ module.exports = '2.0.0-appsec-beta.5'
@@ -43,10 +43,10 @@ class SubscriptionManager {
43
43
  const knownSubscriptions = new Set()
44
44
 
45
45
  // TODO: possible optimization: collect matchedSubscriptions on the fly in Context#setValue
46
- for (let i = 0; i < newAddresses.length; ++i) {
47
- const matchedSubscriptions = this.addressToSubscriptions.get(newAddresses[i])
46
+ newAddresses.forEach((newAddress) => {
47
+ const matchedSubscriptions = this.addressToSubscriptions.get(newAddress)
48
48
 
49
- if (matchedSubscriptions === undefined) continue
49
+ if (matchedSubscriptions === undefined) return
50
50
 
51
51
  for (let j = 0; j < matchedSubscriptions.length; ++j) {
52
52
  const subscription = matchedSubscriptions[j]
@@ -64,24 +64,24 @@ class SubscriptionManager {
64
64
  subscriptions.add(subscription)
65
65
  }
66
66
  }
67
- }
67
+ })
68
68
 
69
69
  return { addresses, subscriptions }
70
70
  }
71
71
 
72
72
  dispatch (newAddresses, allAddresses, context) {
73
- const { addresses, subscriptions } = this.matchSubscriptions(newAddresses, allAddresses)
73
+ const matches = this.matchSubscriptions(newAddresses, allAddresses)
74
74
 
75
75
  // TODO: possible optimization
76
- // if(!subscriptions.size) return []
76
+ // check if matches.subscriptions is empty here instead of in runner.js
77
77
 
78
78
  const params = {}
79
79
 
80
- addresses.forEach((address) => {
80
+ matches.addresses.forEach((address) => {
81
81
  params[address] = context.resolve(address)
82
82
  })
83
83
 
84
- return Runner.runSubscriptions(subscriptions, params)
84
+ return Runner.runSubscriptions(matches.subscriptions, params)
85
85
  }
86
86
  }
87
87
 
@@ -93,48 +93,37 @@ class Context {
93
93
  constructor () {
94
94
  this.store = new Map()
95
95
  this.allAddresses = new Set()
96
- this.newAddresses = [] // TODO: maybe this should be a Set
96
+ this.newAddresses = new Set()
97
97
  }
98
98
 
99
99
  clear () {
100
100
  this.store = new Map()
101
101
  this.allAddresses = new Set()
102
- this.newAddresses = []
102
+ this.newAddresses = new Set()
103
103
  }
104
104
 
105
105
  setValue (address, value) {
106
106
  if (this.allAddresses.size >= MAX_CONTEXT_SIZE) return this
107
107
 
108
- const oldValue = this.store.get(address)
109
- if (oldValue === value) return this
110
-
111
- this.store.set(address, value)
112
-
113
- if (!this.newAddresses.includes(address)) {
114
- this.allAddresses.add(address)
115
- this.newAddresses.push(address)
108
+ // cannot optimize for objects because they're pointers
109
+ if (typeof value !== 'object') {
110
+ const oldValue = this.store.get(address)
111
+ if (oldValue === value) return this
116
112
  }
117
113
 
118
- return this
119
- }
120
-
121
- setMultipleValues (params) {
122
- const addresses = Object.keys(params)
123
-
124
- for (let i = 0; i < addresses.length; ++i) {
125
- const address = addresses[i]
126
- this.setValue(address, params[address])
127
- }
114
+ this.store.set(address, value)
115
+ this.allAddresses.add(address)
116
+ this.newAddresses.add(address)
128
117
 
129
118
  return this
130
119
  }
131
120
 
132
121
  dispatch () {
133
- if (this.newAddresses.length === 0) return []
122
+ if (this.newAddresses.size === 0) return []
134
123
 
135
124
  const result = Context.manager.dispatch(this.newAddresses, this.allAddresses, this)
136
125
 
137
- this.newAddresses = []
126
+ this.newAddresses.clear()
138
127
 
139
128
  return result
140
129
  }
@@ -12,6 +12,8 @@ function runSubscriptions (subscriptions, params) {
12
12
 
13
13
  const store = als.getStore()
14
14
 
15
+ // TODO: possible optimization
16
+ // can we deduplicate those before ?
15
17
  const executedCallbacks = new Set()
16
18
 
17
19
  for (const subscription of subscriptions) {
@@ -27,13 +27,9 @@ function enable (config) {
27
27
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
28
28
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
29
29
 
30
- // add needed fields for HTTP context reporting
31
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_URL)
30
+ // add fields needed for HTTP context reporting
32
31
  Gateway.manager.addresses.add(addresses.HTTP_INCOMING_HEADERS)
33
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_METHOD)
34
32
  Gateway.manager.addresses.add(addresses.HTTP_INCOMING_REMOTE_IP)
35
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_REMOTE_PORT)
36
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_RESPONSE_CODE)
37
33
  Gateway.manager.addresses.add(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
38
34
  }
39
35
 
@@ -85,6 +81,7 @@ function incomingHttpEndTranslator (data) {
85
81
  function disable () {
86
82
  RuleManager.clearAllRules()
87
83
 
84
+ // Channel#unsubscribe() is undefined for non active channels
88
85
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
89
86
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
90
87
  }
@@ -61,16 +61,16 @@ function filterHeaders (headers, passlist, prefix) {
61
61
  const headerName = passlist[i]
62
62
 
63
63
  if (headers[headerName]) {
64
- result[`${prefix}${formatHeaderName(headerName)}`] = headers[headerName].toString()
64
+ result[`${prefix}${formatHeaderName(headerName)}`] = headers[headerName] + ''
65
65
  }
66
66
  }
67
67
 
68
68
  return result
69
69
  }
70
70
 
71
+ // TODO: this can be precomputed at start time
71
72
  function formatHeaderName (name) {
72
73
  return name
73
- .toString()
74
74
  .trim()
75
75
  .slice(0, 200)
76
76
  .replace(/[^a-zA-Z0-9_\-:/]/g, '_')
@@ -24,6 +24,7 @@ const TEST_SUITE = 'test.suite'
24
24
  const TEST_STATUS = 'test.status'
25
25
  const TEST_PARAMETERS = 'test.parameters'
26
26
  const TEST_SKIP_REASON = 'test.skip_reason'
27
+ const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
27
28
 
28
29
  const ERROR_TYPE = 'error.type'
29
30
  const ERROR_MESSAGE = 'error.msg'
@@ -43,6 +44,7 @@ module.exports = {
43
44
  TEST_STATUS,
44
45
  TEST_PARAMETERS,
45
46
  TEST_SKIP_REASON,
47
+ TEST_IS_RUM_ACTIVE,
46
48
  ERROR_TYPE,
47
49
  ERROR_MESSAGE,
48
50
  ERROR_STACK,
@@ -54,7 +56,7 @@ module.exports = {
54
56
  getTestSuitePath
55
57
  }
56
58
 
57
- function getTestEnvironmentMetadata (testFramework) {
59
+ function getTestEnvironmentMetadata (testFramework, config) {
58
60
  // TODO: eventually these will come from the tracer (generally available)
59
61
  const ciMetadata = getCIMetadata()
60
62
  const {
@@ -83,13 +85,17 @@ function getTestEnvironmentMetadata (testFramework) {
83
85
 
84
86
  const runtimeAndOSMetadata = getRuntimeAndOSMetadata()
85
87
 
86
- return {
88
+ const metadata = {
87
89
  [TEST_FRAMEWORK]: testFramework,
88
90
  ...gitMetadata,
89
91
  ...ciMetadata,
90
92
  ...userProvidedGitMetadata,
91
93
  ...runtimeAndOSMetadata
92
94
  }
95
+ if (config && config.service) {
96
+ metadata['service.name'] = config.service
97
+ }
98
+ return metadata
93
99
  }
94
100
 
95
101
  function getTestParametersString (parametersByTestName, testName) {
@@ -55,7 +55,6 @@ class AgentExporter {
55
55
  }
56
56
 
57
57
  export ({ profiles, start, end, tags }) {
58
- const form = new FormData()
59
58
  const types = Object.keys(profiles)
60
59
 
61
60
  const fields = [
@@ -75,10 +74,6 @@ class AgentExporter {
75
74
  ...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])
76
75
  ]
77
76
 
78
- for (const [key, value] of fields) {
79
- form.append(key, value)
80
- }
81
-
82
77
  this._logger.debug(() => {
83
78
  const body = fields.map(([key, value]) => ` ${key}: ${value}`).join('\n')
84
79
  return `Building agent export report: ${'\n' + body}`
@@ -93,36 +88,14 @@ class AgentExporter {
93
88
  return `Adding ${type} profile to agent export: ` + bytes
94
89
  })
95
90
 
96
- form.append(`types[${index}]`, type)
97
- form.append(`data[${index}]`, buffer, {
91
+ fields.push([`types[${index}]`, type])
92
+ fields.push([`data[${index}]`, buffer, {
98
93
  filename: `${type}.pb.gz`,
99
94
  contentType: 'application/octet-stream',
100
95
  knownLength: buffer.length
101
- })
102
- }
103
-
104
- const options = {
105
- method: 'POST',
106
- path: '/profiling/v1/input',
107
- headers: form.getHeaders()
108
- }
109
-
110
- if (containerId) {
111
- options.headers['Datadog-Container-ID'] = containerId
96
+ }])
112
97
  }
113
98
 
114
- if (this._url.protocol === 'unix:') {
115
- options.socketPath = this._url.pathname
116
- } else {
117
- options.protocol = this._url.protocol
118
- options.hostname = this._url.hostname
119
- options.port = this._url.port
120
- }
121
-
122
- this._logger.debug(() => {
123
- return `Submitting agent report to: ${JSON.stringify(options)}`
124
- })
125
-
126
99
  return new Promise((resolve, reject) => {
127
100
  const operation = retry.operation({
128
101
  randomize: true,
@@ -131,8 +104,36 @@ class AgentExporter {
131
104
  })
132
105
 
133
106
  operation.attempt((attempt) => {
134
- const timeout = this._backoffTime * Math.pow(2, attempt)
135
- sendRequest({ ...options, timeout }, form, (err, response) => {
107
+ const form = new FormData()
108
+
109
+ for (const [key, value, options] of fields) {
110
+ form.append(key, value, options)
111
+ }
112
+
113
+ const options = {
114
+ method: 'POST',
115
+ path: '/profiling/v1/input',
116
+ headers: form.getHeaders(),
117
+ timeout: this._backoffTime * Math.pow(2, attempt)
118
+ }
119
+
120
+ if (containerId) {
121
+ options.headers['Datadog-Container-ID'] = containerId
122
+ }
123
+
124
+ if (this._url.protocol === 'unix:') {
125
+ options.socketPath = this._url.pathname
126
+ } else {
127
+ options.protocol = this._url.protocol
128
+ options.hostname = this._url.hostname
129
+ options.port = this._url.port
130
+ }
131
+
132
+ this._logger.debug(() => {
133
+ return `Submitting profiler agent report attempt #${attempt} to: ${JSON.stringify(options)}`
134
+ })
135
+
136
+ sendRequest(options, form, (err, response) => {
136
137
  if (operation.retry(err)) {
137
138
  this._logger.error(`Error from the agent: ${err.message}`)
138
139
  return