dd-trace 3.38.1 → 3.40.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 (82) hide show
  1. package/LICENSE-3rdparty.csv +2 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +9 -1
  8. package/package.json +8 -8
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/cucumber.js +5 -0
  12. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  14. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +39 -10
  16. package/packages/datadog-instrumentations/src/knex.js +24 -17
  17. package/packages/datadog-instrumentations/src/mocha.js +16 -1
  18. package/packages/datadog-instrumentations/src/next.js +58 -23
  19. package/packages/datadog-instrumentations/src/playwright.js +11 -6
  20. package/packages/datadog-instrumentations/src/restify.js +14 -1
  21. package/packages/datadog-plugin-http/src/client.js +2 -0
  22. package/packages/datadog-plugin-jest/src/index.js +11 -3
  23. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
  24. package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
  25. package/packages/datadog-plugin-mocha/src/index.js +7 -1
  26. package/packages/datadog-plugin-next/src/index.js +4 -3
  27. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  28. package/packages/dd-trace/src/appsec/channels.js +1 -1
  29. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  33. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  34. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
  35. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  36. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +7 -2
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  41. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  45. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  46. package/packages/dd-trace/src/appsec/recommended.json +272 -48
  47. package/packages/dd-trace/src/appsec/reporter.js +31 -34
  48. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +16 -4
  49. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  50. package/packages/dd-trace/src/config.js +35 -17
  51. package/packages/dd-trace/src/datastreams/processor.js +60 -15
  52. package/packages/dd-trace/src/format.js +6 -1
  53. package/packages/dd-trace/src/git_properties.js +16 -15
  54. package/packages/dd-trace/src/iitm.js +1 -1
  55. package/packages/dd-trace/src/log/channels.js +1 -1
  56. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  57. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  58. package/packages/dd-trace/src/opentracing/span.js +4 -0
  59. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  60. package/packages/dd-trace/src/plugin_manager.js +1 -1
  61. package/packages/dd-trace/src/plugins/database.js +1 -1
  62. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  63. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  64. package/packages/dd-trace/src/plugins/util/git.js +2 -1
  65. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  66. package/packages/dd-trace/src/plugins/util/test.js +29 -1
  67. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  68. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
  69. package/packages/dd-trace/src/profiling/config.js +23 -20
  70. package/packages/dd-trace/src/profiling/profilers/events.js +161 -0
  71. package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +84 -47
  73. package/packages/dd-trace/src/ritm.js +1 -1
  74. package/packages/dd-trace/src/span_processor.js +4 -0
  75. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  76. package/packages/dd-trace/src/telemetry/index.js +5 -1
  77. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  78. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  79. package/packages/dd-trace/src/tracer.js +4 -2
  80. package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
  81. package/packages/diagnostics_channel/index.js +0 -3
  82. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -1,5 +1,3 @@
1
- const URL = require('url').URL
2
-
3
1
  const {
4
2
  GIT_BRANCH,
5
3
  GIT_COMMIT_SHA,
@@ -24,6 +22,7 @@ const {
24
22
  CI_NODE_LABELS,
25
23
  CI_NODE_NAME
26
24
  } = require('./tags')
25
+ const { filterSensitiveInfoFromRepository } = require('./url')
27
26
 
28
27
  // Receives a string with the form 'John Doe <john.doe@gmail.com>'
29
28
  // and returns { name: 'John Doe', email: 'john.doe@gmail.com' }
@@ -67,20 +66,6 @@ function normalizeRef (ref) {
67
66
  return ref.replace(/origin\/|refs\/heads\/|tags\//gm, '')
68
67
  }
69
68
 
70
- function filterSensitiveInfoFromRepository (repositoryUrl) {
71
- if (repositoryUrl.startsWith('git@')) {
72
- return repositoryUrl
73
- }
74
-
75
- try {
76
- const { protocol, hostname, pathname } = new URL(repositoryUrl)
77
-
78
- return `${protocol}//${hostname}${pathname}`
79
- } catch (e) {
80
- return ''
81
- }
82
- }
83
-
84
69
  function resolveTilde (filePath) {
85
70
  if (!filePath || typeof filePath !== 'string') {
86
71
  return ''
@@ -271,20 +256,22 @@ module.exports = {
271
256
  const ref = GITHUB_HEAD_REF || GITHUB_REF || ''
272
257
  const refKey = ref.includes('tags/') ? GIT_TAG : GIT_BRANCH
273
258
 
259
+ // Both pipeline URL and job URL include GITHUB_SERVER_URL, which can include user credentials,
260
+ // so we pass them through `filterSensitiveInfoFromRepository`.
274
261
  tags = {
275
262
  [CI_PIPELINE_ID]: GITHUB_RUN_ID,
276
263
  [CI_PIPELINE_NAME]: GITHUB_WORKFLOW,
277
264
  [CI_PIPELINE_NUMBER]: GITHUB_RUN_NUMBER,
278
- [CI_PIPELINE_URL]: pipelineURL,
265
+ [CI_PIPELINE_URL]: filterSensitiveInfoFromRepository(pipelineURL),
279
266
  [CI_PROVIDER_NAME]: 'github',
280
267
  [GIT_COMMIT_SHA]: GITHUB_SHA,
281
268
  [GIT_REPOSITORY_URL]: repositoryURL,
282
- [CI_JOB_URL]: jobUrl,
269
+ [CI_JOB_URL]: filterSensitiveInfoFromRepository(jobUrl),
283
270
  [CI_JOB_NAME]: GITHUB_JOB,
284
271
  [CI_WORKSPACE_PATH]: GITHUB_WORKSPACE,
285
272
  [refKey]: ref,
286
273
  [CI_ENV_VARS]: JSON.stringify({
287
- GITHUB_SERVER_URL,
274
+ GITHUB_SERVER_URL: filterSensitiveInfoFromRepository(GITHUB_SERVER_URL),
288
275
  GITHUB_REPOSITORY,
289
276
  GITHUB_RUN_ID,
290
277
  GITHUB_RUN_ATTEMPT
@@ -19,6 +19,7 @@ const {
19
19
  GIT_COMMIT_AUTHOR_NAME,
20
20
  CI_WORKSPACE_PATH
21
21
  } = require('./tags')
22
+ const { filterSensitiveInfoFromRepository } = require('./url')
22
23
 
23
24
  const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
24
25
 
@@ -214,7 +215,7 @@ function getGitMetadata (ciMetadata) {
214
215
 
215
216
  return {
216
217
  [GIT_REPOSITORY_URL]:
217
- repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url']),
218
+ filterSensitiveInfoFromRepository(repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url'])),
218
219
  [GIT_COMMIT_MESSAGE]:
219
220
  commitMessage || sanitizedExec('git', ['show', '-s', '--format=%s']),
220
221
  [GIT_COMMIT_AUTHOR_DATE]: authorDate,
@@ -48,8 +48,8 @@ function extractIp (config, req) {
48
48
 
49
49
  let firstPrivateIp
50
50
  if (headers) {
51
- for (let i = 0; i < ipHeaderList.length; i++) {
52
- const firstIp = findFirstIp(headers[ipHeaderList[i]])
51
+ for (const ipHeaderName of ipHeaderList) {
52
+ const firstIp = findFirstIp(headers[ipHeaderName])
53
53
 
54
54
  if (firstIp.public) {
55
55
  return firstIp.public
@@ -59,7 +59,7 @@ function extractIp (config, req) {
59
59
  }
60
60
  }
61
61
 
62
- return firstPrivateIp || (req.socket && req.socket.remoteAddress)
62
+ return firstPrivateIp || req.socket?.remoteAddress
63
63
  }
64
64
 
65
65
  function findFirstIp (str) {
@@ -68,8 +68,8 @@ function findFirstIp (str) {
68
68
 
69
69
  const splitted = str.split(',')
70
70
 
71
- for (let i = 0; i < splitted.length; i++) {
72
- const chunk = splitted[i].trim()
71
+ for (const part of splitted) {
72
+ const chunk = part.trim()
73
73
 
74
74
  // TODO: strip port and interface data ?
75
75
 
@@ -90,5 +90,6 @@ function findFirstIp (str) {
90
90
  }
91
91
 
92
92
  module.exports = {
93
- extractIp
93
+ extractIp,
94
+ ipHeaderList
94
95
  }
@@ -118,7 +118,8 @@ module.exports = {
118
118
  fromCoverageMapToCoverage,
119
119
  getTestLineStart,
120
120
  getCallSites,
121
- removeInvalidMetadata
121
+ removeInvalidMetadata,
122
+ parseAnnotations
122
123
  }
123
124
 
124
125
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -492,3 +493,30 @@ function getCallSites () {
492
493
 
493
494
  return v8StackTrace
494
495
  }
496
+
497
+ /**
498
+ * Gets an object of test tags from an Playwright annotations array.
499
+ * @param {Object[]} annotations - Annotations from a Playwright test.
500
+ * @param {string} annotations[].type - Type of annotation. A string of the shape DD_TAGS[$tag_name].
501
+ * @param {string} annotations[].description - Value of the tag.
502
+ */
503
+ function parseAnnotations (annotations) {
504
+ return annotations.reduce((tags, annotation) => {
505
+ if (!annotation?.type) {
506
+ return tags
507
+ }
508
+ const { type, description } = annotation
509
+ if (type.startsWith('DD_TAGS')) {
510
+ const regex = /\[(.*?)\]/
511
+ const match = regex.exec(type)
512
+ let tagValue = ''
513
+ if (match) {
514
+ tagValue = match[1]
515
+ }
516
+ if (tagValue) {
517
+ tags[tagValue] = description
518
+ }
519
+ }
520
+ return tags
521
+ }, {})
522
+ }
@@ -0,0 +1,26 @@
1
+ const { URL } = require('url')
2
+
3
+ function filterSensitiveInfoFromRepository (repositoryUrl) {
4
+ if (!repositoryUrl) {
5
+ return ''
6
+ }
7
+ if (repositoryUrl.startsWith('git@')) {
8
+ return repositoryUrl
9
+ }
10
+
11
+ // Remove the username from ssh URLs
12
+ if (repositoryUrl.startsWith('ssh://')) {
13
+ const sshRegex = /^(ssh:\/\/)[^@/]*@/
14
+ return repositoryUrl.replace(sshRegex, '$1')
15
+ }
16
+
17
+ try {
18
+ const { protocol, host, pathname } = new URL(repositoryUrl)
19
+
20
+ return `${protocol}//${host}${pathname === '/' ? '' : pathname}`
21
+ } catch (e) {
22
+ return ''
23
+ }
24
+ }
25
+
26
+ module.exports = { filterSensitiveInfoFromRepository }
@@ -13,7 +13,7 @@ const {
13
13
  } = require('./tags')
14
14
 
15
15
  const { normalizeRef } = require('./ci')
16
- const { URL } = require('url')
16
+ const { filterSensitiveInfoFromRepository } = require('./url')
17
17
 
18
18
  function removeEmptyValues (tags) {
19
19
  return Object.keys(tags).reduce((filteredTags, tag) => {
@@ -27,19 +27,6 @@ function removeEmptyValues (tags) {
27
27
  }, {})
28
28
  }
29
29
 
30
- function filterSensitiveInfoFromRepository (repositoryUrl) {
31
- try {
32
- if (repositoryUrl.startsWith('git@')) {
33
- return repositoryUrl
34
- }
35
- const { protocol, hostname, pathname } = new URL(repositoryUrl)
36
-
37
- return `${protocol}//${hostname}${pathname}`
38
- } catch (e) {
39
- return repositoryUrl
40
- }
41
- }
42
-
43
30
  // The regex is extracted from
44
31
  // https://github.com/jonschlinkert/is-git-url/blob/396965ffabf2f46656c8af4c47bef1d69f09292e/index.js#L9C15-L9C87
45
32
  function validateGitRepositoryUrl (repoUrl) {
@@ -9,6 +9,7 @@ const { FileExporter } = require('./exporters/file')
9
9
  const { ConsoleLogger } = require('./loggers/console')
10
10
  const WallProfiler = require('./profilers/wall')
11
11
  const SpaceProfiler = require('./profilers/space')
12
+ const EventsProfiler = require('./profilers/events')
12
13
  const { oomExportStrategies, snapshotKinds } = require('./constants')
13
14
  const { tagger } = require('./tagger')
14
15
  const { isFalse, isTrue } = require('../util')
@@ -37,6 +38,7 @@ class Config {
37
38
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
38
39
  DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
39
40
  DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
41
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
40
42
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
41
43
  DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
42
44
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
@@ -126,26 +128,17 @@ class Config {
126
128
 
127
129
  const profilers = options.profilers
128
130
  ? options.profilers
129
- : getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
130
-
131
- function getCodeHotspotsOptionsOr (defvalue) {
132
- return coalesce(options.codeHotspotsEnabled,
133
- DD_PROFILING_CODEHOTSPOTS_ENABLED,
134
- DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, defvalue)
135
- }
136
- this.codeHotspotsEnabled = isTrue(getCodeHotspotsOptionsOr(false))
131
+ : getProfilers({
132
+ DD_PROFILING_HEAP_ENABLED,
133
+ DD_PROFILING_WALLTIME_ENABLED,
134
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
135
+ DD_PROFILING_PROFILERS
136
+ })
137
+
138
+ this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
139
+ DD_PROFILING_CODEHOTSPOTS_ENABLED,
140
+ DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
137
141
  logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
138
- if (this.endpointCollectionEnabled && !this.codeHotspotsEnabled) {
139
- if (getCodeHotspotsOptionsOr(undefined) !== undefined) {
140
- this.logger.warn(
141
- 'Endpoint collection is enabled, but Code Hotspots are disabled. ' +
142
- 'Enable Code Hotspots too for endpoint collection to work.')
143
- this.endpointCollectionEnabled = false
144
- } else {
145
- this.logger.info('Code Hotspots are implicitly enabled by endpoint collection.')
146
- this.codeHotspotsEnabled = true
147
- }
148
- }
149
142
 
150
143
  this.profilers = ensureProfilers(profilers, this)
151
144
  }
@@ -153,7 +146,10 @@ class Config {
153
146
 
154
147
  module.exports = { Config }
155
148
 
156
- function getProfilers ({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS }) {
149
+ function getProfilers ({
150
+ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED,
151
+ DD_PROFILING_PROFILERS, DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED
152
+ }) {
157
153
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
158
154
  // Use a Set to avoid duplicates
159
155
  const profilers = new Set(coalesce(DD_PROFILING_PROFILERS, 'wall,space').split(','))
@@ -176,6 +172,11 @@ function getProfilers ({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLE
176
172
  }
177
173
  }
178
174
 
175
+ // Events profiler is a profiler for timeline events that goes with the wall
176
+ // profiler
177
+ if (profilers.has('wall') && DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED) {
178
+ profilers.add('events')
179
+ }
179
180
  return [...profilers]
180
181
  }
181
182
 
@@ -237,6 +238,8 @@ function getProfiler (name, options) {
237
238
  return new WallProfiler(options)
238
239
  case 'space':
239
240
  return new SpaceProfiler(options)
241
+ case 'events':
242
+ return new EventsProfiler(options)
240
243
  default:
241
244
  options.logger.error(`Unknown profiler "${name}"`)
242
245
  }
@@ -0,0 +1,161 @@
1
+ const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
2
+ const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = 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
+ const threadName = `${threadNamePrefix} GC`
18
+
19
+ /**
20
+ * This class generates pprof files with timeline events sourced from Node.js
21
+ * performance measurement APIs.
22
+ */
23
+ class EventsProfiler {
24
+ constructor (options = {}) {
25
+ this.type = 'events'
26
+ this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
27
+ this._observer = undefined
28
+ this.entries = []
29
+ }
30
+
31
+ start () {
32
+ function add (items) {
33
+ this.entries.push(...items.getEntries())
34
+ }
35
+ if (!this._observer) {
36
+ this._observer = new PerformanceObserver(add.bind(this))
37
+ }
38
+ // Currently only support GC
39
+ this._observer.observe({ type: 'gc' })
40
+ }
41
+
42
+ stop () {
43
+ if (this._observer) {
44
+ this._observer.disconnect()
45
+ }
46
+ }
47
+
48
+ profile () {
49
+ const stringTable = new StringTable()
50
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
51
+ const kindLabelKey = stringTable.dedup('gc type')
52
+ const reasonLabelKey = stringTable.dedup('gc reason')
53
+ const kindLabels = []
54
+ const reasonLabels = []
55
+ const locations = []
56
+ const functions = []
57
+ const locationsPerKind = []
58
+ const flagObj = {}
59
+
60
+ function labelFromStr (key, valStr) {
61
+ return new Label({ key, str: stringTable.dedup(valStr) })
62
+ }
63
+
64
+ function labelFromStrStr (keyStr, valStr) {
65
+ return labelFromStr(stringTable.dedup(keyStr), valStr)
66
+ }
67
+
68
+ // Create labels for all GC performance flags and kinds of GC
69
+ for (const [key, value] of Object.entries(constants)) {
70
+ if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
71
+ flagObj[key.substring(26).toLowerCase()] = value
72
+ } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
73
+ // It's a constant for a kind of GC
74
+ const kind = key.substring(20).toLowerCase()
75
+ kindLabels[value] = labelFromStr(kindLabelKey, kind)
76
+ // Construct a single-frame "location" too
77
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
78
+ functions.push(fn)
79
+ const line = new Line({ functionId: fn.id })
80
+ const location = new Location({ id: locations.length + 1, line: [line] })
81
+ locations.push(location)
82
+ locationsPerKind[value] = [location.id]
83
+ }
84
+ }
85
+
86
+ const gcEventLabel = labelFromStrStr('event', 'gc')
87
+ const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
88
+
89
+ function getReasonLabel (flags) {
90
+ if (flags === 0) {
91
+ return null
92
+ }
93
+ let reasonLabel = reasonLabels[flags]
94
+ if (!reasonLabel) {
95
+ const reasons = []
96
+ for (const [key, value] of Object.entries(flagObj)) {
97
+ if (value & flags) {
98
+ reasons.push(key)
99
+ }
100
+ }
101
+ const reasonStr = reasons.join(',')
102
+ reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
103
+ reasonLabels[flags] = reasonLabel
104
+ }
105
+ return reasonLabel
106
+ }
107
+
108
+ let durationFrom = Number.POSITIVE_INFINITY
109
+ let durationTo = 0
110
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
111
+
112
+ const samples = this.entries.map((item) => {
113
+ const { startTime, duration } = item
114
+ const { kind, flags } = node16 ? item.detail : item
115
+ const endTime = startTime + duration
116
+ if (durationFrom > startTime) durationFrom = startTime
117
+ if (durationTo < endTime) durationTo = endTime
118
+ const labels = [
119
+ gcEventLabel,
120
+ threadLabel,
121
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
122
+ kindLabels[kind]
123
+ ]
124
+ const reasonLabel = getReasonLabel(flags)
125
+ if (reasonLabel) {
126
+ labels.push(reasonLabel)
127
+ }
128
+ const sample = new Sample({
129
+ value: [Math.round(duration * MS_TO_NS)],
130
+ label: labels,
131
+ locationId: locationsPerKind[kind]
132
+ })
133
+ return sample
134
+ })
135
+
136
+ this.entries = []
137
+
138
+ const timeValueType = new ValueType({
139
+ type: stringTable.dedup(pprofValueType),
140
+ unit: stringTable.dedup(pprofValueUnit)
141
+ })
142
+
143
+ return new Profile({
144
+ sampleType: [timeValueType],
145
+ timeNanos: dateOffset + BigInt(Math.round(durationFrom * MS_TO_NS)),
146
+ periodType: timeValueType,
147
+ period: this._flushIntervalNanos,
148
+ durationNanos: Math.max(0, Math.round((durationTo - durationFrom) * MS_TO_NS)),
149
+ sample: samples,
150
+ location: locations,
151
+ function: functions,
152
+ stringTable: stringTable
153
+ })
154
+ }
155
+
156
+ encode (profile) {
157
+ return pprof.encode(profile)
158
+ }
159
+ }
160
+
161
+ module.exports = EventsProfiler
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread, threadId } = require('node:worker_threads')
4
+
5
+ module.exports = {
6
+ END_TIMESTAMP: 'end_timestamp_ns',
7
+ THREAD_NAME: 'thread name',
8
+ threadNamePrefix: isMainThread ? 'Main' : `Worker #${threadId}`
9
+ }