dd-trace 4.11.1 → 4.16.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 +1 -0
- package/README.md +4 -9
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +44 -0
- package/package.json +9 -6
- package/packages/datadog-esbuild/index.js +57 -32
- package/packages/datadog-instrumentations/src/body-parser.js +2 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
- package/packages/datadog-instrumentations/src/cucumber.js +30 -11
- package/packages/datadog-instrumentations/src/express.js +1 -1
- package/packages/datadog-instrumentations/src/graphql.js +10 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +22 -11
- package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
- package/packages/datadog-instrumentations/src/mocha.js +33 -8
- package/packages/datadog-instrumentations/src/mysql.js +39 -1
- package/packages/datadog-instrumentations/src/next.js +47 -19
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +60 -15
- package/packages/datadog-instrumentations/src/playwright.js +15 -3
- package/packages/datadog-plugin-cucumber/src/index.js +14 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
- package/packages/datadog-plugin-jest/src/index.js +10 -2
- package/packages/datadog-plugin-jest/src/util.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +14 -2
- package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
- package/packages/datadog-plugin-mysql/src/index.js +2 -2
- package/packages/datadog-plugin-next/src/index.js +22 -5
- package/packages/datadog-plugin-pg/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/channels.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
- package/packages/dd-trace/src/appsec/index.js +42 -7
- package/packages/dd-trace/src/appsec/recommended.json +655 -31
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/reporter.js +26 -0
- package/packages/dd-trace/src/appsec/telemetry.js +132 -0
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
- package/packages/dd-trace/src/datastreams/processor.js +6 -2
- package/packages/dd-trace/src/dogstatsd.js +108 -8
- package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
- package/packages/dd-trace/src/exporters/common/request.js +13 -4
- package/packages/dd-trace/src/format.js +6 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -5
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -3
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +20 -3
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
- package/packages/dd-trace/src/proxy.js +13 -2
- package/packages/dd-trace/src/ritm.js +10 -2
- package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
- package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
- package/packages/dd-trace/src/telemetry/index.js +21 -2
- package/packages/dd-trace/src/util.js +1 -1
|
@@ -8,7 +8,7 @@ const semver = require('semver')
|
|
|
8
8
|
const SpanContext = require('./span_context')
|
|
9
9
|
const id = require('../id')
|
|
10
10
|
const tagger = require('../tagger')
|
|
11
|
-
const
|
|
11
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
12
12
|
const log = require('../log')
|
|
13
13
|
const { storage } = require('../../../datadog-core')
|
|
14
14
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
@@ -79,11 +79,11 @@ class DatadogSpan {
|
|
|
79
79
|
this._startTime = fields.startTime || this._getTime()
|
|
80
80
|
|
|
81
81
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
83
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
runtimeMetrics.increment('runtime.node.spans.open') // unfinished for real
|
|
86
|
+
runtimeMetrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
|
|
87
87
|
|
|
88
88
|
unfinishedRegistry.register(this, operationName, this)
|
|
89
89
|
}
|
|
@@ -159,13 +159,13 @@ class DatadogSpan {
|
|
|
159
159
|
getIntegrationCounter('span_finished', this._integrationName).inc()
|
|
160
160
|
|
|
161
161
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished')
|
|
163
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
|
|
164
|
+
runtimeMetrics.increment('runtime.node.spans.finished')
|
|
165
|
+
runtimeMetrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
runtimeMetrics.decrement('runtime.node.spans.open') // unfinished for real
|
|
168
|
+
runtimeMetrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
|
|
169
169
|
|
|
170
170
|
unfinishedRegistry.unregister(this)
|
|
171
171
|
finishedRegistry.register(this, this._name)
|
|
@@ -243,8 +243,8 @@ function createRegistry (type) {
|
|
|
243
243
|
if (!semver.satisfies(process.version, '>=14.6')) return
|
|
244
244
|
|
|
245
245
|
return new global.FinalizationRegistry(name => {
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}`)
|
|
247
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
|
|
248
248
|
})
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -11,7 +11,7 @@ const LogPropagator = require('./propagation/log')
|
|
|
11
11
|
const formats = require('../../../../ext/formats')
|
|
12
12
|
|
|
13
13
|
const log = require('../log')
|
|
14
|
-
const
|
|
14
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
15
15
|
const getExporter = require('../exporter')
|
|
16
16
|
const SpanContext = require('./span_context')
|
|
17
17
|
|
|
@@ -26,8 +26,6 @@ class DatadogTracer {
|
|
|
26
26
|
this._version = config.version
|
|
27
27
|
this._env = config.env
|
|
28
28
|
this._tags = config.tags
|
|
29
|
-
this._computePeerService = config.spanComputePeerService
|
|
30
|
-
this._peerServiceMapping = config.peerServiceMapping
|
|
31
29
|
this._logInjection = config.logInjection
|
|
32
30
|
this._debug = config.debug
|
|
33
31
|
this._prioritySampler = new PrioritySampler(config.env, config.sampler)
|
|
@@ -82,7 +80,7 @@ class DatadogTracer {
|
|
|
82
80
|
this._propagators[format].inject(spanContext, carrier)
|
|
83
81
|
} catch (e) {
|
|
84
82
|
log.error(e)
|
|
85
|
-
|
|
83
|
+
runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -91,7 +89,7 @@ class DatadogTracer {
|
|
|
91
89
|
return this._propagators[format].extract(carrier)
|
|
92
90
|
} catch (e) {
|
|
93
91
|
log.error(e)
|
|
94
|
-
|
|
92
|
+
runtimeMetrics.increment('datadog.tracer.node.extract.errors', true)
|
|
95
93
|
return null
|
|
96
94
|
}
|
|
97
95
|
}
|
|
@@ -72,11 +72,10 @@ module.exports = class PluginManager {
|
|
|
72
72
|
const Plugin = pluginClasses[name]
|
|
73
73
|
|
|
74
74
|
if (!Plugin) return
|
|
75
|
+
if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
|
|
75
76
|
if (!this._pluginsByName[name]) {
|
|
76
77
|
this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
|
|
77
78
|
}
|
|
78
|
-
if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
|
|
79
|
-
|
|
80
79
|
const pluginConfig = this._configsByName[name] || {
|
|
81
80
|
enabled: this._tracerConfig.plugins !== false
|
|
82
81
|
}
|
|
@@ -12,7 +12,10 @@ const {
|
|
|
12
12
|
TEST_MODULE_ID,
|
|
13
13
|
TEST_SESSION_ID,
|
|
14
14
|
TEST_COMMAND,
|
|
15
|
-
TEST_MODULE
|
|
15
|
+
TEST_MODULE,
|
|
16
|
+
getTestSuiteCommonTags,
|
|
17
|
+
TEST_STATUS,
|
|
18
|
+
TEST_SKIPPED_BY_ITR
|
|
16
19
|
} = require('./util/test')
|
|
17
20
|
const Plugin = require('./plugin')
|
|
18
21
|
const { COMPONENT } = require('../constants')
|
|
@@ -77,6 +80,24 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
77
80
|
}
|
|
78
81
|
})
|
|
79
82
|
})
|
|
83
|
+
|
|
84
|
+
this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
|
|
85
|
+
const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
86
|
+
skippedSuites.forEach((testSuite) => {
|
|
87
|
+
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
|
|
88
|
+
|
|
89
|
+
this.tracer.startSpan(`${this.constructor.id}.test_suite`, {
|
|
90
|
+
childOf: this.testModuleSpan,
|
|
91
|
+
tags: {
|
|
92
|
+
[COMPONENT]: this.constructor.id,
|
|
93
|
+
...this.testEnvironmentMetadata,
|
|
94
|
+
...testSuiteMetadata,
|
|
95
|
+
[TEST_STATUS]: 'skip',
|
|
96
|
+
[TEST_SKIPPED_BY_ITR]: 'true'
|
|
97
|
+
}
|
|
98
|
+
}).finish()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
configure (config) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const StoragePlugin = require('./storage')
|
|
4
|
+
const { PEER_SERVICE_KEY } = require('../constants')
|
|
4
5
|
|
|
5
6
|
class DatabasePlugin extends StoragePlugin {
|
|
6
7
|
static get operation () { return 'query' }
|
|
@@ -38,20 +39,29 @@ class DatabasePlugin extends StoragePlugin {
|
|
|
38
39
|
`ddps='${encodedDdps}',ddpv='${encodedDdpv}'`
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
getDbmServiceName (span, tracerService) {
|
|
43
|
+
if (this._tracerConfig.spanComputePeerService) {
|
|
44
|
+
const peerData = this.getPeerService(span.context()._tags)
|
|
45
|
+
return this.getPeerServiceRemap(peerData)[PEER_SERVICE_KEY] || tracerService
|
|
46
|
+
}
|
|
47
|
+
return tracerService
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
injectDbmQuery (span, query, serviceName, isPreparedStatement = false) {
|
|
42
51
|
const mode = this.config.dbmPropagationMode
|
|
52
|
+
const dbmService = this.getDbmServiceName(span, serviceName)
|
|
43
53
|
|
|
44
54
|
if (mode === 'disabled') {
|
|
45
55
|
return query
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
const servicePropagation = this.createDBMPropagationCommentService(
|
|
58
|
+
const servicePropagation = this.createDBMPropagationCommentService(dbmService)
|
|
49
59
|
|
|
50
60
|
if (isPreparedStatement || mode === 'service') {
|
|
51
61
|
return `/*${servicePropagation}*/ ${query}`
|
|
52
62
|
} else if (mode === 'full') {
|
|
53
|
-
|
|
54
|
-
const traceparent =
|
|
63
|
+
span.setTag('_dd.dbm_trace_injected', 'true')
|
|
64
|
+
const traceparent = span._spanContext.toTraceparent()
|
|
55
65
|
return `/*${servicePropagation},traceparent='${traceparent}'*/ ${query}`
|
|
56
66
|
}
|
|
57
67
|
}
|
|
@@ -64,6 +64,7 @@ module.exports = {
|
|
|
64
64
|
get 'pg' () { return require('../../../datadog-plugin-pg/src') },
|
|
65
65
|
get 'pino' () { return require('../../../datadog-plugin-pino/src') },
|
|
66
66
|
get 'pino-pretty' () { return require('../../../datadog-plugin-pino/src') },
|
|
67
|
+
get 'playwright' () { return require('../../../datadog-plugin-playwright/src') },
|
|
67
68
|
get 'redis' () { return require('../../../datadog-plugin-redis/src') },
|
|
68
69
|
get 'restify' () { return require('../../../datadog-plugin-restify/src') },
|
|
69
70
|
get 'rhea' () { return require('../../../datadog-plugin-rhea/src') },
|
|
@@ -64,10 +64,11 @@ class OutboundPlugin extends TracingPlugin {
|
|
|
64
64
|
* peer service and add the value we overrode.
|
|
65
65
|
*/
|
|
66
66
|
const peerService = peerData[PEER_SERVICE_KEY]
|
|
67
|
-
|
|
67
|
+
const mappedService = this._tracerConfig.peerServiceMapping?.[peerService]
|
|
68
|
+
if (peerService && mappedService) {
|
|
68
69
|
return {
|
|
69
70
|
...peerData,
|
|
70
|
-
[PEER_SERVICE_KEY]:
|
|
71
|
+
[PEER_SERVICE_KEY]: mappedService,
|
|
71
72
|
[PEER_SERVICE_REMAP_KEY]: peerService
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -80,7 +81,7 @@ class OutboundPlugin extends TracingPlugin {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
tagPeerService (span) {
|
|
83
|
-
if (this.
|
|
84
|
+
if (this._tracerConfig.spanComputePeerService) {
|
|
84
85
|
const peerData = this.getPeerService(span.context()._tags)
|
|
85
86
|
if (peerData !== undefined) {
|
|
86
87
|
span.addTags(this.getPeerServiceRemap(peerData))
|
|
@@ -47,6 +47,7 @@ const TEST_SESSION_ID = 'test_session_id'
|
|
|
47
47
|
const TEST_MODULE_ID = 'test_module_id'
|
|
48
48
|
const TEST_SUITE_ID = 'test_suite_id'
|
|
49
49
|
const TEST_TOOLCHAIN = 'test.toolchain'
|
|
50
|
+
const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
|
|
50
51
|
|
|
51
52
|
const CI_APP_ORIGIN = 'ciapp-test'
|
|
52
53
|
|
|
@@ -54,6 +55,8 @@ const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
|
54
55
|
|
|
55
56
|
const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
|
|
56
57
|
const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
|
|
58
|
+
const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
|
|
59
|
+
const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
|
|
57
60
|
const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
|
|
58
61
|
|
|
59
62
|
const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
|
|
@@ -80,6 +83,7 @@ module.exports = {
|
|
|
80
83
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
81
84
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
82
85
|
TEST_SOURCE_START,
|
|
86
|
+
TEST_SKIPPED_BY_ITR,
|
|
83
87
|
getTestEnvironmentMetadata,
|
|
84
88
|
getTestParametersString,
|
|
85
89
|
finishAllTraceSpans,
|
|
@@ -99,6 +103,8 @@ module.exports = {
|
|
|
99
103
|
TEST_ITR_TESTS_SKIPPED,
|
|
100
104
|
TEST_MODULE,
|
|
101
105
|
TEST_ITR_SKIPPING_ENABLED,
|
|
106
|
+
TEST_ITR_SKIPPING_TYPE,
|
|
107
|
+
TEST_ITR_SKIPPING_COUNT,
|
|
102
108
|
TEST_CODE_COVERAGE_ENABLED,
|
|
103
109
|
TEST_CODE_COVERAGE_LINES_PCT,
|
|
104
110
|
addIntelligentTestRunnerSpanTags,
|
|
@@ -133,13 +139,13 @@ function removeInvalidMetadata (metadata) {
|
|
|
133
139
|
return Object.keys(metadata).reduce((filteredTags, tag) => {
|
|
134
140
|
if (tag === GIT_REPOSITORY_URL) {
|
|
135
141
|
if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
|
|
136
|
-
log.error(
|
|
142
|
+
log.error(`Repository URL is not a valid repository URL: ${metadata[GIT_REPOSITORY_URL]}.`)
|
|
137
143
|
return filteredTags
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
if (tag === GIT_COMMIT_SHA) {
|
|
141
147
|
if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
|
|
142
|
-
log.error(
|
|
148
|
+
log.error(`Git commit SHA must be a full-length git SHA: ${metadata[GIT_COMMIT_SHA]}.`)
|
|
143
149
|
return filteredTags
|
|
144
150
|
}
|
|
145
151
|
}
|
|
@@ -354,14 +360,25 @@ function getTestSuiteCommonTags (command, testFrameworkVersion, testSuite, testF
|
|
|
354
360
|
function addIntelligentTestRunnerSpanTags (
|
|
355
361
|
testSessionSpan,
|
|
356
362
|
testModuleSpan,
|
|
357
|
-
{
|
|
363
|
+
{
|
|
364
|
+
isSuitesSkipped,
|
|
365
|
+
isSuitesSkippingEnabled,
|
|
366
|
+
isCodeCoverageEnabled,
|
|
367
|
+
testCodeCoverageLinesTotal,
|
|
368
|
+
skippingCount,
|
|
369
|
+
skippingType = 'suite'
|
|
370
|
+
}
|
|
358
371
|
) {
|
|
359
372
|
testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
360
373
|
testSessionSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
374
|
+
testSessionSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
|
|
375
|
+
testSessionSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
|
|
361
376
|
testSessionSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
362
377
|
|
|
363
378
|
testModuleSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
364
379
|
testModuleSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
380
|
+
testModuleSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
|
|
381
|
+
testModuleSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
|
|
365
382
|
testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
366
383
|
|
|
367
384
|
// If suites have been skipped we don't want to report the total coverage, as it will be wrong
|
|
@@ -31,6 +31,7 @@ class Config {
|
|
|
31
31
|
DD_PROFILING_UPLOAD_PERIOD,
|
|
32
32
|
DD_PROFILING_PPROF_PREFIX,
|
|
33
33
|
DD_PROFILING_HEAP_ENABLED,
|
|
34
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
|
|
34
35
|
DD_PROFILING_WALLTIME_ENABLED,
|
|
35
36
|
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
|
|
36
37
|
DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
@@ -76,7 +77,8 @@ class Config {
|
|
|
76
77
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
77
78
|
this.endpointCollectionEnabled = endpointCollectionEnabled
|
|
78
79
|
this.pprofPrefix = pprofPrefix
|
|
79
|
-
|
|
80
|
+
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
81
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
|
|
80
82
|
const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
|
|
81
83
|
const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
|
|
82
84
|
this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
const { storage } = require('../../../../datadog-core')
|
|
4
4
|
|
|
5
5
|
const dc = require('../../../../diagnostics_channel')
|
|
6
|
+
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
|
+
const { WEB } = require('../../../../../ext/types')
|
|
8
|
+
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
|
+
const telemetryMetrics = require('../../telemetry/metrics')
|
|
6
10
|
|
|
7
11
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
8
12
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
13
|
+
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
9
14
|
|
|
10
15
|
let kSampleCount
|
|
11
16
|
|
|
@@ -41,13 +46,13 @@ function getSpanContextTags (span) {
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
function isWebServerSpan (tags) {
|
|
44
|
-
return tags[
|
|
49
|
+
return tags[SPAN_TYPE] === WEB
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
function endpointNameFromTags (tags) {
|
|
48
|
-
return tags[
|
|
49
|
-
tags[
|
|
50
|
-
tags[
|
|
53
|
+
return tags[RESOURCE_NAME] || [
|
|
54
|
+
tags[HTTP_METHOD],
|
|
55
|
+
tags[HTTP_ROUTE]
|
|
51
56
|
].filter(v => v).join(' ')
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -81,6 +86,7 @@ class NativeWallProfiler {
|
|
|
81
86
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
82
87
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
83
88
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
89
|
+
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
84
90
|
this._mapper = undefined
|
|
85
91
|
this._pprof = undefined
|
|
86
92
|
|
|
@@ -99,7 +105,7 @@ class NativeWallProfiler {
|
|
|
99
105
|
|
|
100
106
|
if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
|
|
101
107
|
this._logger.debug(
|
|
102
|
-
`Wall profiler: Enable
|
|
108
|
+
`Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
|
|
103
109
|
this._emittedFFMessage = true
|
|
104
110
|
}
|
|
105
111
|
|
|
@@ -120,7 +126,8 @@ class NativeWallProfiler {
|
|
|
120
126
|
durationMillis: this._flushIntervalMillis,
|
|
121
127
|
sourceMapper: this._mapper,
|
|
122
128
|
withContexts: this._codeHotspotsEnabled,
|
|
123
|
-
lineNumbers: false
|
|
129
|
+
lineNumbers: false,
|
|
130
|
+
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
124
131
|
})
|
|
125
132
|
|
|
126
133
|
if (this._codeHotspotsEnabled) {
|
|
@@ -163,6 +170,16 @@ class NativeWallProfiler {
|
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
_reportV8bug (maybeBug) {
|
|
174
|
+
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
|
|
175
|
+
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
176
|
+
this._logger?.warn(`Wall profiler: ${maybeBug ? 'possible ' : ''}v8 profiler stuck event loop detected.`)
|
|
177
|
+
// report as runtime metric (can be removed in the future when telemetry is mature)
|
|
178
|
+
runtimeMetrics.increment(`runtime.node.profiler.${metric}`, tag, true)
|
|
179
|
+
// report as telemetry metric
|
|
180
|
+
profilerTelemetryMetrics.count(metric, [tag]).inc()
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
_stop (restart) {
|
|
167
184
|
if (!this._started) return
|
|
168
185
|
if (this._codeHotspotsEnabled) {
|
|
@@ -170,7 +187,14 @@ class NativeWallProfiler {
|
|
|
170
187
|
this._enter()
|
|
171
188
|
this._lastSampleCount = 0
|
|
172
189
|
}
|
|
173
|
-
|
|
190
|
+
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
|
|
191
|
+
if (restart) {
|
|
192
|
+
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
193
|
+
if (v8BugDetected !== 0) {
|
|
194
|
+
this._reportV8bug(v8BugDetected === 1)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return profile
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
profile () {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
const NoopProxy = require('./noop/proxy')
|
|
3
3
|
const DatadogTracer = require('./tracer')
|
|
4
4
|
const Config = require('./config')
|
|
5
|
-
const
|
|
5
|
+
const runtimeMetrics = require('./runtime_metrics')
|
|
6
6
|
const log = require('./log')
|
|
7
7
|
const { setStartupLogPluginManager } = require('./startup-log')
|
|
8
8
|
const telemetry = require('./telemetry')
|
|
9
9
|
const PluginManager = require('./plugin_manager')
|
|
10
10
|
const remoteConfig = require('./appsec/remote_config')
|
|
11
11
|
const AppsecSdk = require('./appsec/sdk')
|
|
12
|
+
const dogstatsd = require('./dogstatsd')
|
|
12
13
|
|
|
13
14
|
class Tracer extends NoopProxy {
|
|
14
15
|
constructor () {
|
|
@@ -16,6 +17,7 @@ class Tracer extends NoopProxy {
|
|
|
16
17
|
|
|
17
18
|
this._initialized = false
|
|
18
19
|
this._pluginManager = new PluginManager(this)
|
|
20
|
+
this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
init (options) {
|
|
@@ -26,6 +28,15 @@ class Tracer extends NoopProxy {
|
|
|
26
28
|
try {
|
|
27
29
|
const config = new Config(options) // TODO: support dynamic code config
|
|
28
30
|
|
|
31
|
+
if (config.dogstatsd) {
|
|
32
|
+
// Custom Metrics
|
|
33
|
+
this.dogstatsd = new dogstatsd.CustomMetrics(config)
|
|
34
|
+
|
|
35
|
+
setInterval(() => {
|
|
36
|
+
this.dogstatsd.flush()
|
|
37
|
+
}, 10 * 1000).unref()
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
if (config.remoteConfig.enabled && !config.isCiVisibility) {
|
|
30
41
|
const rc = remoteConfig.enable(config)
|
|
31
42
|
|
|
@@ -58,7 +69,7 @@ class Tracer extends NoopProxy {
|
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
if (config.runtimeMetrics) {
|
|
61
|
-
|
|
72
|
+
runtimeMetrics.start(config)
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
if (config.tracing) {
|
|
@@ -117,8 +117,16 @@ function Hook (modules, options, onrequire) {
|
|
|
117
117
|
// abort if _resolveLookupPaths return null
|
|
118
118
|
return exports
|
|
119
119
|
}
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
|
|
121
|
+
let res
|
|
122
|
+
try {
|
|
123
|
+
res = Module._findPath(name, [basedir, ...paths])
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// case where the file specified in package.json "main" doesn't exist
|
|
126
|
+
// in this case, the file is treated as module-internal
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!res || res !== filename) {
|
|
122
130
|
// this is a module-internal file
|
|
123
131
|
// use the module-relative path to the file, prefixed by original module name
|
|
124
132
|
name = name + path.sep + path.relative(basedir, filename)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// TODO: capture every second and flush every 10 seconds
|
|
4
4
|
|
|
5
|
-
const { URL, format } = require('url')
|
|
6
5
|
const v8 = require('v8')
|
|
7
6
|
const os = require('os')
|
|
8
7
|
const { DogStatsDClient } = require('./dogstatsd')
|
|
@@ -27,21 +26,7 @@ reset()
|
|
|
27
26
|
|
|
28
27
|
module.exports = {
|
|
29
28
|
start (config) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
Object.keys(config.tags)
|
|
33
|
-
.filter(key => typeof config.tags[key] === 'string')
|
|
34
|
-
.filter(key => {
|
|
35
|
-
// Skip runtime-id unless enabled as cardinality may be too high
|
|
36
|
-
if (key !== 'runtime-id') return true
|
|
37
|
-
return (config.experimental && config.experimental.runtimeId)
|
|
38
|
-
})
|
|
39
|
-
.forEach(key => {
|
|
40
|
-
// https://docs.datadoghq.com/tagging/#defining-tags
|
|
41
|
-
const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
|
|
42
|
-
|
|
43
|
-
tags.push(`${key}:${value}`)
|
|
44
|
-
})
|
|
29
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
45
30
|
|
|
46
31
|
try {
|
|
47
32
|
nativeMetrics = require('@datadog/native-metrics')
|
|
@@ -51,22 +36,6 @@ module.exports = {
|
|
|
51
36
|
nativeMetrics = null
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
const clientConfig = {
|
|
55
|
-
host: config.dogstatsd.hostname,
|
|
56
|
-
port: config.dogstatsd.port,
|
|
57
|
-
tags
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (config.url) {
|
|
61
|
-
clientConfig.metricsProxyUrl = config.url
|
|
62
|
-
} else if (config.port) {
|
|
63
|
-
clientConfig.metricsProxyUrl = new URL(format({
|
|
64
|
-
protocol: 'http:',
|
|
65
|
-
hostname: config.hostname || 'localhost',
|
|
66
|
-
port: config.port
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
39
|
client = new DogStatsDClient(clientConfig)
|
|
71
40
|
|
|
72
41
|
time = process.hrtime()
|
|
@@ -15,6 +15,7 @@ const FILE_URI_START = `file://`
|
|
|
15
15
|
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
16
16
|
|
|
17
17
|
let immediate, config, application, host
|
|
18
|
+
let isFirstModule = true
|
|
18
19
|
|
|
19
20
|
function waitAndSend (config, application, host) {
|
|
20
21
|
if (!immediate) {
|
|
@@ -36,7 +37,21 @@ function waitAndSend (config, application, host) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function loadAllTheLoadedModules () {
|
|
41
|
+
if (require.cache) {
|
|
42
|
+
const filenames = Object.keys(require.cache)
|
|
43
|
+
filenames.forEach(filename => {
|
|
44
|
+
onModuleLoad({ filename })
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
function onModuleLoad (data) {
|
|
50
|
+
if (isFirstModule) {
|
|
51
|
+
isFirstModule = false
|
|
52
|
+
loadAllTheLoadedModules()
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
if (data) {
|
|
41
56
|
let filename = data.filename
|
|
42
57
|
if (filename && filename.startsWith(FILE_URI_START)) {
|
|
@@ -17,6 +17,7 @@ let pluginManager
|
|
|
17
17
|
let application
|
|
18
18
|
let host
|
|
19
19
|
let interval
|
|
20
|
+
let heartbeatTimeout
|
|
20
21
|
let heartbeatInterval
|
|
21
22
|
const sentIntegrations = new Set()
|
|
22
23
|
|
|
@@ -56,11 +57,20 @@ function appStarted () {
|
|
|
56
57
|
return {
|
|
57
58
|
integrations: getIntegrations(),
|
|
58
59
|
dependencies: [],
|
|
59
|
-
configuration: flatten(config),
|
|
60
|
+
configuration: flatten(formatConfig(config)),
|
|
60
61
|
additional_payload: []
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function formatConfig (config) {
|
|
66
|
+
// format peerServiceMapping from an object to a string map in order for
|
|
67
|
+
// telemetry intake to accept the configuration
|
|
68
|
+
config.peerServiceMapping = config.peerServiceMapping
|
|
69
|
+
? Object.entries(config.peerServiceMapping).map(([key, value]) => `${key}:${value}`).join(',')
|
|
70
|
+
: ''
|
|
71
|
+
return config
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
function onBeforeExit () {
|
|
65
75
|
process.removeListener('beforeExit', onBeforeExit)
|
|
66
76
|
sendData(config, application, host, 'app-closing')
|
|
@@ -110,6 +120,14 @@ function getTelemetryData () {
|
|
|
110
120
|
return { config, application, host, heartbeatInterval }
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
function heartbeat (config, application, host) {
|
|
124
|
+
heartbeatTimeout = setTimeout(() => {
|
|
125
|
+
sendData(config, application, host, 'app-heartbeat')
|
|
126
|
+
heartbeat(config, application, host)
|
|
127
|
+
}, heartbeatInterval).unref()
|
|
128
|
+
return heartbeatTimeout
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
function start (aConfig, thePluginManager) {
|
|
114
132
|
if (!aConfig.telemetry.enabled) {
|
|
115
133
|
return
|
|
@@ -122,9 +140,9 @@ function start (aConfig, thePluginManager) {
|
|
|
122
140
|
|
|
123
141
|
dependencies.start(config, application, host)
|
|
124
142
|
sendData(config, application, host, 'app-started', appStarted())
|
|
143
|
+
heartbeat(config, application, host)
|
|
125
144
|
interval = setInterval(() => {
|
|
126
145
|
metricsManager.send(config, application, host)
|
|
127
|
-
sendData(config, application, host, 'app-heartbeat')
|
|
128
146
|
}, heartbeatInterval)
|
|
129
147
|
interval.unref()
|
|
130
148
|
process.on('beforeExit', onBeforeExit)
|
|
@@ -137,6 +155,7 @@ function stop () {
|
|
|
137
155
|
return
|
|
138
156
|
}
|
|
139
157
|
clearInterval(interval)
|
|
158
|
+
clearTimeout(heartbeatTimeout)
|
|
140
159
|
process.removeListener('beforeExit', onBeforeExit)
|
|
141
160
|
|
|
142
161
|
telemetryStopChannel.publish(getTelemetryData())
|
|
@@ -66,7 +66,7 @@ function globMatch (pattern, subject) {
|
|
|
66
66
|
function calculateDDBasePath (dirname) {
|
|
67
67
|
const dirSteps = dirname.split(path.sep)
|
|
68
68
|
const packagesIndex = dirSteps.lastIndexOf('packages')
|
|
69
|
-
return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
|
|
69
|
+
return dirSteps.slice(0, packagesIndex + 1).join(path.sep) + path.sep
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
module.exports = {
|