dd-trace 4.23.0 → 4.24.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/package.json +3 -3
- package/packages/datadog-instrumentations/src/jest.js +2 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +24 -4
- package/packages/datadog-plugin-jest/src/util.js +38 -16
- package/packages/dd-trace/src/config.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +17 -2
- package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
- package/packages/dd-trace/src/profiling/profiler.js +18 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +10 -4
- package/packages/dd-trace/src/profiling/profilers/shared.js +6 -0
- package/packages/dd-trace/src/profiling/profilers/space.js +17 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +34 -21
- package/packages/dd-trace/src/telemetry/send-data.js +35 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.24.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@datadog/native-iast-rewriter": "2.2.2",
|
|
73
73
|
"@datadog/native-iast-taint-tracking": "1.6.4",
|
|
74
74
|
"@datadog/native-metrics": "^2.0.0",
|
|
75
|
-
"@datadog/pprof": "
|
|
75
|
+
"@datadog/pprof": "5.0.0",
|
|
76
76
|
"@datadog/sketches-js": "^2.1.0",
|
|
77
77
|
"@opentelemetry/api": "^1.0.0",
|
|
78
78
|
"@opentelemetry/core": "^1.14.0",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"istanbul-lib-coverage": "3.2.0",
|
|
86
86
|
"jest-docblock": "^29.7.0",
|
|
87
87
|
"koalas": "^1.0.2",
|
|
88
|
-
"limiter": "
|
|
88
|
+
"limiter": "1.1.5",
|
|
89
89
|
"lodash.kebabcase": "^4.1.1",
|
|
90
90
|
"lodash.pick": "^4.4.0",
|
|
91
91
|
"lodash.sortby": "^4.7.0",
|
|
@@ -38,6 +38,20 @@ class JestPlugin extends CiPlugin {
|
|
|
38
38
|
return 'jest'
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// The lists are the same for every test suite, so we can cache them
|
|
42
|
+
getUnskippableSuites (unskippableSuitesList) {
|
|
43
|
+
if (!this.unskippableSuites) {
|
|
44
|
+
this.unskippableSuites = JSON.parse(unskippableSuitesList)
|
|
45
|
+
}
|
|
46
|
+
return this.unskippableSuites
|
|
47
|
+
}
|
|
48
|
+
getForcedToRunSuites (forcedToRunSuitesList) {
|
|
49
|
+
if (!this.forcedToRunSuites) {
|
|
50
|
+
this.forcedToRunSuites = JSON.parse(forcedToRunSuitesList)
|
|
51
|
+
}
|
|
52
|
+
return this.forcedToRunSuites
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
constructor (...args) {
|
|
42
56
|
super(...args)
|
|
43
57
|
|
|
@@ -128,11 +142,17 @@ class JestPlugin extends CiPlugin {
|
|
|
128
142
|
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
|
|
129
143
|
|
|
130
144
|
if (_ddUnskippable) {
|
|
131
|
-
this.
|
|
132
|
-
|
|
145
|
+
const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
|
|
146
|
+
if (unskippableSuites[testSuite]) {
|
|
147
|
+
this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
148
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
149
|
+
}
|
|
133
150
|
if (_ddForcedToRun) {
|
|
134
|
-
this.
|
|
135
|
-
|
|
151
|
+
const forcedToRunSuites = this.getForcedToRunSuites(_ddForcedToRun)
|
|
152
|
+
if (forcedToRunSuites[testSuite]) {
|
|
153
|
+
this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
|
|
154
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
155
|
+
}
|
|
136
156
|
}
|
|
137
157
|
}
|
|
138
158
|
|
|
@@ -77,30 +77,52 @@ function isMarkedAsUnskippable (test) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
|
|
80
|
-
|
|
80
|
+
const unskippableSuites = {}
|
|
81
|
+
const forcedToRunSuites = {}
|
|
82
|
+
|
|
83
|
+
const skippedSuites = []
|
|
84
|
+
const suitesToRun = []
|
|
85
|
+
|
|
86
|
+
for (const test of originalTests) {
|
|
81
87
|
const relativePath = getTestSuitePath(test.path, rootDir)
|
|
82
88
|
const shouldBeSkipped = skippableSuites.includes(relativePath)
|
|
83
|
-
|
|
84
89
|
if (isMarkedAsUnskippable(test)) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (shouldBeSkipped) {
|
|
90
|
-
test.context.config.testEnvironmentOptions['_ddForcedToRun'] = true
|
|
91
|
-
acc.hasForcedToRunSuites = true
|
|
92
|
-
}
|
|
90
|
+
suitesToRun.push(test)
|
|
91
|
+
unskippableSuites[relativePath] = true
|
|
92
|
+
if (shouldBeSkipped) {
|
|
93
|
+
forcedToRunSuites[relativePath] = true
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
continue
|
|
95
96
|
}
|
|
96
|
-
|
|
97
97
|
if (shouldBeSkipped) {
|
|
98
|
-
|
|
98
|
+
skippedSuites.push(relativePath)
|
|
99
99
|
} else {
|
|
100
|
-
|
|
100
|
+
suitesToRun.push(test)
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
|
|
105
|
+
const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
|
|
106
|
+
|
|
107
|
+
if (originalTests.length) {
|
|
108
|
+
// The config object is shared by all tests, so we can just take the first one
|
|
109
|
+
const [test] = originalTests
|
|
110
|
+
if (test?.context?.config?.testEnvironmentOptions) {
|
|
111
|
+
if (hasUnskippableSuites) {
|
|
112
|
+
test.context.config.testEnvironmentOptions._ddUnskippable = JSON.stringify(unskippableSuites)
|
|
113
|
+
}
|
|
114
|
+
if (hasForcedToRunSuites) {
|
|
115
|
+
test.context.config.testEnvironmentOptions._ddForcedToRun = JSON.stringify(forcedToRunSuites)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
skippedSuites,
|
|
122
|
+
suitesToRun,
|
|
123
|
+
hasUnskippableSuites,
|
|
124
|
+
hasForcedToRunSuites
|
|
125
|
+
}
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }
|
|
@@ -611,7 +611,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
611
611
|
this.startupLogs = isTrue(DD_TRACE_STARTUP_LOGS)
|
|
612
612
|
// Disabled for CI Visibility's agentless
|
|
613
613
|
this.telemetry = {
|
|
614
|
-
enabled:
|
|
614
|
+
enabled: isTrue(DD_INSTRUMENTATION_TELEMETRY_ENABLED),
|
|
615
615
|
heartbeatInterval: DD_TELEMETRY_HEARTBEAT_INTERVAL,
|
|
616
616
|
debug: isTrue(DD_TELEMETRY_DEBUG),
|
|
617
617
|
logCollection: isTrue(DD_TELEMETRY_LOG_COLLECTION_ENABLED),
|
|
@@ -35,6 +35,7 @@ class Config {
|
|
|
35
35
|
DD_PROFILING_HEAP_ENABLED,
|
|
36
36
|
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
|
|
37
37
|
DD_PROFILING_WALLTIME_ENABLED,
|
|
38
|
+
DD_PROFILING_EXPERIMENTAL_CPU_ENABLED,
|
|
38
39
|
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
|
|
39
40
|
DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
40
41
|
DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
|
|
@@ -91,14 +92,24 @@ class Config {
|
|
|
91
92
|
logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`)
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
// Profiler sampling contexts are not available on Windows, so features
|
|
96
|
+
// depending on those (code hotspots and endpoint collection) need to default
|
|
97
|
+
// to false on Windows.
|
|
98
|
+
const samplingContextsAvailable = process.platform !== 'win32'
|
|
99
|
+
function checkOptionAllowed (option, description) {
|
|
100
|
+
if (option && !samplingContextsAvailable) {
|
|
101
|
+
throw new Error(`${description} not supported on ${process.platform}.`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
94
104
|
this.flushInterval = flushInterval
|
|
95
105
|
this.uploadTimeout = uploadTimeout
|
|
96
106
|
this.sourceMap = sourceMap
|
|
97
107
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
98
108
|
this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
|
|
99
109
|
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
100
|
-
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED,
|
|
110
|
+
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable))
|
|
101
111
|
logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED')
|
|
112
|
+
checkOptionAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
|
|
102
113
|
|
|
103
114
|
this.pprofPrefix = pprofPrefix
|
|
104
115
|
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
@@ -147,8 +158,12 @@ class Config {
|
|
|
147
158
|
|
|
148
159
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
149
160
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
150
|
-
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
|
|
161
|
+
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
|
|
151
162
|
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
|
|
163
|
+
checkOptionAllowed(this.codeHotspotsEnabled, 'Code hotspots')
|
|
164
|
+
|
|
165
|
+
this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
|
|
166
|
+
DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, false))
|
|
152
167
|
|
|
153
168
|
this.profilers = ensureProfilers(profilers, this)
|
|
154
169
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const { promisify } = require('util')
|
|
5
|
+
const { threadId } = require('worker_threads')
|
|
5
6
|
const writeFile = promisify(fs.writeFile)
|
|
6
7
|
|
|
7
8
|
function formatDateTime (t) {
|
|
@@ -19,7 +20,7 @@ class FileExporter {
|
|
|
19
20
|
const types = Object.keys(profiles)
|
|
20
21
|
const dateStr = formatDateTime(end)
|
|
21
22
|
const tasks = types.map(type => {
|
|
22
|
-
return writeFile(`${this._pprofPrefix}${type}_${dateStr}.pprof`, profiles[type])
|
|
23
|
+
return writeFile(`${this._pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
return Promise.all(tasks)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { EventEmitter } = require('events')
|
|
4
4
|
const { Config } = require('./config')
|
|
5
5
|
const { snapshotKinds } = require('./constants')
|
|
6
|
+
const { threadNamePrefix } = require('./profilers/shared')
|
|
6
7
|
|
|
7
8
|
function maybeSourceMap (sourceMap, SourceMapper, debug) {
|
|
8
9
|
if (!sourceMap) return
|
|
@@ -68,7 +69,7 @@ class Profiler extends EventEmitter {
|
|
|
68
69
|
mapper,
|
|
69
70
|
nearOOMCallback: this._nearOOMExport.bind(this)
|
|
70
71
|
})
|
|
71
|
-
this._logger.debug(`Started ${profiler.type} profiler`)
|
|
72
|
+
this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
this._capture(this._timeoutInterval, start)
|
|
@@ -97,7 +98,7 @@ class Profiler extends EventEmitter {
|
|
|
97
98
|
|
|
98
99
|
// collect and export current profiles
|
|
99
100
|
// once collect returns, profilers can be safely stopped
|
|
100
|
-
this._collect(snapshotKinds.ON_SHUTDOWN)
|
|
101
|
+
this._collect(snapshotKinds.ON_SHUTDOWN, false)
|
|
101
102
|
this._stop()
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -108,13 +109,11 @@ class Profiler extends EventEmitter {
|
|
|
108
109
|
|
|
109
110
|
for (const profiler of this._config.profilers) {
|
|
110
111
|
profiler.stop()
|
|
111
|
-
this._logger.debug(`Stopped ${profiler.type} profiler`)
|
|
112
|
+
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
clearTimeout(this._timer)
|
|
115
116
|
this._timer = undefined
|
|
116
|
-
|
|
117
|
-
return this
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
_capture (timeout, start) {
|
|
@@ -128,18 +127,21 @@ class Profiler extends EventEmitter {
|
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
async _collect (snapshotKind) {
|
|
130
|
+
async _collect (snapshotKind, restart = true) {
|
|
132
131
|
if (!this._enabled) return
|
|
133
132
|
|
|
134
|
-
const
|
|
135
|
-
const
|
|
133
|
+
const startDate = this._lastStart
|
|
134
|
+
const endDate = new Date()
|
|
136
135
|
const profiles = []
|
|
137
136
|
const encodedProfiles = {}
|
|
138
137
|
|
|
139
138
|
try {
|
|
140
139
|
// collect profiles synchronously so that profilers can be safely stopped asynchronously
|
|
141
140
|
for (const profiler of this._config.profilers) {
|
|
142
|
-
const profile = profiler.profile(
|
|
141
|
+
const profile = profiler.profile(restart, startDate, endDate)
|
|
142
|
+
if (!restart) {
|
|
143
|
+
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
144
|
+
}
|
|
143
145
|
if (!profile) continue
|
|
144
146
|
profiles.push({ profiler, profile })
|
|
145
147
|
}
|
|
@@ -155,8 +157,10 @@ class Profiler extends EventEmitter {
|
|
|
155
157
|
})
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
if (restart) {
|
|
161
|
+
this._capture(this._timeoutInterval, endDate)
|
|
162
|
+
}
|
|
163
|
+
await this._submit(encodedProfiles, startDate, endDate, snapshotKind)
|
|
160
164
|
this._logger.debug('Submitted profiles')
|
|
161
165
|
} catch (err) {
|
|
162
166
|
this._logger.error(err)
|
|
@@ -196,10 +200,10 @@ class ServerlessProfiler extends Profiler {
|
|
|
196
200
|
this._flushAfterIntervals = this._config.flushInterval / 1000
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
async _collect (snapshotKind) {
|
|
200
|
-
if (this._profiledIntervals >= this._flushAfterIntervals) {
|
|
203
|
+
async _collect (snapshotKind, restart = true) {
|
|
204
|
+
if (this._profiledIntervals >= this._flushAfterIntervals || !restart) {
|
|
201
205
|
this._profiledIntervals = 0
|
|
202
|
-
await super._collect(snapshotKind)
|
|
206
|
+
await super._collect(snapshotKind, restart)
|
|
203
207
|
} else {
|
|
204
208
|
this._profiledIntervals += 1
|
|
205
209
|
this._capture(this._timeoutInterval, new Date())
|
|
@@ -159,22 +159,24 @@ class EventsProfiler {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
start () {
|
|
162
|
+
// if already started, do nothing
|
|
163
|
+
if (this._observer) return
|
|
164
|
+
|
|
162
165
|
function add (items) {
|
|
163
166
|
this.entries.push(...items.getEntries())
|
|
164
167
|
}
|
|
165
|
-
|
|
166
|
-
this._observer = new PerformanceObserver(add.bind(this))
|
|
167
|
-
}
|
|
168
|
+
this._observer = new PerformanceObserver(add.bind(this))
|
|
168
169
|
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
stop () {
|
|
172
173
|
if (this._observer) {
|
|
173
174
|
this._observer.disconnect()
|
|
175
|
+
this._observer = undefined
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
profile (startDate, endDate) {
|
|
179
|
+
profile (restart, startDate, endDate) {
|
|
178
180
|
if (this.entries.length === 0) {
|
|
179
181
|
// No events in the period; don't produce a profile
|
|
180
182
|
return null
|
|
@@ -243,6 +245,10 @@ class EventsProfiler {
|
|
|
243
245
|
unit: stringTable.dedup(pprofValueUnit)
|
|
244
246
|
})
|
|
245
247
|
|
|
248
|
+
if (!restart) {
|
|
249
|
+
this.stop()
|
|
250
|
+
}
|
|
251
|
+
|
|
246
252
|
return new Profile({
|
|
247
253
|
sampleType: [timeValueType],
|
|
248
254
|
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
@@ -29,11 +29,17 @@ function cacheThreadLabels () {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function getNonJSThreadsLabels () {
|
|
33
|
+
return { [THREAD_NAME_LABEL]: 'Non-JS threads', [THREAD_ID_LABEL]: 'NA', [OS_THREAD_ID_LABEL]: 'NA' }
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
module.exports = {
|
|
33
37
|
END_TIMESTAMP_LABEL,
|
|
34
38
|
THREAD_NAME_LABEL,
|
|
35
39
|
THREAD_ID_LABEL,
|
|
40
|
+
OS_THREAD_ID_LABEL,
|
|
36
41
|
threadNamePrefix,
|
|
37
42
|
eventLoopThreadName,
|
|
43
|
+
getNonJSThreadsLabels,
|
|
38
44
|
getThreadLabels: cacheThreadLabels()
|
|
39
45
|
}
|
|
@@ -14,9 +14,12 @@ class NativeSpaceProfiler {
|
|
|
14
14
|
this._stackDepth = options.stackDepth || 64
|
|
15
15
|
this._pprof = undefined
|
|
16
16
|
this._oomMonitoring = options.oomMonitoring || {}
|
|
17
|
+
this._started = false
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
start ({ mapper, nearOOMCallback } = {}) {
|
|
21
|
+
if (this._started) return
|
|
22
|
+
|
|
20
23
|
this._mapper = mapper
|
|
21
24
|
this._pprof = require('@datadog/pprof')
|
|
22
25
|
this._pprof.heap.start(this._samplingInterval, this._stackDepth)
|
|
@@ -31,10 +34,16 @@ class NativeSpaceProfiler {
|
|
|
31
34
|
strategiesToCallbackMode(strategies, this._pprof.heap.CallbackMode)
|
|
32
35
|
)
|
|
33
36
|
}
|
|
37
|
+
|
|
38
|
+
this._started = true
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
profile () {
|
|
37
|
-
|
|
41
|
+
profile (restart) {
|
|
42
|
+
const profile = this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
|
|
43
|
+
if (!restart) {
|
|
44
|
+
this.stop()
|
|
45
|
+
}
|
|
46
|
+
return profile
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
encode (profile) {
|
|
@@ -42,7 +51,13 @@ class NativeSpaceProfiler {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
stop () {
|
|
54
|
+
if (!this._started) return
|
|
45
55
|
this._pprof.heap.stop()
|
|
56
|
+
this._started = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isStarted () {
|
|
60
|
+
return this._started
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
63
|
|
|
@@ -7,7 +7,7 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
|
|
|
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_LABEL, getThreadLabels } = require('./shared')
|
|
10
|
+
const { END_TIMESTAMP_LABEL, getNonJSThreadsLabels, getThreadLabels } = require('./shared')
|
|
11
11
|
|
|
12
12
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
13
13
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
@@ -78,13 +78,15 @@ class NativeWallProfiler {
|
|
|
78
78
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
79
79
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
80
80
|
this._timelineEnabled = !!options.timelineEnabled
|
|
81
|
+
this._cpuProfilingEnabled = !!options.cpuProfilingEnabled
|
|
81
82
|
// We need to capture span data into the sample context for either code hotspots
|
|
82
83
|
// or endpoint collection.
|
|
83
84
|
this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
|
|
84
85
|
// We need to run the pprof wall profiler with sample contexts if we're either
|
|
85
86
|
// capturing span data or timeline is enabled (so we need sample timestamps, and for now
|
|
86
|
-
// timestamps require the sample contexts feature in the pprof wall profiler
|
|
87
|
-
|
|
87
|
+
// timestamps require the sample contexts feature in the pprof wall profiler), or
|
|
88
|
+
// cpu profiling is enabled.
|
|
89
|
+
this._withContexts = this._captureSpanData || this._timelineEnabled || this._cpuProfilingEnabled
|
|
88
90
|
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
89
91
|
this._mapper = undefined
|
|
90
92
|
this._pprof = undefined
|
|
@@ -131,7 +133,8 @@ class NativeWallProfiler {
|
|
|
131
133
|
sourceMapper: this._mapper,
|
|
132
134
|
withContexts: this._withContexts,
|
|
133
135
|
lineNumbers: false,
|
|
134
|
-
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
136
|
+
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled,
|
|
137
|
+
collectCpuTime: this._cpuProfilingEnabled
|
|
135
138
|
})
|
|
136
139
|
|
|
137
140
|
if (this._withContexts) {
|
|
@@ -220,22 +223,42 @@ class NativeWallProfiler {
|
|
|
220
223
|
|
|
221
224
|
_stop (restart) {
|
|
222
225
|
if (!this._started) return
|
|
226
|
+
|
|
223
227
|
if (this._captureSpanData) {
|
|
224
228
|
// update last sample context if needed
|
|
225
229
|
this._enter()
|
|
226
230
|
this._lastSampleCount = 0
|
|
227
231
|
}
|
|
228
232
|
const profile = this._pprof.time.stop(restart, this._generateLabels)
|
|
233
|
+
|
|
229
234
|
if (restart) {
|
|
230
235
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
231
236
|
if (v8BugDetected !== 0) {
|
|
232
237
|
this._reportV8bug(v8BugDetected === 1)
|
|
233
238
|
}
|
|
239
|
+
} else {
|
|
240
|
+
if (this._captureSpanData) {
|
|
241
|
+
beforeCh.unsubscribe(this._enter)
|
|
242
|
+
enterCh.unsubscribe(this._enter)
|
|
243
|
+
spanFinishCh.unsubscribe(this._spanFinished)
|
|
244
|
+
this._profilerState = undefined
|
|
245
|
+
this._lastSpan = undefined
|
|
246
|
+
this._lastStartedSpans = undefined
|
|
247
|
+
this._lastWebTags = undefined
|
|
248
|
+
}
|
|
249
|
+
this._started = false
|
|
234
250
|
}
|
|
251
|
+
|
|
235
252
|
return profile
|
|
236
253
|
}
|
|
237
254
|
|
|
238
|
-
_generateLabels (context) {
|
|
255
|
+
_generateLabels ({ node, context }) {
|
|
256
|
+
// check for special node that represents CPU time all non-JS threads.
|
|
257
|
+
// In that case only return a special thread name label since we cannot associate any timestamp/span/endpoint to it.
|
|
258
|
+
if (node.name === this._pprof.time.constants.NON_JS_THREADS_FUNCTION_NAME) {
|
|
259
|
+
return getNonJSThreadsLabels()
|
|
260
|
+
}
|
|
261
|
+
|
|
239
262
|
if (context == null) {
|
|
240
263
|
// generateLabels is also called for samples without context.
|
|
241
264
|
// In that case just return thread labels.
|
|
@@ -267,8 +290,8 @@ class NativeWallProfiler {
|
|
|
267
290
|
return labels
|
|
268
291
|
}
|
|
269
292
|
|
|
270
|
-
profile () {
|
|
271
|
-
return this._stop(
|
|
293
|
+
profile (restart) {
|
|
294
|
+
return this._stop(restart)
|
|
272
295
|
}
|
|
273
296
|
|
|
274
297
|
encode (profile) {
|
|
@@ -276,21 +299,11 @@ class NativeWallProfiler {
|
|
|
276
299
|
}
|
|
277
300
|
|
|
278
301
|
stop () {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const profile = this._stop(false)
|
|
282
|
-
if (this._captureSpanData) {
|
|
283
|
-
beforeCh.unsubscribe(this._enter)
|
|
284
|
-
enterCh.unsubscribe(this._enter)
|
|
285
|
-
spanFinishCh.unsubscribe(this._spanFinished)
|
|
286
|
-
this._profilerState = undefined
|
|
287
|
-
this._lastSpan = undefined
|
|
288
|
-
this._lastStartedSpans = undefined
|
|
289
|
-
this._lastWebTags = undefined
|
|
290
|
-
}
|
|
302
|
+
this._stop(false)
|
|
303
|
+
}
|
|
291
304
|
|
|
292
|
-
|
|
293
|
-
return
|
|
305
|
+
isStarted () {
|
|
306
|
+
return this._started
|
|
294
307
|
}
|
|
295
308
|
}
|
|
296
309
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
const request = require('../exporters/common/request')
|
|
3
3
|
const log = require('../log')
|
|
4
|
+
|
|
4
5
|
let agentTelemetry = true
|
|
5
6
|
|
|
6
7
|
function getHeaders (config, application, reqType) {
|
|
@@ -15,9 +16,22 @@ function getHeaders (config, application, reqType) {
|
|
|
15
16
|
if (debug) {
|
|
16
17
|
headers['dd-telemetry-debug-enabled'] = 'true'
|
|
17
18
|
}
|
|
19
|
+
if (config.apiKey) {
|
|
20
|
+
headers['dd-api-key'] = config.apiKey
|
|
21
|
+
}
|
|
18
22
|
return headers
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
function getAgentlessTelemetryEndpoint (site) {
|
|
26
|
+
if (site === 'datad0g.com') { // staging
|
|
27
|
+
return 'https://all-http-intake.logs.datad0g.com'
|
|
28
|
+
}
|
|
29
|
+
if (site === 'datadoghq.eu') {
|
|
30
|
+
return 'https://instrumentation-telemetry-intake.eu1.datadoghq.com'
|
|
31
|
+
}
|
|
32
|
+
return `https://instrumentation-telemetry-intake.${site}`
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
let seqId = 0
|
|
22
36
|
|
|
23
37
|
function getPayload (payload) {
|
|
@@ -35,17 +49,33 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
|
|
|
35
49
|
const {
|
|
36
50
|
hostname,
|
|
37
51
|
port,
|
|
38
|
-
|
|
52
|
+
experimental,
|
|
53
|
+
isCiVisibility
|
|
39
54
|
} = config
|
|
40
55
|
|
|
56
|
+
let url = config.url
|
|
57
|
+
|
|
58
|
+
const isCiVisibilityAgentlessMode = isCiVisibility && experimental?.exporter === 'datadog'
|
|
59
|
+
|
|
60
|
+
if (isCiVisibilityAgentlessMode) {
|
|
61
|
+
try {
|
|
62
|
+
url = url || new URL(getAgentlessTelemetryEndpoint(config.site))
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log.error(err)
|
|
65
|
+
// No point to do the request if the URL is invalid
|
|
66
|
+
return cb(err, { payload, reqType })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
const options = {
|
|
42
71
|
url,
|
|
43
72
|
hostname,
|
|
44
73
|
port,
|
|
45
74
|
method: 'POST',
|
|
46
|
-
path: '/telemetry/proxy/api/v2/apmtelemetry',
|
|
75
|
+
path: isCiVisibilityAgentlessMode ? '/api/v2/apmtelemetry' : '/telemetry/proxy/api/v2/apmtelemetry',
|
|
47
76
|
headers: getHeaders(config, application, reqType)
|
|
48
77
|
}
|
|
78
|
+
|
|
49
79
|
const data = JSON.stringify({
|
|
50
80
|
api_version: 'v2',
|
|
51
81
|
naming_schema_version: config.spanAttributeSchema ? config.spanAttributeSchema : '',
|
|
@@ -65,24 +95,13 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
|
|
|
65
95
|
agentTelemetry = false
|
|
66
96
|
}
|
|
67
97
|
// figure out which data center to send to
|
|
68
|
-
|
|
69
|
-
const dataCenters = [
|
|
70
|
-
'datadoghq.com',
|
|
71
|
-
'us3.datadoghq.com',
|
|
72
|
-
'us5.datadoghq.com',
|
|
73
|
-
'ap1.datadoghq.com',
|
|
74
|
-
'eu1.datadoghq.com'
|
|
75
|
-
]
|
|
76
|
-
if (config.site === 'datad0g.com') { // staging
|
|
77
|
-
backendUrl = 'https://all-http-intake.logs.datad0g.com/api/v2/apmtelemetry'
|
|
78
|
-
} else if (dataCenters.includes(config.site)) {
|
|
79
|
-
backendUrl = 'https://instrumentation-telemetry-intake.' + config.site + '/api/v2/apmtelemetry'
|
|
80
|
-
}
|
|
98
|
+
const backendUrl = getAgentlessTelemetryEndpoint(config.site)
|
|
81
99
|
const backendHeader = { ...options.headers, 'DD-API-KEY': process.env.DD_API_KEY }
|
|
82
100
|
const backendOptions = {
|
|
83
101
|
...options,
|
|
84
102
|
url: backendUrl,
|
|
85
|
-
headers: backendHeader
|
|
103
|
+
headers: backendHeader,
|
|
104
|
+
path: '/api/v2/apmtelemetry'
|
|
86
105
|
}
|
|
87
106
|
if (backendUrl) {
|
|
88
107
|
request(data, backendOptions, (error) => { log.error(error) })
|