dd-trace 2.41.0 → 2.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/package.json +3 -3
  3. package/packages/datadog-core/src/storage/async_resource.js +4 -0
  4. package/packages/datadog-instrumentations/src/aws-sdk.js +6 -2
  5. package/packages/datadog-instrumentations/src/fetch.js +24 -21
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  7. package/packages/datadog-instrumentations/src/http/client.js +41 -32
  8. package/packages/datadog-instrumentations/src/http2/client.js +1 -0
  9. package/packages/datadog-instrumentations/src/jest.js +32 -4
  10. package/packages/datadog-plugin-fetch/src/index.js +7 -13
  11. package/packages/datadog-plugin-graphql/src/execute.js +6 -4
  12. package/packages/datadog-plugin-http/src/client.js +25 -20
  13. package/packages/datadog-plugin-jest/src/index.js +8 -3
  14. package/packages/datadog-plugin-openai/src/index.js +39 -16
  15. package/packages/datadog-plugin-openai/src/services.js +13 -9
  16. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -1
  17. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +45 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -3
  19. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +66 -0
  20. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +25 -8
  21. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +19 -0
  22. package/packages/dd-trace/src/appsec/iast/index.js +5 -2
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +4 -2
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +17 -1
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +1 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +5 -1
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -1
  28. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  29. package/packages/dd-trace/src/config.js +26 -10
  30. package/packages/dd-trace/src/external-logger/src/index.js +9 -1
  31. package/packages/dd-trace/src/external-logger/test/index.spec.js +1 -1
  32. package/packages/dd-trace/src/format.js +1 -1
  33. package/packages/dd-trace/src/lambda/handler.js +8 -1
  34. package/packages/dd-trace/src/opentelemetry/span.js +3 -1
  35. package/packages/dd-trace/src/opentracing/span_context.js +2 -1
  36. package/packages/dd-trace/src/plugins/index.js +1 -0
  37. package/packages/dd-trace/src/plugins/util/ci.js +2 -1
  38. package/packages/dd-trace/src/plugins/util/web.js +1 -0
  39. package/packages/dd-trace/src/profiling/config.js +8 -5
  40. package/packages/dd-trace/src/profiling/exporters/agent.js +4 -1
  41. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  42. package/packages/dd-trace/src/profiling/profilers/wall.js +144 -4
  43. package/packages/dd-trace/src/service-naming/index.js +2 -2
  44. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +12 -0
  45. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  46. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +12 -0
  47. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  48. package/packages/dd-trace/src/span_processor.js +0 -4
  49. package/packages/dd-trace/src/span_sampler.js +1 -1
  50. package/packages/dd-trace/src/telemetry/dependencies.js +24 -12
  51. package/packages/dd-trace/src/telemetry/metrics.js +11 -1
  52. package/packages/diagnostics_channel/src/index.js +1 -1
  53. package/scripts/version.js +0 -66
@@ -86,6 +86,13 @@ exports.datadog = function datadog (lambdaHandler) {
86
86
  const context = extractContext(args)
87
87
 
88
88
  checkTimeout(context)
89
- return lambdaHandler.apply(this, args).then((res) => { clearTimeout(__lambdaTimeout); return res })
89
+ const result = lambdaHandler.apply(this, args)
90
+ if (result && typeof result.then === 'function') {
91
+ return result.then((res) => {
92
+ clearTimeout(__lambdaTimeout)
93
+ return res
94
+ })
95
+ }
96
+ return result
90
97
  }
91
98
  }
@@ -10,6 +10,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
12
  const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
13
+ const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
13
14
 
14
15
  const SpanContext = require('./span_context')
15
16
 
@@ -40,7 +41,8 @@ class Span {
40
41
  hostname: _tracer._hostname,
41
42
  integrationName: 'otel',
42
43
  tags: {
43
- 'service.name': _tracer._service
44
+ [SERVICE_NAME]: _tracer._service,
45
+ [RESOURCE_NAME]: spanName
44
46
  }
45
47
  }, _tracer._debug)
46
48
 
@@ -12,7 +12,8 @@ class DatadogSpanContext {
12
12
  this._name = props.name
13
13
  this._isFinished = props.isFinished || false
14
14
  this._tags = props.tags || {}
15
- this._sampling = Object.assign({}, props.sampling)
15
+ this._sampling = props.sampling || {}
16
+ this._spanSampling = undefined
16
17
  this._baggageItems = props.baggageItems || {}
17
18
  this._traceparent = props.traceparent
18
19
  this._tracestate = props.tracestate
@@ -10,6 +10,7 @@ module.exports = {
10
10
  get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
11
11
  get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
12
12
  get '@jest/core' () { return require('../../../datadog-plugin-jest/src') },
13
+ get '@jest/transform' () { return require('../../../datadog-plugin-jest/src') },
13
14
  get '@koa/router' () { return require('../../../datadog-plugin-koa/src') },
14
15
  get '@node-redis/client' () { return require('../../../datadog-plugin-redis/src') },
15
16
  get '@opensearch-project/opensearch' () { return require('../../../datadog-plugin-opensearch/src') },
@@ -399,6 +399,7 @@ module.exports = {
399
399
  BITBUCKET_BRANCH,
400
400
  BITBUCKET_COMMIT,
401
401
  BITBUCKET_GIT_SSH_ORIGIN,
402
+ BITBUCKET_GIT_HTTP_ORIGIN,
402
403
  BITBUCKET_TAG,
403
404
  BITBUCKET_PIPELINE_UUID,
404
405
  BITBUCKET_CLONE_DIR
@@ -416,7 +417,7 @@ module.exports = {
416
417
  [CI_PIPELINE_URL]: url,
417
418
  [GIT_BRANCH]: BITBUCKET_BRANCH,
418
419
  [GIT_TAG]: BITBUCKET_TAG,
419
- [GIT_REPOSITORY_URL]: BITBUCKET_GIT_SSH_ORIGIN,
420
+ [GIT_REPOSITORY_URL]: BITBUCKET_GIT_SSH_ORIGIN || BITBUCKET_GIT_HTTP_ORIGIN,
420
421
  [CI_WORKSPACE_PATH]: BITBUCKET_CLONE_DIR,
421
422
  [CI_PIPELINE_ID]: BITBUCKET_PIPELINE_UUID && BITBUCKET_PIPELINE_UUID.replace(/{|}/gm, '')
422
423
  }
@@ -103,6 +103,7 @@ const web = {
103
103
  context.res = res
104
104
 
105
105
  this.setConfig(req, config)
106
+ addRequestTags(context)
106
107
 
107
108
  return span
108
109
  },
@@ -18,7 +18,6 @@ class Config {
18
18
  const {
19
19
  DD_PROFILING_ENABLED,
20
20
  DD_PROFILING_PROFILERS,
21
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
22
21
  DD_ENV,
23
22
  DD_TAGS,
24
23
  DD_SERVICE,
@@ -36,7 +35,9 @@ class Config {
36
35
  DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
37
36
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
38
37
  DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
39
- DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES
38
+ DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
39
+ DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
40
+ DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED
40
41
  } = process.env
41
42
 
42
43
  const enabled = isTrue(coalesce(options.enabled, DD_PROFILING_ENABLED, true))
@@ -51,8 +52,8 @@ class Config {
51
52
  Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
52
53
  const sourceMap = coalesce(options.sourceMap,
53
54
  DD_PROFILING_SOURCE_MAP, true)
54
- const endpointCollection = coalesce(options.endpointCollection,
55
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, false)
55
+ const endpointCollectionEnabled = coalesce(options.endpointCollection,
56
+ DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false)
56
57
  const pprofPrefix = coalesce(options.pprofPrefix,
57
58
  DD_PROFILING_PPROF_PREFIX, '')
58
59
 
@@ -73,7 +74,7 @@ class Config {
73
74
  this.uploadTimeout = uploadTimeout
74
75
  this.sourceMap = sourceMap
75
76
  this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
76
- this.endpointCollection = endpointCollection
77
+ this.endpointCollectionEnabled = endpointCollectionEnabled
77
78
  this.pprofPrefix = pprofPrefix
78
79
 
79
80
  const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
@@ -110,6 +111,8 @@ class Config {
110
111
  const profilers = options.profilers
111
112
  ? options.profilers
112
113
  : getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
114
+ this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
115
+ DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
113
116
 
114
117
  this.profilers = ensureProfilers(profilers, this)
115
118
  }
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const retry = require('retry')
4
- const { request } = require('http')
4
+ const { request: httpRequest } = require('http')
5
+ const { request: httpsRequest } = require('https')
5
6
 
6
7
  // TODO: avoid using dd-trace internals. Make this a separate module?
7
8
  const docker = require('../../exporters/common/docker')
@@ -12,6 +13,8 @@ const version = require('../../../../../package.json').version
12
13
  const containerId = docker.id()
13
14
 
14
15
  function sendRequest (options, form, callback) {
16
+ const request = options.protocol === 'https:' ? httpsRequest : httpRequest
17
+
15
18
  const store = storage.getStore()
16
19
  storage.enterWith({ noop: true })
17
20
  const req = request(options, res => {
@@ -23,7 +23,7 @@ class Profiler extends EventEmitter {
23
23
  }
24
24
 
25
25
  start (options) {
26
- this._start(options).catch(() => {})
26
+ this._start(options).catch((err) => { if (options.logger) options.logger.error(err) })
27
27
  return this
28
28
  }
29
29
 
@@ -1,23 +1,111 @@
1
1
  'use strict'
2
2
 
3
+ const { storage } = require('../../../../datadog-core')
4
+
5
+ const dc = require('../../../../diagnostics_channel')
6
+
7
+ const beforeCh = dc.channel('dd-trace:storage:before')
8
+ const enterCh = dc.channel('dd-trace:storage:enter')
9
+
10
+ let kSampleCount
11
+
12
+ function getActiveSpan () {
13
+ const store = storage.getStore()
14
+ return store && store.span
15
+ }
16
+
17
+ function getStartedSpans (context) {
18
+ return context._trace.started
19
+ }
20
+
21
+ function generateLabels ({ spanId, rootSpanId, webTags, endpoint }) {
22
+ const labels = {}
23
+ if (spanId) {
24
+ labels['span id'] = spanId
25
+ }
26
+ if (rootSpanId) {
27
+ labels['local root span id'] = rootSpanId
28
+ }
29
+ if (webTags && Object.keys(webTags).length !== 0) {
30
+ labels['trace endpoint'] = endpointNameFromTags(webTags)
31
+ } else if (endpoint) {
32
+ // fallback to endpoint computed when sample was taken
33
+ labels['trace endpoint'] = endpoint
34
+ }
35
+
36
+ return labels
37
+ }
38
+
39
+ function getSpanContextTags (span) {
40
+ return span.context()._tags
41
+ }
42
+
43
+ function isWebServerSpan (tags) {
44
+ return tags['span.type'] === 'web'
45
+ }
46
+
47
+ function endpointNameFromTags (tags) {
48
+ return tags['resource.name'] || [
49
+ tags['http.method'],
50
+ tags['http.route']
51
+ ].filter(v => v).join(' ')
52
+ }
53
+
54
+ function updateContext (context, span, startedSpans, endpointCollectionEnabled) {
55
+ context.spanId = span.context().toSpanId()
56
+ const rootSpan = startedSpans[0]
57
+ if (rootSpan) {
58
+ context.rootSpanId = rootSpan.context().toSpanId()
59
+ if (endpointCollectionEnabled) {
60
+ // Find the first webspan starting from the end:
61
+ // There might be several webspans, for example with next.js, http plugin creates a first span
62
+ // and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
63
+ for (let i = startedSpans.length - 1; i >= 0; i--) {
64
+ const tags = getSpanContextTags(startedSpans[i])
65
+ if (isWebServerSpan(tags)) {
66
+ context.webTags = tags
67
+ // endpoint may not be determined yet, but keep it as fallback
68
+ // if tags are not available anymore during serialization
69
+ context.endpoint = endpointNameFromTags(tags)
70
+ break
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
3
77
  class NativeWallProfiler {
4
78
  constructor (options = {}) {
5
79
  this.type = 'wall'
6
80
  this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz
7
81
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
8
82
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
83
+ this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
9
84
  this._mapper = undefined
10
85
  this._pprof = undefined
11
86
 
87
+ // Bind to this so the same value can be used to unsubscribe later
88
+ this._enter = this._enter.bind(this)
12
89
  this._logger = options.logger
13
90
  this._started = false
14
91
  }
15
92
 
93
+ codeHotspotsEnabled () {
94
+ return this._codeHotspotsEnabled
95
+ }
96
+
16
97
  start ({ mapper } = {}) {
17
98
  if (this._started) return
18
99
 
100
+ if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
101
+ this._logger.debug(
102
+ `Wall profiler: Enable config_trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
103
+ this._emittedFFMessage = true
104
+ }
105
+
19
106
  this._mapper = mapper
20
107
  this._pprof = require('@datadog/pprof')
108
+ kSampleCount = this._pprof.time.constants.kSampleCount
21
109
 
22
110
  // pprof otherwise crashes in worker threads
23
111
  if (!process._startProfilerIdleNotifier) {
@@ -31,16 +119,62 @@ class NativeWallProfiler {
31
119
  intervalMicros: this._samplingIntervalMicros,
32
120
  durationMillis: this._flushIntervalMillis,
33
121
  sourceMapper: this._mapper,
34
- customLabels: this._codeHotspotsEnabled,
122
+ withContexts: this._codeHotspotsEnabled,
35
123
  lineNumbers: false
36
124
  })
37
125
 
126
+ if (this._codeHotspotsEnabled) {
127
+ this._profilerState = this._pprof.time.getState()
128
+ this._currentContext = {}
129
+ this._pprof.time.setContext(this._currentContext)
130
+ this._lastSpan = undefined
131
+ this._lastStartedSpans = undefined
132
+ this._lastSampleCount = 0
133
+
134
+ beforeCh.subscribe(this._enter)
135
+ enterCh.subscribe(this._enter)
136
+ }
137
+
38
138
  this._started = true
39
139
  }
40
140
 
41
- profile () {
141
+ _enter () {
42
142
  if (!this._started) return
43
- return this._pprof.time.stop(true)
143
+
144
+ const sampleCount = this._profilerState[kSampleCount]
145
+ if (sampleCount !== this._lastSampleCount) {
146
+ this._lastSampleCount = sampleCount
147
+ const context = this._currentContext
148
+ this._currentContext = {}
149
+ this._pprof.time.setContext(this._currentContext)
150
+
151
+ if (this._lastSpan) {
152
+ updateContext(context, this._lastSpan, this._lastStartedSpans, this._endpointCollectionEnabled)
153
+ }
154
+ }
155
+
156
+ const span = getActiveSpan()
157
+ if (span) {
158
+ this._lastSpan = span
159
+ this._lastStartedSpans = getStartedSpans(span.context())
160
+ } else {
161
+ this._lastStartedSpans = undefined
162
+ this._lastSpan = undefined
163
+ }
164
+ }
165
+
166
+ _stop (restart) {
167
+ if (!this._started) return
168
+ if (this._codeHotspotsEnabled) {
169
+ // update last sample context if needed
170
+ this._enter()
171
+ this._lastSampleCount = 0
172
+ }
173
+ return this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
174
+ }
175
+
176
+ profile () {
177
+ return this._stop(true)
44
178
  }
45
179
 
46
180
  encode (profile) {
@@ -50,7 +184,13 @@ class NativeWallProfiler {
50
184
  stop () {
51
185
  if (!this._started) return
52
186
 
53
- const profile = this._pprof.time.stop()
187
+ const profile = this._stop(false)
188
+ if (this._codeHotspotsEnabled) {
189
+ beforeCh.unsubscribe(this._enter)
190
+ enterCh.subscribe(this._enter)
191
+ this._profilerState = undefined
192
+ }
193
+
54
194
  this._started = false
55
195
  return profile
56
196
  }
@@ -3,7 +3,7 @@ const { schemaDefinitions } = require('./schemas')
3
3
  class SchemaManager {
4
4
  constructor () {
5
5
  this.schemas = schemaDefinitions
6
- this.config = { spanAttributeSchema: 'v0', traceRemoveIntegrationServiceNamesEnabled: false }
6
+ this.config = { spanAttributeSchema: 'v0', spanRemoveIntegrationFromService: false }
7
7
  }
8
8
 
9
9
  get schema () {
@@ -15,7 +15,7 @@ class SchemaManager {
15
15
  }
16
16
 
17
17
  get shouldUseConsistentServiceNaming () {
18
- return this.config.traceRemoveIntegrationServiceNamesEnabled && this.version === 'v0'
18
+ return this.config.spanRemoveIntegrationFromService && this.version === 'v0'
19
19
  }
20
20
 
21
21
  opName (type, kind, plugin, ...opNameArgs) {
@@ -0,0 +1,12 @@
1
+ const { identityService } = require('../util')
2
+
3
+ const graphql = {
4
+ server: {
5
+ graphql: {
6
+ opName: () => 'graphql.execute',
7
+ serviceName: identityService
8
+ }
9
+ }
10
+ }
11
+
12
+ module.exports = graphql
@@ -1,6 +1,7 @@
1
1
  const SchemaDefinition = require('../definition')
2
2
  const messaging = require('./messaging')
3
3
  const storage = require('./storage')
4
+ const graphql = require('./graphql')
4
5
  const web = require('./web')
5
6
 
6
- module.exports = new SchemaDefinition({ messaging, storage, web })
7
+ module.exports = new SchemaDefinition({ messaging, storage, web, graphql })
@@ -0,0 +1,12 @@
1
+ const { identityService } = require('../util')
2
+
3
+ const graphql = {
4
+ server: {
5
+ graphql: {
6
+ opName: () => 'graphql.server.request',
7
+ serviceName: identityService
8
+ }
9
+ }
10
+ }
11
+
12
+ module.exports = graphql
@@ -1,6 +1,7 @@
1
1
  const SchemaDefinition = require('../definition')
2
2
  const messaging = require('./messaging')
3
3
  const storage = require('./storage')
4
+ const graphql = require('./graphql')
4
5
  const web = require('./web')
5
6
 
6
- module.exports = new SchemaDefinition({ messaging, storage, web })
7
+ module.exports = new SchemaDefinition({ messaging, storage, web, graphql })
@@ -138,10 +138,6 @@ class SpanProcessor {
138
138
  }
139
139
  }
140
140
 
141
- for (const span of trace.finished) {
142
- span.context()._tags = {}
143
- }
144
-
145
141
  trace.started = active
146
142
  trace.finished = []
147
143
  }
@@ -82,7 +82,7 @@ class SpanSampler {
82
82
 
83
83
  const rule = this.findRule(service, name)
84
84
  if (rule && rule.sample()) {
85
- span.context()._sampling.spanSampling = {
85
+ span.context()._spanSampling = {
86
86
  sampleRate: rule.sampleRate,
87
87
  maxPerSecond: rule.maxPerSecond
88
88
  }
@@ -7,8 +7,10 @@ const { sendData } = require('./send-data')
7
7
  const dc = require('../../../diagnostics_channel')
8
8
  const { fileURLToPath } = require('url')
9
9
 
10
- const savedDependencies = new Set()
11
- const detectedDependencyNames = new Set()
10
+ const savedDependenciesToSend = new Set()
11
+ const detectedDependencyKeys = new Set()
12
+ const detectedDependencyVersions = new Set()
13
+
12
14
  const FILE_URI_START = `file://`
13
15
  const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
14
16
 
@@ -18,14 +20,14 @@ function waitAndSend (config, application, host) {
18
20
  if (!immediate) {
19
21
  immediate = setImmediate(() => {
20
22
  immediate = null
21
- if (savedDependencies.size > 0) {
22
- const dependencies = Array.from(savedDependencies.values()).splice(0, 1000).map(pair => {
23
- savedDependencies.delete(pair)
23
+ if (savedDependenciesToSend.size > 0) {
24
+ const dependencies = Array.from(savedDependenciesToSend.values()).splice(0, 1000).map(pair => {
25
+ savedDependenciesToSend.delete(pair)
24
26
  const [name, version] = pair.split(' ')
25
27
  return { name, version }
26
28
  })
27
29
  sendData(config, application, host, 'app-dependencies-loaded', { dependencies })
28
- if (savedDependencies.size > 0) {
30
+ if (savedDependenciesToSend.size > 0) {
29
31
  waitAndSend(config, application, host)
30
32
  }
31
33
  }
@@ -46,15 +48,24 @@ function onModuleLoad (data) {
46
48
  }
47
49
  const parseResult = filename && parse(filename)
48
50
  const request = data.request || (parseResult && parseResult.name)
49
- if (filename && request && isDependency(filename, request) && !detectedDependencyNames.has(request)) {
50
- detectedDependencyNames.add(request)
51
+ const dependencyKey = parseResult && parseResult.basedir ? parseResult.basedir : request
52
+
53
+ if (filename && request && isDependency(filename, request) && !detectedDependencyKeys.has(dependencyKey)) {
54
+ detectedDependencyKeys.add(dependencyKey)
55
+
51
56
  if (parseResult) {
52
57
  const { name, basedir } = parseResult
53
58
  if (basedir) {
54
59
  try {
55
60
  const { version } = requirePackageJson(basedir, module)
56
- savedDependencies.add(`${name} ${version}`)
57
- waitAndSend(config, application, host)
61
+ const dependencyAndVersion = `${name} ${version}`
62
+
63
+ if (!detectedDependencyVersions.has(dependencyAndVersion)) {
64
+ savedDependenciesToSend.add(dependencyAndVersion)
65
+ detectedDependencyVersions.add(dependencyAndVersion)
66
+
67
+ waitAndSend(config, application, host)
68
+ }
58
69
  } catch (e) {
59
70
  // can not read the package.json, do nothing
60
71
  }
@@ -88,8 +99,9 @@ function stop () {
88
99
  config = null
89
100
  application = null
90
101
  host = null
91
- detectedDependencyNames.clear()
92
- savedDependencies.clear()
102
+ detectedDependencyKeys.clear()
103
+ savedDependenciesToSend.clear()
104
+ detectedDependencyVersions.clear()
93
105
  if (moduleLoadStartChannel.hasSubscribers) {
94
106
  moduleLoadStartChannel.unsubscribe(onModuleLoad)
95
107
  }
@@ -25,6 +25,10 @@ function mapToJsonArray (map) {
25
25
  return Array.from(map.values()).map(v => v.toJSON())
26
26
  }
27
27
 
28
+ function hasPoints (metric) {
29
+ return metric.points.length > 0
30
+ }
31
+
28
32
  class Metric {
29
33
  constructor (namespace, metric, common, tags) {
30
34
  this.namespace = namespace.toString()
@@ -172,10 +176,16 @@ class MetricsCollection extends Map {
172
176
 
173
177
  toJSON () {
174
178
  if (!this.size) return
179
+
180
+ const series = mapToJsonArray(this)
181
+ .filter(hasPoints)
182
+
183
+ if (!series.length) return
184
+
175
185
  const { namespace } = this
176
186
  return {
177
187
  namespace,
178
- series: mapToJsonArray(this)
188
+ series
179
189
  }
180
190
  }
181
191
  }
@@ -64,7 +64,7 @@ if (!Channel.prototype.runStores) {
64
64
  this._stores.set(store, transform)
65
65
  }
66
66
 
67
- Channel.prototype.unbindStore = ActiveChannelPrototype.runStores = function (store) {
67
+ Channel.prototype.unbindStore = ActiveChannelPrototype.unbindStore = function (store) {
68
68
  if (!this._stores) return
69
69
  this._stores.delete(store)
70
70
  }
@@ -1,66 +0,0 @@
1
- 'use strict'
2
-
3
- const path = require('path')
4
- const fs = require('fs')
5
- const semver = require('semver')
6
- const exec = require('./helpers/exec')
7
- const title = require('./helpers/title')
8
-
9
- const pkg = require('../package.json')
10
- const increment = getIncrement()
11
- const version = semver.inc(pkg.version, increment, 'pre')
12
-
13
- title(`Bumping version to v${version}.`)
14
-
15
- const currentBranch = exec.pipe(`git branch --show-current`)
16
-
17
- if (currentBranch === 'master') {
18
- const major = semver.major(pkg.version)
19
- const nextMajor = semver.major(pkg.version) + 1
20
-
21
- exec(`git checkout -b v${major}.x`)
22
- exec(`git push -u origin HEAD`)
23
-
24
- bump(`${nextMajor}.0.0-pre`)
25
-
26
- exec(`git checkout v${major}.x`)
27
- }
28
-
29
- bump(version)
30
-
31
- exec(`git checkout ${currentBranch}`)
32
-
33
- function bump (newVersion) {
34
- pkg.version = newVersion
35
-
36
- exec(`git checkout -b v${newVersion}-bump`)
37
- write('package.json', JSON.stringify(pkg, null, 2) + '\n')
38
- write('packages/dd-trace/lib/version.js', `module.exports = '${newVersion}'\n`)
39
- add('package.json')
40
- add('packages/dd-trace/lib/version.js')
41
- exec(`git commit -m "v${newVersion}"`)
42
- exec(`git push -u origin HEAD`)
43
- }
44
-
45
- function getIncrement () {
46
- const increments = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease']
47
- const index = increments.indexOf(process.argv[2])
48
-
49
- if (index === -1) {
50
- throw new Error(`increment must be one of ${increments.join(', ')}`)
51
- }
52
-
53
- return increments[index]
54
- }
55
-
56
- function filename (relativePath) {
57
- return path.normalize(path.join(__dirname, '..', relativePath))
58
- }
59
-
60
- function write (file, data) {
61
- fs.writeFileSync(filename(file), data)
62
- }
63
-
64
- function add (file) {
65
- exec(`git add ${filename(file)}`)
66
- }