dd-trace 4.18.0 → 5.6.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 (209) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/LICENSE-3rdparty.csv +4 -5
  3. package/MIGRATING.md +15 -0
  4. package/README.md +20 -140
  5. package/ci/cypress/after-run.js +1 -0
  6. package/ci/cypress/after-spec.js +1 -0
  7. package/ci/init.js +1 -4
  8. package/ext/kinds.d.ts +1 -0
  9. package/ext/kinds.js +2 -1
  10. package/ext/tags.d.ts +2 -1
  11. package/ext/tags.js +6 -1
  12. package/index.d.ts +1523 -1460
  13. package/package.json +19 -19
  14. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  15. package/packages/datadog-core/src/utils/src/get.js +11 -0
  16. package/packages/datadog-core/src/utils/src/has.js +14 -0
  17. package/packages/datadog-core/src/utils/src/kebabcase.js +16 -0
  18. package/packages/datadog-core/src/utils/src/pick.js +11 -0
  19. package/packages/datadog-core/src/utils/src/set.js +16 -0
  20. package/packages/datadog-core/src/utils/src/uniq.js +5 -0
  21. package/packages/datadog-esbuild/index.js +1 -20
  22. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  23. package/packages/datadog-instrumentations/src/amqplib.js +2 -2
  24. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  25. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  26. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  27. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  28. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  29. package/packages/datadog-instrumentations/src/cucumber.js +163 -46
  30. package/packages/datadog-instrumentations/src/dns.js +2 -1
  31. package/packages/datadog-instrumentations/src/express.js +20 -0
  32. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  33. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  34. package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
  35. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  36. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -3
  37. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  38. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  39. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  40. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  41. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  42. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  43. package/packages/datadog-instrumentations/src/jest.js +239 -52
  44. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  45. package/packages/datadog-instrumentations/src/mocha.js +154 -18
  46. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
  47. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  48. package/packages/datadog-instrumentations/src/mquery.js +65 -0
  49. package/packages/datadog-instrumentations/src/net.js +10 -2
  50. package/packages/datadog-instrumentations/src/next.js +35 -9
  51. package/packages/datadog-instrumentations/src/playwright.js +110 -16
  52. package/packages/datadog-instrumentations/src/restify.js +14 -1
  53. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  54. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  55. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -1
  56. package/packages/datadog-plugin-amqplib/src/producer.js +13 -1
  57. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
  58. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +163 -27
  59. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +46 -8
  60. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +129 -22
  61. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  62. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +70 -13
  64. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  65. package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
  66. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
  67. package/packages/datadog-plugin-cypress/src/plugin.js +6 -454
  68. package/packages/datadog-plugin-cypress/src/support.js +50 -3
  69. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/index.js +1 -6
  71. package/packages/datadog-plugin-graphql/src/resolve.js +28 -18
  72. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  73. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  74. package/packages/datadog-plugin-http/src/client.js +19 -2
  75. package/packages/datadog-plugin-jest/src/index.js +118 -12
  76. package/packages/datadog-plugin-jest/src/util.js +38 -16
  77. package/packages/datadog-plugin-kafkajs/src/consumer.js +76 -6
  78. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -8
  79. package/packages/datadog-plugin-mocha/src/index.js +87 -17
  80. package/packages/datadog-plugin-next/src/index.js +40 -14
  81. package/packages/datadog-plugin-playwright/src/index.js +71 -8
  82. package/packages/datadog-plugin-rhea/src/consumer.js +16 -1
  83. package/packages/datadog-plugin-rhea/src/producer.js +10 -0
  84. package/packages/dd-trace/src/appsec/activation.js +29 -0
  85. package/packages/dd-trace/src/appsec/addresses.js +5 -1
  86. package/packages/dd-trace/src/appsec/api_security_sampler.js +61 -0
  87. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  88. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  89. package/packages/dd-trace/src/appsec/channels.js +7 -3
  90. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  91. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  92. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  94. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
  95. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  96. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  97. package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
  98. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  99. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  100. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  101. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +13 -2
  102. package/packages/dd-trace/src/appsec/iast/index.js +15 -5
  103. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  104. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -0
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  107. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  109. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  110. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  111. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +19 -6
  112. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +41 -3
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  116. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  117. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  119. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  120. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  121. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  122. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  123. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  124. package/packages/dd-trace/src/appsec/index.js +49 -33
  125. package/packages/dd-trace/src/appsec/recommended.json +1763 -106
  126. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -1
  127. package/packages/dd-trace/src/appsec/remote_config/index.js +42 -16
  128. package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
  129. package/packages/dd-trace/src/appsec/reporter.js +51 -34
  130. package/packages/dd-trace/src/appsec/rule_manager.js +11 -8
  131. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  132. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  133. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  134. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → early-flake-detection/get-known-tests.js} +17 -22
  135. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  136. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  137. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  139. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +95 -37
  140. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +134 -61
  141. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +37 -4
  142. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +131 -0
  143. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  144. package/packages/dd-trace/src/config.js +561 -470
  145. package/packages/dd-trace/src/data_streams_context.js +1 -1
  146. package/packages/dd-trace/src/datastreams/pathway.js +58 -1
  147. package/packages/dd-trace/src/datastreams/processor.js +196 -27
  148. package/packages/dd-trace/src/datastreams/writer.js +11 -5
  149. package/packages/dd-trace/src/dogstatsd.js +3 -5
  150. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -6
  151. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  152. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  153. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  154. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  155. package/packages/dd-trace/src/format.js +30 -2
  156. package/packages/dd-trace/src/id.js +12 -0
  157. package/packages/dd-trace/src/iitm.js +1 -1
  158. package/packages/dd-trace/src/log/channels.js +1 -1
  159. package/packages/dd-trace/src/noop/proxy.js +4 -0
  160. package/packages/dd-trace/src/noop/span.js +1 -0
  161. package/packages/dd-trace/src/opentelemetry/span.js +104 -4
  162. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  163. package/packages/dd-trace/src/opentracing/propagation/text_map.js +16 -7
  164. package/packages/dd-trace/src/opentracing/span.js +48 -4
  165. package/packages/dd-trace/src/opentracing/span_context.js +15 -6
  166. package/packages/dd-trace/src/opentracing/tracer.js +4 -3
  167. package/packages/dd-trace/src/plugin_manager.js +1 -1
  168. package/packages/dd-trace/src/plugins/ci_plugin.js +78 -19
  169. package/packages/dd-trace/src/plugins/database.js +1 -1
  170. package/packages/dd-trace/src/plugins/index.js +7 -0
  171. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  172. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  173. package/packages/dd-trace/src/plugins/util/git.js +104 -22
  174. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  175. package/packages/dd-trace/src/plugins/util/test.js +60 -10
  176. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  177. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  178. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  179. package/packages/dd-trace/src/priority_sampler.js +30 -38
  180. package/packages/dd-trace/src/profiler.js +5 -3
  181. package/packages/dd-trace/src/profiling/config.js +77 -24
  182. package/packages/dd-trace/src/profiling/exporters/agent.js +77 -31
  183. package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
  184. package/packages/dd-trace/src/profiling/profiler.js +33 -22
  185. package/packages/dd-trace/src/profiling/profilers/events.js +270 -0
  186. package/packages/dd-trace/src/profiling/profilers/shared.js +45 -0
  187. package/packages/dd-trace/src/profiling/profilers/space.js +18 -2
  188. package/packages/dd-trace/src/profiling/profilers/wall.js +146 -70
  189. package/packages/dd-trace/src/proxy.js +56 -24
  190. package/packages/dd-trace/src/ritm.js +1 -1
  191. package/packages/dd-trace/src/sampling_rule.js +130 -0
  192. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  193. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  194. package/packages/dd-trace/src/span_processor.js +9 -1
  195. package/packages/dd-trace/src/span_sampler.js +6 -64
  196. package/packages/dd-trace/src/spanleak.js +98 -0
  197. package/packages/dd-trace/src/startup-log.js +7 -1
  198. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  199. package/packages/dd-trace/src/telemetry/index.js +182 -53
  200. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  201. package/packages/dd-trace/src/telemetry/send-data.js +65 -7
  202. package/packages/dd-trace/src/tracer.js +12 -5
  203. package/register.js +4 -0
  204. package/scripts/install_plugin_modules.js +11 -3
  205. package/scripts/st.js +105 -0
  206. package/packages/datadog-instrumentations/src/child-process.js +0 -30
  207. package/packages/dd-trace/src/plugins/util/exec.js +0 -13
  208. package/packages/diagnostics_channel/index.js +0 -3
  209. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -9,6 +9,8 @@ const docker = require('../../exporters/common/docker')
9
9
  const FormData = require('../../exporters/common/form-data')
10
10
  const { storage } = require('../../../../datadog-core')
11
11
  const version = require('../../../../../package.json').version
12
+ const os = require('os')
13
+ const perf = require('perf_hooks').performance
12
14
 
13
15
  const containerId = docker.id()
14
16
 
@@ -50,7 +52,7 @@ function computeRetries (uploadTimeout) {
50
52
  }
51
53
 
52
54
  class AgentExporter {
53
- constructor ({ url, logger, uploadTimeout } = {}) {
55
+ constructor ({ url, logger, uploadTimeout, env, host, service, version } = {}) {
54
56
  this._url = url
55
57
  this._logger = logger
56
58
 
@@ -58,47 +60,87 @@ class AgentExporter {
58
60
 
59
61
  this._backoffTime = backoffTime
60
62
  this._backoffTries = backoffTries
63
+ this._env = env
64
+ this._host = host
65
+ this._service = service
66
+ this._appVersion = version
61
67
  }
62
68
 
63
69
  export ({ profiles, start, end, tags }) {
64
- const types = Object.keys(profiles)
65
-
66
- const fields = [
67
- ['recording-start', start.toISOString()],
68
- ['recording-end', end.toISOString()],
69
- ['language', 'javascript'],
70
- ['runtime', 'nodejs'],
71
- ['runtime_version', process.version],
72
- ['profiler_version', version],
73
- ['format', 'pprof'],
74
-
75
- ['tags[]', 'language:javascript'],
76
- ['tags[]', 'runtime:nodejs'],
77
- ['tags[]', `runtime_version:${process.version}`],
78
- ['tags[]', `profiler_version:${version}`],
79
- ['tags[]', 'format:pprof'],
80
- ...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])
81
- ]
70
+ const fields = []
82
71
 
83
- this._logger.debug(() => {
84
- const body = fields.map(([key, value]) => ` ${key}: ${value}`).join('\n')
85
- return `Building agent export report: ${'\n' + body}`
72
+ function typeToFile (type) {
73
+ return `${type}.pprof`
74
+ }
75
+
76
+ const event = JSON.stringify({
77
+ attachments: Object.keys(profiles).map(typeToFile),
78
+ start: start.toISOString(),
79
+ end: end.toISOString(),
80
+ family: 'node',
81
+ version: '4',
82
+ tags_profiler: [
83
+ 'language:javascript',
84
+ 'runtime:nodejs',
85
+ `runtime_arch:${process.arch}`,
86
+ `runtime_os:${process.platform}`,
87
+ `runtime_version:${process.version}`,
88
+ `process_id:${process.pid}`,
89
+ `profiler_version:${version}`,
90
+ 'format:pprof',
91
+ ...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
92
+ ].join(','),
93
+ info: {
94
+ application: {
95
+ env: this._env,
96
+ service: this._service,
97
+ start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
98
+ version: this._appVersion
99
+ },
100
+ platform: {
101
+ hostname: this._host,
102
+ kernel_name: os.type(),
103
+ kernel_release: os.release(),
104
+ kernel_version: os.version()
105
+ },
106
+ profiler: {
107
+ version
108
+ },
109
+ runtime: {
110
+ // Using `nodejs` for consistency with the existing `runtime` tag.
111
+ // Note that the event `family` property uses `node`, as that's what's
112
+ // proscribed by the Intake API, but that's an internal enum and is
113
+ // not customer visible.
114
+ engine: 'nodejs',
115
+ // strip off leading 'v'. This makes the format consistent with other
116
+ // runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
117
+ // We'll keep it like this as we want cross-engine consistency. We
118
+ // also aren't changing the format of the existing tag as we don't want
119
+ // to break it.
120
+ version: process.version.substring(1)
121
+ }
122
+ }
86
123
  })
87
124
 
88
- for (let index = 0; index < types.length; index++) {
89
- const type = types[index]
90
- const buffer = profiles[type]
125
+ fields.push(['event', event, {
126
+ filename: 'event.json',
127
+ contentType: 'application/json'
128
+ }])
129
+
130
+ this._logger.debug(() => {
131
+ return `Building agent export report:\n${event}`
132
+ })
91
133
 
134
+ for (const [type, buffer] of Object.entries(profiles)) {
92
135
  this._logger.debug(() => {
93
136
  const bytes = buffer.toString('hex').match(/../g).join(' ')
94
137
  return `Adding ${type} profile to agent export: ` + bytes
95
138
  })
96
139
 
97
- fields.push([`types[${index}]`, type])
98
- fields.push([`data[${index}]`, buffer, {
99
- filename: `${type}.pb.gz`,
100
- contentType: 'application/octet-stream',
101
- knownLength: buffer.length
140
+ const filename = typeToFile(type)
141
+ fields.push([filename, buffer, {
142
+ filename,
143
+ contentType: 'application/octet-stream'
102
144
  }])
103
145
  }
104
146
 
@@ -120,7 +162,11 @@ class AgentExporter {
120
162
  const options = {
121
163
  method: 'POST',
122
164
  path: '/profiling/v1/input',
123
- headers: form.getHeaders(),
165
+ headers: {
166
+ 'DD-EVP-ORIGIN': 'dd-trace-js',
167
+ 'DD-EVP-ORIGIN-VERSION': version,
168
+ ...form.getHeaders()
169
+ },
124
170
  timeout: this._backoffTime * Math.pow(2, attempt)
125
171
  }
126
172
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('fs')
4
4
  const { promisify } = require('util')
5
+ const { threadId } = require('worker_threads')
5
6
  const writeFile = promisify(fs.writeFile)
6
7
 
7
8
  function formatDateTime (t) {
@@ -19,7 +20,7 @@ class FileExporter {
19
20
  const types = Object.keys(profiles)
20
21
  const dateStr = formatDateTime(end)
21
22
  const tasks = types.map(type => {
22
- return writeFile(`${this._pprofPrefix}${type}_${dateStr}.pprof`, profiles[type])
23
+ return writeFile(`${this._pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
23
24
  })
24
25
 
25
26
  return Promise.all(tasks)
@@ -3,6 +3,7 @@
3
3
  const { EventEmitter } = require('events')
4
4
  const { Config } = require('./config')
5
5
  const { snapshotKinds } = require('./constants')
6
+ const { threadNamePrefix } = require('./profilers/shared')
6
7
 
7
8
  function maybeSourceMap (sourceMap, SourceMapper, debug) {
8
9
  if (!sourceMap) return
@@ -23,15 +24,19 @@ class Profiler extends EventEmitter {
23
24
  }
24
25
 
25
26
  start (options) {
26
- this._start(options).catch((err) => { if (options.logger) options.logger.error(err) })
27
- return this
27
+ return this._start(options).catch((err) => {
28
+ if (options.logger) {
29
+ options.logger.error(err)
30
+ }
31
+ return false
32
+ })
28
33
  }
29
34
 
30
35
  async _start (options) {
31
- if (this._enabled) return
36
+ if (this._enabled) return true
32
37
 
33
38
  const config = this._config = new Config(options)
34
- if (!config.enabled) return
39
+ if (!config.enabled) return false
35
40
 
36
41
  this._logger = config.logger
37
42
  this._enabled = true
@@ -57,19 +62,22 @@ class Profiler extends EventEmitter {
57
62
  }
58
63
 
59
64
  try {
65
+ const start = new Date()
60
66
  for (const profiler of config.profilers) {
61
67
  // TODO: move this out of Profiler when restoring sourcemap support
62
68
  profiler.start({
63
69
  mapper,
64
70
  nearOOMCallback: this._nearOOMExport.bind(this)
65
71
  })
66
- this._logger.debug(`Started ${profiler.type} profiler`)
72
+ this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
67
73
  }
68
74
 
69
- this._capture(this._timeoutInterval)
75
+ this._capture(this._timeoutInterval, start)
76
+ return true
70
77
  } catch (e) {
71
78
  this._logger.error(e)
72
79
  this._stop()
80
+ return false
73
81
  }
74
82
  }
75
83
 
@@ -90,7 +98,7 @@ class Profiler extends EventEmitter {
90
98
 
91
99
  // collect and export current profiles
92
100
  // once collect returns, profilers can be safely stopped
93
- this._collect(snapshotKinds.ON_SHUTDOWN)
101
+ this._collect(snapshotKinds.ON_SHUTDOWN, false)
94
102
  this._stop()
95
103
  }
96
104
 
@@ -101,18 +109,16 @@ class Profiler extends EventEmitter {
101
109
 
102
110
  for (const profiler of this._config.profilers) {
103
111
  profiler.stop()
104
- this._logger.debug(`Stopped ${profiler.type} profiler`)
112
+ this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
105
113
  }
106
114
 
107
115
  clearTimeout(this._timer)
108
116
  this._timer = undefined
109
-
110
- return this
111
117
  }
112
118
 
113
- _capture (timeout) {
119
+ _capture (timeout, start) {
114
120
  if (!this._enabled) return
115
- this._lastStart = new Date()
121
+ this._lastStart = start
116
122
  if (!this._timer || timeout !== this._timeoutInterval) {
117
123
  this._timer = setTimeout(() => this._collect(snapshotKinds.PERIODIC), timeout)
118
124
  this._timer.unref()
@@ -121,18 +127,21 @@ class Profiler extends EventEmitter {
121
127
  }
122
128
  }
123
129
 
124
- async _collect (snapshotKind) {
130
+ async _collect (snapshotKind, restart = true) {
125
131
  if (!this._enabled) return
126
132
 
127
- const start = this._lastStart
128
- const end = new Date()
133
+ const startDate = this._lastStart
134
+ const endDate = new Date()
129
135
  const profiles = []
130
136
  const encodedProfiles = {}
131
137
 
132
138
  try {
133
139
  // collect profiles synchronously so that profilers can be safely stopped asynchronously
134
140
  for (const profiler of this._config.profilers) {
135
- const profile = profiler.profile()
141
+ const profile = profiler.profile(restart, startDate, endDate)
142
+ if (!restart) {
143
+ this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
144
+ }
136
145
  if (!profile) continue
137
146
  profiles.push({ profiler, profile })
138
147
  }
@@ -148,8 +157,10 @@ class Profiler extends EventEmitter {
148
157
  })
149
158
  }
150
159
 
151
- this._capture(this._timeoutInterval)
152
- await this._submit(encodedProfiles, start, end, snapshotKind)
160
+ if (restart) {
161
+ this._capture(this._timeoutInterval, endDate)
162
+ }
163
+ await this._submit(encodedProfiles, startDate, endDate, snapshotKind)
153
164
  this._logger.debug('Submitted profiles')
154
165
  } catch (err) {
155
166
  this._logger.error(err)
@@ -189,13 +200,13 @@ class ServerlessProfiler extends Profiler {
189
200
  this._flushAfterIntervals = this._config.flushInterval / 1000
190
201
  }
191
202
 
192
- async _collect (snapshotKind) {
193
- if (this._profiledIntervals >= this._flushAfterIntervals) {
203
+ async _collect (snapshotKind, restart = true) {
204
+ if (this._profiledIntervals >= this._flushAfterIntervals || !restart) {
194
205
  this._profiledIntervals = 0
195
- await super._collect(snapshotKind)
206
+ await super._collect(snapshotKind, restart)
196
207
  } else {
197
208
  this._profiledIntervals += 1
198
- this._capture(this._timeoutInterval)
209
+ this._capture(this._timeoutInterval, new Date())
199
210
  // Don't submit profile until 65 (flushAfterIntervals) intervals have elapsed
200
211
  }
201
212
  }
@@ -0,0 +1,270 @@
1
+ const { performance, constants, PerformanceObserver } = require('perf_hooks')
2
+ const { END_TIMESTAMP_LABEL } = require('./shared')
3
+ const semver = require('semver')
4
+ const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
5
+ const pprof = require('@datadog/pprof/')
6
+
7
+ // Format of perf_hooks events changed with Node 16, we need to be mindful of it.
8
+ const node16 = semver.gte(process.version, '16.0.0')
9
+
10
+ // perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
11
+ const MS_TO_NS = 1000000
12
+
13
+ // While this is an "events profiler", meaning it emits a pprof file based on events observed as
14
+ // perf_hooks events, the emitted pprof file uses the type "timeline".
15
+ const pprofValueType = 'timeline'
16
+ const pprofValueUnit = 'nanoseconds'
17
+
18
+ function labelFromStr (stringTable, key, valStr) {
19
+ return new Label({ key, str: stringTable.dedup(valStr) })
20
+ }
21
+
22
+ function labelFromStrStr (stringTable, keyStr, valStr) {
23
+ return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
24
+ }
25
+
26
+ class GCDecorator {
27
+ constructor (stringTable) {
28
+ this.stringTable = stringTable
29
+ this.reasonLabelKey = stringTable.dedup('gc reason')
30
+ this.kindLabels = []
31
+ this.reasonLabels = []
32
+ this.flagObj = {}
33
+
34
+ const kindLabelKey = stringTable.dedup('gc type')
35
+
36
+ // Create labels for all GC performance flags and kinds of GC
37
+ for (const [key, value] of Object.entries(constants)) {
38
+ if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
39
+ this.flagObj[key.substring(26).toLowerCase()] = value
40
+ } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
41
+ // It's a constant for a kind of GC
42
+ const kind = key.substring(20).toLowerCase()
43
+ this.kindLabels[value] = labelFromStr(stringTable, kindLabelKey, kind)
44
+ }
45
+ }
46
+ }
47
+
48
+ decorateSample (sampleInput, item) {
49
+ const { kind, flags } = node16 ? item.detail : item
50
+ sampleInput.label.push(this.kindLabels[kind])
51
+ const reasonLabel = this.getReasonLabel(flags)
52
+ if (reasonLabel) {
53
+ sampleInput.label.push(reasonLabel)
54
+ }
55
+ }
56
+
57
+ getReasonLabel (flags) {
58
+ if (flags === 0) {
59
+ return null
60
+ }
61
+ let reasonLabel = this.reasonLabels[flags]
62
+ if (!reasonLabel) {
63
+ const reasons = []
64
+ for (const [key, value] of Object.entries(this.flagObj)) {
65
+ if (value & flags) {
66
+ reasons.push(key)
67
+ }
68
+ }
69
+ const reasonStr = reasons.join(',')
70
+ reasonLabel = labelFromStr(this.stringTable, this.reasonLabelKey, reasonStr)
71
+ this.reasonLabels[flags] = reasonLabel
72
+ }
73
+ return reasonLabel
74
+ }
75
+ }
76
+
77
+ class DNSDecorator {
78
+ constructor (stringTable) {
79
+ this.stringTable = stringTable
80
+ this.operationNameLabelKey = stringTable.dedup('operation')
81
+ this.hostLabelKey = stringTable.dedup('host')
82
+ this.addressLabelKey = stringTable.dedup('address')
83
+ this.portLabelKey = stringTable.dedup('port')
84
+ }
85
+
86
+ decorateSample (sampleInput, item) {
87
+ const labels = sampleInput.label
88
+ const stringTable = this.stringTable
89
+ function addLabel (labelNameKey, labelValue) {
90
+ labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
91
+ }
92
+ const op = item.name
93
+ addLabel(this.operationNameLabelKey, item.name)
94
+ const detail = item.detail
95
+ switch (op) {
96
+ case 'lookup':
97
+ addLabel(this.hostLabelKey, detail.hostname)
98
+ break
99
+ case 'lookupService':
100
+ addLabel(this.addressLabelKey, detail.host)
101
+ labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
102
+ break
103
+ case 'getHostByAddr':
104
+ addLabel(this.addressLabelKey, detail.host)
105
+ break
106
+ default:
107
+ if (op.startsWith('query')) {
108
+ addLabel(this.hostLabelKey, detail.host)
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ class NetDecorator {
115
+ constructor (stringTable) {
116
+ this.stringTable = stringTable
117
+ this.operationNameLabelKey = stringTable.dedup('operation')
118
+ this.hostLabelKey = stringTable.dedup('host')
119
+ this.portLabelKey = stringTable.dedup('port')
120
+ }
121
+
122
+ decorateSample (sampleInput, item) {
123
+ const labels = sampleInput.label
124
+ const stringTable = this.stringTable
125
+ function addLabel (labelNameKey, labelValue) {
126
+ labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
127
+ }
128
+ const op = item.name
129
+ addLabel(this.operationNameLabelKey, op)
130
+ if (op === 'connect') {
131
+ const detail = item.detail
132
+ addLabel(this.hostLabelKey, detail.host)
133
+ labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
134
+ }
135
+ }
136
+ }
137
+
138
+ // Keys correspond to PerformanceEntry.entryType, values are constructor
139
+ // functions for type-specific decorators.
140
+ const decoratorTypes = {
141
+ gc: GCDecorator
142
+ }
143
+ // Needs at least node 16 for DNS and Net
144
+ if (node16) {
145
+ decoratorTypes.dns = DNSDecorator
146
+ decoratorTypes.net = NetDecorator
147
+ }
148
+
149
+ /**
150
+ * This class generates pprof files with timeline events sourced from Node.js
151
+ * performance measurement APIs.
152
+ */
153
+ class EventsProfiler {
154
+ constructor (options = {}) {
155
+ this.type = 'events'
156
+ this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
157
+ this._observer = undefined
158
+ this.entries = []
159
+ }
160
+
161
+ start () {
162
+ // if already started, do nothing
163
+ if (this._observer) return
164
+
165
+ function add (items) {
166
+ this.entries.push(...items.getEntries())
167
+ }
168
+ this._observer = new PerformanceObserver(add.bind(this))
169
+ this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
170
+ }
171
+
172
+ stop () {
173
+ if (this._observer) {
174
+ this._observer.disconnect()
175
+ this._observer = undefined
176
+ }
177
+ }
178
+
179
+ profile (restart, startDate, endDate) {
180
+ if (this.entries.length === 0) {
181
+ // No events in the period; don't produce a profile
182
+ return null
183
+ }
184
+
185
+ const stringTable = new StringTable()
186
+ const locations = []
187
+ const functions = []
188
+
189
+ // A synthetic single-frame location to serve as the location for timeline
190
+ // samples. We need these as the profiling backend (mimicking official pprof
191
+ // tool's behavior) ignores these.
192
+ const locationId = (() => {
193
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
194
+ functions.push(fn)
195
+ const line = new Line({ functionId: fn.id })
196
+ const location = new Location({ id: locations.length + 1, line: [line] })
197
+ locations.push(location)
198
+ return [location.id]
199
+ })()
200
+
201
+ const decorators = {}
202
+ for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
203
+ const decorator = new DecoratorCtor(stringTable)
204
+ decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
205
+ decorators[eventType] = decorator
206
+ }
207
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
208
+
209
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
210
+ const lateEntries = []
211
+ const perfEndDate = endDate.getTime() - performance.timeOrigin
212
+ const samples = this.entries.map((item) => {
213
+ const decorator = decorators[item.entryType]
214
+ if (!decorator) {
215
+ // Shouldn't happen but it's better to not rely on observer only getting
216
+ // requested event types.
217
+ return null
218
+ }
219
+ const { startTime, duration } = item
220
+ if (startTime >= perfEndDate) {
221
+ // An event past the current recording end date; save it for the next
222
+ // profile. Not supposed to happen as long as there's no async activity
223
+ // between capture of the endDate value in profiler.js _collect() and
224
+ // here, but better be safe than sorry.
225
+ lateEntries.push(item)
226
+ return null
227
+ }
228
+ const endTime = startTime + duration
229
+ const sampleInput = {
230
+ value: [Math.round(duration * MS_TO_NS)],
231
+ locationId,
232
+ label: [
233
+ decorator.eventTypeLabel,
234
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
235
+ ]
236
+ }
237
+ decorator.decorateSample(sampleInput, item)
238
+ return new Sample(sampleInput)
239
+ }).filter(v => v)
240
+
241
+ this.entries = lateEntries
242
+
243
+ const timeValueType = new ValueType({
244
+ type: stringTable.dedup(pprofValueType),
245
+ unit: stringTable.dedup(pprofValueUnit)
246
+ })
247
+
248
+ if (!restart) {
249
+ this.stop()
250
+ }
251
+
252
+ return new Profile({
253
+ sampleType: [timeValueType],
254
+ timeNanos: endDate.getTime() * MS_TO_NS,
255
+ periodType: timeValueType,
256
+ period: 1,
257
+ durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
258
+ sample: samples,
259
+ location: locations,
260
+ function: functions,
261
+ stringTable: stringTable
262
+ })
263
+ }
264
+
265
+ encode (profile) {
266
+ return pprof.encode(profile)
267
+ }
268
+ }
269
+
270
+ module.exports = EventsProfiler
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread, threadId } = require('worker_threads')
4
+
5
+ const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
6
+ const THREAD_NAME_LABEL = 'thread name'
7
+ const OS_THREAD_ID_LABEL = 'os thread id'
8
+ const THREAD_ID_LABEL = 'thread id'
9
+ const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
10
+ const eventLoopThreadName = `${threadNamePrefix} Event Loop`
11
+
12
+ function getThreadLabels () {
13
+ const pprof = require('@datadog/pprof')
14
+ const nativeThreadId = pprof.getNativeThreadId()
15
+ return {
16
+ [THREAD_NAME_LABEL]: eventLoopThreadName,
17
+ [THREAD_ID_LABEL]: `${threadId}`,
18
+ [OS_THREAD_ID_LABEL]: `${nativeThreadId}`
19
+ }
20
+ }
21
+
22
+ function cacheThreadLabels () {
23
+ let labels
24
+ return () => {
25
+ if (!labels) {
26
+ labels = getThreadLabels()
27
+ }
28
+ return labels
29
+ }
30
+ }
31
+
32
+ function getNonJSThreadsLabels () {
33
+ return { [THREAD_NAME_LABEL]: 'Non-JS threads', [THREAD_ID_LABEL]: 'NA', [OS_THREAD_ID_LABEL]: 'NA' }
34
+ }
35
+
36
+ module.exports = {
37
+ END_TIMESTAMP_LABEL,
38
+ THREAD_NAME_LABEL,
39
+ THREAD_ID_LABEL,
40
+ OS_THREAD_ID_LABEL,
41
+ threadNamePrefix,
42
+ eventLoopThreadName,
43
+ getNonJSThreadsLabels,
44
+ getThreadLabels: cacheThreadLabels()
45
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { oomExportStrategies } = require('../constants')
4
+ const { getThreadLabels } = require('./shared')
4
5
 
5
6
  function strategiesToCallbackMode (strategies, callbackMode) {
6
7
  return strategies.includes(oomExportStrategies.ASYNC_CALLBACK) ? callbackMode.Async : 0
@@ -13,9 +14,12 @@ class NativeSpaceProfiler {
13
14
  this._stackDepth = options.stackDepth || 64
14
15
  this._pprof = undefined
15
16
  this._oomMonitoring = options.oomMonitoring || {}
17
+ this._started = false
16
18
  }
17
19
 
18
20
  start ({ mapper, nearOOMCallback } = {}) {
21
+ if (this._started) return
22
+
19
23
  this._mapper = mapper
20
24
  this._pprof = require('@datadog/pprof')
21
25
  this._pprof.heap.start(this._samplingInterval, this._stackDepth)
@@ -30,10 +34,16 @@ class NativeSpaceProfiler {
30
34
  strategiesToCallbackMode(strategies, this._pprof.heap.CallbackMode)
31
35
  )
32
36
  }
37
+
38
+ this._started = true
33
39
  }
34
40
 
35
- profile () {
36
- return this._pprof.heap.profile(undefined, this._mapper)
41
+ profile (restart) {
42
+ const profile = this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
43
+ if (!restart) {
44
+ this.stop()
45
+ }
46
+ return profile
37
47
  }
38
48
 
39
49
  encode (profile) {
@@ -41,7 +51,13 @@ class NativeSpaceProfiler {
41
51
  }
42
52
 
43
53
  stop () {
54
+ if (!this._started) return
44
55
  this._pprof.heap.stop()
56
+ this._started = false
57
+ }
58
+
59
+ isStarted () {
60
+ return this._started
45
61
  }
46
62
  }
47
63