dd-trace 5.28.0 → 5.29.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 (148) hide show
  1. package/LICENSE-3rdparty.csv +8 -2
  2. package/ci/init.js +16 -0
  3. package/index.d.ts +31 -13
  4. package/init.js +4 -68
  5. package/loader-hook.mjs +4 -0
  6. package/package.json +16 -11
  7. package/packages/datadog-core/src/storage.js +39 -2
  8. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +29 -3
  10. package/packages/datadog-instrumentations/src/express.js +38 -4
  11. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
  13. package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
  14. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +27 -8
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +13 -8
  18. package/packages/datadog-instrumentations/src/next.js +7 -4
  19. package/packages/datadog-instrumentations/src/passport-http.js +2 -14
  20. package/packages/datadog-instrumentations/src/passport-local.js +2 -14
  21. package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
  22. package/packages/datadog-instrumentations/src/pg.js +6 -6
  23. package/packages/datadog-instrumentations/src/playwright.js +17 -4
  24. package/packages/datadog-instrumentations/src/router.js +97 -1
  25. package/packages/datadog-instrumentations/src/sequelize.js +9 -4
  26. package/packages/datadog-instrumentations/src/url.js +4 -0
  27. package/packages/datadog-instrumentations/src/vitest.js +27 -2
  28. package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
  29. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  31. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  32. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  35. package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
  36. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  37. package/packages/datadog-plugin-cucumber/src/index.js +39 -4
  38. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
  39. package/packages/datadog-plugin-grpc/src/client.js +2 -2
  40. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  41. package/packages/datadog-plugin-jest/src/index.js +39 -4
  42. package/packages/datadog-plugin-mocha/src/index.js +36 -2
  43. package/packages/datadog-plugin-oracledb/src/index.js +1 -1
  44. package/packages/datadog-plugin-vitest/src/index.js +34 -2
  45. package/packages/datadog-shimmer/src/shimmer.js +8 -4
  46. package/packages/dd-trace/src/appsec/addresses.js +3 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
  48. package/packages/dd-trace/src/appsec/channels.js +1 -0
  49. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
  50. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
  52. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
  54. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
  56. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  58. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
  63. package/packages/dd-trace/src/appsec/index.js +16 -10
  64. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  65. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
  66. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  67. package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
  68. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  69. package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
  70. package/packages/dd-trace/src/azure_metadata.js +4 -4
  71. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
  73. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  74. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
  78. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  79. package/packages/dd-trace/src/config.js +24 -32
  80. package/packages/dd-trace/src/constants.js +1 -0
  81. package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
  82. package/packages/dd-trace/src/datastreams/processor.js +4 -6
  83. package/packages/dd-trace/src/datastreams/writer.js +6 -5
  84. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
  85. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
  88. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
  89. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
  91. package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
  92. package/packages/dd-trace/src/debugger/index.js +14 -10
  93. package/packages/dd-trace/src/dogstatsd.js +2 -2
  94. package/packages/dd-trace/src/encode/0.4.js +23 -78
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
  96. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
  97. package/packages/dd-trace/src/encode/span-stats.js +0 -30
  98. package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
  99. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  100. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
  101. package/packages/dd-trace/src/flare/index.js +1 -1
  102. package/packages/dd-trace/src/guardrails/index.js +64 -0
  103. package/packages/dd-trace/src/guardrails/log.js +32 -0
  104. package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
  105. package/packages/dd-trace/src/guardrails/util.js +10 -0
  106. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  107. package/packages/dd-trace/src/llmobs/storage.js +2 -3
  108. package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
  109. package/packages/dd-trace/src/log/channels.js +9 -2
  110. package/packages/dd-trace/src/log/index.js +11 -1
  111. package/packages/dd-trace/src/log/writer.js +14 -3
  112. package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
  113. package/packages/dd-trace/src/msgpack/encoder.js +309 -0
  114. package/packages/dd-trace/src/msgpack/index.js +6 -0
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
  117. package/packages/dd-trace/src/opentracing/span.js +1 -1
  118. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  119. package/packages/dd-trace/src/plugin_manager.js +4 -2
  120. package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
  121. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  122. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  123. package/packages/dd-trace/src/plugins/util/git.js +7 -7
  124. package/packages/dd-trace/src/plugins/util/test.js +36 -3
  125. package/packages/dd-trace/src/plugins/util/web.js +2 -2
  126. package/packages/dd-trace/src/priority_sampler.js +11 -1
  127. package/packages/dd-trace/src/profiling/config.js +3 -0
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
  129. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
  130. package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
  131. package/packages/dd-trace/src/profiling/profiler.js +62 -10
  132. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
  133. package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
  134. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
  135. package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
  136. package/packages/dd-trace/src/proxy.js +7 -2
  137. package/packages/dd-trace/src/runtime_metrics.js +107 -4
  138. package/packages/dd-trace/src/serverless.js +1 -1
  139. package/packages/dd-trace/src/span_processor.js +10 -10
  140. package/packages/dd-trace/src/tagger.js +1 -1
  141. package/packages/dd-trace/src/telemetry/index.js +1 -0
  142. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
  144. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  145. package/packages/dd-trace/src/util.js +5 -16
  146. package/packages/datadog-instrumentations/src/qs.js +0 -24
  147. package/packages/dd-trace/src/appsec/passport.js +0 -110
  148. package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
@@ -79,7 +79,7 @@ module.exports = class Plugin {
79
79
  return handler.apply(this, arguments)
80
80
  } catch (e) {
81
81
  logger.error('Error in plugin handler:', e)
82
- logger.info('Disabling plugin:', plugin.id)
82
+ logger.info('Disabling plugin: %s', plugin.id)
83
83
  plugin.configure(false)
84
84
  }
85
85
  }
@@ -94,7 +94,7 @@ class TracingPlugin extends Plugin {
94
94
  }
95
95
 
96
96
  addError (error, span = this.activeSpan) {
97
- if (!span._spanContext._tags.error) {
97
+ if (span && !span._spanContext._tags.error) {
98
98
  // Errors may be wrapped in a context.
99
99
  error = (error && error.error) || error
100
100
  span.setTag('error', error || 1)
@@ -61,7 +61,7 @@ function sanitizedExec (
61
61
  exitCode: err.status || err.errno
62
62
  })
63
63
  }
64
- log.error(err)
64
+ log.error('Git plugin error executing command', err)
65
65
  return ''
66
66
  } finally {
67
67
  storage.enterWith(store)
@@ -144,7 +144,7 @@ function unshallowRepository () {
144
144
  ], { stdio: 'pipe' })
145
145
  } catch (err) {
146
146
  // If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
147
- log.error(err)
147
+ log.error('Git plugin error executing git command', err)
148
148
  incrementCountMetric(
149
149
  TELEMETRY_GIT_COMMAND_ERRORS,
150
150
  { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
@@ -157,7 +157,7 @@ function unshallowRepository () {
157
157
  ], { stdio: 'pipe' })
158
158
  } catch (err) {
159
159
  // If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
160
- log.error(err)
160
+ log.error('Git plugin error executing fallback git command', err)
161
161
  incrementCountMetric(
162
162
  TELEMETRY_GIT_COMMAND_ERRORS,
163
163
  { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
@@ -196,7 +196,7 @@ function getLatestCommits () {
196
196
  distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_local_commits' }, Date.now() - startTime)
197
197
  return result
198
198
  } catch (err) {
199
- log.error(`Get latest commits failed: ${err.message}`)
199
+ log.error('Get latest commits failed: %s', err.message)
200
200
  incrementCountMetric(
201
201
  TELEMETRY_GIT_COMMAND_ERRORS,
202
202
  { command: 'get_local_commits', errorType: err.status }
@@ -229,7 +229,7 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
229
229
  .split('\n')
230
230
  .filter(commit => commit)
231
231
  } catch (err) {
232
- log.error(`Get commits to upload failed: ${err.message}`)
232
+ log.error('Get commits to upload failed: %s', err.message)
233
233
  incrementCountMetric(
234
234
  TELEMETRY_GIT_COMMAND_ERRORS,
235
235
  { command: 'get_objects', errorType: err.code, exitCode: err.status || err.errno } // err.status might be null
@@ -272,7 +272,7 @@ function generatePackFilesForCommits (commitsToUpload) {
272
272
  try {
273
273
  result = execGitPackObjects(temporaryPath)
274
274
  } catch (err) {
275
- log.error(err)
275
+ log.error('Git plugin error executing git pack-objects command', err)
276
276
  incrementCountMetric(
277
277
  TELEMETRY_GIT_COMMAND_ERRORS,
278
278
  { command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
@@ -292,7 +292,7 @@ function generatePackFilesForCommits (commitsToUpload) {
292
292
  try {
293
293
  result = execGitPackObjects(cwdPath)
294
294
  } catch (err) {
295
- log.error(err)
295
+ log.error('Git plugin error executing fallback git pack-objects command', err)
296
296
  incrementCountMetric(
297
297
  TELEMETRY_GIT_COMMAND_ERRORS,
298
298
  { command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
@@ -106,6 +106,13 @@ const TEST_LEVEL_EVENT_TYPES = [
106
106
  'test_session_end'
107
107
  ]
108
108
 
109
+ // Dynamic instrumentation - Test optimization integration tags
110
+ const DI_ERROR_DEBUG_INFO_CAPTURED = 'error.debug_info_captured'
111
+ // TODO: for the moment we'll only use a single snapshot id, so `0` is hardcoded
112
+ const DI_DEBUG_ERROR_SNAPSHOT_ID = '_dd.debug.error.0.snapshot_id'
113
+ const DI_DEBUG_ERROR_FILE = '_dd.debug.error.0.file'
114
+ const DI_DEBUG_ERROR_LINE = '_dd.debug.error.0.line'
115
+
109
116
  module.exports = {
110
117
  TEST_CODE_OWNERS,
111
118
  TEST_SESSION_NAME,
@@ -181,7 +188,12 @@ module.exports = {
181
188
  TEST_BROWSER_VERSION,
182
189
  getTestSessionName,
183
190
  TEST_LEVEL_EVENT_TYPES,
184
- getNumFromKnownTests
191
+ getNumFromKnownTests,
192
+ getFileAndLineNumberFromError,
193
+ DI_ERROR_DEBUG_INFO_CAPTURED,
194
+ DI_DEBUG_ERROR_SNAPSHOT_ID,
195
+ DI_DEBUG_ERROR_FILE,
196
+ DI_DEBUG_ERROR_LINE
185
197
  }
186
198
 
187
199
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -206,13 +218,13 @@ function removeInvalidMetadata (metadata) {
206
218
  return Object.keys(metadata).reduce((filteredTags, tag) => {
207
219
  if (tag === GIT_REPOSITORY_URL) {
208
220
  if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
209
- log.error(`Repository URL is not a valid repository URL: ${metadata[GIT_REPOSITORY_URL]}.`)
221
+ log.error('Repository URL is not a valid repository URL: %s.', metadata[GIT_REPOSITORY_URL])
210
222
  return filteredTags
211
223
  }
212
224
  }
213
225
  if (tag === GIT_COMMIT_SHA) {
214
226
  if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
215
- log.error(`Git commit SHA must be a full-length git SHA: ${metadata[GIT_COMMIT_SHA]}.`)
227
+ log.error('Git commit SHA must be a full-length git SHA: %s.', metadata[GIT_COMMIT_SHA])
216
228
  return filteredTags
217
229
  }
218
230
  }
@@ -637,3 +649,24 @@ function getNumFromKnownTests (knownTests) {
637
649
 
638
650
  return totalNumTests
639
651
  }
652
+
653
+ function getFileAndLineNumberFromError (error) {
654
+ // Split the stack trace into individual lines
655
+ const stackLines = error.stack.split('\n')
656
+
657
+ // The top frame is usually the second line
658
+ const topFrame = stackLines[1]
659
+
660
+ // Regular expression to match the file path, line number, and column number
661
+ const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
662
+ const match = topFrame.match(regex)
663
+
664
+ if (match) {
665
+ const filePath = match[1]
666
+ const lineNumber = Number(match[2])
667
+ const columnNumber = Number(match[3])
668
+
669
+ return [filePath, lineNumber, columnNumber]
670
+ }
671
+ return []
672
+ }
@@ -546,7 +546,7 @@ function getHeadersToRecord (config) {
546
546
  .map(h => h.split(':'))
547
547
  .map(([key, tag]) => [key.toLowerCase(), tag])
548
548
  } catch (err) {
549
- log.error(err)
549
+ log.error('Web plugin error getting headers', err)
550
550
  }
551
551
  } else if (config.hasOwnProperty('headers')) {
552
552
  log.error('Expected `headers` to be an array of strings.')
@@ -595,7 +595,7 @@ function getQsObfuscator (config) {
595
595
  try {
596
596
  return new RegExp(obfuscator, 'gi')
597
597
  } catch (err) {
598
- log.error(err)
598
+ log.error('Web plugin error getting qs obfuscator', err)
599
599
  }
600
600
  }
601
601
 
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('./log')
3
4
  const RateLimiter = require('./rate_limiter')
4
5
  const Sampler = require('./sampler')
5
6
  const { setSamplingRules } = require('./startup-log')
@@ -44,16 +45,19 @@ class PrioritySampler {
44
45
  this.update({})
45
46
  }
46
47
 
47
- configure (env, { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = {}) {
48
+ configure (env, opts = {}) {
49
+ const { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = opts
48
50
  this._env = env
49
51
  this._rules = this._normalizeRules(rules, sampleRate, rateLimit, provenance)
50
52
  this._limiter = new RateLimiter(rateLimit)
51
53
 
54
+ log.trace(env, opts)
52
55
  setSamplingRules(this._rules)
53
56
  }
54
57
 
55
58
  isSampled (span) {
56
59
  const priority = this._getPriorityFromAuto(span)
60
+ log.trace(span)
57
61
  return priority === USER_KEEP || priority === AUTO_KEEP
58
62
  }
59
63
 
@@ -67,6 +71,8 @@ class PrioritySampler {
67
71
  if (context._sampling.priority !== undefined) return
68
72
  if (!root) return // noop span
69
73
 
74
+ log.trace(span, auto)
75
+
70
76
  const tag = this._getPriorityFromTags(context._tags, context)
71
77
 
72
78
  if (this.validate(tag)) {
@@ -94,6 +100,8 @@ class PrioritySampler {
94
100
  samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler
95
101
 
96
102
  this._samplers = samplers
103
+
104
+ log.trace(rates)
97
105
  }
98
106
 
99
107
  validate (samplingPriority) {
@@ -117,6 +125,8 @@ class PrioritySampler {
117
125
  context._sampling.mechanism = mechanism
118
126
 
119
127
  const root = context._trace.started[0]
128
+
129
+ log.trace(span, samplingPriority, mechanism)
120
130
  this._addDecisionMaker(root)
121
131
  }
122
132
 
@@ -21,6 +21,7 @@ class Config {
21
21
  const {
22
22
  DD_AGENT_HOST,
23
23
  DD_ENV,
24
+ DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, // used for testing
24
25
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
25
26
  DD_PROFILING_CPU_ENABLED,
26
27
  DD_PROFILING_DEBUG_SOURCE_MAPS,
@@ -175,6 +176,8 @@ class Config {
175
176
  DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, samplingContextsAvailable))
176
177
  logExperimentalVarDeprecation('TIMELINE_ENABLED')
177
178
  checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
179
+ this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
180
+ DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
178
181
 
179
182
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
180
183
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
@@ -3,13 +3,13 @@
3
3
  const retry = require('retry')
4
4
  const { request: httpRequest } = require('http')
5
5
  const { request: httpsRequest } = require('https')
6
+ const { EventSerializer } = require('./event_serializer')
6
7
 
7
8
  // TODO: avoid using dd-trace internals. Make this a separate module?
8
9
  const docker = require('../../exporters/common/docker')
9
10
  const FormData = require('../../exporters/common/form-data')
10
11
  const { storage } = require('../../../../datadog-core')
11
12
  const version = require('../../../../../package.json').version
12
- const os = require('os')
13
13
  const { urlToHttpOptions } = require('url')
14
14
  const perf = require('perf_hooks').performance
15
15
 
@@ -89,8 +89,10 @@ function computeRetries (uploadTimeout) {
89
89
  return [tries, Math.floor(uploadTimeout)]
90
90
  }
91
91
 
92
- class AgentExporter {
93
- constructor ({ url, logger, uploadTimeout, env, host, service, version, libraryInjected, activation } = {}) {
92
+ class AgentExporter extends EventSerializer {
93
+ constructor (config = {}) {
94
+ super(config)
95
+ const { url, logger, uploadTimeout } = config
94
96
  this._url = url
95
97
  this._logger = logger
96
98
 
@@ -98,74 +100,13 @@ class AgentExporter {
98
100
 
99
101
  this._backoffTime = backoffTime
100
102
  this._backoffTries = backoffTries
101
- this._env = env
102
- this._host = host
103
- this._service = service
104
- this._appVersion = version
105
- this._libraryInjected = !!libraryInjected
106
- this._activation = activation || 'unknown'
107
103
  }
108
104
 
109
- export ({ profiles, start, end, tags }) {
105
+ export (exportSpec) {
106
+ const { profiles } = exportSpec
110
107
  const fields = []
111
108
 
112
- function typeToFile (type) {
113
- return `${type}.pprof`
114
- }
115
-
116
- const event = JSON.stringify({
117
- attachments: Object.keys(profiles).map(typeToFile),
118
- start: start.toISOString(),
119
- end: end.toISOString(),
120
- family: 'node',
121
- version: '4',
122
- tags_profiler: [
123
- 'language:javascript',
124
- 'runtime:nodejs',
125
- `runtime_arch:${process.arch}`,
126
- `runtime_os:${process.platform}`,
127
- `runtime_version:${process.version}`,
128
- `process_id:${process.pid}`,
129
- `profiler_version:${version}`,
130
- 'format:pprof',
131
- ...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
132
- ].join(','),
133
- info: {
134
- application: {
135
- env: this._env,
136
- service: this._service,
137
- start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
138
- version: this._appVersion
139
- },
140
- platform: {
141
- hostname: this._host,
142
- kernel_name: os.type(),
143
- kernel_release: os.release(),
144
- kernel_version: os.version()
145
- },
146
- profiler: {
147
- activation: this._activation,
148
- ssi: {
149
- mechanism: this._libraryInjected ? 'injected_agent' : 'none'
150
- },
151
- version
152
- },
153
- runtime: {
154
- // Using `nodejs` for consistency with the existing `runtime` tag.
155
- // Note that the event `family` property uses `node`, as that's what's
156
- // proscribed by the Intake API, but that's an internal enum and is
157
- // not customer visible.
158
- engine: 'nodejs',
159
- // strip off leading 'v'. This makes the format consistent with other
160
- // runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
161
- // We'll keep it like this as we want cross-engine consistency. We
162
- // also aren't changing the format of the existing tag as we don't want
163
- // to break it.
164
- version: process.version.substring(1)
165
- }
166
- }
167
- })
168
-
109
+ const event = this.getEventJSON(exportSpec)
169
110
  fields.push(['event', event, {
170
111
  filename: 'event.json',
171
112
  contentType: 'application/json'
@@ -181,7 +122,7 @@ class AgentExporter {
181
122
  return `Adding ${type} profile to agent export: ` + bytes
182
123
  })
183
124
 
184
- const filename = typeToFile(type)
125
+ const filename = this.typeToFile(type)
185
126
  fields.push([filename, buffer, {
186
127
  filename,
187
128
  contentType: 'application/octet-stream'
@@ -0,0 +1,76 @@
1
+ const os = require('os')
2
+ const perf = require('perf_hooks').performance
3
+ const version = require('../../../../../package.json').version
4
+
5
+ class EventSerializer {
6
+ constructor ({ env, host, service, version, libraryInjected, activation } = {}) {
7
+ this._env = env
8
+ this._host = host
9
+ this._service = service
10
+ this._appVersion = version
11
+ this._libraryInjected = !!libraryInjected
12
+ this._activation = activation || 'unknown'
13
+ }
14
+
15
+ typeToFile (type) {
16
+ return `${type}.pprof`
17
+ }
18
+
19
+ getEventJSON ({ profiles, start, end, tags = {}, endpointCounts }) {
20
+ return JSON.stringify({
21
+ attachments: Object.keys(profiles).map(t => this.typeToFile(t)),
22
+ start: start.toISOString(),
23
+ end: end.toISOString(),
24
+ family: 'node',
25
+ version: '4',
26
+ tags_profiler: [
27
+ 'language:javascript',
28
+ 'runtime:nodejs',
29
+ `runtime_arch:${process.arch}`,
30
+ `runtime_os:${process.platform}`,
31
+ `runtime_version:${process.version}`,
32
+ `process_id:${process.pid}`,
33
+ `profiler_version:${version}`,
34
+ 'format:pprof',
35
+ ...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
36
+ ].join(','),
37
+ endpoint_counts: endpointCounts,
38
+ info: {
39
+ application: {
40
+ env: this._env,
41
+ service: this._service,
42
+ start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
43
+ version: this._appVersion
44
+ },
45
+ platform: {
46
+ hostname: this._host,
47
+ kernel_name: os.type(),
48
+ kernel_release: os.release(),
49
+ kernel_version: os.version()
50
+ },
51
+ profiler: {
52
+ activation: this._activation,
53
+ ssi: {
54
+ mechanism: this._libraryInjected ? 'injected_agent' : 'none'
55
+ },
56
+ version
57
+ },
58
+ runtime: {
59
+ // Using `nodejs` for consistency with the existing `runtime` tag.
60
+ // Note that the event `family` property uses `node`, as that's what's
61
+ // proscribed by the Intake API, but that's an internal enum and is
62
+ // not customer visible.
63
+ engine: 'nodejs',
64
+ // strip off leading 'v'. This makes the format consistent with other
65
+ // runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
66
+ // We'll keep it like this as we want cross-engine consistency. We
67
+ // also aren't changing the format of the existing tag as we don't want
68
+ // to break it.
69
+ version: process.version.substring(1)
70
+ }
71
+ }
72
+ })
73
+ }
74
+ }
75
+
76
+ module.exports = { EventSerializer }
@@ -4,6 +4,7 @@ const fs = require('fs')
4
4
  const { promisify } = require('util')
5
5
  const { threadId } = require('worker_threads')
6
6
  const writeFile = promisify(fs.writeFile)
7
+ const { EventSerializer } = require('./event_serializer')
7
8
 
8
9
  function formatDateTime (t) {
9
10
  const pad = (n) => String(n).padStart(2, '0')
@@ -11,18 +12,21 @@ function formatDateTime (t) {
11
12
  `T${pad(t.getUTCHours())}${pad(t.getUTCMinutes())}${pad(t.getUTCSeconds())}Z`
12
13
  }
13
14
 
14
- class FileExporter {
15
- constructor ({ pprofPrefix } = {}) {
15
+ class FileExporter extends EventSerializer {
16
+ constructor (config = {}) {
17
+ super(config)
18
+ const { pprofPrefix } = config
16
19
  this._pprofPrefix = pprofPrefix || ''
17
20
  }
18
21
 
19
- export ({ profiles, end }) {
22
+ export (exportSpec) {
23
+ const { profiles, end } = exportSpec
20
24
  const types = Object.keys(profiles)
21
25
  const dateStr = formatDateTime(end)
22
26
  const tasks = types.map(type => {
23
27
  return writeFile(`${this._pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
24
28
  })
25
-
29
+ tasks.push(writeFile(`event_worker_${threadId}_${dateStr}.json`, this.getEventJSON(exportSpec)))
26
30
  return Promise.all(tasks)
27
31
  }
28
32
  }
@@ -4,9 +4,11 @@ const { EventEmitter } = require('events')
4
4
  const { Config } = require('./config')
5
5
  const { snapshotKinds } = require('./constants')
6
6
  const { threadNamePrefix } = require('./profilers/shared')
7
+ const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('./webspan-utils')
7
8
  const dc = require('dc-polyfill')
8
9
 
9
10
  const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
11
+ const spanFinishedChannel = dc.channel('dd-trace:span:finish')
10
12
 
11
13
  function maybeSourceMap (sourceMap, SourceMapper, debug) {
12
14
  if (!sourceMap) return
@@ -21,6 +23,20 @@ function logError (logger, err) {
21
23
  }
22
24
  }
23
25
 
26
+ function findWebSpan (startedSpans, spanId) {
27
+ for (let i = startedSpans.length; --i >= 0;) {
28
+ const ispan = startedSpans[i]
29
+ const context = ispan.context()
30
+ if (context._spanId === spanId) {
31
+ if (isWebServerSpan(context._tags)) {
32
+ return true
33
+ }
34
+ spanId = context._parentId
35
+ }
36
+ }
37
+ return false
38
+ }
39
+
24
40
  class Profiler extends EventEmitter {
25
41
  constructor () {
26
42
  super()
@@ -30,6 +46,7 @@ class Profiler extends EventEmitter {
30
46
  this._timer = undefined
31
47
  this._lastStart = undefined
32
48
  this._timeoutInterval = undefined
49
+ this.endpointCounts = new Map()
33
50
  }
34
51
 
35
52
  start (options) {
@@ -82,6 +99,11 @@ class Profiler extends EventEmitter {
82
99
  this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
83
100
  }
84
101
 
102
+ if (config.endpointCollectionEnabled) {
103
+ this._spanFinishListener = this._onSpanFinish.bind(this)
104
+ spanFinishedChannel.subscribe(this._spanFinishListener)
105
+ }
106
+
85
107
  this._capture(this._timeoutInterval, start)
86
108
  return true
87
109
  } catch (e) {
@@ -117,6 +139,11 @@ class Profiler extends EventEmitter {
117
139
 
118
140
  this._enabled = false
119
141
 
142
+ if (this._spanFinishListener !== undefined) {
143
+ spanFinishedChannel.unsubscribe(this._spanFinishListener)
144
+ this._spanFinishListener = undefined
145
+ }
146
+
120
147
  for (const profiler of this._config.profilers) {
121
148
  profiler.stop()
122
149
  this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
@@ -137,6 +164,26 @@ class Profiler extends EventEmitter {
137
164
  }
138
165
  }
139
166
 
167
+ _onSpanFinish (span) {
168
+ const context = span.context()
169
+ const tags = context._tags
170
+ if (!isWebServerSpan(tags)) return
171
+
172
+ const endpointName = endpointNameFromTags(tags)
173
+ if (!endpointName) return
174
+
175
+ // Make sure this is the outermost web span, just in case so we don't overcount
176
+ if (findWebSpan(getStartedSpans(context), context._parentId)) return
177
+
178
+ let counter = this.endpointCounts.get(endpointName)
179
+ if (counter === undefined) {
180
+ counter = { count: 1 }
181
+ this.endpointCounts.set(endpointName, counter)
182
+ } else {
183
+ counter.count++
184
+ }
185
+ }
186
+
140
187
  async _collect (snapshotKind, restart = true) {
141
188
  if (!this._enabled) return
142
189
 
@@ -194,18 +241,23 @@ class Profiler extends EventEmitter {
194
241
 
195
242
  _submit (profiles, start, end, snapshotKind) {
196
243
  const { tags } = this._config
197
- const tasks = []
198
244
 
199
- tags.snapshot = snapshotKind
200
- for (const exporter of this._config.exporters) {
201
- const task = exporter.export({ profiles, start, end, tags })
202
- .catch(err => {
203
- if (this._logger) {
204
- this._logger.warn(err)
205
- }
206
- })
207
- tasks.push(task)
245
+ // Flatten endpoint counts
246
+ const endpointCounts = {}
247
+ for (const [endpoint, { count }] of this.endpointCounts) {
248
+ endpointCounts[endpoint] = count
208
249
  }
250
+ this.endpointCounts.clear()
251
+
252
+ tags.snapshot = snapshotKind
253
+ const exportSpec = { profiles, start, end, tags, endpointCounts }
254
+ const tasks = this._config.exporters.map(exporter =>
255
+ exporter.export(exportSpec).catch(err => {
256
+ if (this._logger) {
257
+ this._logger.warn(err)
258
+ }
259
+ })
260
+ )
209
261
 
210
262
  return Promise.all(tasks)
211
263
  }
@@ -1,14 +1,15 @@
1
- const { AsyncLocalStorage } = require('async_hooks')
1
+ const { storage } = require('../../../../../datadog-core')
2
2
  const TracingPlugin = require('../../../plugins/tracing')
3
3
  const { performance } = require('perf_hooks')
4
4
 
5
5
  // We are leveraging the TracingPlugin class for its functionality to bind
6
6
  // start/error/finish methods to the appropriate diagnostic channels.
7
7
  class EventPlugin extends TracingPlugin {
8
- constructor (eventHandler) {
8
+ constructor (eventHandler, eventFilter) {
9
9
  super()
10
10
  this.eventHandler = eventHandler
11
- this.store = new AsyncLocalStorage()
11
+ this.eventFilter = eventFilter
12
+ this.store = storage('profiling')
12
13
  this.entryType = this.constructor.entryType
13
14
  }
14
15
 
@@ -20,27 +21,36 @@ class EventPlugin extends TracingPlugin {
20
21
  }
21
22
 
22
23
  error () {
23
- this.store.getStore().error = true
24
+ const store = this.store.getStore()
25
+ if (store) {
26
+ store.error = true
27
+ }
24
28
  }
25
29
 
26
30
  finish () {
27
- const { startEvent, startTime, error } = this.store.getStore()
31
+ const store = this.store.getStore()
32
+ if (!store) return
33
+
34
+ const { startEvent, startTime, error } = store
28
35
  if (error) {
29
36
  return // don't emit perf events for failed operations
30
37
  }
31
38
  const duration = performance.now() - startTime
32
39
 
33
- const context = this.activeSpan?.context()
34
- const _ddSpanId = context?.toSpanId()
35
- const _ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || _ddSpanId
36
-
37
40
  const event = {
38
41
  entryType: this.entryType,
39
42
  startTime,
40
- duration,
41
- _ddSpanId,
42
- _ddRootSpanId
43
+ duration
43
44
  }
45
+
46
+ if (!this.eventFilter(event)) {
47
+ return
48
+ }
49
+
50
+ const context = this.activeSpan?.context()
51
+ event._ddSpanId = context?.toSpanId()
52
+ event._ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || event._ddSpanId
53
+
44
54
  this.eventHandler(this.extendEvent(event, startEvent))
45
55
  }
46
56
  }