dd-trace 4.22.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 +4 -4
- package/packages/datadog-instrumentations/src/child-process.js +4 -5
- package/packages/datadog-instrumentations/src/couchbase.js +5 -4
- package/packages/datadog-instrumentations/src/crypto.js +2 -1
- package/packages/datadog-instrumentations/src/dns.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +7 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +8 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +18 -2
- package/packages/datadog-instrumentations/src/http/client.js +2 -2
- package/packages/datadog-instrumentations/src/http/server.js +7 -4
- package/packages/datadog-instrumentations/src/http2/client.js +3 -1
- package/packages/datadog-instrumentations/src/http2/server.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/net.js +10 -2
- package/packages/datadog-plugin-cucumber/src/index.js +34 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +60 -6
- package/packages/datadog-plugin-jest/src/util.js +38 -16
- package/packages/datadog-plugin-mocha/src/index.js +32 -1
- package/packages/datadog-plugin-playwright/src/index.js +17 -1
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +36 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +18 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
- package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
- package/packages/dd-trace/src/config.js +100 -59
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
- package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +44 -8
- package/packages/dd-trace/src/plugins/index.js +5 -0
- package/packages/dd-trace/src/plugins/util/exec.js +23 -2
- package/packages/dd-trace/src/plugins/util/git.js +94 -19
- package/packages/dd-trace/src/priority_sampler.js +30 -38
- package/packages/dd-trace/src/profiling/config.js +17 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
- 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 +11 -5
- package/packages/dd-trace/src/profiling/profilers/shared.js +7 -1
- 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/sampling_rule.js +130 -0
- package/packages/dd-trace/src/span_sampler.js +6 -64
- package/packages/dd-trace/src/telemetry/index.js +43 -5
- package/packages/dd-trace/src/telemetry/send-data.js +35 -16
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const cp = require('child_process')
|
|
2
2
|
const os = require('os')
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
|
|
6
6
|
const log = require('../../log')
|
|
7
|
-
const { sanitizedExec } = require('./exec')
|
|
8
7
|
const {
|
|
9
8
|
GIT_COMMIT_SHA,
|
|
10
9
|
GIT_BRANCH,
|
|
@@ -19,10 +18,46 @@ const {
|
|
|
19
18
|
GIT_COMMIT_AUTHOR_NAME,
|
|
20
19
|
CI_WORKSPACE_PATH
|
|
21
20
|
} = require('./tags')
|
|
21
|
+
const {
|
|
22
|
+
incrementCountMetric,
|
|
23
|
+
distributionMetric,
|
|
24
|
+
TELEMETRY_GIT_COMMAND,
|
|
25
|
+
TELEMETRY_GIT_COMMAND_MS,
|
|
26
|
+
TELEMETRY_GIT_COMMAND_ERRORS
|
|
27
|
+
} = require('../../ci-visibility/telemetry')
|
|
22
28
|
const { filterSensitiveInfoFromRepository } = require('./url')
|
|
23
29
|
|
|
24
30
|
const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
|
|
25
31
|
|
|
32
|
+
function sanitizedExec (
|
|
33
|
+
cmd,
|
|
34
|
+
flags,
|
|
35
|
+
operationMetric,
|
|
36
|
+
durationMetric,
|
|
37
|
+
errorMetric
|
|
38
|
+
) {
|
|
39
|
+
let startTime
|
|
40
|
+
if (operationMetric) {
|
|
41
|
+
incrementCountMetric(operationMetric.name, operationMetric.tags)
|
|
42
|
+
}
|
|
43
|
+
if (durationMetric) {
|
|
44
|
+
startTime = Date.now()
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = cp.execFileSync(cmd, flags, { stdio: 'pipe' }).toString().replace(/(\r\n|\n|\r)/gm, '')
|
|
48
|
+
if (durationMetric) {
|
|
49
|
+
distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
|
|
50
|
+
}
|
|
51
|
+
return result
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (errorMetric) {
|
|
54
|
+
incrementCountMetric(errorMetric.name, { ...errorMetric.tags, exitCode: e.status })
|
|
55
|
+
}
|
|
56
|
+
log.error(e)
|
|
57
|
+
return ''
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
function isDirectory (path) {
|
|
27
62
|
try {
|
|
28
63
|
const stats = fs.statSync(path)
|
|
@@ -33,7 +68,13 @@ function isDirectory (path) {
|
|
|
33
68
|
}
|
|
34
69
|
|
|
35
70
|
function isShallowRepository () {
|
|
36
|
-
return sanitizedExec(
|
|
71
|
+
return sanitizedExec(
|
|
72
|
+
'git',
|
|
73
|
+
['rev-parse', '--is-shallow-repository'],
|
|
74
|
+
{ name: TELEMETRY_GIT_COMMAND, tags: { command: 'check_shallow' } },
|
|
75
|
+
{ name: TELEMETRY_GIT_COMMAND_MS, tags: { command: 'check_shallow' } },
|
|
76
|
+
{ name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'check_shallow' } }
|
|
77
|
+
) === 'true'
|
|
37
78
|
}
|
|
38
79
|
|
|
39
80
|
function getGitVersion () {
|
|
@@ -72,50 +113,76 @@ function unshallowRepository () {
|
|
|
72
113
|
defaultRemoteName
|
|
73
114
|
]
|
|
74
115
|
|
|
116
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'unshallow' })
|
|
117
|
+
const start = Date.now()
|
|
75
118
|
try {
|
|
76
|
-
execFileSync('git', [
|
|
119
|
+
cp.execFileSync('git', [
|
|
77
120
|
...baseGitOptions,
|
|
78
121
|
revParseHead
|
|
79
122
|
], { stdio: 'pipe' })
|
|
80
|
-
} catch (
|
|
123
|
+
} catch (err) {
|
|
81
124
|
// If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
|
|
82
|
-
log.error(
|
|
125
|
+
log.error(err)
|
|
126
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
|
|
83
127
|
const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
|
|
84
128
|
try {
|
|
85
|
-
execFileSync('git', [
|
|
129
|
+
cp.execFileSync('git', [
|
|
86
130
|
...baseGitOptions,
|
|
87
131
|
upstreamRemote
|
|
88
132
|
], { stdio: 'pipe' })
|
|
89
|
-
} catch (
|
|
133
|
+
} catch (err) {
|
|
90
134
|
// If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
|
|
91
|
-
log.error(
|
|
135
|
+
log.error(err)
|
|
136
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
|
|
92
137
|
// We use sanitizedExec here because if this last option fails, we'll give up.
|
|
93
|
-
sanitizedExec(
|
|
138
|
+
sanitizedExec(
|
|
139
|
+
'git',
|
|
140
|
+
baseGitOptions,
|
|
141
|
+
null,
|
|
142
|
+
null,
|
|
143
|
+
{ name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'unshallow' } } // we log the error in sanitizedExec
|
|
144
|
+
)
|
|
94
145
|
}
|
|
95
146
|
}
|
|
147
|
+
distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'unshallow' }, Date.now() - start)
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
function getRepositoryUrl () {
|
|
99
|
-
return sanitizedExec(
|
|
151
|
+
return sanitizedExec(
|
|
152
|
+
'git',
|
|
153
|
+
['config', '--get', 'remote.origin.url'],
|
|
154
|
+
{ name: TELEMETRY_GIT_COMMAND, tags: { command: 'get_repository' } },
|
|
155
|
+
{ name: TELEMETRY_GIT_COMMAND_MS, tags: { command: 'get_repository' } },
|
|
156
|
+
{ name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'get_repository' } }
|
|
157
|
+
)
|
|
100
158
|
}
|
|
101
159
|
|
|
102
160
|
function getLatestCommits () {
|
|
161
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_local_commits' })
|
|
162
|
+
const startTime = Date.now()
|
|
103
163
|
try {
|
|
104
|
-
|
|
164
|
+
const result = cp.execFileSync('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'], { stdio: 'pipe' })
|
|
105
165
|
.toString()
|
|
106
166
|
.split('\n')
|
|
107
167
|
.filter(commit => commit)
|
|
168
|
+
distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_local_commits' }, Date.now() - startTime)
|
|
169
|
+
return result
|
|
108
170
|
} catch (err) {
|
|
109
171
|
log.error(`Get latest commits failed: ${err.message}`)
|
|
172
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_local_commits', errorType: err.status })
|
|
110
173
|
return []
|
|
111
174
|
}
|
|
112
175
|
}
|
|
113
176
|
|
|
114
177
|
function getCommitsRevList (commitsToExclude, commitsToInclude) {
|
|
178
|
+
let result = []
|
|
179
|
+
|
|
115
180
|
const commitsToExcludeString = commitsToExclude.map(commit => `^${commit}`)
|
|
116
181
|
|
|
182
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_objects' })
|
|
183
|
+
const startTime = Date.now()
|
|
117
184
|
try {
|
|
118
|
-
|
|
185
|
+
result = cp.execFileSync(
|
|
119
186
|
'git',
|
|
120
187
|
[
|
|
121
188
|
'rev-list',
|
|
@@ -132,11 +199,14 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
|
|
|
132
199
|
.filter(commit => commit)
|
|
133
200
|
} catch (err) {
|
|
134
201
|
log.error(`Get commits to upload failed: ${err.message}`)
|
|
135
|
-
|
|
202
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_objects', errorType: err.status })
|
|
136
203
|
}
|
|
204
|
+
distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_objects' }, Date.now() - startTime)
|
|
205
|
+
return result
|
|
137
206
|
}
|
|
138
207
|
|
|
139
208
|
function generatePackFilesForCommits (commitsToUpload) {
|
|
209
|
+
let result = []
|
|
140
210
|
const tmpFolder = os.tmpdir()
|
|
141
211
|
|
|
142
212
|
if (!isDirectory(tmpFolder)) {
|
|
@@ -148,10 +218,12 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
148
218
|
const temporaryPath = path.join(tmpFolder, randomPrefix)
|
|
149
219
|
const cwdPath = path.join(process.cwd(), randomPrefix)
|
|
150
220
|
|
|
221
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'pack_objects' })
|
|
222
|
+
const startTime = Date.now()
|
|
151
223
|
// Generates pack files to upload and
|
|
152
224
|
// returns the ordered list of packfiles' paths
|
|
153
225
|
function execGitPackObjects (targetPath) {
|
|
154
|
-
return execFileSync(
|
|
226
|
+
return cp.execFileSync(
|
|
155
227
|
'git',
|
|
156
228
|
[
|
|
157
229
|
'pack-objects',
|
|
@@ -164,9 +236,10 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
164
236
|
}
|
|
165
237
|
|
|
166
238
|
try {
|
|
167
|
-
|
|
239
|
+
result = execGitPackObjects(temporaryPath)
|
|
168
240
|
} catch (err) {
|
|
169
241
|
log.error(err)
|
|
242
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
|
|
170
243
|
/**
|
|
171
244
|
* The generation of pack files in the temporary folder (from `os.tmpdir()`)
|
|
172
245
|
* sometimes fails in certain CI setups with the error message
|
|
@@ -180,13 +253,15 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
180
253
|
* TODO: fix issue and remove workaround.
|
|
181
254
|
*/
|
|
182
255
|
try {
|
|
183
|
-
|
|
256
|
+
result = execGitPackObjects(cwdPath)
|
|
184
257
|
} catch (err) {
|
|
185
258
|
log.error(err)
|
|
259
|
+
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
|
|
186
260
|
}
|
|
187
|
-
|
|
188
|
-
return []
|
|
189
261
|
}
|
|
262
|
+
distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'pack_objects' }, Date.now() - startTime)
|
|
263
|
+
|
|
264
|
+
return result
|
|
190
265
|
}
|
|
191
266
|
|
|
192
267
|
// If there is ciMetadata, it takes precedence.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const RateLimiter = require('./rate_limiter')
|
|
4
3
|
const Sampler = require('./sampler')
|
|
5
|
-
const ext = require('../../../ext')
|
|
6
4
|
const { setSamplingRules } = require('./startup-log')
|
|
5
|
+
const SamplingRule = require('./sampling_rule')
|
|
7
6
|
|
|
8
7
|
const {
|
|
9
8
|
SAMPLING_MECHANISM_DEFAULT,
|
|
@@ -16,14 +15,21 @@ const {
|
|
|
16
15
|
DECISION_MAKER_KEY
|
|
17
16
|
} = require('./constants')
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
const {
|
|
19
|
+
tags: {
|
|
20
|
+
MANUAL_KEEP,
|
|
21
|
+
MANUAL_DROP,
|
|
22
|
+
SAMPLING_PRIORITY,
|
|
23
|
+
SERVICE_NAME
|
|
24
|
+
},
|
|
25
|
+
priority: {
|
|
26
|
+
AUTO_REJECT,
|
|
27
|
+
AUTO_KEEP,
|
|
28
|
+
USER_REJECT,
|
|
29
|
+
USER_KEEP
|
|
30
|
+
}
|
|
31
|
+
} = require('../../../ext')
|
|
32
|
+
|
|
27
33
|
const DEFAULT_KEY = 'service:,env:'
|
|
28
34
|
|
|
29
35
|
const defaultSampler = new Sampler(AUTO_KEEP)
|
|
@@ -36,8 +42,7 @@ class PrioritySampler {
|
|
|
36
42
|
|
|
37
43
|
configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
|
|
38
44
|
this._env = env
|
|
39
|
-
this._rules = this._normalizeRules(rules, sampleRate)
|
|
40
|
-
this._limiter = new RateLimiter(rateLimit)
|
|
45
|
+
this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
|
|
41
46
|
|
|
42
47
|
setSamplingRules(this._rules)
|
|
43
48
|
}
|
|
@@ -104,7 +109,7 @@ class PrioritySampler {
|
|
|
104
109
|
|
|
105
110
|
_getPriorityFromAuto (span) {
|
|
106
111
|
const context = this._getContext(span)
|
|
107
|
-
const rule = this._findRule(
|
|
112
|
+
const rule = this._findRule(span)
|
|
108
113
|
|
|
109
114
|
return rule
|
|
110
115
|
? this._getPriorityByRule(context, rule)
|
|
@@ -131,15 +136,14 @@ class PrioritySampler {
|
|
|
131
136
|
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
132
137
|
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
const sampled = rule.sample()
|
|
140
|
+
const priority = sampled ? USER_KEEP : USER_REJECT
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
|
|
142
|
+
if (sampled) {
|
|
143
|
+
context._trace[SAMPLING_LIMIT_DECISION] = rule.effectiveRate
|
|
144
|
+
}
|
|
141
145
|
|
|
142
|
-
return
|
|
146
|
+
return priority
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
_getPriorityByAgent (context) {
|
|
@@ -172,33 +176,21 @@ class PrioritySampler {
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
_normalizeRules (rules, sampleRate) {
|
|
179
|
+
_normalizeRules (rules, sampleRate, rateLimit) {
|
|
176
180
|
rules = [].concat(rules || [])
|
|
177
181
|
|
|
178
182
|
return rules
|
|
179
|
-
.concat({ sampleRate })
|
|
183
|
+
.concat({ sampleRate, maxPerSecond: rateLimit })
|
|
180
184
|
.map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
|
|
181
185
|
.filter(rule => !isNaN(rule.sampleRate))
|
|
182
|
-
.map(
|
|
186
|
+
.map(SamplingRule.from)
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
_findRule (
|
|
186
|
-
for (
|
|
187
|
-
if (
|
|
189
|
+
_findRule (span) {
|
|
190
|
+
for (const rule of this._rules) {
|
|
191
|
+
if (rule.match(span)) return rule
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
|
-
|
|
191
|
-
_matchRule (context, rule) {
|
|
192
|
-
const name = context._name
|
|
193
|
-
const service = context._tags['service.name']
|
|
194
|
-
|
|
195
|
-
if (rule.name instanceof RegExp && !rule.name.test(name)) return false
|
|
196
|
-
if (typeof rule.name === 'string' && rule.name !== name) return false
|
|
197
|
-
if (rule.service instanceof RegExp && !rule.service.test(service)) return false
|
|
198
|
-
if (typeof rule.service === 'string' && rule.service !== service) return false
|
|
199
|
-
|
|
200
|
-
return true
|
|
201
|
-
}
|
|
202
194
|
}
|
|
203
195
|
|
|
204
196
|
function hasOwn (object, prop) {
|
|
@@ -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
|
}
|
|
@@ -75,6 +75,7 @@ class AgentExporter {
|
|
|
75
75
|
['tags[]', 'language:javascript'],
|
|
76
76
|
['tags[]', 'runtime:nodejs'],
|
|
77
77
|
['tags[]', `runtime_version:${process.version}`],
|
|
78
|
+
['tags[]', `process_id:${process.pid}`],
|
|
78
79
|
['tags[]', `profiler_version:${version}`],
|
|
79
80
|
['tags[]', 'format:pprof'],
|
|
80
81
|
...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])
|
|
@@ -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())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { performance, constants, PerformanceObserver } = require('
|
|
1
|
+
const { performance, constants, PerformanceObserver } = require('perf_hooks')
|
|
2
2
|
const { END_TIMESTAMP_LABEL } = require('./shared')
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
@@ -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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { isMainThread, threadId } = require('
|
|
3
|
+
const { isMainThread, threadId } = require('worker_threads')
|
|
4
4
|
|
|
5
5
|
const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
|
|
6
6
|
const THREAD_NAME_LABEL = 'thread name'
|
|
@@ -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
|
|