dd-trace 5.28.0 → 5.29.1
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 +8 -2
- package/ci/init.js +16 -0
- package/index.d.ts +31 -13
- package/init.js +4 -68
- package/loader-hook.mjs +4 -0
- package/package.json +16 -11
- package/packages/datadog-core/src/storage.js +39 -2
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +29 -3
- package/packages/datadog-instrumentations/src/express.js +38 -4
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
- package/packages/datadog-instrumentations/src/http/client.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +27 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
- package/packages/datadog-instrumentations/src/mysql2.js +13 -8
- package/packages/datadog-instrumentations/src/next.js +7 -4
- package/packages/datadog-instrumentations/src/passport-http.js +2 -14
- package/packages/datadog-instrumentations/src/passport-local.js +2 -14
- package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
- package/packages/datadog-instrumentations/src/pg.js +6 -6
- package/packages/datadog-instrumentations/src/playwright.js +17 -4
- package/packages/datadog-instrumentations/src/router.js +97 -1
- package/packages/datadog-instrumentations/src/sequelize.js +9 -4
- package/packages/datadog-instrumentations/src/url.js +4 -0
- package/packages/datadog-instrumentations/src/vitest.js +27 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +39 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
- package/packages/datadog-plugin-grpc/src/client.js +2 -2
- package/packages/datadog-plugin-grpc/src/util.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +39 -4
- package/packages/datadog-plugin-mocha/src/index.js +36 -2
- package/packages/datadog-plugin-oracledb/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +34 -2
- package/packages/datadog-shimmer/src/shimmer.js +8 -4
- package/packages/dd-trace/src/appsec/addresses.js +3 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
- package/packages/dd-trace/src/appsec/index.js +16 -10
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
- package/packages/dd-trace/src/appsec/reporter.js +3 -1
- package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
- package/packages/dd-trace/src/appsec/telemetry.js +10 -0
- package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
- package/packages/dd-trace/src/azure_metadata.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/config.js +24 -32
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
- package/packages/dd-trace/src/datastreams/processor.js +4 -6
- package/packages/dd-trace/src/datastreams/writer.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
- package/packages/dd-trace/src/debugger/index.js +14 -10
- package/packages/dd-trace/src/dogstatsd.js +2 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -78
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
- package/packages/dd-trace/src/encode/span-stats.js +0 -30
- package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
- package/packages/dd-trace/src/flare/index.js +1 -1
- package/packages/dd-trace/src/guardrails/index.js +64 -0
- package/packages/dd-trace/src/guardrails/log.js +32 -0
- package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
- package/packages/dd-trace/src/guardrails/util.js +10 -0
- package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
- package/packages/dd-trace/src/llmobs/storage.js +2 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
- package/packages/dd-trace/src/log/channels.js +9 -2
- package/packages/dd-trace/src/log/index.js +11 -1
- package/packages/dd-trace/src/log/writer.js +14 -3
- package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
- package/packages/dd-trace/src/msgpack/encoder.js +309 -0
- package/packages/dd-trace/src/msgpack/index.js +6 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/git.js +7 -7
- package/packages/dd-trace/src/plugins/util/test.js +36 -3
- package/packages/dd-trace/src/plugins/util/web.js +2 -2
- package/packages/dd-trace/src/priority_sampler.js +11 -1
- package/packages/dd-trace/src/profiling/config.js +3 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
- package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
- package/packages/dd-trace/src/profiling/profiler.js +62 -10
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
- package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
- package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
- package/packages/dd-trace/src/proxy.js +7 -2
- package/packages/dd-trace/src/runtime_metrics.js +107 -4
- package/packages/dd-trace/src/serverless.js +1 -1
- package/packages/dd-trace/src/span_processor.js +10 -10
- package/packages/dd-trace/src/tagger.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +1 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +5 -16
- package/packages/datadog-instrumentations/src/qs.js +0 -24
- package/packages/dd-trace/src/appsec/passport.js +0 -110
- package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
|
@@ -79,7 +79,7 @@ module.exports = class Plugin {
|
|
|
79
79
|
return handler.apply(this, arguments)
|
|
80
80
|
} catch (e) {
|
|
81
81
|
logger.error('Error in plugin handler:', e)
|
|
82
|
-
logger.info('Disabling plugin:', plugin.id)
|
|
82
|
+
logger.info('Disabling plugin: %s', plugin.id)
|
|
83
83
|
plugin.configure(false)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -94,7 +94,7 @@ class TracingPlugin extends Plugin {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
addError (error, span = this.activeSpan) {
|
|
97
|
-
if (!span._spanContext._tags.error) {
|
|
97
|
+
if (span && !span._spanContext._tags.error) {
|
|
98
98
|
// Errors may be wrapped in a context.
|
|
99
99
|
error = (error && error.error) || error
|
|
100
100
|
span.setTag('error', error || 1)
|
|
@@ -61,7 +61,7 @@ function sanitizedExec (
|
|
|
61
61
|
exitCode: err.status || err.errno
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
|
-
log.error(err)
|
|
64
|
+
log.error('Git plugin error executing command', err)
|
|
65
65
|
return ''
|
|
66
66
|
} finally {
|
|
67
67
|
storage.enterWith(store)
|
|
@@ -144,7 +144,7 @@ function unshallowRepository () {
|
|
|
144
144
|
], { stdio: 'pipe' })
|
|
145
145
|
} catch (err) {
|
|
146
146
|
// If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
|
|
147
|
-
log.error(err)
|
|
147
|
+
log.error('Git plugin error executing git command', err)
|
|
148
148
|
incrementCountMetric(
|
|
149
149
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
150
150
|
{ command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
|
|
@@ -157,7 +157,7 @@ function unshallowRepository () {
|
|
|
157
157
|
], { stdio: 'pipe' })
|
|
158
158
|
} catch (err) {
|
|
159
159
|
// If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
|
|
160
|
-
log.error(err)
|
|
160
|
+
log.error('Git plugin error executing fallback git command', err)
|
|
161
161
|
incrementCountMetric(
|
|
162
162
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
163
163
|
{ command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
|
|
@@ -196,7 +196,7 @@ function getLatestCommits () {
|
|
|
196
196
|
distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_local_commits' }, Date.now() - startTime)
|
|
197
197
|
return result
|
|
198
198
|
} catch (err) {
|
|
199
|
-
log.error(
|
|
199
|
+
log.error('Get latest commits failed: %s', err.message)
|
|
200
200
|
incrementCountMetric(
|
|
201
201
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
202
202
|
{ command: 'get_local_commits', errorType: err.status }
|
|
@@ -229,7 +229,7 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
|
|
|
229
229
|
.split('\n')
|
|
230
230
|
.filter(commit => commit)
|
|
231
231
|
} catch (err) {
|
|
232
|
-
log.error(
|
|
232
|
+
log.error('Get commits to upload failed: %s', err.message)
|
|
233
233
|
incrementCountMetric(
|
|
234
234
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
235
235
|
{ command: 'get_objects', errorType: err.code, exitCode: err.status || err.errno } // err.status might be null
|
|
@@ -272,7 +272,7 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
272
272
|
try {
|
|
273
273
|
result = execGitPackObjects(temporaryPath)
|
|
274
274
|
} catch (err) {
|
|
275
|
-
log.error(err)
|
|
275
|
+
log.error('Git plugin error executing git pack-objects command', err)
|
|
276
276
|
incrementCountMetric(
|
|
277
277
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
278
278
|
{ command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
|
|
@@ -292,7 +292,7 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
292
292
|
try {
|
|
293
293
|
result = execGitPackObjects(cwdPath)
|
|
294
294
|
} catch (err) {
|
|
295
|
-
log.error(err)
|
|
295
|
+
log.error('Git plugin error executing fallback git pack-objects command', err)
|
|
296
296
|
incrementCountMetric(
|
|
297
297
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
298
298
|
{ command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
|
|
@@ -106,6 +106,13 @@ const TEST_LEVEL_EVENT_TYPES = [
|
|
|
106
106
|
'test_session_end'
|
|
107
107
|
]
|
|
108
108
|
|
|
109
|
+
// Dynamic instrumentation - Test optimization integration tags
|
|
110
|
+
const DI_ERROR_DEBUG_INFO_CAPTURED = 'error.debug_info_captured'
|
|
111
|
+
// TODO: for the moment we'll only use a single snapshot id, so `0` is hardcoded
|
|
112
|
+
const DI_DEBUG_ERROR_SNAPSHOT_ID = '_dd.debug.error.0.snapshot_id'
|
|
113
|
+
const DI_DEBUG_ERROR_FILE = '_dd.debug.error.0.file'
|
|
114
|
+
const DI_DEBUG_ERROR_LINE = '_dd.debug.error.0.line'
|
|
115
|
+
|
|
109
116
|
module.exports = {
|
|
110
117
|
TEST_CODE_OWNERS,
|
|
111
118
|
TEST_SESSION_NAME,
|
|
@@ -181,7 +188,12 @@ module.exports = {
|
|
|
181
188
|
TEST_BROWSER_VERSION,
|
|
182
189
|
getTestSessionName,
|
|
183
190
|
TEST_LEVEL_EVENT_TYPES,
|
|
184
|
-
getNumFromKnownTests
|
|
191
|
+
getNumFromKnownTests,
|
|
192
|
+
getFileAndLineNumberFromError,
|
|
193
|
+
DI_ERROR_DEBUG_INFO_CAPTURED,
|
|
194
|
+
DI_DEBUG_ERROR_SNAPSHOT_ID,
|
|
195
|
+
DI_DEBUG_ERROR_FILE,
|
|
196
|
+
DI_DEBUG_ERROR_LINE
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -206,13 +218,13 @@ function removeInvalidMetadata (metadata) {
|
|
|
206
218
|
return Object.keys(metadata).reduce((filteredTags, tag) => {
|
|
207
219
|
if (tag === GIT_REPOSITORY_URL) {
|
|
208
220
|
if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
|
|
209
|
-
log.error(
|
|
221
|
+
log.error('Repository URL is not a valid repository URL: %s.', metadata[GIT_REPOSITORY_URL])
|
|
210
222
|
return filteredTags
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
if (tag === GIT_COMMIT_SHA) {
|
|
214
226
|
if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
|
|
215
|
-
log.error(
|
|
227
|
+
log.error('Git commit SHA must be a full-length git SHA: %s.', metadata[GIT_COMMIT_SHA])
|
|
216
228
|
return filteredTags
|
|
217
229
|
}
|
|
218
230
|
}
|
|
@@ -637,3 +649,24 @@ function getNumFromKnownTests (knownTests) {
|
|
|
637
649
|
|
|
638
650
|
return totalNumTests
|
|
639
651
|
}
|
|
652
|
+
|
|
653
|
+
function getFileAndLineNumberFromError (error) {
|
|
654
|
+
// Split the stack trace into individual lines
|
|
655
|
+
const stackLines = error.stack.split('\n')
|
|
656
|
+
|
|
657
|
+
// The top frame is usually the second line
|
|
658
|
+
const topFrame = stackLines[1]
|
|
659
|
+
|
|
660
|
+
// Regular expression to match the file path, line number, and column number
|
|
661
|
+
const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
|
|
662
|
+
const match = topFrame.match(regex)
|
|
663
|
+
|
|
664
|
+
if (match) {
|
|
665
|
+
const filePath = match[1]
|
|
666
|
+
const lineNumber = Number(match[2])
|
|
667
|
+
const columnNumber = Number(match[3])
|
|
668
|
+
|
|
669
|
+
return [filePath, lineNumber, columnNumber]
|
|
670
|
+
}
|
|
671
|
+
return []
|
|
672
|
+
}
|
|
@@ -546,7 +546,7 @@ function getHeadersToRecord (config) {
|
|
|
546
546
|
.map(h => h.split(':'))
|
|
547
547
|
.map(([key, tag]) => [key.toLowerCase(), tag])
|
|
548
548
|
} catch (err) {
|
|
549
|
-
log.error(err)
|
|
549
|
+
log.error('Web plugin error getting headers', err)
|
|
550
550
|
}
|
|
551
551
|
} else if (config.hasOwnProperty('headers')) {
|
|
552
552
|
log.error('Expected `headers` to be an array of strings.')
|
|
@@ -595,7 +595,7 @@ function getQsObfuscator (config) {
|
|
|
595
595
|
try {
|
|
596
596
|
return new RegExp(obfuscator, 'gi')
|
|
597
597
|
} catch (err) {
|
|
598
|
-
log.error(err)
|
|
598
|
+
log.error('Web plugin error getting qs obfuscator', err)
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('./log')
|
|
3
4
|
const RateLimiter = require('./rate_limiter')
|
|
4
5
|
const Sampler = require('./sampler')
|
|
5
6
|
const { setSamplingRules } = require('./startup-log')
|
|
@@ -44,16 +45,19 @@ class PrioritySampler {
|
|
|
44
45
|
this.update({})
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
configure (env,
|
|
48
|
+
configure (env, opts = {}) {
|
|
49
|
+
const { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = opts
|
|
48
50
|
this._env = env
|
|
49
51
|
this._rules = this._normalizeRules(rules, sampleRate, rateLimit, provenance)
|
|
50
52
|
this._limiter = new RateLimiter(rateLimit)
|
|
51
53
|
|
|
54
|
+
log.trace(env, opts)
|
|
52
55
|
setSamplingRules(this._rules)
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
isSampled (span) {
|
|
56
59
|
const priority = this._getPriorityFromAuto(span)
|
|
60
|
+
log.trace(span)
|
|
57
61
|
return priority === USER_KEEP || priority === AUTO_KEEP
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -67,6 +71,8 @@ class PrioritySampler {
|
|
|
67
71
|
if (context._sampling.priority !== undefined) return
|
|
68
72
|
if (!root) return // noop span
|
|
69
73
|
|
|
74
|
+
log.trace(span, auto)
|
|
75
|
+
|
|
70
76
|
const tag = this._getPriorityFromTags(context._tags, context)
|
|
71
77
|
|
|
72
78
|
if (this.validate(tag)) {
|
|
@@ -94,6 +100,8 @@ class PrioritySampler {
|
|
|
94
100
|
samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler
|
|
95
101
|
|
|
96
102
|
this._samplers = samplers
|
|
103
|
+
|
|
104
|
+
log.trace(rates)
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
validate (samplingPriority) {
|
|
@@ -117,6 +125,8 @@ class PrioritySampler {
|
|
|
117
125
|
context._sampling.mechanism = mechanism
|
|
118
126
|
|
|
119
127
|
const root = context._trace.started[0]
|
|
128
|
+
|
|
129
|
+
log.trace(span, samplingPriority, mechanism)
|
|
120
130
|
this._addDecisionMaker(root)
|
|
121
131
|
}
|
|
122
132
|
|
|
@@ -21,6 +21,7 @@ class Config {
|
|
|
21
21
|
const {
|
|
22
22
|
DD_AGENT_HOST,
|
|
23
23
|
DD_ENV,
|
|
24
|
+
DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, // used for testing
|
|
24
25
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
25
26
|
DD_PROFILING_CPU_ENABLED,
|
|
26
27
|
DD_PROFILING_DEBUG_SOURCE_MAPS,
|
|
@@ -175,6 +176,8 @@ class Config {
|
|
|
175
176
|
DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, samplingContextsAvailable))
|
|
176
177
|
logExperimentalVarDeprecation('TIMELINE_ENABLED')
|
|
177
178
|
checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
|
|
179
|
+
this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
|
|
180
|
+
DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
|
|
178
181
|
|
|
179
182
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
180
183
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
const retry = require('retry')
|
|
4
4
|
const { request: httpRequest } = require('http')
|
|
5
5
|
const { request: httpsRequest } = require('https')
|
|
6
|
+
const { EventSerializer } = require('./event_serializer')
|
|
6
7
|
|
|
7
8
|
// TODO: avoid using dd-trace internals. Make this a separate module?
|
|
8
9
|
const docker = require('../../exporters/common/docker')
|
|
9
10
|
const FormData = require('../../exporters/common/form-data')
|
|
10
11
|
const { storage } = require('../../../../datadog-core')
|
|
11
12
|
const version = require('../../../../../package.json').version
|
|
12
|
-
const os = require('os')
|
|
13
13
|
const { urlToHttpOptions } = require('url')
|
|
14
14
|
const perf = require('perf_hooks').performance
|
|
15
15
|
|
|
@@ -89,8 +89,10 @@ function computeRetries (uploadTimeout) {
|
|
|
89
89
|
return [tries, Math.floor(uploadTimeout)]
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
class AgentExporter {
|
|
93
|
-
constructor (
|
|
92
|
+
class AgentExporter extends EventSerializer {
|
|
93
|
+
constructor (config = {}) {
|
|
94
|
+
super(config)
|
|
95
|
+
const { url, logger, uploadTimeout } = config
|
|
94
96
|
this._url = url
|
|
95
97
|
this._logger = logger
|
|
96
98
|
|
|
@@ -98,74 +100,13 @@ class AgentExporter {
|
|
|
98
100
|
|
|
99
101
|
this._backoffTime = backoffTime
|
|
100
102
|
this._backoffTries = backoffTries
|
|
101
|
-
this._env = env
|
|
102
|
-
this._host = host
|
|
103
|
-
this._service = service
|
|
104
|
-
this._appVersion = version
|
|
105
|
-
this._libraryInjected = !!libraryInjected
|
|
106
|
-
this._activation = activation || 'unknown'
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
export (
|
|
105
|
+
export (exportSpec) {
|
|
106
|
+
const { profiles } = exportSpec
|
|
110
107
|
const fields = []
|
|
111
108
|
|
|
112
|
-
|
|
113
|
-
return `${type}.pprof`
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const event = JSON.stringify({
|
|
117
|
-
attachments: Object.keys(profiles).map(typeToFile),
|
|
118
|
-
start: start.toISOString(),
|
|
119
|
-
end: end.toISOString(),
|
|
120
|
-
family: 'node',
|
|
121
|
-
version: '4',
|
|
122
|
-
tags_profiler: [
|
|
123
|
-
'language:javascript',
|
|
124
|
-
'runtime:nodejs',
|
|
125
|
-
`runtime_arch:${process.arch}`,
|
|
126
|
-
`runtime_os:${process.platform}`,
|
|
127
|
-
`runtime_version:${process.version}`,
|
|
128
|
-
`process_id:${process.pid}`,
|
|
129
|
-
`profiler_version:${version}`,
|
|
130
|
-
'format:pprof',
|
|
131
|
-
...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
|
|
132
|
-
].join(','),
|
|
133
|
-
info: {
|
|
134
|
-
application: {
|
|
135
|
-
env: this._env,
|
|
136
|
-
service: this._service,
|
|
137
|
-
start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
|
|
138
|
-
version: this._appVersion
|
|
139
|
-
},
|
|
140
|
-
platform: {
|
|
141
|
-
hostname: this._host,
|
|
142
|
-
kernel_name: os.type(),
|
|
143
|
-
kernel_release: os.release(),
|
|
144
|
-
kernel_version: os.version()
|
|
145
|
-
},
|
|
146
|
-
profiler: {
|
|
147
|
-
activation: this._activation,
|
|
148
|
-
ssi: {
|
|
149
|
-
mechanism: this._libraryInjected ? 'injected_agent' : 'none'
|
|
150
|
-
},
|
|
151
|
-
version
|
|
152
|
-
},
|
|
153
|
-
runtime: {
|
|
154
|
-
// Using `nodejs` for consistency with the existing `runtime` tag.
|
|
155
|
-
// Note that the event `family` property uses `node`, as that's what's
|
|
156
|
-
// proscribed by the Intake API, but that's an internal enum and is
|
|
157
|
-
// not customer visible.
|
|
158
|
-
engine: 'nodejs',
|
|
159
|
-
// strip off leading 'v'. This makes the format consistent with other
|
|
160
|
-
// runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
|
|
161
|
-
// We'll keep it like this as we want cross-engine consistency. We
|
|
162
|
-
// also aren't changing the format of the existing tag as we don't want
|
|
163
|
-
// to break it.
|
|
164
|
-
version: process.version.substring(1)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
|
|
109
|
+
const event = this.getEventJSON(exportSpec)
|
|
169
110
|
fields.push(['event', event, {
|
|
170
111
|
filename: 'event.json',
|
|
171
112
|
contentType: 'application/json'
|
|
@@ -181,7 +122,7 @@ class AgentExporter {
|
|
|
181
122
|
return `Adding ${type} profile to agent export: ` + bytes
|
|
182
123
|
})
|
|
183
124
|
|
|
184
|
-
const filename = typeToFile(type)
|
|
125
|
+
const filename = this.typeToFile(type)
|
|
185
126
|
fields.push([filename, buffer, {
|
|
186
127
|
filename,
|
|
187
128
|
contentType: 'application/octet-stream'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const os = require('os')
|
|
2
|
+
const perf = require('perf_hooks').performance
|
|
3
|
+
const version = require('../../../../../package.json').version
|
|
4
|
+
|
|
5
|
+
class EventSerializer {
|
|
6
|
+
constructor ({ env, host, service, version, libraryInjected, activation } = {}) {
|
|
7
|
+
this._env = env
|
|
8
|
+
this._host = host
|
|
9
|
+
this._service = service
|
|
10
|
+
this._appVersion = version
|
|
11
|
+
this._libraryInjected = !!libraryInjected
|
|
12
|
+
this._activation = activation || 'unknown'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
typeToFile (type) {
|
|
16
|
+
return `${type}.pprof`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getEventJSON ({ profiles, start, end, tags = {}, endpointCounts }) {
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
attachments: Object.keys(profiles).map(t => this.typeToFile(t)),
|
|
22
|
+
start: start.toISOString(),
|
|
23
|
+
end: end.toISOString(),
|
|
24
|
+
family: 'node',
|
|
25
|
+
version: '4',
|
|
26
|
+
tags_profiler: [
|
|
27
|
+
'language:javascript',
|
|
28
|
+
'runtime:nodejs',
|
|
29
|
+
`runtime_arch:${process.arch}`,
|
|
30
|
+
`runtime_os:${process.platform}`,
|
|
31
|
+
`runtime_version:${process.version}`,
|
|
32
|
+
`process_id:${process.pid}`,
|
|
33
|
+
`profiler_version:${version}`,
|
|
34
|
+
'format:pprof',
|
|
35
|
+
...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
|
|
36
|
+
].join(','),
|
|
37
|
+
endpoint_counts: endpointCounts,
|
|
38
|
+
info: {
|
|
39
|
+
application: {
|
|
40
|
+
env: this._env,
|
|
41
|
+
service: this._service,
|
|
42
|
+
start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
|
|
43
|
+
version: this._appVersion
|
|
44
|
+
},
|
|
45
|
+
platform: {
|
|
46
|
+
hostname: this._host,
|
|
47
|
+
kernel_name: os.type(),
|
|
48
|
+
kernel_release: os.release(),
|
|
49
|
+
kernel_version: os.version()
|
|
50
|
+
},
|
|
51
|
+
profiler: {
|
|
52
|
+
activation: this._activation,
|
|
53
|
+
ssi: {
|
|
54
|
+
mechanism: this._libraryInjected ? 'injected_agent' : 'none'
|
|
55
|
+
},
|
|
56
|
+
version
|
|
57
|
+
},
|
|
58
|
+
runtime: {
|
|
59
|
+
// Using `nodejs` for consistency with the existing `runtime` tag.
|
|
60
|
+
// Note that the event `family` property uses `node`, as that's what's
|
|
61
|
+
// proscribed by the Intake API, but that's an internal enum and is
|
|
62
|
+
// not customer visible.
|
|
63
|
+
engine: 'nodejs',
|
|
64
|
+
// strip off leading 'v'. This makes the format consistent with other
|
|
65
|
+
// runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
|
|
66
|
+
// We'll keep it like this as we want cross-engine consistency. We
|
|
67
|
+
// also aren't changing the format of the existing tag as we don't want
|
|
68
|
+
// to break it.
|
|
69
|
+
version: process.version.substring(1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { EventSerializer }
|
|
@@ -4,6 +4,7 @@ const fs = require('fs')
|
|
|
4
4
|
const { promisify } = require('util')
|
|
5
5
|
const { threadId } = require('worker_threads')
|
|
6
6
|
const writeFile = promisify(fs.writeFile)
|
|
7
|
+
const { EventSerializer } = require('./event_serializer')
|
|
7
8
|
|
|
8
9
|
function formatDateTime (t) {
|
|
9
10
|
const pad = (n) => String(n).padStart(2, '0')
|
|
@@ -11,18 +12,21 @@ function formatDateTime (t) {
|
|
|
11
12
|
`T${pad(t.getUTCHours())}${pad(t.getUTCMinutes())}${pad(t.getUTCSeconds())}Z`
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
class FileExporter {
|
|
15
|
-
constructor (
|
|
15
|
+
class FileExporter extends EventSerializer {
|
|
16
|
+
constructor (config = {}) {
|
|
17
|
+
super(config)
|
|
18
|
+
const { pprofPrefix } = config
|
|
16
19
|
this._pprofPrefix = pprofPrefix || ''
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
export (
|
|
22
|
+
export (exportSpec) {
|
|
23
|
+
const { profiles, end } = exportSpec
|
|
20
24
|
const types = Object.keys(profiles)
|
|
21
25
|
const dateStr = formatDateTime(end)
|
|
22
26
|
const tasks = types.map(type => {
|
|
23
27
|
return writeFile(`${this._pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
|
|
24
28
|
})
|
|
25
|
-
|
|
29
|
+
tasks.push(writeFile(`event_worker_${threadId}_${dateStr}.json`, this.getEventJSON(exportSpec)))
|
|
26
30
|
return Promise.all(tasks)
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -4,9 +4,11 @@ const { EventEmitter } = require('events')
|
|
|
4
4
|
const { Config } = require('./config')
|
|
5
5
|
const { snapshotKinds } = require('./constants')
|
|
6
6
|
const { threadNamePrefix } = require('./profilers/shared')
|
|
7
|
+
const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('./webspan-utils')
|
|
7
8
|
const dc = require('dc-polyfill')
|
|
8
9
|
|
|
9
10
|
const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
|
|
11
|
+
const spanFinishedChannel = dc.channel('dd-trace:span:finish')
|
|
10
12
|
|
|
11
13
|
function maybeSourceMap (sourceMap, SourceMapper, debug) {
|
|
12
14
|
if (!sourceMap) return
|
|
@@ -21,6 +23,20 @@ function logError (logger, err) {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function findWebSpan (startedSpans, spanId) {
|
|
27
|
+
for (let i = startedSpans.length; --i >= 0;) {
|
|
28
|
+
const ispan = startedSpans[i]
|
|
29
|
+
const context = ispan.context()
|
|
30
|
+
if (context._spanId === spanId) {
|
|
31
|
+
if (isWebServerSpan(context._tags)) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
spanId = context._parentId
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
class Profiler extends EventEmitter {
|
|
25
41
|
constructor () {
|
|
26
42
|
super()
|
|
@@ -30,6 +46,7 @@ class Profiler extends EventEmitter {
|
|
|
30
46
|
this._timer = undefined
|
|
31
47
|
this._lastStart = undefined
|
|
32
48
|
this._timeoutInterval = undefined
|
|
49
|
+
this.endpointCounts = new Map()
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
start (options) {
|
|
@@ -82,6 +99,11 @@ class Profiler extends EventEmitter {
|
|
|
82
99
|
this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
83
100
|
}
|
|
84
101
|
|
|
102
|
+
if (config.endpointCollectionEnabled) {
|
|
103
|
+
this._spanFinishListener = this._onSpanFinish.bind(this)
|
|
104
|
+
spanFinishedChannel.subscribe(this._spanFinishListener)
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
this._capture(this._timeoutInterval, start)
|
|
86
108
|
return true
|
|
87
109
|
} catch (e) {
|
|
@@ -117,6 +139,11 @@ class Profiler extends EventEmitter {
|
|
|
117
139
|
|
|
118
140
|
this._enabled = false
|
|
119
141
|
|
|
142
|
+
if (this._spanFinishListener !== undefined) {
|
|
143
|
+
spanFinishedChannel.unsubscribe(this._spanFinishListener)
|
|
144
|
+
this._spanFinishListener = undefined
|
|
145
|
+
}
|
|
146
|
+
|
|
120
147
|
for (const profiler of this._config.profilers) {
|
|
121
148
|
profiler.stop()
|
|
122
149
|
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
@@ -137,6 +164,26 @@ class Profiler extends EventEmitter {
|
|
|
137
164
|
}
|
|
138
165
|
}
|
|
139
166
|
|
|
167
|
+
_onSpanFinish (span) {
|
|
168
|
+
const context = span.context()
|
|
169
|
+
const tags = context._tags
|
|
170
|
+
if (!isWebServerSpan(tags)) return
|
|
171
|
+
|
|
172
|
+
const endpointName = endpointNameFromTags(tags)
|
|
173
|
+
if (!endpointName) return
|
|
174
|
+
|
|
175
|
+
// Make sure this is the outermost web span, just in case so we don't overcount
|
|
176
|
+
if (findWebSpan(getStartedSpans(context), context._parentId)) return
|
|
177
|
+
|
|
178
|
+
let counter = this.endpointCounts.get(endpointName)
|
|
179
|
+
if (counter === undefined) {
|
|
180
|
+
counter = { count: 1 }
|
|
181
|
+
this.endpointCounts.set(endpointName, counter)
|
|
182
|
+
} else {
|
|
183
|
+
counter.count++
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
140
187
|
async _collect (snapshotKind, restart = true) {
|
|
141
188
|
if (!this._enabled) return
|
|
142
189
|
|
|
@@ -194,18 +241,23 @@ class Profiler extends EventEmitter {
|
|
|
194
241
|
|
|
195
242
|
_submit (profiles, start, end, snapshotKind) {
|
|
196
243
|
const { tags } = this._config
|
|
197
|
-
const tasks = []
|
|
198
244
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (this._logger) {
|
|
204
|
-
this._logger.warn(err)
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
tasks.push(task)
|
|
245
|
+
// Flatten endpoint counts
|
|
246
|
+
const endpointCounts = {}
|
|
247
|
+
for (const [endpoint, { count }] of this.endpointCounts) {
|
|
248
|
+
endpointCounts[endpoint] = count
|
|
208
249
|
}
|
|
250
|
+
this.endpointCounts.clear()
|
|
251
|
+
|
|
252
|
+
tags.snapshot = snapshotKind
|
|
253
|
+
const exportSpec = { profiles, start, end, tags, endpointCounts }
|
|
254
|
+
const tasks = this._config.exporters.map(exporter =>
|
|
255
|
+
exporter.export(exportSpec).catch(err => {
|
|
256
|
+
if (this._logger) {
|
|
257
|
+
this._logger.warn(err)
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
)
|
|
209
261
|
|
|
210
262
|
return Promise.all(tasks)
|
|
211
263
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { storage } = require('../../../../../datadog-core')
|
|
2
2
|
const TracingPlugin = require('../../../plugins/tracing')
|
|
3
3
|
const { performance } = require('perf_hooks')
|
|
4
4
|
|
|
5
5
|
// We are leveraging the TracingPlugin class for its functionality to bind
|
|
6
6
|
// start/error/finish methods to the appropriate diagnostic channels.
|
|
7
7
|
class EventPlugin extends TracingPlugin {
|
|
8
|
-
constructor (eventHandler) {
|
|
8
|
+
constructor (eventHandler, eventFilter) {
|
|
9
9
|
super()
|
|
10
10
|
this.eventHandler = eventHandler
|
|
11
|
-
this.
|
|
11
|
+
this.eventFilter = eventFilter
|
|
12
|
+
this.store = storage('profiling')
|
|
12
13
|
this.entryType = this.constructor.entryType
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -20,27 +21,36 @@ class EventPlugin extends TracingPlugin {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
error () {
|
|
23
|
-
this.store.getStore()
|
|
24
|
+
const store = this.store.getStore()
|
|
25
|
+
if (store) {
|
|
26
|
+
store.error = true
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
finish () {
|
|
27
|
-
const
|
|
31
|
+
const store = this.store.getStore()
|
|
32
|
+
if (!store) return
|
|
33
|
+
|
|
34
|
+
const { startEvent, startTime, error } = store
|
|
28
35
|
if (error) {
|
|
29
36
|
return // don't emit perf events for failed operations
|
|
30
37
|
}
|
|
31
38
|
const duration = performance.now() - startTime
|
|
32
39
|
|
|
33
|
-
const context = this.activeSpan?.context()
|
|
34
|
-
const _ddSpanId = context?.toSpanId()
|
|
35
|
-
const _ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || _ddSpanId
|
|
36
|
-
|
|
37
40
|
const event = {
|
|
38
41
|
entryType: this.entryType,
|
|
39
42
|
startTime,
|
|
40
|
-
duration
|
|
41
|
-
_ddSpanId,
|
|
42
|
-
_ddRootSpanId
|
|
43
|
+
duration
|
|
43
44
|
}
|
|
45
|
+
|
|
46
|
+
if (!this.eventFilter(event)) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const context = this.activeSpan?.context()
|
|
51
|
+
event._ddSpanId = context?.toSpanId()
|
|
52
|
+
event._ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || event._ddSpanId
|
|
53
|
+
|
|
44
54
|
this.eventHandler(this.extendEvent(event, startEvent))
|
|
45
55
|
}
|
|
46
56
|
}
|