dd-trace 3.44.0 → 3.46.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/MIGRATING.md +15 -0
- package/README.md +11 -9
- package/package.json +8 -7
- package/packages/datadog-instrumentations/src/cucumber.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +3 -0
- package/packages/datadog-instrumentations/src/mocha.js +9 -2
- package/packages/datadog-plugin-cucumber/src/index.js +11 -7
- package/packages/datadog-plugin-cypress/src/plugin.js +60 -46
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +31 -5
- package/packages/datadog-plugin-jest/src/util.js +38 -16
- package/packages/datadog-plugin-mocha/src/index.js +11 -2
- package/packages/datadog-plugin-playwright/src/index.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +12 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
- package/packages/dd-trace/src/appsec/reporter.js +2 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +4 -2
- package/packages/dd-trace/src/config.js +11 -7
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +25 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
- package/packages/dd-trace/src/plugins/util/test.js +2 -0
- package/packages/dd-trace/src/profiling/config.js +32 -3
- package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
- package/packages/dd-trace/src/profiling/profiler.js +18 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +10 -4
- package/packages/dd-trace/src/profiling/profilers/shared.js +6 -0
- package/packages/dd-trace/src/profiling/profilers/space.js +17 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +34 -21
- package/packages/dd-trace/src/telemetry/index.js +8 -3
- package/packages/dd-trace/src/telemetry/send-data.js +35 -16
- package/scripts/st.js +105 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
3
4
|
const TaintedUtils = require('@datadog/native-iast-taint-tracking')
|
|
4
5
|
const { storage } = require('../../../../../datadog-core')
|
|
5
6
|
const iastContextFunctions = require('../iast-context')
|
|
@@ -7,12 +8,15 @@ const iastLog = require('../iast-log')
|
|
|
7
8
|
const { EXECUTED_PROPAGATION } = require('../telemetry/iast-metric')
|
|
8
9
|
const { isDebugAllowed } = require('../telemetry/verbosity')
|
|
9
10
|
|
|
11
|
+
const mathRandomCallCh = dc.channel('datadog:random:call')
|
|
12
|
+
|
|
10
13
|
function noop (res) { return res }
|
|
11
14
|
// NOTE: methods of this object must be synchronized with csi-methods.js file definitions!
|
|
12
15
|
// Otherwise you may end up rewriting a method and not providing its rewritten implementation
|
|
13
16
|
const TaintTrackingNoop = {
|
|
14
17
|
plusOperator: noop,
|
|
15
18
|
concat: noop,
|
|
19
|
+
random: noop,
|
|
16
20
|
replace: noop,
|
|
17
21
|
slice: noop,
|
|
18
22
|
substr: noop,
|
|
@@ -110,7 +114,14 @@ function csiMethodsOverrides (getContext) {
|
|
|
110
114
|
getContext,
|
|
111
115
|
String.prototype.trim,
|
|
112
116
|
String.prototype.trimStart
|
|
113
|
-
)
|
|
117
|
+
),
|
|
118
|
+
|
|
119
|
+
random: function (res, fn) {
|
|
120
|
+
if (mathRandomCallCh.hasSubscribers) {
|
|
121
|
+
mathRandomCallCh.publish({ fn })
|
|
122
|
+
}
|
|
123
|
+
return res
|
|
124
|
+
}
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
|
|
@@ -25,7 +25,8 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
25
25
|
super()
|
|
26
26
|
|
|
27
27
|
const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
this.url = config.url || new URL(format({
|
|
29
30
|
protocol: 'http:',
|
|
30
31
|
hostname: config.hostname || 'localhost',
|
|
31
32
|
port: config.port
|
|
@@ -33,12 +34,6 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
33
34
|
|
|
34
35
|
this.scheduler = new Scheduler((cb) => this.poll(cb), pollInterval)
|
|
35
36
|
|
|
36
|
-
this.requestOptions = {
|
|
37
|
-
url,
|
|
38
|
-
method: 'POST',
|
|
39
|
-
path: '/v0.7/config'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
37
|
this.state = {
|
|
43
38
|
client: {
|
|
44
39
|
state: { // updated by `parseConfig()`
|
|
@@ -122,7 +117,13 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
122
117
|
}
|
|
123
118
|
|
|
124
119
|
poll (cb) {
|
|
125
|
-
|
|
120
|
+
const options = {
|
|
121
|
+
url: this.url,
|
|
122
|
+
method: 'POST',
|
|
123
|
+
path: '/v0.7/config'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
request(this.getPayload(), options, (err, data, statusCode) => {
|
|
126
127
|
// 404 means RC is disabled, ignore it
|
|
127
128
|
if (statusCode === 404) return cb()
|
|
128
129
|
|
|
@@ -83,7 +83,8 @@ function getSkippableSuites ({
|
|
|
83
83
|
} else {
|
|
84
84
|
let skippableSuites = []
|
|
85
85
|
try {
|
|
86
|
-
|
|
86
|
+
const parsedResponse = JSON.parse(res)
|
|
87
|
+
skippableSuites = parsedResponse
|
|
87
88
|
.data
|
|
88
89
|
.filter(({ type }) => type === testLevel)
|
|
89
90
|
.map(({ attributes: { suite, name } }) => {
|
|
@@ -92,6 +93,7 @@ function getSkippableSuites ({
|
|
|
92
93
|
}
|
|
93
94
|
return { suite, name }
|
|
94
95
|
})
|
|
96
|
+
const { meta: { correlation_id: correlationId } } = parsedResponse
|
|
95
97
|
incrementCountMetric(
|
|
96
98
|
testLevel === 'test'
|
|
97
99
|
? TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_TESTS : TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_SUITES,
|
|
@@ -100,7 +102,7 @@ function getSkippableSuites ({
|
|
|
100
102
|
)
|
|
101
103
|
distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, res.length)
|
|
102
104
|
log.debug(() => `Number of received skippable ${testLevel}s: ${skippableSuites.length}`)
|
|
103
|
-
done(null, skippableSuites)
|
|
105
|
+
done(null, skippableSuites, correlationId)
|
|
104
106
|
} catch (err) {
|
|
105
107
|
done(err)
|
|
106
108
|
}
|
|
@@ -611,7 +611,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
611
611
|
this.startupLogs = isTrue(DD_TRACE_STARTUP_LOGS)
|
|
612
612
|
// Disabled for CI Visibility's agentless
|
|
613
613
|
this.telemetry = {
|
|
614
|
-
enabled:
|
|
614
|
+
enabled: isTrue(DD_INSTRUMENTATION_TELEMETRY_ENABLED),
|
|
615
615
|
heartbeatInterval: DD_TELEMETRY_HEARTBEAT_INTERVAL,
|
|
616
616
|
debug: isTrue(DD_TELEMETRY_DEBUG),
|
|
617
617
|
logCollection: isTrue(DD_TELEMETRY_LOG_COLLECTION_ENABLED),
|
|
@@ -795,9 +795,9 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
795
795
|
tagger.add(tags, DD_TRACE_TAGS)
|
|
796
796
|
tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
|
|
797
797
|
|
|
798
|
-
this.
|
|
799
|
-
this.
|
|
800
|
-
this.
|
|
798
|
+
this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service)
|
|
799
|
+
this._setString(env, 'env', DD_ENV || tags.env)
|
|
800
|
+
this._setString(env, 'version', DD_VERSION || tags.version)
|
|
801
801
|
this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE)
|
|
802
802
|
this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
|
|
803
803
|
this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS)
|
|
@@ -812,9 +812,9 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
812
812
|
|
|
813
813
|
tagger.add(tags, options.tags)
|
|
814
814
|
|
|
815
|
-
this.
|
|
816
|
-
this.
|
|
817
|
-
this.
|
|
815
|
+
this._setString(opts, 'service', options.service || tags.service)
|
|
816
|
+
this._setString(opts, 'env', options.env || tags.env)
|
|
817
|
+
this._setString(opts, 'version', options.version || tags.version)
|
|
818
818
|
this._setUnit(opts, 'sampleRate', coalesce(options.sampleRate, options.ingestion.sampleRate))
|
|
819
819
|
this._setBoolean(opts, 'logInjection', options.logInjection)
|
|
820
820
|
this._setArray(opts, 'headerTags', options.headerTags)
|
|
@@ -875,6 +875,10 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
875
875
|
}
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
+
_setString (obj, name, value) {
|
|
879
|
+
obj[name] = value || undefined // unset for empty strings
|
|
880
|
+
}
|
|
881
|
+
|
|
878
882
|
_setTags (obj, name, value) {
|
|
879
883
|
if (!value || Object.keys(value).length === 0) {
|
|
880
884
|
return this._setValue(obj, name, null)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
const { truncateSpan, normalizeSpan } = require('./tags-processors')
|
|
3
3
|
const { AgentEncoder } = require('./0.4')
|
|
4
4
|
const { version: ddTraceVersion } = require('../../../../package.json')
|
|
5
|
-
const
|
|
5
|
+
const { ITR_CORRELATION_ID } = require('../../src/plugins/util/test')
|
|
6
|
+
const id = require('../../src/id')
|
|
6
7
|
const {
|
|
7
8
|
distributionMetric,
|
|
8
9
|
TELEMETRY_ENDPOINT_PAYLOAD_SERIALIZATION_MS,
|
|
@@ -45,7 +46,13 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
_encodeTestSuite (bytes, content) {
|
|
48
|
-
|
|
49
|
+
let keysLength = TEST_SUITE_KEYS_LENGTH
|
|
50
|
+
const itrCorrelationId = content.meta[ITR_CORRELATION_ID]
|
|
51
|
+
if (itrCorrelationId) {
|
|
52
|
+
keysLength++
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._encodeMapPrefix(bytes, keysLength)
|
|
49
56
|
this._encodeString(bytes, 'type')
|
|
50
57
|
this._encodeString(bytes, content.type)
|
|
51
58
|
|
|
@@ -58,6 +65,12 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
58
65
|
this._encodeString(bytes, 'test_suite_id')
|
|
59
66
|
this._encodeId(bytes, content.span_id)
|
|
60
67
|
|
|
68
|
+
if (itrCorrelationId) {
|
|
69
|
+
this._encodeString(bytes, ITR_CORRELATION_ID)
|
|
70
|
+
this._encodeString(bytes, itrCorrelationId)
|
|
71
|
+
delete content.meta[ITR_CORRELATION_ID]
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
this._encodeString(bytes, 'error')
|
|
62
75
|
this._encodeNumber(bytes, content.error)
|
|
63
76
|
this._encodeString(bytes, 'name')
|
|
@@ -144,6 +157,10 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
144
157
|
if (content.meta.test_suite_id) {
|
|
145
158
|
totalKeysLength = totalKeysLength + 1
|
|
146
159
|
}
|
|
160
|
+
const itrCorrelationId = content.meta[ITR_CORRELATION_ID]
|
|
161
|
+
if (itrCorrelationId) {
|
|
162
|
+
totalKeysLength = totalKeysLength + 1
|
|
163
|
+
}
|
|
147
164
|
this._encodeMapPrefix(bytes, totalKeysLength)
|
|
148
165
|
if (content.type) {
|
|
149
166
|
this._encodeString(bytes, 'type')
|
|
@@ -194,6 +211,12 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
194
211
|
delete content.meta.test_suite_id
|
|
195
212
|
}
|
|
196
213
|
|
|
214
|
+
if (itrCorrelationId) {
|
|
215
|
+
this._encodeString(bytes, ITR_CORRELATION_ID)
|
|
216
|
+
this._encodeString(bytes, itrCorrelationId)
|
|
217
|
+
delete content.meta[ITR_CORRELATION_ID]
|
|
218
|
+
}
|
|
219
|
+
|
|
197
220
|
this._encodeString(bytes, 'meta')
|
|
198
221
|
this._encodeMap(bytes, content.meta)
|
|
199
222
|
this._encodeString(bytes, 'metrics')
|
|
@@ -15,7 +15,8 @@ const {
|
|
|
15
15
|
TEST_MODULE,
|
|
16
16
|
getTestSuiteCommonTags,
|
|
17
17
|
TEST_STATUS,
|
|
18
|
-
TEST_SKIPPED_BY_ITR
|
|
18
|
+
TEST_SKIPPED_BY_ITR,
|
|
19
|
+
ITR_CORRELATION_ID
|
|
19
20
|
} = require('./util/test')
|
|
20
21
|
const Plugin = require('./plugin')
|
|
21
22
|
const { COMPONENT } = require('../constants')
|
|
@@ -53,11 +54,13 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
53
54
|
if (!this.tracer._exporter || !this.tracer._exporter.getSkippableSuites) {
|
|
54
55
|
return onDone({ err: new Error('CI Visibility was not initialized correctly') })
|
|
55
56
|
}
|
|
56
|
-
this.tracer._exporter.getSkippableSuites(this.testConfiguration, (err, skippableSuites) => {
|
|
57
|
+
this.tracer._exporter.getSkippableSuites(this.testConfiguration, (err, skippableSuites, itrCorrelationId) => {
|
|
57
58
|
if (err) {
|
|
58
59
|
log.error(`Skippable suites could not be fetched. ${err.message}`)
|
|
60
|
+
} else {
|
|
61
|
+
this.itrCorrelationId = itrCorrelationId
|
|
59
62
|
}
|
|
60
|
-
onDone({ err, skippableSuites })
|
|
63
|
+
onDone({ err, skippableSuites, itrCorrelationId })
|
|
61
64
|
})
|
|
62
65
|
})
|
|
63
66
|
|
|
@@ -95,6 +98,9 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
95
98
|
const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
96
99
|
skippedSuites.forEach((testSuite) => {
|
|
97
100
|
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
|
|
101
|
+
if (this.itrCorrelationId) {
|
|
102
|
+
testSuiteMetadata[ITR_CORRELATION_ID] = this.itrCorrelationId
|
|
103
|
+
}
|
|
98
104
|
|
|
99
105
|
this.tracer.startSpan(`${this.constructor.id}.test_suite`, {
|
|
100
106
|
childOf: this.testModuleSpan,
|
|
@@ -60,6 +60,7 @@ const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
|
|
|
60
60
|
const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
|
|
61
61
|
const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
|
|
62
62
|
const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
|
|
63
|
+
const ITR_CORRELATION_ID = 'itr_correlation_id'
|
|
63
64
|
|
|
64
65
|
const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
|
|
65
66
|
|
|
@@ -111,6 +112,7 @@ module.exports = {
|
|
|
111
112
|
TEST_CODE_COVERAGE_LINES_PCT,
|
|
112
113
|
TEST_ITR_UNSKIPPABLE,
|
|
113
114
|
TEST_ITR_FORCED_RUN,
|
|
115
|
+
ITR_CORRELATION_ID,
|
|
114
116
|
addIntelligentTestRunnerSpanTags,
|
|
115
117
|
getCoveredFilenamesFromCoverage,
|
|
116
118
|
resetCoverage,
|
|
@@ -35,10 +35,12 @@ 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,
|
|
41
42
|
DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
|
|
43
|
+
DD_PROFILING_TIMELINE_ENABLED,
|
|
42
44
|
DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
|
|
43
45
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
44
46
|
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
@@ -91,14 +93,28 @@ class Config {
|
|
|
91
93
|
logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`)
|
|
92
94
|
}
|
|
93
95
|
}
|
|
96
|
+
// Profiler sampling contexts are not available on Windows, so features
|
|
97
|
+
// depending on those (code hotspots and endpoint collection) need to default
|
|
98
|
+
// to false on Windows.
|
|
99
|
+
const samplingContextsAvailable = process.platform !== 'win32'
|
|
100
|
+
function checkOptionAllowed (option, description, condition) {
|
|
101
|
+
if (option && !condition) {
|
|
102
|
+
throw new Error(`${description} not supported on ${process.platform}.`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function checkOptionWithSamplingContextAllowed (option, description) {
|
|
106
|
+
checkOptionAllowed(option, description, samplingContextsAvailable)
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
this.flushInterval = flushInterval
|
|
95
110
|
this.uploadTimeout = uploadTimeout
|
|
96
111
|
this.sourceMap = sourceMap
|
|
97
112
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
98
113
|
this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
|
|
99
114
|
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
100
|
-
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED,
|
|
115
|
+
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable))
|
|
101
116
|
logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED')
|
|
117
|
+
checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
|
|
102
118
|
|
|
103
119
|
this.pprofPrefix = pprofPrefix
|
|
104
120
|
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
@@ -115,8 +131,13 @@ class Config {
|
|
|
115
131
|
new AgentExporter(this)
|
|
116
132
|
], this)
|
|
117
133
|
|
|
134
|
+
// OOM monitoring does not work well on Windows, so it is disabled by default.
|
|
135
|
+
const oomMonitoringSupported = process.platform !== 'win32'
|
|
136
|
+
|
|
118
137
|
const oomMonitoringEnabled = isTrue(coalesce(options.oomMonitoring,
|
|
119
|
-
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
|
|
138
|
+
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED, oomMonitoringSupported))
|
|
139
|
+
checkOptionAllowed(oomMonitoringEnabled, 'OOM monitoring', oomMonitoringSupported)
|
|
140
|
+
|
|
120
141
|
const heapLimitExtensionSize = coalesce(options.oomHeapLimitExtensionSize,
|
|
121
142
|
Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE), 0)
|
|
122
143
|
const maxHeapExtensionCount = coalesce(options.oomMaxHeapExtensionCount,
|
|
@@ -143,12 +164,20 @@ class Config {
|
|
|
143
164
|
})
|
|
144
165
|
|
|
145
166
|
this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
|
|
167
|
+
DD_PROFILING_TIMELINE_ENABLED,
|
|
146
168
|
DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
|
|
169
|
+
logExperimentalVarDeprecation('TIMELINE_ENABLED')
|
|
170
|
+
checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
|
|
147
171
|
|
|
148
172
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
149
173
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
150
|
-
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
|
|
174
|
+
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
|
|
151
175
|
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
|
|
176
|
+
checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
|
|
177
|
+
|
|
178
|
+
this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
|
|
179
|
+
DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, false))
|
|
180
|
+
checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
|
|
152
181
|
|
|
153
182
|
this.profilers = ensureProfilers(profilers, this)
|
|
154
183
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const { promisify } = require('util')
|
|
5
|
+
const { threadId } = require('worker_threads')
|
|
5
6
|
const writeFile = promisify(fs.writeFile)
|
|
6
7
|
|
|
7
8
|
function formatDateTime (t) {
|
|
@@ -19,7 +20,7 @@ class FileExporter {
|
|
|
19
20
|
const types = Object.keys(profiles)
|
|
20
21
|
const dateStr = formatDateTime(end)
|
|
21
22
|
const tasks = types.map(type => {
|
|
22
|
-
return writeFile(`${this._pprofPrefix}${type}_${dateStr}.pprof`, profiles[type])
|
|
23
|
+
return writeFile(`${this._pprofPrefix}${type}_worker_${threadId}_${dateStr}.pprof`, profiles[type])
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
return Promise.all(tasks)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { EventEmitter } = require('events')
|
|
4
4
|
const { Config } = require('./config')
|
|
5
5
|
const { snapshotKinds } = require('./constants')
|
|
6
|
+
const { threadNamePrefix } = require('./profilers/shared')
|
|
6
7
|
|
|
7
8
|
function maybeSourceMap (sourceMap, SourceMapper, debug) {
|
|
8
9
|
if (!sourceMap) return
|
|
@@ -68,7 +69,7 @@ class Profiler extends EventEmitter {
|
|
|
68
69
|
mapper,
|
|
69
70
|
nearOOMCallback: this._nearOOMExport.bind(this)
|
|
70
71
|
})
|
|
71
|
-
this._logger.debug(`Started ${profiler.type} profiler`)
|
|
72
|
+
this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
this._capture(this._timeoutInterval, start)
|
|
@@ -97,7 +98,7 @@ class Profiler extends EventEmitter {
|
|
|
97
98
|
|
|
98
99
|
// collect and export current profiles
|
|
99
100
|
// once collect returns, profilers can be safely stopped
|
|
100
|
-
this._collect(snapshotKinds.ON_SHUTDOWN)
|
|
101
|
+
this._collect(snapshotKinds.ON_SHUTDOWN, false)
|
|
101
102
|
this._stop()
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -108,13 +109,11 @@ class Profiler extends EventEmitter {
|
|
|
108
109
|
|
|
109
110
|
for (const profiler of this._config.profilers) {
|
|
110
111
|
profiler.stop()
|
|
111
|
-
this._logger.debug(`Stopped ${profiler.type} profiler`)
|
|
112
|
+
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
clearTimeout(this._timer)
|
|
115
116
|
this._timer = undefined
|
|
116
|
-
|
|
117
|
-
return this
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
_capture (timeout, start) {
|
|
@@ -128,18 +127,21 @@ class Profiler extends EventEmitter {
|
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
async _collect (snapshotKind) {
|
|
130
|
+
async _collect (snapshotKind, restart = true) {
|
|
132
131
|
if (!this._enabled) return
|
|
133
132
|
|
|
134
|
-
const
|
|
135
|
-
const
|
|
133
|
+
const startDate = this._lastStart
|
|
134
|
+
const endDate = new Date()
|
|
136
135
|
const profiles = []
|
|
137
136
|
const encodedProfiles = {}
|
|
138
137
|
|
|
139
138
|
try {
|
|
140
139
|
// collect profiles synchronously so that profilers can be safely stopped asynchronously
|
|
141
140
|
for (const profiler of this._config.profilers) {
|
|
142
|
-
const profile = profiler.profile(
|
|
141
|
+
const profile = profiler.profile(restart, startDate, endDate)
|
|
142
|
+
if (!restart) {
|
|
143
|
+
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
144
|
+
}
|
|
143
145
|
if (!profile) continue
|
|
144
146
|
profiles.push({ profiler, profile })
|
|
145
147
|
}
|
|
@@ -155,8 +157,10 @@ class Profiler extends EventEmitter {
|
|
|
155
157
|
})
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
if (restart) {
|
|
161
|
+
this._capture(this._timeoutInterval, endDate)
|
|
162
|
+
}
|
|
163
|
+
await this._submit(encodedProfiles, startDate, endDate, snapshotKind)
|
|
160
164
|
this._logger.debug('Submitted profiles')
|
|
161
165
|
} catch (err) {
|
|
162
166
|
this._logger.error(err)
|
|
@@ -196,10 +200,10 @@ class ServerlessProfiler extends Profiler {
|
|
|
196
200
|
this._flushAfterIntervals = this._config.flushInterval / 1000
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
async _collect (snapshotKind) {
|
|
200
|
-
if (this._profiledIntervals >= this._flushAfterIntervals) {
|
|
203
|
+
async _collect (snapshotKind, restart = true) {
|
|
204
|
+
if (this._profiledIntervals >= this._flushAfterIntervals || !restart) {
|
|
201
205
|
this._profiledIntervals = 0
|
|
202
|
-
await super._collect(snapshotKind)
|
|
206
|
+
await super._collect(snapshotKind, restart)
|
|
203
207
|
} else {
|
|
204
208
|
this._profiledIntervals += 1
|
|
205
209
|
this._capture(this._timeoutInterval, new Date())
|
|
@@ -159,22 +159,24 @@ class EventsProfiler {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
start () {
|
|
162
|
+
// if already started, do nothing
|
|
163
|
+
if (this._observer) return
|
|
164
|
+
|
|
162
165
|
function add (items) {
|
|
163
166
|
this.entries.push(...items.getEntries())
|
|
164
167
|
}
|
|
165
|
-
|
|
166
|
-
this._observer = new PerformanceObserver(add.bind(this))
|
|
167
|
-
}
|
|
168
|
+
this._observer = new PerformanceObserver(add.bind(this))
|
|
168
169
|
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
stop () {
|
|
172
173
|
if (this._observer) {
|
|
173
174
|
this._observer.disconnect()
|
|
175
|
+
this._observer = undefined
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
profile (startDate, endDate) {
|
|
179
|
+
profile (restart, startDate, endDate) {
|
|
178
180
|
if (this.entries.length === 0) {
|
|
179
181
|
// No events in the period; don't produce a profile
|
|
180
182
|
return null
|
|
@@ -243,6 +245,10 @@ class EventsProfiler {
|
|
|
243
245
|
unit: stringTable.dedup(pprofValueUnit)
|
|
244
246
|
})
|
|
245
247
|
|
|
248
|
+
if (!restart) {
|
|
249
|
+
this.stop()
|
|
250
|
+
}
|
|
251
|
+
|
|
246
252
|
return new Profile({
|
|
247
253
|
sampleType: [timeValueType],
|
|
248
254
|
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
@@ -29,11 +29,17 @@ function cacheThreadLabels () {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function getNonJSThreadsLabels () {
|
|
33
|
+
return { [THREAD_NAME_LABEL]: 'Non-JS threads', [THREAD_ID_LABEL]: 'NA', [OS_THREAD_ID_LABEL]: 'NA' }
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
module.exports = {
|
|
33
37
|
END_TIMESTAMP_LABEL,
|
|
34
38
|
THREAD_NAME_LABEL,
|
|
35
39
|
THREAD_ID_LABEL,
|
|
40
|
+
OS_THREAD_ID_LABEL,
|
|
36
41
|
threadNamePrefix,
|
|
37
42
|
eventLoopThreadName,
|
|
43
|
+
getNonJSThreadsLabels,
|
|
38
44
|
getThreadLabels: cacheThreadLabels()
|
|
39
45
|
}
|
|
@@ -14,9 +14,12 @@ class NativeSpaceProfiler {
|
|
|
14
14
|
this._stackDepth = options.stackDepth || 64
|
|
15
15
|
this._pprof = undefined
|
|
16
16
|
this._oomMonitoring = options.oomMonitoring || {}
|
|
17
|
+
this._started = false
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
start ({ mapper, nearOOMCallback } = {}) {
|
|
21
|
+
if (this._started) return
|
|
22
|
+
|
|
20
23
|
this._mapper = mapper
|
|
21
24
|
this._pprof = require('@datadog/pprof')
|
|
22
25
|
this._pprof.heap.start(this._samplingInterval, this._stackDepth)
|
|
@@ -31,10 +34,16 @@ class NativeSpaceProfiler {
|
|
|
31
34
|
strategiesToCallbackMode(strategies, this._pprof.heap.CallbackMode)
|
|
32
35
|
)
|
|
33
36
|
}
|
|
37
|
+
|
|
38
|
+
this._started = true
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
profile () {
|
|
37
|
-
|
|
41
|
+
profile (restart) {
|
|
42
|
+
const profile = this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
|
|
43
|
+
if (!restart) {
|
|
44
|
+
this.stop()
|
|
45
|
+
}
|
|
46
|
+
return profile
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
encode (profile) {
|
|
@@ -42,7 +51,13 @@ class NativeSpaceProfiler {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
stop () {
|
|
54
|
+
if (!this._started) return
|
|
45
55
|
this._pprof.heap.stop()
|
|
56
|
+
this._started = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isStarted () {
|
|
60
|
+
return this._started
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
63
|
|