dd-trace 4.18.0 → 4.20.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 (57) 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/package.json +6 -6
  8. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  9. package/packages/datadog-esbuild/index.js +1 -20
  10. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  12. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  13. package/packages/datadog-instrumentations/src/restify.js +14 -1
  14. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
  15. package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
  16. package/packages/dd-trace/src/appsec/channels.js +1 -1
  17. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  18. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  19. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  20. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  21. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  22. package/packages/dd-trace/src/appsec/index.js +1 -1
  23. package/packages/dd-trace/src/appsec/recommended.json +272 -48
  24. package/packages/dd-trace/src/appsec/reporter.js +31 -34
  25. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  26. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +17 -7
  27. package/packages/dd-trace/src/config.js +12 -5
  28. package/packages/dd-trace/src/datastreams/processor.js +60 -15
  29. package/packages/dd-trace/src/format.js +6 -1
  30. package/packages/dd-trace/src/id.js +12 -0
  31. package/packages/dd-trace/src/iitm.js +1 -1
  32. package/packages/dd-trace/src/log/channels.js +1 -1
  33. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  34. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  35. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  36. package/packages/dd-trace/src/opentracing/span.js +4 -0
  37. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  38. package/packages/dd-trace/src/plugin_manager.js +1 -1
  39. package/packages/dd-trace/src/plugins/database.js +1 -1
  40. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  41. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  42. package/packages/dd-trace/src/plugins/util/git.js +2 -1
  43. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  44. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  45. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
  46. package/packages/dd-trace/src/profiling/config.js +18 -2
  47. package/packages/dd-trace/src/profiling/profilers/events.js +166 -0
  48. package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
  49. package/packages/dd-trace/src/profiling/profilers/wall.js +116 -58
  50. package/packages/dd-trace/src/ritm.js +1 -1
  51. package/packages/dd-trace/src/span_processor.js +4 -0
  52. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  53. package/packages/dd-trace/src/telemetry/index.js +1 -1
  54. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  55. package/packages/dd-trace/src/tracer.js +4 -2
  56. package/packages/diagnostics_channel/index.js +0 -3
  57. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -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,7 +128,14 @@ 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 })
131
+ : getProfilers({
132
+ DD_PROFILING_HEAP_ENABLED,
133
+ DD_PROFILING_WALLTIME_ENABLED,
134
+ DD_PROFILING_PROFILERS
135
+ })
136
+
137
+ this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
138
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
130
139
 
131
140
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
132
141
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
@@ -139,7 +148,9 @@ class Config {
139
148
 
140
149
  module.exports = { Config }
141
150
 
142
- function getProfilers ({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS }) {
151
+ function getProfilers ({
152
+ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS
153
+ }) {
143
154
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
144
155
  // Use a Set to avoid duplicates
145
156
  const profilers = new Set(coalesce(DD_PROFILING_PROFILERS, 'wall,space').split(','))
@@ -240,6 +251,11 @@ function ensureProfilers (profilers, options) {
240
251
  }
241
252
  }
242
253
 
254
+ // Events profiler is a profiler for timeline events
255
+ if (options.timelineEnabled) {
256
+ profilers.push(new EventsProfiler(options))
257
+ }
258
+
243
259
  // Filter out any invalid profilers
244
260
  return profilers.filter(v => v)
245
261
  }
@@ -0,0 +1,166 @@
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({ entryTypes: ['gc'] })
40
+ }
41
+
42
+ stop () {
43
+ if (this._observer) {
44
+ this._observer.disconnect()
45
+ }
46
+ }
47
+
48
+ profile () {
49
+ if (this.entries.length === 0) {
50
+ // No events in the period; don't produce a profile
51
+ return null
52
+ }
53
+
54
+ const stringTable = new StringTable()
55
+ const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
56
+ const kindLabelKey = stringTable.dedup('gc type')
57
+ const reasonLabelKey = stringTable.dedup('gc reason')
58
+ const kindLabels = []
59
+ const reasonLabels = []
60
+ const locations = []
61
+ const functions = []
62
+ const locationsPerKind = []
63
+ const flagObj = {}
64
+
65
+ function labelFromStr (key, valStr) {
66
+ return new Label({ key, str: stringTable.dedup(valStr) })
67
+ }
68
+
69
+ function labelFromStrStr (keyStr, valStr) {
70
+ return labelFromStr(stringTable.dedup(keyStr), valStr)
71
+ }
72
+
73
+ // Create labels for all GC performance flags and kinds of GC
74
+ for (const [key, value] of Object.entries(constants)) {
75
+ if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
76
+ flagObj[key.substring(26).toLowerCase()] = value
77
+ } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
78
+ // It's a constant for a kind of GC
79
+ const kind = key.substring(20).toLowerCase()
80
+ kindLabels[value] = labelFromStr(kindLabelKey, kind)
81
+ // Construct a single-frame "location" too
82
+ const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
83
+ functions.push(fn)
84
+ const line = new Line({ functionId: fn.id })
85
+ const location = new Location({ id: locations.length + 1, line: [line] })
86
+ locations.push(location)
87
+ locationsPerKind[value] = [location.id]
88
+ }
89
+ }
90
+
91
+ const gcEventLabel = labelFromStrStr('event', 'gc')
92
+ const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
93
+
94
+ function getReasonLabel (flags) {
95
+ if (flags === 0) {
96
+ return null
97
+ }
98
+ let reasonLabel = reasonLabels[flags]
99
+ if (!reasonLabel) {
100
+ const reasons = []
101
+ for (const [key, value] of Object.entries(flagObj)) {
102
+ if (value & flags) {
103
+ reasons.push(key)
104
+ }
105
+ }
106
+ const reasonStr = reasons.join(',')
107
+ reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
108
+ reasonLabels[flags] = reasonLabel
109
+ }
110
+ return reasonLabel
111
+ }
112
+
113
+ let durationFrom = Number.POSITIVE_INFINITY
114
+ let durationTo = 0
115
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
116
+
117
+ const samples = this.entries.map((item) => {
118
+ const { startTime, duration } = item
119
+ const { kind, flags } = node16 ? item.detail : item
120
+ const endTime = startTime + duration
121
+ if (durationFrom > startTime) durationFrom = startTime
122
+ if (durationTo < endTime) durationTo = endTime
123
+ const labels = [
124
+ gcEventLabel,
125
+ threadLabel,
126
+ new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
127
+ kindLabels[kind]
128
+ ]
129
+ const reasonLabel = getReasonLabel(flags)
130
+ if (reasonLabel) {
131
+ labels.push(reasonLabel)
132
+ }
133
+ const sample = new Sample({
134
+ value: [Math.round(duration * MS_TO_NS)],
135
+ label: labels,
136
+ locationId: locationsPerKind[kind]
137
+ })
138
+ return sample
139
+ })
140
+
141
+ this.entries = []
142
+
143
+ const timeValueType = new ValueType({
144
+ type: stringTable.dedup(pprofValueType),
145
+ unit: stringTable.dedup(pprofValueUnit)
146
+ })
147
+
148
+ return new Profile({
149
+ sampleType: [timeValueType],
150
+ timeNanos: dateOffset + BigInt(Math.round(durationFrom * MS_TO_NS)),
151
+ periodType: timeValueType,
152
+ period: this._flushIntervalNanos,
153
+ durationNanos: Math.max(0, Math.round((durationTo - durationFrom) * MS_TO_NS)),
154
+ sample: samples,
155
+ location: locations,
156
+ function: functions,
157
+ stringTable: stringTable
158
+ })
159
+ }
160
+
161
+ encode (profile) {
162
+ return pprof.encode(profile)
163
+ }
164
+ }
165
+
166
+ 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
+ }
@@ -2,21 +2,20 @@
2
2
 
3
3
  const { storage } = require('../../../../datadog-core')
4
4
 
5
- const dc = require('../../../../diagnostics_channel')
5
+ const dc = require('dc-polyfill')
6
6
  const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
7
7
  const { WEB } = require('../../../../../ext/types')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const telemetryMetrics = require('../../telemetry/metrics')
10
+ const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
10
11
 
11
12
  const beforeCh = dc.channel('dd-trace:storage:before')
12
13
  const enterCh = dc.channel('dd-trace:storage:enter')
14
+ const spanFinishCh = dc.channel('dd-trace:span:finish')
13
15
  const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
16
+ const threadName = `${threadNamePrefix} Event Loop`
14
17
 
15
- const threadName = (function () {
16
- const { isMainThread, threadId } = require('node:worker_threads')
17
- const name = isMainThread ? 'Main' : `Worker #${threadId}`
18
- return `${name} Event Loop`
19
- })()
18
+ const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
20
19
 
21
20
  let kSampleCount
22
21
 
@@ -29,30 +28,6 @@ function getStartedSpans (context) {
29
28
  return context._trace.started
30
29
  }
31
30
 
32
- function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
33
- const labels = { 'thread name': threadName }
34
- if (spanId) {
35
- labels['span id'] = spanId
36
- }
37
- if (rootSpanId) {
38
- labels['local root span id'] = rootSpanId
39
- }
40
- if (webTags && Object.keys(webTags).length !== 0) {
41
- labels['trace endpoint'] = endpointNameFromTags(webTags)
42
- } else if (endpoint) {
43
- // fallback to endpoint computed when sample was taken
44
- labels['trace endpoint'] = endpoint
45
- }
46
- // Incoming timestamps are in microseconds, we emit nanos.
47
- labels['end_timestamp_ns'] = timestamp * 1000n
48
-
49
- return labels
50
- }
51
-
52
- function getSpanContextTags (span) {
53
- return span.context()._tags
54
- }
55
-
56
31
  function isWebServerSpan (tags) {
57
32
  return tags[SPAN_TYPE] === WEB
58
33
  }
@@ -64,6 +39,38 @@ function endpointNameFromTags (tags) {
64
39
  ].filter(v => v).join(' ')
65
40
  }
66
41
 
42
+ function getWebTags (startedSpans, i, span) {
43
+ // Are web tags for this span already memoized?
44
+ const memoizedWebTags = span[MemoizedWebTags]
45
+ if (memoizedWebTags !== undefined) {
46
+ return memoizedWebTags
47
+ }
48
+ // No, we'll have to memoize a new value
49
+ function memoize (tags) {
50
+ span[MemoizedWebTags] = tags
51
+ return tags
52
+ }
53
+ // Is this span itself a web span?
54
+ const context = span.context()
55
+ const tags = context._tags
56
+ if (isWebServerSpan(tags)) {
57
+ return memoize(tags)
58
+ }
59
+ // It isn't. Get parent's web tags (memoize them too recursively.)
60
+ // There might be several webspans, for example with next.js, http plugin creates the first span
61
+ // and then next.js plugin creates a child span, and this child span has the correct endpoint
62
+ // information. That's why we always use the tags of the closest ancestor web span.
63
+ const parentId = context._parentId
64
+ while (--i >= 0) {
65
+ const ispan = startedSpans[i]
66
+ if (ispan.context()._spanId === parentId) {
67
+ return memoize(getWebTags(startedSpans, i, ispan))
68
+ }
69
+ }
70
+ // Local root span with no web span
71
+ return memoize(null)
72
+ }
73
+
67
74
  class NativeWallProfiler {
68
75
  constructor (options = {}) {
69
76
  this.type = 'wall'
@@ -71,13 +78,30 @@ class NativeWallProfiler {
71
78
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
72
79
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
73
80
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
74
- this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
81
+ this._timelineEnabled = !!options.timelineEnabled
82
+ // We need to capture span data into the sample context for either code hotspots
83
+ // or endpoint collection.
84
+ this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
85
+ // We need to run the pprof wall profiler with sample contexts if we're either
86
+ // capturing span data or timeline is enabled (so we need sample timestamps, and for now
87
+ // timestamps require the sample contexts feature in the pprof wall profiler.)
88
+ this._withContexts = this._captureSpanData || this._timelineEnabled
75
89
  this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
76
90
  this._mapper = undefined
77
91
  this._pprof = undefined
78
92
 
79
- // Bind to this so the same value can be used to unsubscribe later
80
- this._enter = this._enter.bind(this)
93
+ // Bind these to this so they can be used as callbacks
94
+ if (this._withContexts) {
95
+ if (this._captureSpanData) {
96
+ this._enter = this._enter.bind(this)
97
+ this._spanFinished = this._spanFinished.bind(this)
98
+ }
99
+ this._generateLabels = this._generateLabels.bind(this)
100
+ } else {
101
+ // Explicitly assigning, to express the intent that this is meant to be
102
+ // undefined when passed to pprof.time.stop() when not using sample contexts.
103
+ this._generateLabels = undefined
104
+ }
81
105
  this._logger = options.logger
82
106
  this._started = false
83
107
  }
@@ -115,15 +139,20 @@ class NativeWallProfiler {
115
139
  })
116
140
 
117
141
  if (this._withContexts) {
118
- this._profilerState = this._pprof.time.getState()
119
142
  this._currentContext = {}
120
143
  this._pprof.time.setContext(this._currentContext)
121
- this._lastSpan = undefined
122
- this._lastStartedSpans = undefined
123
- this._lastSampleCount = 0
124
144
 
125
- beforeCh.subscribe(this._enter)
126
- enterCh.subscribe(this._enter)
145
+ if (this._captureSpanData) {
146
+ this._profilerState = this._pprof.time.getState()
147
+ this._lastSpan = undefined
148
+ this._lastStartedSpans = undefined
149
+ this._lastWebTags = undefined
150
+ this._lastSampleCount = 0
151
+
152
+ beforeCh.subscribe(this._enter)
153
+ enterCh.subscribe(this._enter)
154
+ spanFinishCh.subscribe(this._spanFinished)
155
+ }
127
156
  }
128
157
 
129
158
  this._started = true
@@ -144,11 +173,17 @@ class NativeWallProfiler {
144
173
 
145
174
  const span = getActiveSpan()
146
175
  if (span) {
176
+ const context = span.context()
147
177
  this._lastSpan = span
148
- this._lastStartedSpans = getStartedSpans(span.context())
178
+ const startedSpans = getStartedSpans(context)
179
+ this._lastStartedSpans = startedSpans
180
+ if (this._endpointCollectionEnabled) {
181
+ this._lastWebTags = getWebTags(startedSpans, startedSpans.length, span)
182
+ }
149
183
  } else {
150
184
  this._lastStartedSpans = undefined
151
185
  this._lastSpan = undefined
186
+ this._lastWebTags = undefined
152
187
  }
153
188
  }
154
189
 
@@ -163,21 +198,17 @@ class NativeWallProfiler {
163
198
  context.rootSpanId = rootSpan.context().toSpanId()
164
199
  }
165
200
  }
166
- if (this._endpointCollectionEnabled) {
167
- const startedSpans = this._lastStartedSpans
168
- // Find the first webspan starting from the end:
169
- // There might be several webspans, for example with next.js, http plugin creates a first span
170
- // and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
171
- for (let i = startedSpans.length - 1; i >= 0; i--) {
172
- const tags = getSpanContextTags(startedSpans[i])
173
- if (isWebServerSpan(tags)) {
174
- context.webTags = tags
175
- // endpoint may not be determined yet, but keep it as fallback
176
- // if tags are not available anymore during serialization
177
- context.endpoint = endpointNameFromTags(tags)
178
- break
179
- }
180
- }
201
+ if (this._lastWebTags) {
202
+ context.webTags = this._lastWebTags
203
+ // endpoint may not be determined yet, but keep it as fallback
204
+ // if tags are not available anymore during serialization
205
+ context.endpoint = endpointNameFromTags(this._lastWebTags)
206
+ }
207
+ }
208
+
209
+ _spanFinished (span) {
210
+ if (span[MemoizedWebTags]) {
211
+ span[MemoizedWebTags] = undefined
181
212
  }
182
213
  }
183
214
 
@@ -193,12 +224,12 @@ class NativeWallProfiler {
193
224
 
194
225
  _stop (restart) {
195
226
  if (!this._started) return
196
- if (this._withContexts) {
227
+ if (this._captureSpanData) {
197
228
  // update last sample context if needed
198
229
  this._enter()
199
230
  this._lastSampleCount = 0
200
231
  }
201
- const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
232
+ const profile = this._pprof.time.stop(restart, this._generateLabels)
202
233
  if (restart) {
203
234
  const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
204
235
  if (v8BugDetected !== 0) {
@@ -208,6 +239,29 @@ class NativeWallProfiler {
208
239
  return profile
209
240
  }
210
241
 
242
+ _generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
243
+ const labels = this._timelineEnabled ? {
244
+ [THREAD_NAME]: threadName,
245
+ // Incoming timestamps are in microseconds, we emit nanos.
246
+ [END_TIMESTAMP]: timestamp * 1000n
247
+ } : {}
248
+
249
+ if (spanId) {
250
+ labels['span id'] = spanId
251
+ }
252
+ if (rootSpanId) {
253
+ labels['local root span id'] = rootSpanId
254
+ }
255
+ if (webTags && Object.keys(webTags).length !== 0) {
256
+ labels['trace endpoint'] = endpointNameFromTags(webTags)
257
+ } else if (endpoint) {
258
+ // fallback to endpoint computed when sample was taken
259
+ labels['trace endpoint'] = endpoint
260
+ }
261
+
262
+ return labels
263
+ }
264
+
211
265
  profile () {
212
266
  return this._stop(true)
213
267
  }
@@ -220,10 +274,14 @@ class NativeWallProfiler {
220
274
  if (!this._started) return
221
275
 
222
276
  const profile = this._stop(false)
223
- if (this._withContexts) {
277
+ if (this._captureSpanData) {
224
278
  beforeCh.unsubscribe(this._enter)
225
279
  enterCh.unsubscribe(this._enter)
280
+ spanFinishCh.unsubscribe(this._spanFinished)
226
281
  this._profilerState = undefined
282
+ this._lastSpan = undefined
283
+ this._lastStartedSpans = undefined
284
+ this._lastWebTags = undefined
227
285
  }
228
286
 
229
287
  this._started = false
@@ -3,7 +3,7 @@
3
3
  const path = require('path')
4
4
  const Module = require('module')
5
5
  const parse = require('module-details-from-path')
6
- const dc = require('../../diagnostics_channel')
6
+ const dc = require('dc-polyfill')
7
7
 
8
8
  const origRequire = Module.prototype.require
9
9
 
@@ -138,6 +138,10 @@ class SpanProcessor {
138
138
  }
139
139
  }
140
140
 
141
+ for (const span of trace.finished) {
142
+ span.context()._tags = {}
143
+ }
144
+
141
145
  trace.started = active
142
146
  trace.finished = []
143
147
  }
@@ -4,7 +4,7 @@ const path = require('path')
4
4
  const parse = require('module-details-from-path')
5
5
  const requirePackageJson = require('../require-package-json')
6
6
  const { sendData } = require('./send-data')
7
- const dc = require('../../../diagnostics_channel')
7
+ const dc = require('dc-polyfill')
8
8
  const { fileURLToPath } = require('url')
9
9
 
10
10
  const savedDependenciesToSend = new Set()
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const tracerVersion = require('../../../../package.json').version
4
- const dc = require('../../../diagnostics_channel')
4
+ const dc = require('dc-polyfill')
5
5
  const os = require('os')
6
6
  const dependencies = require('./dependencies')
7
7
  const { sendData } = require('./send-data')
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
  const logCollector = require('./log-collector')
5
5
  const { sendData } = require('../send-data')
6
6
 
@@ -31,8 +31,10 @@ class DatadogTracer extends Tracer {
31
31
 
32
32
  // todo[piochelepiotr] These two methods are not related to the tracer, but to data streams monitoring.
33
33
  // They should be moved outside of the tracer in the future.
34
- setCheckpoint (edgeTags) {
35
- const ctx = this._dataStreamsProcessor.setCheckpoint(edgeTags, DataStreamsContext.getDataStreamsContext())
34
+ setCheckpoint (edgeTags, span, payloadSize = 0) {
35
+ const ctx = this._dataStreamsProcessor.setCheckpoint(
36
+ edgeTags, span, DataStreamsContext.getDataStreamsContext(), payloadSize
37
+ )
36
38
  DataStreamsContext.setDataStreamsContext(ctx)
37
39
  return ctx
38
40
  }
@@ -1,3 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = require('./src')