dd-trace 4.19.0 → 4.21.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/index.d.ts +24 -0
- package/package.json +6 -5
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/client.js +22 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/next.js +3 -1
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
- package/packages/datadog-plugin-next/src/index.js +7 -7
- package/packages/dd-trace/src/appsec/addresses.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +14 -2
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
- package/packages/dd-trace/src/config.js +37 -3
- package/packages/dd-trace/src/datastreams/processor.js +107 -12
- package/packages/dd-trace/src/id.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +17 -10
- package/packages/dd-trace/src/profiling/profiler.js +10 -4
- package/packages/dd-trace/src/profiling/profilers/events.js +171 -73
- package/packages/dd-trace/src/profiling/profilers/wall.js +93 -67
- package/packages/dd-trace/src/proxy.js +21 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
- package/packages/dd-trace/src/telemetry/index.js +135 -43
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
|
@@ -236,11 +236,20 @@ class TextMapPropagator {
|
|
|
236
236
|
_extractDatadogContext (carrier) {
|
|
237
237
|
const spanContext = this._extractGenericContext(carrier, traceKey, spanKey, 10)
|
|
238
238
|
|
|
239
|
-
if (spanContext)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
if (!spanContext) return spanContext
|
|
240
|
+
|
|
241
|
+
this._extractOrigin(carrier, spanContext)
|
|
242
|
+
this._extractBaggageItems(carrier, spanContext)
|
|
243
|
+
this._extractSamplingPriority(carrier, spanContext)
|
|
244
|
+
this._extractTags(carrier, spanContext)
|
|
245
|
+
|
|
246
|
+
if (this._config.tracePropagationExtractFirst) return spanContext
|
|
247
|
+
|
|
248
|
+
const tc = this._extractTraceparentContext(carrier)
|
|
249
|
+
|
|
250
|
+
if (tc && spanContext._traceId.equals(tc._traceId)) {
|
|
251
|
+
spanContext._traceparent = tc._traceparent
|
|
252
|
+
spanContext._tracestate = tc._tracestate
|
|
244
253
|
}
|
|
245
254
|
|
|
246
255
|
return spanContext
|
|
@@ -13,6 +13,7 @@ const log = require('../log')
|
|
|
13
13
|
const { storage } = require('../../../datadog-core')
|
|
14
14
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
15
15
|
const { channel } = require('dc-polyfill')
|
|
16
|
+
const spanleak = require('../spanleak')
|
|
16
17
|
|
|
17
18
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
18
19
|
|
|
@@ -90,6 +91,7 @@ class DatadogSpan {
|
|
|
90
91
|
|
|
91
92
|
unfinishedRegistry.register(this, operationName, this)
|
|
92
93
|
}
|
|
94
|
+
spanleak.addSpan(this, operationName)
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
toString () {
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
get '@opensearch-project/opensearch' () { return require('../../../datadog-plugin-opensearch/src') },
|
|
18
18
|
get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
|
|
19
19
|
get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
20
|
+
get 'aerospike' () { return require('../../../datadog-plugin-aerospike/src') },
|
|
20
21
|
get 'amqp10' () { return require('../../../datadog-plugin-amqp10/src') },
|
|
21
22
|
get 'amqplib' () { return require('../../../datadog-plugin-amqplib/src') },
|
|
22
23
|
get 'aws-sdk' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
@@ -111,7 +111,7 @@ function getLatestCommits () {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function
|
|
114
|
+
function getCommitsRevList (commitsToExclude, commitsToInclude) {
|
|
115
115
|
const commitsToExcludeString = commitsToExclude.map(commit => `^${commit}`)
|
|
116
116
|
|
|
117
117
|
try {
|
|
@@ -236,7 +236,7 @@ module.exports = {
|
|
|
236
236
|
getLatestCommits,
|
|
237
237
|
getRepositoryUrl,
|
|
238
238
|
generatePackFilesForCommits,
|
|
239
|
-
|
|
239
|
+
getCommitsRevList,
|
|
240
240
|
GIT_REV_LIST_MAX_BUFFER,
|
|
241
241
|
isShallowRepository,
|
|
242
242
|
unshallowRepository
|
|
@@ -397,8 +397,9 @@ function addIntelligentTestRunnerSpanTags (
|
|
|
397
397
|
testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
//
|
|
401
|
-
|
|
400
|
+
// This will not be reported unless the user has manually added code coverage.
|
|
401
|
+
// This is always the case for Mocha and Cucumber, but not for Jest.
|
|
402
|
+
if (testCodeCoverageLinesTotal !== undefined) {
|
|
402
403
|
testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
403
404
|
testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
404
405
|
}
|
|
@@ -8,7 +8,7 @@ process.once('beforeExit', () => { profiler.stop() })
|
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
start: config => {
|
|
11
|
-
const { service, version, env, url, hostname, port, tags } = config
|
|
11
|
+
const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
|
|
12
12
|
const { enabled, sourceMap, exporters } = config.profiling
|
|
13
13
|
const logger = {
|
|
14
14
|
debug: (message) => log.debug(message),
|
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
error: (message) => log.error(message)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
profiler.start({
|
|
20
|
+
return profiler.start({
|
|
21
21
|
enabled,
|
|
22
22
|
service,
|
|
23
23
|
version,
|
|
@@ -28,7 +28,9 @@ module.exports = {
|
|
|
28
28
|
url,
|
|
29
29
|
hostname,
|
|
30
30
|
port,
|
|
31
|
-
tags
|
|
31
|
+
tags,
|
|
32
|
+
repositoryUrl,
|
|
33
|
+
commitSHA
|
|
32
34
|
})
|
|
33
35
|
},
|
|
34
36
|
|
|
@@ -11,6 +11,7 @@ const WallProfiler = require('./profilers/wall')
|
|
|
11
11
|
const SpaceProfiler = require('./profilers/space')
|
|
12
12
|
const EventsProfiler = require('./profilers/events')
|
|
13
13
|
const { oomExportStrategies, snapshotKinds } = require('./constants')
|
|
14
|
+
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
14
15
|
const { tagger } = require('./tagger')
|
|
15
16
|
const { isFalse, isTrue } = require('../util')
|
|
16
17
|
|
|
@@ -72,6 +73,13 @@ class Config {
|
|
|
72
73
|
tagger.parse(options.tags),
|
|
73
74
|
tagger.parse({ env, host, service, version, functionname })
|
|
74
75
|
)
|
|
76
|
+
|
|
77
|
+
// Add source code integration tags if available
|
|
78
|
+
if (options.repositoryUrl && options.commitSHA) {
|
|
79
|
+
this.tags[GIT_REPOSITORY_URL] = options.repositoryUrl
|
|
80
|
+
this.tags[GIT_COMMIT_SHA] = options.commitSHA
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
this.logger = ensureLogger(options.logger)
|
|
76
84
|
const logger = this.logger
|
|
77
85
|
function logExperimentalVarDeprecation (shortVarName) {
|
|
@@ -131,10 +139,12 @@ class Config {
|
|
|
131
139
|
: getProfilers({
|
|
132
140
|
DD_PROFILING_HEAP_ENABLED,
|
|
133
141
|
DD_PROFILING_WALLTIME_ENABLED,
|
|
134
|
-
DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
|
|
135
142
|
DD_PROFILING_PROFILERS
|
|
136
143
|
})
|
|
137
144
|
|
|
145
|
+
this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
|
|
146
|
+
DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
|
|
147
|
+
|
|
138
148
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
139
149
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
140
150
|
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
|
|
@@ -147,8 +157,7 @@ class Config {
|
|
|
147
157
|
module.exports = { Config }
|
|
148
158
|
|
|
149
159
|
function getProfilers ({
|
|
150
|
-
DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED,
|
|
151
|
-
DD_PROFILING_PROFILERS, DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED
|
|
160
|
+
DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS
|
|
152
161
|
}) {
|
|
153
162
|
// First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
|
|
154
163
|
// Use a Set to avoid duplicates
|
|
@@ -172,11 +181,6 @@ function getProfilers ({
|
|
|
172
181
|
}
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
// Events profiler is a profiler for timeline events that goes with the wall
|
|
176
|
-
// profiler
|
|
177
|
-
if (profilers.has('wall') && DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED) {
|
|
178
|
-
profilers.add('events')
|
|
179
|
-
}
|
|
180
184
|
return [...profilers]
|
|
181
185
|
}
|
|
182
186
|
|
|
@@ -238,8 +242,6 @@ function getProfiler (name, options) {
|
|
|
238
242
|
return new WallProfiler(options)
|
|
239
243
|
case 'space':
|
|
240
244
|
return new SpaceProfiler(options)
|
|
241
|
-
case 'events':
|
|
242
|
-
return new EventsProfiler(options)
|
|
243
245
|
default:
|
|
244
246
|
options.logger.error(`Unknown profiler "${name}"`)
|
|
245
247
|
}
|
|
@@ -257,6 +259,11 @@ function ensureProfilers (profilers, options) {
|
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
// Events profiler is a profiler for timeline events
|
|
263
|
+
if (options.timelineEnabled) {
|
|
264
|
+
profilers.push(new EventsProfiler(options))
|
|
265
|
+
}
|
|
266
|
+
|
|
260
267
|
// Filter out any invalid profilers
|
|
261
268
|
return profilers.filter(v => v)
|
|
262
269
|
}
|
|
@@ -23,15 +23,19 @@ class Profiler extends EventEmitter {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
start (options) {
|
|
26
|
-
this._start(options).catch((err) => {
|
|
27
|
-
|
|
26
|
+
return this._start(options).catch((err) => {
|
|
27
|
+
if (options.logger) {
|
|
28
|
+
options.logger.error(err)
|
|
29
|
+
}
|
|
30
|
+
return false
|
|
31
|
+
})
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
async _start (options) {
|
|
31
|
-
if (this._enabled) return
|
|
35
|
+
if (this._enabled) return true
|
|
32
36
|
|
|
33
37
|
const config = this._config = new Config(options)
|
|
34
|
-
if (!config.enabled) return
|
|
38
|
+
if (!config.enabled) return false
|
|
35
39
|
|
|
36
40
|
this._logger = config.logger
|
|
37
41
|
this._enabled = true
|
|
@@ -67,9 +71,11 @@ class Profiler extends EventEmitter {
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
this._capture(this._timeoutInterval)
|
|
74
|
+
return true
|
|
70
75
|
} catch (e) {
|
|
71
76
|
this._logger.error(e)
|
|
72
77
|
this._stop()
|
|
78
|
+
return false
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
|
|
2
|
-
const { END_TIMESTAMP
|
|
2
|
+
const { END_TIMESTAMP } = require('./shared')
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
5
|
const pprof = require('@datadog/pprof/')
|
|
@@ -14,7 +14,137 @@ const MS_TO_NS = 1000000
|
|
|
14
14
|
// perf_hooks events, the emitted pprof file uses the type "timeline".
|
|
15
15
|
const pprofValueType = 'timeline'
|
|
16
16
|
const pprofValueUnit = 'nanoseconds'
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
function labelFromStr (stringTable, key, valStr) {
|
|
19
|
+
return new Label({ key, str: stringTable.dedup(valStr) })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function labelFromStrStr (stringTable, keyStr, valStr) {
|
|
23
|
+
return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class GCDecorator {
|
|
27
|
+
constructor (stringTable) {
|
|
28
|
+
this.stringTable = stringTable
|
|
29
|
+
this.reasonLabelKey = stringTable.dedup('gc reason')
|
|
30
|
+
this.kindLabels = []
|
|
31
|
+
this.reasonLabels = []
|
|
32
|
+
this.flagObj = {}
|
|
33
|
+
|
|
34
|
+
const kindLabelKey = stringTable.dedup('gc type')
|
|
35
|
+
|
|
36
|
+
// Create labels for all GC performance flags and kinds of GC
|
|
37
|
+
for (const [key, value] of Object.entries(constants)) {
|
|
38
|
+
if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
|
|
39
|
+
this.flagObj[key.substring(26).toLowerCase()] = value
|
|
40
|
+
} else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
|
|
41
|
+
// It's a constant for a kind of GC
|
|
42
|
+
const kind = key.substring(20).toLowerCase()
|
|
43
|
+
this.kindLabels[value] = labelFromStr(stringTable, kindLabelKey, kind)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
decorateSample (sampleInput, item) {
|
|
49
|
+
const { kind, flags } = node16 ? item.detail : item
|
|
50
|
+
sampleInput.label.push(this.kindLabels[kind])
|
|
51
|
+
const reasonLabel = this.getReasonLabel(flags)
|
|
52
|
+
if (reasonLabel) {
|
|
53
|
+
sampleInput.label.push(reasonLabel)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getReasonLabel (flags) {
|
|
58
|
+
if (flags === 0) {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
let reasonLabel = this.reasonLabels[flags]
|
|
62
|
+
if (!reasonLabel) {
|
|
63
|
+
const reasons = []
|
|
64
|
+
for (const [key, value] of Object.entries(this.flagObj)) {
|
|
65
|
+
if (value & flags) {
|
|
66
|
+
reasons.push(key)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const reasonStr = reasons.join(',')
|
|
70
|
+
reasonLabel = labelFromStr(this.stringTable, this.reasonLabelKey, reasonStr)
|
|
71
|
+
this.reasonLabels[flags] = reasonLabel
|
|
72
|
+
}
|
|
73
|
+
return reasonLabel
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class DNSDecorator {
|
|
78
|
+
constructor (stringTable) {
|
|
79
|
+
this.stringTable = stringTable
|
|
80
|
+
this.operationNameLabelKey = stringTable.dedup('operation')
|
|
81
|
+
this.hostLabelKey = stringTable.dedup('host')
|
|
82
|
+
this.addressLabelKey = stringTable.dedup('address')
|
|
83
|
+
this.portLabelKey = stringTable.dedup('port')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
decorateSample (sampleInput, item) {
|
|
87
|
+
const labels = sampleInput.label
|
|
88
|
+
const stringTable = this.stringTable
|
|
89
|
+
function addLabel (labelNameKey, labelValue) {
|
|
90
|
+
labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
|
|
91
|
+
}
|
|
92
|
+
const op = item.name
|
|
93
|
+
addLabel(this.operationNameLabelKey, item.name)
|
|
94
|
+
const detail = item.detail
|
|
95
|
+
switch (op) {
|
|
96
|
+
case 'lookup':
|
|
97
|
+
addLabel(this.hostLabelKey, detail.hostname)
|
|
98
|
+
break
|
|
99
|
+
case 'lookupService':
|
|
100
|
+
addLabel(this.addressLabelKey, detail.host)
|
|
101
|
+
labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
|
|
102
|
+
break
|
|
103
|
+
case 'getHostByAddr':
|
|
104
|
+
addLabel(this.addressLabelKey, detail.host)
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
if (op.startsWith('query')) {
|
|
108
|
+
addLabel(this.hostLabelKey, detail.host)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class NetDecorator {
|
|
115
|
+
constructor (stringTable) {
|
|
116
|
+
this.stringTable = stringTable
|
|
117
|
+
this.operationNameLabelKey = stringTable.dedup('operation')
|
|
118
|
+
this.hostLabelKey = stringTable.dedup('host')
|
|
119
|
+
this.portLabelKey = stringTable.dedup('port')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
decorateSample (sampleInput, item) {
|
|
123
|
+
const labels = sampleInput.label
|
|
124
|
+
const stringTable = this.stringTable
|
|
125
|
+
function addLabel (labelNameKey, labelValue) {
|
|
126
|
+
labels.push(labelFromStr(stringTable, labelNameKey, labelValue))
|
|
127
|
+
}
|
|
128
|
+
const op = item.name
|
|
129
|
+
addLabel(this.operationNameLabelKey, op)
|
|
130
|
+
if (op === 'connect') {
|
|
131
|
+
const detail = item.detail
|
|
132
|
+
addLabel(this.hostLabelKey, detail.host)
|
|
133
|
+
labels.push(new Label({ key: this.portLabelKey, num: detail.port }))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Keys correspond to PerformanceEntry.entryType, values are constructor
|
|
139
|
+
// functions for type-specific decorators.
|
|
140
|
+
const decoratorTypes = {
|
|
141
|
+
gc: GCDecorator
|
|
142
|
+
}
|
|
143
|
+
// Needs at least node 16 for DNS and Net
|
|
144
|
+
if (node16) {
|
|
145
|
+
decoratorTypes.dns = DNSDecorator
|
|
146
|
+
decoratorTypes.net = NetDecorator
|
|
147
|
+
}
|
|
18
148
|
|
|
19
149
|
/**
|
|
20
150
|
* This class generates pprof files with timeline events sourced from Node.js
|
|
@@ -35,8 +165,7 @@ class EventsProfiler {
|
|
|
35
165
|
if (!this._observer) {
|
|
36
166
|
this._observer = new PerformanceObserver(add.bind(this))
|
|
37
167
|
}
|
|
38
|
-
|
|
39
|
-
this._observer.observe({ type: 'gc' })
|
|
168
|
+
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
|
|
40
169
|
}
|
|
41
170
|
|
|
42
171
|
stop () {
|
|
@@ -46,92 +175,61 @@ class EventsProfiler {
|
|
|
46
175
|
}
|
|
47
176
|
|
|
48
177
|
profile () {
|
|
178
|
+
if (this.entries.length === 0) {
|
|
179
|
+
// No events in the period; don't produce a profile
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
49
183
|
const stringTable = new StringTable()
|
|
50
|
-
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
|
|
51
|
-
const kindLabelKey = stringTable.dedup('gc type')
|
|
52
|
-
const reasonLabelKey = stringTable.dedup('gc reason')
|
|
53
|
-
const kindLabels = []
|
|
54
|
-
const reasonLabels = []
|
|
55
184
|
const locations = []
|
|
56
185
|
const functions = []
|
|
57
|
-
const locationsPerKind = []
|
|
58
|
-
const flagObj = {}
|
|
59
|
-
|
|
60
|
-
function labelFromStr (key, valStr) {
|
|
61
|
-
return new Label({ key, str: stringTable.dedup(valStr) })
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function labelFromStrStr (keyStr, valStr) {
|
|
65
|
-
return labelFromStr(stringTable.dedup(keyStr), valStr)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Create labels for all GC performance flags and kinds of GC
|
|
69
|
-
for (const [key, value] of Object.entries(constants)) {
|
|
70
|
-
if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
|
|
71
|
-
flagObj[key.substring(26).toLowerCase()] = value
|
|
72
|
-
} else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
|
|
73
|
-
// It's a constant for a kind of GC
|
|
74
|
-
const kind = key.substring(20).toLowerCase()
|
|
75
|
-
kindLabels[value] = labelFromStr(kindLabelKey, kind)
|
|
76
|
-
// Construct a single-frame "location" too
|
|
77
|
-
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
|
|
78
|
-
functions.push(fn)
|
|
79
|
-
const line = new Line({ functionId: fn.id })
|
|
80
|
-
const location = new Location({ id: locations.length + 1, line: [line] })
|
|
81
|
-
locations.push(location)
|
|
82
|
-
locationsPerKind[value] = [location.id]
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
186
|
|
|
86
|
-
|
|
87
|
-
|
|
187
|
+
// A synthetic single-frame location to serve as the location for timeline
|
|
188
|
+
// samples. We need these as the profiling backend (mimicking official pprof
|
|
189
|
+
// tool's behavior) ignores these.
|
|
190
|
+
const locationId = (() => {
|
|
191
|
+
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
|
|
192
|
+
functions.push(fn)
|
|
193
|
+
const line = new Line({ functionId: fn.id })
|
|
194
|
+
const location = new Location({ id: locations.length + 1, line: [line] })
|
|
195
|
+
locations.push(location)
|
|
196
|
+
return [location.id]
|
|
197
|
+
})()
|
|
88
198
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (!reasonLabel) {
|
|
95
|
-
const reasons = []
|
|
96
|
-
for (const [key, value] of Object.entries(flagObj)) {
|
|
97
|
-
if (value & flags) {
|
|
98
|
-
reasons.push(key)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const reasonStr = reasons.join(',')
|
|
102
|
-
reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
|
|
103
|
-
reasonLabels[flags] = reasonLabel
|
|
104
|
-
}
|
|
105
|
-
return reasonLabel
|
|
199
|
+
const decorators = {}
|
|
200
|
+
for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
|
|
201
|
+
const decorator = new DecoratorCtor(stringTable)
|
|
202
|
+
decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
|
|
203
|
+
decorators[eventType] = decorator
|
|
106
204
|
}
|
|
205
|
+
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
|
|
107
206
|
|
|
108
207
|
let durationFrom = Number.POSITIVE_INFINITY
|
|
109
208
|
let durationTo = 0
|
|
110
209
|
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
111
210
|
|
|
112
211
|
const samples = this.entries.map((item) => {
|
|
212
|
+
const decorator = decorators[item.entryType]
|
|
213
|
+
if (!decorator) {
|
|
214
|
+
// Shouldn't happen but it's better to not rely on observer only getting
|
|
215
|
+
// requested event types.
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
113
218
|
const { startTime, duration } = item
|
|
114
|
-
const { kind, flags } = node16 ? item.detail : item
|
|
115
219
|
const endTime = startTime + duration
|
|
116
220
|
if (durationFrom > startTime) durationFrom = startTime
|
|
117
221
|
if (durationTo < endTime) durationTo = endTime
|
|
118
|
-
const
|
|
119
|
-
gcEventLabel,
|
|
120
|
-
threadLabel,
|
|
121
|
-
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
|
|
122
|
-
kindLabels[kind]
|
|
123
|
-
]
|
|
124
|
-
const reasonLabel = getReasonLabel(flags)
|
|
125
|
-
if (reasonLabel) {
|
|
126
|
-
labels.push(reasonLabel)
|
|
127
|
-
}
|
|
128
|
-
const sample = new Sample({
|
|
222
|
+
const sampleInput = {
|
|
129
223
|
value: [Math.round(duration * MS_TO_NS)],
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
224
|
+
locationId,
|
|
225
|
+
label: [
|
|
226
|
+
decorator.eventTypeLabel,
|
|
227
|
+
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
decorator.decorateSample(sampleInput, item)
|
|
231
|
+
return new Sample(sampleInput)
|
|
232
|
+
}).filter(v => v)
|
|
135
233
|
|
|
136
234
|
this.entries = []
|
|
137
235
|
|