dd-trace 4.18.0 → 5.6.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/CONTRIBUTING.md +98 -0
- package/LICENSE-3rdparty.csv +4 -5
- package/MIGRATING.md +15 -0
- package/README.md +20 -140
- package/ci/cypress/after-run.js +1 -0
- package/ci/cypress/after-spec.js +1 -0
- package/ci/init.js +1 -4
- package/ext/kinds.d.ts +1 -0
- package/ext/kinds.js +2 -1
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +6 -1
- package/index.d.ts +1523 -1460
- package/package.json +19 -19
- package/packages/datadog-core/src/storage/async_resource.js +1 -1
- package/packages/datadog-core/src/utils/src/get.js +11 -0
- package/packages/datadog-core/src/utils/src/has.js +14 -0
- package/packages/datadog-core/src/utils/src/kebabcase.js +16 -0
- package/packages/datadog-core/src/utils/src/pick.js +11 -0
- package/packages/datadog-core/src/utils/src/set.js +16 -0
- package/packages/datadog-core/src/utils/src/uniq.js +5 -0
- package/packages/datadog-esbuild/index.js +1 -20
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/amqplib.js +2 -2
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/child_process.js +150 -0
- package/packages/datadog-instrumentations/src/couchbase.js +5 -4
- package/packages/datadog-instrumentations/src/crypto.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +163 -46
- package/packages/datadog-instrumentations/src/dns.js +2 -1
- package/packages/datadog-instrumentations/src/express.js +20 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
- package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -3
- package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
- package/packages/datadog-instrumentations/src/http/client.js +12 -2
- package/packages/datadog-instrumentations/src/http/server.js +7 -4
- package/packages/datadog-instrumentations/src/http2/client.js +3 -1
- package/packages/datadog-instrumentations/src/http2/server.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +239 -52
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/mocha.js +154 -18
- package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/mquery.js +65 -0
- package/packages/datadog-instrumentations/src/net.js +10 -2
- package/packages/datadog-instrumentations/src/next.js +35 -9
- package/packages/datadog-instrumentations/src/playwright.js +110 -16
- package/packages/datadog-instrumentations/src/restify.js +14 -1
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-amqplib/src/consumer.js +14 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +13 -1
- package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +163 -27
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +46 -8
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +129 -22
- package/packages/datadog-plugin-child_process/src/index.js +91 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
- package/packages/datadog-plugin-cucumber/src/index.js +70 -13
- package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
- package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +6 -454
- package/packages/datadog-plugin-cypress/src/support.js +50 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
- package/packages/datadog-plugin-graphql/src/index.js +1 -6
- package/packages/datadog-plugin-graphql/src/resolve.js +28 -18
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-grpc/src/util.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-jest/src/index.js +118 -12
- package/packages/datadog-plugin-jest/src/util.js +38 -16
- package/packages/datadog-plugin-kafkajs/src/consumer.js +76 -6
- package/packages/datadog-plugin-kafkajs/src/producer.js +64 -8
- package/packages/datadog-plugin-mocha/src/index.js +87 -17
- package/packages/datadog-plugin-next/src/index.js +40 -14
- package/packages/datadog-plugin-playwright/src/index.js +71 -8
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -1
- package/packages/datadog-plugin-rhea/src/producer.js +10 -0
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +5 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +61 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +7 -3
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
- package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
- package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
- package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +13 -2
- package/packages/dd-trace/src/appsec/iast/index.js +15 -5
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +19 -6
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +41 -3
- 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-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
- package/packages/dd-trace/src/appsec/index.js +49 -33
- package/packages/dd-trace/src/appsec/recommended.json +1763 -106
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +42 -16
- package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
- package/packages/dd-trace/src/appsec/reporter.js +51 -34
- package/packages/dd-trace/src/appsec/rule_manager.js +11 -8
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → early-flake-detection/get-known-tests.js} +17 -22
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +95 -37
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +134 -61
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +37 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +131 -0
- package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
- package/packages/dd-trace/src/config.js +561 -470
- package/packages/dd-trace/src/data_streams_context.js +1 -1
- package/packages/dd-trace/src/datastreams/pathway.js +58 -1
- package/packages/dd-trace/src/datastreams/processor.js +196 -27
- package/packages/dd-trace/src/datastreams/writer.js +11 -5
- package/packages/dd-trace/src/dogstatsd.js +3 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -6
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
- package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
- package/packages/dd-trace/src/exporters/common/request.js +21 -3
- package/packages/dd-trace/src/format.js +30 -2
- package/packages/dd-trace/src/id.js +12 -0
- package/packages/dd-trace/src/iitm.js +1 -1
- package/packages/dd-trace/src/log/channels.js +1 -1
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +104 -4
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +16 -7
- package/packages/dd-trace/src/opentracing/span.js +48 -4
- package/packages/dd-trace/src/opentracing/span_context.js +15 -6
- package/packages/dd-trace/src/opentracing/tracer.js +4 -3
- package/packages/dd-trace/src/plugin_manager.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +78 -19
- package/packages/dd-trace/src/plugins/database.js +1 -1
- package/packages/dd-trace/src/plugins/index.js +7 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +6 -19
- package/packages/dd-trace/src/plugins/util/git.js +104 -22
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
- package/packages/dd-trace/src/plugins/util/test.js +60 -10
- package/packages/dd-trace/src/plugins/util/url.js +26 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +30 -38
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +77 -24
- package/packages/dd-trace/src/profiling/exporters/agent.js +77 -31
- package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
- package/packages/dd-trace/src/profiling/profiler.js +33 -22
- package/packages/dd-trace/src/profiling/profilers/events.js +270 -0
- package/packages/dd-trace/src/profiling/profilers/shared.js +45 -0
- package/packages/dd-trace/src/profiling/profilers/space.js +18 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +146 -70
- package/packages/dd-trace/src/proxy.js +56 -24
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/sampling_rule.js +130 -0
- 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/span_processor.js +9 -1
- package/packages/dd-trace/src/span_sampler.js +6 -64
- 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 +56 -10
- package/packages/dd-trace/src/telemetry/index.js +182 -53
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +65 -7
- package/packages/dd-trace/src/tracer.js +12 -5
- package/register.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
- package/scripts/st.js +105 -0
- package/packages/datadog-instrumentations/src/child-process.js +0 -30
- package/packages/dd-trace/src/plugins/util/exec.js +0 -13
- package/packages/diagnostics_channel/index.js +0 -3
- package/packages/diagnostics_channel/src/index.js +0 -121
|
@@ -9,6 +9,8 @@ const docker = require('../../exporters/common/docker')
|
|
|
9
9
|
const FormData = require('../../exporters/common/form-data')
|
|
10
10
|
const { storage } = require('../../../../datadog-core')
|
|
11
11
|
const version = require('../../../../../package.json').version
|
|
12
|
+
const os = require('os')
|
|
13
|
+
const perf = require('perf_hooks').performance
|
|
12
14
|
|
|
13
15
|
const containerId = docker.id()
|
|
14
16
|
|
|
@@ -50,7 +52,7 @@ function computeRetries (uploadTimeout) {
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
class AgentExporter {
|
|
53
|
-
constructor ({ url, logger, uploadTimeout } = {}) {
|
|
55
|
+
constructor ({ url, logger, uploadTimeout, env, host, service, version } = {}) {
|
|
54
56
|
this._url = url
|
|
55
57
|
this._logger = logger
|
|
56
58
|
|
|
@@ -58,47 +60,87 @@ class AgentExporter {
|
|
|
58
60
|
|
|
59
61
|
this._backoffTime = backoffTime
|
|
60
62
|
this._backoffTries = backoffTries
|
|
63
|
+
this._env = env
|
|
64
|
+
this._host = host
|
|
65
|
+
this._service = service
|
|
66
|
+
this._appVersion = version
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export ({ profiles, start, end, tags }) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
const fields = [
|
|
67
|
-
['recording-start', start.toISOString()],
|
|
68
|
-
['recording-end', end.toISOString()],
|
|
69
|
-
['language', 'javascript'],
|
|
70
|
-
['runtime', 'nodejs'],
|
|
71
|
-
['runtime_version', process.version],
|
|
72
|
-
['profiler_version', version],
|
|
73
|
-
['format', 'pprof'],
|
|
74
|
-
|
|
75
|
-
['tags[]', 'language:javascript'],
|
|
76
|
-
['tags[]', 'runtime:nodejs'],
|
|
77
|
-
['tags[]', `runtime_version:${process.version}`],
|
|
78
|
-
['tags[]', `profiler_version:${version}`],
|
|
79
|
-
['tags[]', 'format:pprof'],
|
|
80
|
-
...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])
|
|
81
|
-
]
|
|
70
|
+
const fields = []
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
function typeToFile (type) {
|
|
73
|
+
return `${type}.pprof`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const event = JSON.stringify({
|
|
77
|
+
attachments: Object.keys(profiles).map(typeToFile),
|
|
78
|
+
start: start.toISOString(),
|
|
79
|
+
end: end.toISOString(),
|
|
80
|
+
family: 'node',
|
|
81
|
+
version: '4',
|
|
82
|
+
tags_profiler: [
|
|
83
|
+
'language:javascript',
|
|
84
|
+
'runtime:nodejs',
|
|
85
|
+
`runtime_arch:${process.arch}`,
|
|
86
|
+
`runtime_os:${process.platform}`,
|
|
87
|
+
`runtime_version:${process.version}`,
|
|
88
|
+
`process_id:${process.pid}`,
|
|
89
|
+
`profiler_version:${version}`,
|
|
90
|
+
'format:pprof',
|
|
91
|
+
...Object.entries(tags).map(([key, value]) => `${key}:${value}`)
|
|
92
|
+
].join(','),
|
|
93
|
+
info: {
|
|
94
|
+
application: {
|
|
95
|
+
env: this._env,
|
|
96
|
+
service: this._service,
|
|
97
|
+
start_time: new Date(perf.nodeTiming.nodeStart + perf.timeOrigin).toISOString(),
|
|
98
|
+
version: this._appVersion
|
|
99
|
+
},
|
|
100
|
+
platform: {
|
|
101
|
+
hostname: this._host,
|
|
102
|
+
kernel_name: os.type(),
|
|
103
|
+
kernel_release: os.release(),
|
|
104
|
+
kernel_version: os.version()
|
|
105
|
+
},
|
|
106
|
+
profiler: {
|
|
107
|
+
version
|
|
108
|
+
},
|
|
109
|
+
runtime: {
|
|
110
|
+
// Using `nodejs` for consistency with the existing `runtime` tag.
|
|
111
|
+
// Note that the event `family` property uses `node`, as that's what's
|
|
112
|
+
// proscribed by the Intake API, but that's an internal enum and is
|
|
113
|
+
// not customer visible.
|
|
114
|
+
engine: 'nodejs',
|
|
115
|
+
// strip off leading 'v'. This makes the format consistent with other
|
|
116
|
+
// runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
|
|
117
|
+
// We'll keep it like this as we want cross-engine consistency. We
|
|
118
|
+
// also aren't changing the format of the existing tag as we don't want
|
|
119
|
+
// to break it.
|
|
120
|
+
version: process.version.substring(1)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
86
123
|
})
|
|
87
124
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
125
|
+
fields.push(['event', event, {
|
|
126
|
+
filename: 'event.json',
|
|
127
|
+
contentType: 'application/json'
|
|
128
|
+
}])
|
|
129
|
+
|
|
130
|
+
this._logger.debug(() => {
|
|
131
|
+
return `Building agent export report:\n${event}`
|
|
132
|
+
})
|
|
91
133
|
|
|
134
|
+
for (const [type, buffer] of Object.entries(profiles)) {
|
|
92
135
|
this._logger.debug(() => {
|
|
93
136
|
const bytes = buffer.toString('hex').match(/../g).join(' ')
|
|
94
137
|
return `Adding ${type} profile to agent export: ` + bytes
|
|
95
138
|
})
|
|
96
139
|
|
|
97
|
-
|
|
98
|
-
fields.push([
|
|
99
|
-
filename
|
|
100
|
-
contentType: 'application/octet-stream'
|
|
101
|
-
knownLength: buffer.length
|
|
140
|
+
const filename = typeToFile(type)
|
|
141
|
+
fields.push([filename, buffer, {
|
|
142
|
+
filename,
|
|
143
|
+
contentType: 'application/octet-stream'
|
|
102
144
|
}])
|
|
103
145
|
}
|
|
104
146
|
|
|
@@ -120,7 +162,11 @@ class AgentExporter {
|
|
|
120
162
|
const options = {
|
|
121
163
|
method: 'POST',
|
|
122
164
|
path: '/profiling/v1/input',
|
|
123
|
-
headers:
|
|
165
|
+
headers: {
|
|
166
|
+
'DD-EVP-ORIGIN': 'dd-trace-js',
|
|
167
|
+
'DD-EVP-ORIGIN-VERSION': version,
|
|
168
|
+
...form.getHeaders()
|
|
169
|
+
},
|
|
124
170
|
timeout: this._backoffTime * Math.pow(2, attempt)
|
|
125
171
|
}
|
|
126
172
|
|
|
@@ -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
|
|
@@ -23,15 +24,19 @@ class Profiler extends EventEmitter {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
start (options) {
|
|
26
|
-
this._start(options).catch((err) => {
|
|
27
|
-
|
|
27
|
+
return this._start(options).catch((err) => {
|
|
28
|
+
if (options.logger) {
|
|
29
|
+
options.logger.error(err)
|
|
30
|
+
}
|
|
31
|
+
return false
|
|
32
|
+
})
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
async _start (options) {
|
|
31
|
-
if (this._enabled) return
|
|
36
|
+
if (this._enabled) return true
|
|
32
37
|
|
|
33
38
|
const config = this._config = new Config(options)
|
|
34
|
-
if (!config.enabled) return
|
|
39
|
+
if (!config.enabled) return false
|
|
35
40
|
|
|
36
41
|
this._logger = config.logger
|
|
37
42
|
this._enabled = true
|
|
@@ -57,19 +62,22 @@ class Profiler extends EventEmitter {
|
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
try {
|
|
65
|
+
const start = new Date()
|
|
60
66
|
for (const profiler of config.profilers) {
|
|
61
67
|
// TODO: move this out of Profiler when restoring sourcemap support
|
|
62
68
|
profiler.start({
|
|
63
69
|
mapper,
|
|
64
70
|
nearOOMCallback: this._nearOOMExport.bind(this)
|
|
65
71
|
})
|
|
66
|
-
this._logger.debug(`Started ${profiler.type} profiler`)
|
|
72
|
+
this._logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
this._capture(this._timeoutInterval)
|
|
75
|
+
this._capture(this._timeoutInterval, start)
|
|
76
|
+
return true
|
|
70
77
|
} catch (e) {
|
|
71
78
|
this._logger.error(e)
|
|
72
79
|
this._stop()
|
|
80
|
+
return false
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
|
|
@@ -90,7 +98,7 @@ class Profiler extends EventEmitter {
|
|
|
90
98
|
|
|
91
99
|
// collect and export current profiles
|
|
92
100
|
// once collect returns, profilers can be safely stopped
|
|
93
|
-
this._collect(snapshotKinds.ON_SHUTDOWN)
|
|
101
|
+
this._collect(snapshotKinds.ON_SHUTDOWN, false)
|
|
94
102
|
this._stop()
|
|
95
103
|
}
|
|
96
104
|
|
|
@@ -101,18 +109,16 @@ class Profiler extends EventEmitter {
|
|
|
101
109
|
|
|
102
110
|
for (const profiler of this._config.profilers) {
|
|
103
111
|
profiler.stop()
|
|
104
|
-
this._logger.debug(`Stopped ${profiler.type} profiler`)
|
|
112
|
+
this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
clearTimeout(this._timer)
|
|
108
116
|
this._timer = undefined
|
|
109
|
-
|
|
110
|
-
return this
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
_capture (timeout) {
|
|
119
|
+
_capture (timeout, start) {
|
|
114
120
|
if (!this._enabled) return
|
|
115
|
-
this._lastStart =
|
|
121
|
+
this._lastStart = start
|
|
116
122
|
if (!this._timer || timeout !== this._timeoutInterval) {
|
|
117
123
|
this._timer = setTimeout(() => this._collect(snapshotKinds.PERIODIC), timeout)
|
|
118
124
|
this._timer.unref()
|
|
@@ -121,18 +127,21 @@ class Profiler extends EventEmitter {
|
|
|
121
127
|
}
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
async _collect (snapshotKind) {
|
|
130
|
+
async _collect (snapshotKind, restart = true) {
|
|
125
131
|
if (!this._enabled) return
|
|
126
132
|
|
|
127
|
-
const
|
|
128
|
-
const
|
|
133
|
+
const startDate = this._lastStart
|
|
134
|
+
const endDate = new Date()
|
|
129
135
|
const profiles = []
|
|
130
136
|
const encodedProfiles = {}
|
|
131
137
|
|
|
132
138
|
try {
|
|
133
139
|
// collect profiles synchronously so that profilers can be safely stopped asynchronously
|
|
134
140
|
for (const profiler of this._config.profilers) {
|
|
135
|
-
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
|
+
}
|
|
136
145
|
if (!profile) continue
|
|
137
146
|
profiles.push({ profiler, profile })
|
|
138
147
|
}
|
|
@@ -148,8 +157,10 @@ class Profiler extends EventEmitter {
|
|
|
148
157
|
})
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
if (restart) {
|
|
161
|
+
this._capture(this._timeoutInterval, endDate)
|
|
162
|
+
}
|
|
163
|
+
await this._submit(encodedProfiles, startDate, endDate, snapshotKind)
|
|
153
164
|
this._logger.debug('Submitted profiles')
|
|
154
165
|
} catch (err) {
|
|
155
166
|
this._logger.error(err)
|
|
@@ -189,13 +200,13 @@ class ServerlessProfiler extends Profiler {
|
|
|
189
200
|
this._flushAfterIntervals = this._config.flushInterval / 1000
|
|
190
201
|
}
|
|
191
202
|
|
|
192
|
-
async _collect (snapshotKind) {
|
|
193
|
-
if (this._profiledIntervals >= this._flushAfterIntervals) {
|
|
203
|
+
async _collect (snapshotKind, restart = true) {
|
|
204
|
+
if (this._profiledIntervals >= this._flushAfterIntervals || !restart) {
|
|
194
205
|
this._profiledIntervals = 0
|
|
195
|
-
await super._collect(snapshotKind)
|
|
206
|
+
await super._collect(snapshotKind, restart)
|
|
196
207
|
} else {
|
|
197
208
|
this._profiledIntervals += 1
|
|
198
|
-
this._capture(this._timeoutInterval)
|
|
209
|
+
this._capture(this._timeoutInterval, new Date())
|
|
199
210
|
// Don't submit profile until 65 (flushAfterIntervals) intervals have elapsed
|
|
200
211
|
}
|
|
201
212
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const { performance, constants, PerformanceObserver } = require('perf_hooks')
|
|
2
|
+
const { END_TIMESTAMP_LABEL } = require('./shared')
|
|
3
|
+
const semver = require('semver')
|
|
4
|
+
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
|
+
const pprof = require('@datadog/pprof/')
|
|
6
|
+
|
|
7
|
+
// Format of perf_hooks events changed with Node 16, we need to be mindful of it.
|
|
8
|
+
const node16 = semver.gte(process.version, '16.0.0')
|
|
9
|
+
|
|
10
|
+
// perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
|
|
11
|
+
const MS_TO_NS = 1000000
|
|
12
|
+
|
|
13
|
+
// While this is an "events profiler", meaning it emits a pprof file based on events observed as
|
|
14
|
+
// perf_hooks events, the emitted pprof file uses the type "timeline".
|
|
15
|
+
const pprofValueType = 'timeline'
|
|
16
|
+
const pprofValueUnit = 'nanoseconds'
|
|
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
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* This class generates pprof files with timeline events sourced from Node.js
|
|
151
|
+
* performance measurement APIs.
|
|
152
|
+
*/
|
|
153
|
+
class EventsProfiler {
|
|
154
|
+
constructor (options = {}) {
|
|
155
|
+
this.type = 'events'
|
|
156
|
+
this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
|
|
157
|
+
this._observer = undefined
|
|
158
|
+
this.entries = []
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
start () {
|
|
162
|
+
// if already started, do nothing
|
|
163
|
+
if (this._observer) return
|
|
164
|
+
|
|
165
|
+
function add (items) {
|
|
166
|
+
this.entries.push(...items.getEntries())
|
|
167
|
+
}
|
|
168
|
+
this._observer = new PerformanceObserver(add.bind(this))
|
|
169
|
+
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
stop () {
|
|
173
|
+
if (this._observer) {
|
|
174
|
+
this._observer.disconnect()
|
|
175
|
+
this._observer = undefined
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
profile (restart, startDate, endDate) {
|
|
180
|
+
if (this.entries.length === 0) {
|
|
181
|
+
// No events in the period; don't produce a profile
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const stringTable = new StringTable()
|
|
186
|
+
const locations = []
|
|
187
|
+
const functions = []
|
|
188
|
+
|
|
189
|
+
// A synthetic single-frame location to serve as the location for timeline
|
|
190
|
+
// samples. We need these as the profiling backend (mimicking official pprof
|
|
191
|
+
// tool's behavior) ignores these.
|
|
192
|
+
const locationId = (() => {
|
|
193
|
+
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
|
|
194
|
+
functions.push(fn)
|
|
195
|
+
const line = new Line({ functionId: fn.id })
|
|
196
|
+
const location = new Location({ id: locations.length + 1, line: [line] })
|
|
197
|
+
locations.push(location)
|
|
198
|
+
return [location.id]
|
|
199
|
+
})()
|
|
200
|
+
|
|
201
|
+
const decorators = {}
|
|
202
|
+
for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
|
|
203
|
+
const decorator = new DecoratorCtor(stringTable)
|
|
204
|
+
decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
|
|
205
|
+
decorators[eventType] = decorator
|
|
206
|
+
}
|
|
207
|
+
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
208
|
+
|
|
209
|
+
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
210
|
+
const lateEntries = []
|
|
211
|
+
const perfEndDate = endDate.getTime() - performance.timeOrigin
|
|
212
|
+
const samples = this.entries.map((item) => {
|
|
213
|
+
const decorator = decorators[item.entryType]
|
|
214
|
+
if (!decorator) {
|
|
215
|
+
// Shouldn't happen but it's better to not rely on observer only getting
|
|
216
|
+
// requested event types.
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
const { startTime, duration } = item
|
|
220
|
+
if (startTime >= perfEndDate) {
|
|
221
|
+
// An event past the current recording end date; save it for the next
|
|
222
|
+
// profile. Not supposed to happen as long as there's no async activity
|
|
223
|
+
// between capture of the endDate value in profiler.js _collect() and
|
|
224
|
+
// here, but better be safe than sorry.
|
|
225
|
+
lateEntries.push(item)
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
const endTime = startTime + duration
|
|
229
|
+
const sampleInput = {
|
|
230
|
+
value: [Math.round(duration * MS_TO_NS)],
|
|
231
|
+
locationId,
|
|
232
|
+
label: [
|
|
233
|
+
decorator.eventTypeLabel,
|
|
234
|
+
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
decorator.decorateSample(sampleInput, item)
|
|
238
|
+
return new Sample(sampleInput)
|
|
239
|
+
}).filter(v => v)
|
|
240
|
+
|
|
241
|
+
this.entries = lateEntries
|
|
242
|
+
|
|
243
|
+
const timeValueType = new ValueType({
|
|
244
|
+
type: stringTable.dedup(pprofValueType),
|
|
245
|
+
unit: stringTable.dedup(pprofValueUnit)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
if (!restart) {
|
|
249
|
+
this.stop()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return new Profile({
|
|
253
|
+
sampleType: [timeValueType],
|
|
254
|
+
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
255
|
+
periodType: timeValueType,
|
|
256
|
+
period: 1,
|
|
257
|
+
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
|
|
258
|
+
sample: samples,
|
|
259
|
+
location: locations,
|
|
260
|
+
function: functions,
|
|
261
|
+
stringTable: stringTable
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
encode (profile) {
|
|
266
|
+
return pprof.encode(profile)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = EventsProfiler
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isMainThread, threadId } = require('worker_threads')
|
|
4
|
+
|
|
5
|
+
const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
|
|
6
|
+
const THREAD_NAME_LABEL = 'thread name'
|
|
7
|
+
const OS_THREAD_ID_LABEL = 'os thread id'
|
|
8
|
+
const THREAD_ID_LABEL = 'thread id'
|
|
9
|
+
const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
|
|
10
|
+
const eventLoopThreadName = `${threadNamePrefix} Event Loop`
|
|
11
|
+
|
|
12
|
+
function getThreadLabels () {
|
|
13
|
+
const pprof = require('@datadog/pprof')
|
|
14
|
+
const nativeThreadId = pprof.getNativeThreadId()
|
|
15
|
+
return {
|
|
16
|
+
[THREAD_NAME_LABEL]: eventLoopThreadName,
|
|
17
|
+
[THREAD_ID_LABEL]: `${threadId}`,
|
|
18
|
+
[OS_THREAD_ID_LABEL]: `${nativeThreadId}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function cacheThreadLabels () {
|
|
23
|
+
let labels
|
|
24
|
+
return () => {
|
|
25
|
+
if (!labels) {
|
|
26
|
+
labels = getThreadLabels()
|
|
27
|
+
}
|
|
28
|
+
return labels
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getNonJSThreadsLabels () {
|
|
33
|
+
return { [THREAD_NAME_LABEL]: 'Non-JS threads', [THREAD_ID_LABEL]: 'NA', [OS_THREAD_ID_LABEL]: 'NA' }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
END_TIMESTAMP_LABEL,
|
|
38
|
+
THREAD_NAME_LABEL,
|
|
39
|
+
THREAD_ID_LABEL,
|
|
40
|
+
OS_THREAD_ID_LABEL,
|
|
41
|
+
threadNamePrefix,
|
|
42
|
+
eventLoopThreadName,
|
|
43
|
+
getNonJSThreadsLabels,
|
|
44
|
+
getThreadLabels: cacheThreadLabels()
|
|
45
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { oomExportStrategies } = require('../constants')
|
|
4
|
+
const { getThreadLabels } = require('./shared')
|
|
4
5
|
|
|
5
6
|
function strategiesToCallbackMode (strategies, callbackMode) {
|
|
6
7
|
return strategies.includes(oomExportStrategies.ASYNC_CALLBACK) ? callbackMode.Async : 0
|
|
@@ -13,9 +14,12 @@ class NativeSpaceProfiler {
|
|
|
13
14
|
this._stackDepth = options.stackDepth || 64
|
|
14
15
|
this._pprof = undefined
|
|
15
16
|
this._oomMonitoring = options.oomMonitoring || {}
|
|
17
|
+
this._started = false
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
start ({ mapper, nearOOMCallback } = {}) {
|
|
21
|
+
if (this._started) return
|
|
22
|
+
|
|
19
23
|
this._mapper = mapper
|
|
20
24
|
this._pprof = require('@datadog/pprof')
|
|
21
25
|
this._pprof.heap.start(this._samplingInterval, this._stackDepth)
|
|
@@ -30,10 +34,16 @@ class NativeSpaceProfiler {
|
|
|
30
34
|
strategiesToCallbackMode(strategies, this._pprof.heap.CallbackMode)
|
|
31
35
|
)
|
|
32
36
|
}
|
|
37
|
+
|
|
38
|
+
this._started = true
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
profile () {
|
|
36
|
-
|
|
41
|
+
profile (restart) {
|
|
42
|
+
const profile = this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
|
|
43
|
+
if (!restart) {
|
|
44
|
+
this.stop()
|
|
45
|
+
}
|
|
46
|
+
return profile
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
encode (profile) {
|
|
@@ -41,7 +51,13 @@ class NativeSpaceProfiler {
|
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
stop () {
|
|
54
|
+
if (!this._started) return
|
|
44
55
|
this._pprof.heap.stop()
|
|
56
|
+
this._started = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isStarted () {
|
|
60
|
+
return this._started
|
|
45
61
|
}
|
|
46
62
|
}
|
|
47
63
|
|