dd-trace 4.19.0 → 4.21.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 (62) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +24 -0
  3. package/package.json +6 -5
  4. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/http/client.js +22 -0
  7. package/packages/datadog-instrumentations/src/jest.js +11 -5
  8. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  9. package/packages/datadog-instrumentations/src/next.js +3 -1
  10. package/packages/datadog-instrumentations/src/restify.js +1 -1
  11. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  12. package/packages/datadog-plugin-http/src/client.js +19 -2
  13. package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
  14. package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
  15. package/packages/datadog-plugin-next/src/index.js +7 -7
  16. package/packages/dd-trace/src/appsec/addresses.js +2 -1
  17. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  19. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  21. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  22. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  23. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  24. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  25. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/index.js +14 -2
  29. package/packages/dd-trace/src/appsec/recommended.json +1395 -2
  30. package/packages/dd-trace/src/appsec/reporter.js +19 -0
  31. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  32. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -3
  33. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  34. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  35. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  36. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
  37. package/packages/dd-trace/src/config.js +37 -3
  38. package/packages/dd-trace/src/datastreams/processor.js +107 -12
  39. package/packages/dd-trace/src/id.js +12 -0
  40. package/packages/dd-trace/src/noop/proxy.js +4 -0
  41. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  42. package/packages/dd-trace/src/opentracing/span.js +2 -0
  43. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  44. package/packages/dd-trace/src/plugins/index.js +1 -0
  45. package/packages/dd-trace/src/plugins/util/git.js +2 -2
  46. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  47. package/packages/dd-trace/src/profiler.js +5 -3
  48. package/packages/dd-trace/src/profiling/config.js +17 -10
  49. package/packages/dd-trace/src/profiling/profiler.js +10 -4
  50. package/packages/dd-trace/src/profiling/profilers/events.js +171 -73
  51. package/packages/dd-trace/src/profiling/profilers/wall.js +93 -67
  52. package/packages/dd-trace/src/proxy.js +21 -1
  53. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  54. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  55. package/packages/dd-trace/src/spanleak.js +98 -0
  56. package/packages/dd-trace/src/startup-log.js +7 -1
  57. package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
  58. package/packages/dd-trace/src/telemetry/index.js +135 -43
  59. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  60. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  61. package/packages/dd-trace/src/tracer.js +4 -0
  62. package/scripts/install_plugin_modules.js +11 -3
@@ -20,6 +20,10 @@ class Tracer {
20
20
  return this
21
21
  }
22
22
 
23
+ profilerStarted () {
24
+ return Promise.resolve(false)
25
+ }
26
+
23
27
  trace (name, options, fn) {
24
28
  if (!fn) {
25
29
  fn = options
@@ -236,11 +236,20 @@ class TextMapPropagator {
236
236
  _extractDatadogContext (carrier) {
237
237
  const spanContext = this._extractGenericContext(carrier, traceKey, spanKey, 10)
238
238
 
239
- if (spanContext) {
240
- this._extractOrigin(carrier, spanContext)
241
- this._extractBaggageItems(carrier, spanContext)
242
- this._extractSamplingPriority(carrier, spanContext)
243
- this._extractTags(carrier, spanContext)
239
+ if (!spanContext) return spanContext
240
+
241
+ this._extractOrigin(carrier, spanContext)
242
+ this._extractBaggageItems(carrier, spanContext)
243
+ this._extractSamplingPriority(carrier, spanContext)
244
+ this._extractTags(carrier, spanContext)
245
+
246
+ if (this._config.tracePropagationExtractFirst) return spanContext
247
+
248
+ const tc = this._extractTraceparentContext(carrier)
249
+
250
+ if (tc && spanContext._traceId.equals(tc._traceId)) {
251
+ spanContext._traceparent = tc._traceparent
252
+ spanContext._tracestate = tc._tracestate
244
253
  }
245
254
 
246
255
  return spanContext
@@ -13,6 +13,7 @@ const log = require('../log')
13
13
  const { storage } = require('../../../datadog-core')
14
14
  const telemetryMetrics = require('../telemetry/metrics')
15
15
  const { channel } = require('dc-polyfill')
16
+ const spanleak = require('../spanleak')
16
17
 
17
18
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
18
19
 
@@ -90,6 +91,7 @@ class DatadogSpan {
90
91
 
91
92
  unfinishedRegistry.register(this, operationName, this)
92
93
  }
94
+ spanleak.addSpan(this, operationName)
93
95
  }
94
96
 
95
97
  toString () {
@@ -124,7 +124,8 @@ module.exports = class CiPlugin extends Plugin {
124
124
  osArchitecture,
125
125
  runtimeName,
126
126
  runtimeVersion,
127
- branch
127
+ branch,
128
+ testLevel: 'suite'
128
129
  }
129
130
  }
130
131
 
@@ -17,6 +17,7 @@ module.exports = {
17
17
  get '@opensearch-project/opensearch' () { return require('../../../datadog-plugin-opensearch/src') },
18
18
  get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
19
19
  get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
20
+ get 'aerospike' () { return require('../../../datadog-plugin-aerospike/src') },
20
21
  get 'amqp10' () { return require('../../../datadog-plugin-amqp10/src') },
21
22
  get 'amqplib' () { return require('../../../datadog-plugin-amqplib/src') },
22
23
  get 'aws-sdk' () { return require('../../../datadog-plugin-aws-sdk/src') },
@@ -111,7 +111,7 @@ function getLatestCommits () {
111
111
  }
112
112
  }
113
113
 
114
- function getCommitsToUpload (commitsToExclude, commitsToInclude) {
114
+ function getCommitsRevList (commitsToExclude, commitsToInclude) {
115
115
  const commitsToExcludeString = commitsToExclude.map(commit => `^${commit}`)
116
116
 
117
117
  try {
@@ -236,7 +236,7 @@ module.exports = {
236
236
  getLatestCommits,
237
237
  getRepositoryUrl,
238
238
  generatePackFilesForCommits,
239
- getCommitsToUpload,
239
+ getCommitsRevList,
240
240
  GIT_REV_LIST_MAX_BUFFER,
241
241
  isShallowRepository,
242
242
  unshallowRepository
@@ -397,8 +397,9 @@ function addIntelligentTestRunnerSpanTags (
397
397
  testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
398
398
  }
399
399
 
400
- // If suites have been skipped we don't want to report the total coverage, as it will be wrong
401
- if (testCodeCoverageLinesTotal !== undefined && !isSuitesSkipped) {
400
+ // This will not be reported unless the user has manually added code coverage.
401
+ // This is always the case for Mocha and Cucumber, but not for Jest.
402
+ if (testCodeCoverageLinesTotal !== undefined) {
402
403
  testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
403
404
  testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
404
405
  }
@@ -8,7 +8,7 @@ process.once('beforeExit', () => { profiler.stop() })
8
8
 
9
9
  module.exports = {
10
10
  start: config => {
11
- const { service, version, env, url, hostname, port, tags } = config
11
+ const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
12
12
  const { enabled, sourceMap, exporters } = config.profiling
13
13
  const logger = {
14
14
  debug: (message) => log.debug(message),
@@ -17,7 +17,7 @@ module.exports = {
17
17
  error: (message) => log.error(message)
18
18
  }
19
19
 
20
- profiler.start({
20
+ return profiler.start({
21
21
  enabled,
22
22
  service,
23
23
  version,
@@ -28,7 +28,9 @@ module.exports = {
28
28
  url,
29
29
  hostname,
30
30
  port,
31
- tags
31
+ tags,
32
+ repositoryUrl,
33
+ commitSHA
32
34
  })
33
35
  },
34
36
 
@@ -11,6 +11,7 @@ const WallProfiler = require('./profilers/wall')
11
11
  const SpaceProfiler = require('./profilers/space')
12
12
  const EventsProfiler = require('./profilers/events')
13
13
  const { oomExportStrategies, snapshotKinds } = require('./constants')
14
+ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
14
15
  const { tagger } = require('./tagger')
15
16
  const { isFalse, isTrue } = require('../util')
16
17
 
@@ -72,6 +73,13 @@ class Config {
72
73
  tagger.parse(options.tags),
73
74
  tagger.parse({ env, host, service, version, functionname })
74
75
  )
76
+
77
+ // Add source code integration tags if available
78
+ if (options.repositoryUrl && options.commitSHA) {
79
+ this.tags[GIT_REPOSITORY_URL] = options.repositoryUrl
80
+ this.tags[GIT_COMMIT_SHA] = options.commitSHA
81
+ }
82
+
75
83
  this.logger = ensureLogger(options.logger)
76
84
  const logger = this.logger
77
85
  function logExperimentalVarDeprecation (shortVarName) {
@@ -131,10 +139,12 @@ class Config {
131
139
  : getProfilers({
132
140
  DD_PROFILING_HEAP_ENABLED,
133
141
  DD_PROFILING_WALLTIME_ENABLED,
134
- DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
135
142
  DD_PROFILING_PROFILERS
136
143
  })
137
144
 
145
+ this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
146
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
147
+
138
148
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
139
149
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
140
150
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
@@ -147,8 +157,7 @@ class Config {
147
157
  module.exports = { Config }
148
158
 
149
159
  function getProfilers ({
150
- DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED,
151
- DD_PROFILING_PROFILERS, DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED
160
+ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS
152
161
  }) {
153
162
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
154
163
  // Use a Set to avoid duplicates
@@ -172,11 +181,6 @@ function getProfilers ({
172
181
  }
173
182
  }
174
183
 
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
- }
180
184
  return [...profilers]
181
185
  }
182
186
 
@@ -238,8 +242,6 @@ function getProfiler (name, options) {
238
242
  return new WallProfiler(options)
239
243
  case 'space':
240
244
  return new SpaceProfiler(options)
241
- case 'events':
242
- return new EventsProfiler(options)
243
245
  default:
244
246
  options.logger.error(`Unknown profiler "${name}"`)
245
247
  }
@@ -257,6 +259,11 @@ function ensureProfilers (profilers, options) {
257
259
  }
258
260
  }
259
261
 
262
+ // Events profiler is a profiler for timeline events
263
+ if (options.timelineEnabled) {
264
+ profilers.push(new EventsProfiler(options))
265
+ }
266
+
260
267
  // Filter out any invalid profilers
261
268
  return profilers.filter(v => v)
262
269
  }
@@ -23,15 +23,19 @@ class Profiler extends EventEmitter {
23
23
  }
24
24
 
25
25
  start (options) {
26
- this._start(options).catch((err) => { if (options.logger) options.logger.error(err) })
27
- return this
26
+ return this._start(options).catch((err) => {
27
+ if (options.logger) {
28
+ options.logger.error(err)
29
+ }
30
+ return false
31
+ })
28
32
  }
29
33
 
30
34
  async _start (options) {
31
- if (this._enabled) return
35
+ if (this._enabled) return true
32
36
 
33
37
  const config = this._config = new Config(options)
34
- if (!config.enabled) return
38
+ if (!config.enabled) return false
35
39
 
36
40
  this._logger = config.logger
37
41
  this._enabled = true
@@ -67,9 +71,11 @@ class Profiler extends EventEmitter {
67
71
  }
68
72
 
69
73
  this._capture(this._timeoutInterval)
74
+ return true
70
75
  } catch (e) {
71
76
  this._logger.error(e)
72
77
  this._stop()
78
+ return false
73
79
  }
74
80
  }
75
81
 
@@ -1,5 +1,5 @@
1
1
  const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
2
- const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
2
+ const { END_TIMESTAMP } = require('./shared')
3
3
  const semver = require('semver')
4
4
  const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
5
5
  const pprof = require('@datadog/pprof/')
@@ -14,7 +14,137 @@ const MS_TO_NS = 1000000
14
14
  // perf_hooks events, the emitted pprof file uses the type "timeline".
15
15
  const pprofValueType = 'timeline'
16
16
  const pprofValueUnit = 'nanoseconds'
17
- const threadName = `${threadNamePrefix} GC`
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
+ }
18
148
 
19
149
  /**
20
150
  * This class generates pprof files with timeline events sourced from Node.js
@@ -35,8 +165,7 @@ class EventsProfiler {
35
165
  if (!this._observer) {
36
166
  this._observer = new PerformanceObserver(add.bind(this))
37
167
  }
38
- // Currently only support GC
39
- this._observer.observe({ type: 'gc' })
168
+ this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
40
169
  }
41
170
 
42
171
  stop () {
@@ -46,92 +175,61 @@ class EventsProfiler {
46
175
  }
47
176
 
48
177
  profile () {
178
+ if (this.entries.length === 0) {
179
+ // No events in the period; don't produce a profile
180
+ return null
181
+ }
182
+
49
183
  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
184
  const locations = []
56
185
  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
186
 
86
- const gcEventLabel = labelFromStrStr('event', 'gc')
87
- const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
187
+ // A synthetic single-frame location to serve as the location for timeline
188
+ // samples. We need these as the profiling backend (mimicking official pprof
189
+ // tool's behavior) ignores these.
190
+ const locationId = (() => {
191
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
192
+ functions.push(fn)
193
+ const line = new Line({ functionId: fn.id })
194
+ const location = new Location({ id: locations.length + 1, line: [line] })
195
+ locations.push(location)
196
+ return [location.id]
197
+ })()
88
198
 
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
199
+ const decorators = {}
200
+ for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
201
+ const decorator = new DecoratorCtor(stringTable)
202
+ decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
203
+ decorators[eventType] = decorator
106
204
  }
205
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
107
206
 
108
207
  let durationFrom = Number.POSITIVE_INFINITY
109
208
  let durationTo = 0
110
209
  const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
111
210
 
112
211
  const samples = this.entries.map((item) => {
212
+ const decorator = decorators[item.entryType]
213
+ if (!decorator) {
214
+ // Shouldn't happen but it's better to not rely on observer only getting
215
+ // requested event types.
216
+ return null
217
+ }
113
218
  const { startTime, duration } = item
114
- const { kind, flags } = node16 ? item.detail : item
115
219
  const endTime = startTime + duration
116
220
  if (durationFrom > startTime) durationFrom = startTime
117
221
  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({
222
+ const sampleInput = {
129
223
  value: [Math.round(duration * MS_TO_NS)],
130
- label: labels,
131
- locationId: locationsPerKind[kind]
132
- })
133
- return sample
134
- })
224
+ locationId,
225
+ label: [
226
+ decorator.eventTypeLabel,
227
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
228
+ ]
229
+ }
230
+ decorator.decorateSample(sampleInput, item)
231
+ return new Sample(sampleInput)
232
+ }).filter(v => v)
135
233
 
136
234
  this.entries = []
137
235