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.
- package/LICENSE-3rdparty.csv +2 -2
- package/README.md +3 -3
- package/ext/kinds.d.ts +1 -0
- package/ext/kinds.js +2 -1
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +6 -1
- package/package.json +6 -6
- package/packages/datadog-core/src/storage/async_resource.js +1 -1
- package/packages/datadog-esbuild/index.js +1 -20
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/restify.js +14 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
- package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
- package/packages/dd-trace/src/appsec/channels.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +1 -1
- package/packages/dd-trace/src/appsec/recommended.json +272 -48
- package/packages/dd-trace/src/appsec/reporter.js +31 -34
- package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +17 -7
- package/packages/dd-trace/src/config.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +60 -15
- package/packages/dd-trace/src/format.js +6 -1
- package/packages/dd-trace/src/id.js +12 -0
- package/packages/dd-trace/src/iitm.js +1 -1
- package/packages/dd-trace/src/log/channels.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +95 -2
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
- package/packages/dd-trace/src/opentracing/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span_context.js +5 -2
- package/packages/dd-trace/src/plugin_manager.js +1 -1
- package/packages/dd-trace/src/plugins/database.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +6 -19
- package/packages/dd-trace/src/plugins/util/git.js +2 -1
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
- package/packages/dd-trace/src/plugins/util/url.js +26 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
- package/packages/dd-trace/src/profiling/config.js +18 -2
- package/packages/dd-trace/src/profiling/profilers/events.js +166 -0
- package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +116 -58
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/span_processor.js +4 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/tracer.js +4 -2
- package/packages/diagnostics_channel/index.js +0 -3
- 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 {
|
|
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({
|
|
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 ({
|
|
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
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../../datadog-core')
|
|
4
4
|
|
|
5
|
-
const dc = require('
|
|
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
|
|
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.
|
|
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
|
|
80
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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.
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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('
|
|
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('
|
|
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')
|
|
@@ -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(
|
|
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
|
}
|