dd-trace 3.27.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +2 -2
  2. package/package.json +4 -4
  3. package/packages/datadog-core/src/storage/async_resource.js +4 -0
  4. package/packages/datadog-instrumentations/src/couchbase.js +4 -4
  5. package/packages/datadog-instrumentations/src/cucumber.js +5 -2
  6. package/packages/datadog-instrumentations/src/grpc/client.js +44 -42
  7. package/packages/datadog-instrumentations/src/grpc/server.js +69 -60
  8. package/packages/datadog-instrumentations/src/http2/client.js +25 -26
  9. package/packages/datadog-instrumentations/src/jest.js +9 -5
  10. package/packages/datadog-instrumentations/src/mocha.js +5 -3
  11. package/packages/datadog-plugin-cypress/src/plugin.js +4 -2
  12. package/packages/datadog-plugin-graphql/src/execute.js +6 -4
  13. package/packages/datadog-plugin-grpc/src/client.js +29 -11
  14. package/packages/datadog-plugin-grpc/src/server.js +22 -6
  15. package/packages/datadog-plugin-http2/src/client.js +46 -29
  16. package/packages/datadog-plugin-jest/src/index.js +8 -3
  17. package/packages/datadog-plugin-openai/src/index.js +39 -16
  18. package/packages/datadog-plugin-openai/src/services.js +13 -9
  19. package/packages/datadog-plugin-router/src/index.js +1 -1
  20. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -1
  21. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +3 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +7 -1
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +45 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -3
  25. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +3 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +66 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +19 -15
  28. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +5 -2
  29. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +2 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +27 -8
  31. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +18 -19
  32. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +3 -0
  33. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +3 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +19 -0
  35. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +205 -0
  37. package/packages/dd-trace/src/appsec/iast/index.js +11 -7
  38. package/packages/dd-trace/src/appsec/iast/tags.js +2 -1
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +6 -6
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +7 -5
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +23 -4
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +49 -17
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +33 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +23 -16
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/{origin-types.js → source-types.js} +1 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +76 -37
  47. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +101 -0
  48. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +45 -0
  49. package/packages/dd-trace/src/appsec/iast/telemetry/{logs.js → log/index.js} +5 -5
  50. package/packages/dd-trace/src/appsec/iast/telemetry/{log_collector.js → log/log-collector.js} +1 -1
  51. package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +76 -0
  52. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +53 -0
  53. package/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js +42 -0
  54. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +5 -1
  55. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -1
  56. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  57. package/packages/dd-trace/src/config.js +47 -12
  58. package/packages/dd-trace/src/constants.js +1 -0
  59. package/packages/dd-trace/src/external-logger/src/index.js +9 -1
  60. package/packages/dd-trace/src/external-logger/test/index.spec.js +1 -1
  61. package/packages/dd-trace/src/format.js +1 -1
  62. package/packages/dd-trace/src/lambda/handler.js +8 -1
  63. package/packages/dd-trace/src/opentelemetry/span.js +3 -1
  64. package/packages/dd-trace/src/opentracing/span_context.js +2 -1
  65. package/packages/dd-trace/src/opentracing/tracer.js +1 -0
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  67. package/packages/dd-trace/src/plugins/outbound.js +29 -12
  68. package/packages/dd-trace/src/plugins/plugin.js +28 -0
  69. package/packages/dd-trace/src/plugins/tracing.js +33 -16
  70. package/packages/dd-trace/src/plugins/util/ci.js +3 -2
  71. package/packages/dd-trace/src/plugins/util/test.js +55 -11
  72. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -22
  73. package/packages/dd-trace/src/plugins/util/web.js +1 -0
  74. package/packages/dd-trace/src/profiling/config.js +8 -8
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +4 -1
  76. package/packages/dd-trace/src/profiling/index.js +0 -2
  77. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  78. package/packages/dd-trace/src/profiling/profilers/wall.js +162 -10
  79. package/packages/dd-trace/src/service-naming/index.js +2 -2
  80. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +12 -0
  81. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  82. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +12 -0
  83. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  84. package/packages/dd-trace/src/span_processor.js +0 -4
  85. package/packages/dd-trace/src/span_sampler.js +1 -1
  86. package/packages/dd-trace/src/telemetry/dependencies.js +24 -12
  87. package/packages/dd-trace/src/telemetry/metrics.js +11 -1
  88. package/packages/diagnostics_channel/src/index.js +64 -0
  89. package/scripts/install_plugin_modules.js +1 -0
  90. package/packages/dd-trace/src/profiling/profilers/cpu.js +0 -126
  91. package/scripts/version.js +0 -66
@@ -1,11 +1,13 @@
1
1
  const path = require('path')
2
2
  const fs = require('fs')
3
+ const { URL } = require('url')
4
+ const log = require('../../log')
3
5
 
4
6
  const istanbul = require('istanbul-lib-coverage')
5
7
  const ignore = require('ignore')
6
8
 
7
9
  const { getGitMetadata } = require('./git')
8
- const { getUserProviderGitMetadata } = require('./user-provided-git')
10
+ const { getUserProviderGitMetadata, validateGitRepositoryUrl, validateGitCommitSha } = require('./user-provided-git')
9
11
  const { getCIMetadata } = require('./ci')
10
12
  const { getRuntimeAndOSMetadata } = require('./env')
11
13
  const {
@@ -16,7 +18,8 @@ const {
16
18
  GIT_COMMIT_AUTHOR_EMAIL,
17
19
  GIT_COMMIT_AUTHOR_NAME,
18
20
  GIT_COMMIT_MESSAGE,
19
- CI_WORKSPACE_PATH
21
+ CI_WORKSPACE_PATH,
22
+ CI_PIPELINE_URL
20
23
  } = require('./tags')
21
24
  const id = require('../../id')
22
25
 
@@ -104,7 +107,8 @@ module.exports = {
104
107
  mergeCoverage,
105
108
  fromCoverageMapToCoverage,
106
109
  getTestLineStart,
107
- getCallSites
110
+ getCallSites,
111
+ removeInvalidMetadata
108
112
  }
109
113
 
110
114
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -116,6 +120,39 @@ function getPkgManager () {
116
120
  }
117
121
  }
118
122
 
123
+ function validateUrl (url) {
124
+ try {
125
+ const urlObject = new URL(url)
126
+ return (urlObject.protocol === 'https:' || urlObject.protocol === 'http:')
127
+ } catch (e) {
128
+ return false
129
+ }
130
+ }
131
+
132
+ function removeInvalidMetadata (metadata) {
133
+ return Object.keys(metadata).reduce((filteredTags, tag) => {
134
+ if (tag === GIT_REPOSITORY_URL) {
135
+ if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
136
+ log.error('DD_GIT_REPOSITORY_URL must be a valid URL')
137
+ return filteredTags
138
+ }
139
+ }
140
+ if (tag === GIT_COMMIT_SHA) {
141
+ if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
142
+ log.error('DD_GIT_COMMIT_SHA must be a full-length git SHA')
143
+ return filteredTags
144
+ }
145
+ }
146
+ if (tag === CI_PIPELINE_URL) {
147
+ if (!validateUrl(metadata[CI_PIPELINE_URL])) {
148
+ return filteredTags
149
+ }
150
+ }
151
+ filteredTags[tag] = metadata[tag]
152
+ return filteredTags
153
+ }, {})
154
+ }
155
+
119
156
  function getTestEnvironmentMetadata (testFramework, config) {
120
157
  // TODO: eventually these will come from the tracer (generally available)
121
158
  const ciMetadata = getCIMetadata()
@@ -155,7 +192,7 @@ function getTestEnvironmentMetadata (testFramework, config) {
155
192
  if (config && config.service) {
156
193
  metadata['service.name'] = config.service
157
194
  }
158
- return metadata
195
+ return removeInvalidMetadata(metadata)
159
196
  }
160
197
 
161
198
  function getTestParametersString (parametersByTestName, testName) {
@@ -173,6 +210,13 @@ function getTestParametersString (parametersByTestName, testName) {
173
210
  }
174
211
  }
175
212
 
213
+ function getTestTypeFromFramework (testFramework) {
214
+ if (testFramework === 'playwright' || testFramework === 'cypress') {
215
+ return 'browser'
216
+ }
217
+ return 'test'
218
+ }
219
+
176
220
  function finishAllTraceSpans (span) {
177
221
  span.context()._trace.started.forEach(traceSpan => {
178
222
  if (traceSpan !== span) {
@@ -188,10 +232,10 @@ function getTestParentSpan (tracer) {
188
232
  })
189
233
  }
190
234
 
191
- function getTestCommonTags (name, suite, version) {
235
+ function getTestCommonTags (name, suite, version, testFramework) {
192
236
  return {
193
237
  [SPAN_TYPE]: 'test',
194
- [TEST_TYPE]: 'test',
238
+ [TEST_TYPE]: getTestTypeFromFramework(testFramework),
195
239
  [SAMPLING_RULE_DECISION]: 1,
196
240
  [SAMPLING_PRIORITY]: AUTO_KEEP,
197
241
  [TEST_NAME]: name,
@@ -269,12 +313,12 @@ function getCodeOwnersForFilename (filename, entries) {
269
313
  return null
270
314
  }
271
315
 
272
- function getTestLevelCommonTags (command, testFrameworkVersion) {
316
+ function getTestLevelCommonTags (command, testFrameworkVersion, testFramework) {
273
317
  return {
274
318
  [TEST_FRAMEWORK_VERSION]: testFrameworkVersion,
275
319
  [LIBRARY_VERSION]: ddTraceVersion,
276
320
  [TEST_COMMAND]: command,
277
- [TEST_TYPE]: 'test'
321
+ [TEST_TYPE]: getTestTypeFromFramework(testFramework)
278
322
  }
279
323
  }
280
324
 
@@ -284,7 +328,7 @@ function getTestSessionCommonTags (command, testFrameworkVersion, testFramework)
284
328
  [RESOURCE_NAME]: `test_session.${command}`,
285
329
  [TEST_MODULE]: testFramework,
286
330
  [TEST_TOOLCHAIN]: getPkgManager(),
287
- ...getTestLevelCommonTags(command, testFrameworkVersion)
331
+ ...getTestLevelCommonTags(command, testFrameworkVersion, testFramework)
288
332
  }
289
333
  }
290
334
 
@@ -293,7 +337,7 @@ function getTestModuleCommonTags (command, testFrameworkVersion, testFramework)
293
337
  [SPAN_TYPE]: 'test_module_end',
294
338
  [RESOURCE_NAME]: `test_module.${command}`,
295
339
  [TEST_MODULE]: testFramework,
296
- ...getTestLevelCommonTags(command, testFrameworkVersion)
340
+ ...getTestLevelCommonTags(command, testFrameworkVersion, testFramework)
297
341
  }
298
342
  }
299
343
 
@@ -303,7 +347,7 @@ function getTestSuiteCommonTags (command, testFrameworkVersion, testSuite, testF
303
347
  [RESOURCE_NAME]: `test_suite.${testSuite}`,
304
348
  [TEST_MODULE]: testFramework,
305
349
  [TEST_SUITE]: testSuite,
306
- ...getTestLevelCommonTags(command, testFrameworkVersion)
350
+ ...getTestLevelCommonTags(command, testFrameworkVersion, testFramework)
307
351
  }
308
352
  }
309
353
 
@@ -13,7 +13,6 @@ const {
13
13
  } = require('./tags')
14
14
 
15
15
  const { normalizeRef } = require('./ci')
16
- const log = require('../../log')
17
16
  const { URL } = require('url')
18
17
 
19
18
  function removeEmptyValues (tags) {
@@ -53,25 +52,6 @@ function validateGitCommitSha (gitCommitSha) {
53
52
  return isValidSha1 || isValidSha256
54
53
  }
55
54
 
56
- function removeInvalidGitMetadata (metadata) {
57
- return Object.keys(metadata).reduce((filteredTags, tag) => {
58
- if (tag === GIT_REPOSITORY_URL) {
59
- if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
60
- log.error('DD_GIT_REPOSITORY_URL must be a valid URL')
61
- return filteredTags
62
- }
63
- }
64
- if (tag === GIT_COMMIT_SHA) {
65
- if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
66
- log.error('DD_GIT_COMMIT_SHA must be a full-length git SHA')
67
- return filteredTags
68
- }
69
- }
70
- filteredTags[tag] = metadata[tag]
71
- return filteredTags
72
- }, {})
73
- }
74
-
75
55
  function getUserProviderGitMetadata () {
76
56
  const {
77
57
  DD_GIT_COMMIT_SHA,
@@ -95,7 +75,7 @@ function getUserProviderGitMetadata () {
95
75
  tag = normalizeRef(DD_GIT_BRANCH)
96
76
  }
97
77
 
98
- const metadata = removeEmptyValues({
78
+ return removeEmptyValues({
99
79
  [GIT_COMMIT_SHA]: DD_GIT_COMMIT_SHA,
100
80
  [GIT_BRANCH]: branch,
101
81
  [GIT_REPOSITORY_URL]: filterSensitiveInfoFromRepository(DD_GIT_REPOSITORY_URL),
@@ -108,7 +88,6 @@ function getUserProviderGitMetadata () {
108
88
  [GIT_COMMIT_AUTHOR_EMAIL]: DD_GIT_COMMIT_AUTHOR_EMAIL,
109
89
  [GIT_COMMIT_AUTHOR_DATE]: DD_GIT_COMMIT_AUTHOR_DATE
110
90
  })
111
- return removeInvalidGitMetadata(metadata)
112
91
  }
113
92
 
114
93
  module.exports = { getUserProviderGitMetadata, validateGitRepositoryUrl, validateGitCommitSha }
@@ -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
  },
@@ -7,7 +7,6 @@ const { URL, format, pathToFileURL } = require('url')
7
7
  const { AgentExporter } = require('./exporters/agent')
8
8
  const { FileExporter } = require('./exporters/file')
9
9
  const { ConsoleLogger } = require('./loggers/console')
10
- const CpuProfiler = require('./profilers/cpu')
11
10
  const WallProfiler = require('./profilers/wall')
12
11
  const SpaceProfiler = require('./profilers/space')
13
12
  const { oomExportStrategies, snapshotKinds } = require('./constants')
@@ -19,7 +18,6 @@ class Config {
19
18
  const {
20
19
  DD_PROFILING_ENABLED,
21
20
  DD_PROFILING_PROFILERS,
22
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
23
21
  DD_ENV,
24
22
  DD_TAGS,
25
23
  DD_SERVICE,
@@ -37,7 +35,9 @@ class Config {
37
35
  DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
38
36
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
39
37
  DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
40
- 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
41
41
  } = process.env
42
42
 
43
43
  const enabled = isTrue(coalesce(options.enabled, DD_PROFILING_ENABLED, true))
@@ -52,8 +52,8 @@ class Config {
52
52
  Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
53
53
  const sourceMap = coalesce(options.sourceMap,
54
54
  DD_PROFILING_SOURCE_MAP, true)
55
- const endpointCollection = coalesce(options.endpointCollection,
56
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, false)
55
+ const endpointCollectionEnabled = coalesce(options.endpointCollection,
56
+ DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false)
57
57
  const pprofPrefix = coalesce(options.pprofPrefix,
58
58
  DD_PROFILING_PPROF_PREFIX, '')
59
59
 
@@ -74,7 +74,7 @@ class Config {
74
74
  this.uploadTimeout = uploadTimeout
75
75
  this.sourceMap = sourceMap
76
76
  this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
77
- this.endpointCollection = endpointCollection
77
+ this.endpointCollectionEnabled = endpointCollectionEnabled
78
78
  this.pprofPrefix = pprofPrefix
79
79
 
80
80
  const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
@@ -111,6 +111,8 @@ class Config {
111
111
  const profilers = options.profilers
112
112
  ? options.profilers
113
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))
114
116
 
115
117
  this.profilers = ensureProfilers(profilers, this)
116
118
  }
@@ -202,8 +204,6 @@ function getProfiler (name, options) {
202
204
  return new WallProfiler(options)
203
205
  case 'space':
204
206
  return new SpaceProfiler(options)
205
- case 'cpu-experimental':
206
- return new CpuProfiler(options)
207
207
  default:
208
208
  options.logger.error(`Unknown profiler "${name}"`)
209
209
  }
@@ -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 => {
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { Profiler, ServerlessProfiler } = require('./profiler')
4
- const CpuProfiler = require('./profilers/cpu')
5
4
  const WallProfiler = require('./profilers/wall')
6
5
  const SpaceProfiler = require('./profilers/space')
7
6
  const { AgentExporter } = require('./exporters/agent')
@@ -14,7 +13,6 @@ module.exports = {
14
13
  profiler,
15
14
  AgentExporter,
16
15
  FileExporter,
17
- CpuProfiler,
18
16
  WallProfiler,
19
17
  SpaceProfiler,
20
18
  ConsoleLogger
@@ -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,16 +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
- this._samplingInterval = options.samplingInterval || 1e6 / 99 // 99hz
80
+ this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz
81
+ this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
82
+ this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
83
+ this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
7
84
  this._mapper = undefined
8
85
  this._pprof = undefined
86
+
87
+ // Bind to this so the same value can be used to unsubscribe later
88
+ this._enter = this._enter.bind(this)
89
+ this._logger = options.logger
90
+ this._started = false
91
+ }
92
+
93
+ codeHotspotsEnabled () {
94
+ return this._codeHotspotsEnabled
9
95
  }
10
96
 
11
97
  start ({ mapper } = {}) {
98
+ if (this._started) return
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
+
12
106
  this._mapper = mapper
13
107
  this._pprof = require('@datadog/pprof')
108
+ kSampleCount = this._pprof.time.constants.kSampleCount
14
109
 
15
110
  // pprof otherwise crashes in worker threads
16
111
  if (!process._startProfilerIdleNotifier) {
@@ -20,11 +115,65 @@ class NativeWallProfiler {
20
115
  process._stopProfilerIdleNotifier = () => {}
21
116
  }
22
117
 
23
- this._record()
118
+ this._pprof.time.start({
119
+ intervalMicros: this._samplingIntervalMicros,
120
+ durationMillis: this._flushIntervalMillis,
121
+ sourceMapper: this._mapper,
122
+ withContexts: this._codeHotspotsEnabled,
123
+ lineNumbers: false
124
+ })
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
+
138
+ this._started = true
139
+ }
140
+
141
+ _enter () {
142
+ if (!this._started) return
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)
24
174
  }
25
175
 
26
176
  profile () {
27
- if (!this._stop) return
28
177
  return this._stop(true)
29
178
  }
30
179
 
@@ -33,14 +182,17 @@ class NativeWallProfiler {
33
182
  }
34
183
 
35
184
  stop () {
36
- if (!this._stop) return
37
- this._stop()
38
- this._stop = undefined
39
- }
185
+ if (!this._started) return
186
+
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
+ }
40
193
 
41
- _record () {
42
- this._stop = this._pprof.time.start(this._samplingInterval, null,
43
- this._mapper, false)
194
+ this._started = false
195
+ return profile
44
196
  }
45
197
  }
46
198
 
@@ -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
  }