dd-trace 5.0.0-pre-e2df7ec → 5.0.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/README.md +1 -1
- package/index.d.ts +8 -5
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/couchbase.js +5 -4
- package/packages/datadog-instrumentations/src/jest.js +2 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +24 -4
- package/packages/datadog-plugin-jest/src/util.js +38 -16
- package/packages/dd-trace/src/config.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +30 -38
- package/packages/dd-trace/src/profiling/config.js +17 -2
- 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/sampling_rule.js +130 -0
- package/packages/dd-trace/src/span_sampler.js +6 -64
- package/packages/dd-trace/src/telemetry/send-data.js +35 -16
package/README.md
CHANGED
package/index.d.ts
CHANGED
|
@@ -83,8 +83,9 @@ export declare interface Tracer extends opentracing.Tracer {
|
|
|
83
83
|
* unless there is already an active span or `childOf` option. Note that this
|
|
84
84
|
* option is deprecated and has been removed in version 4.0.
|
|
85
85
|
*/
|
|
86
|
-
trace<T> (name: string, fn: (span
|
|
87
|
-
trace<T> (name: string,
|
|
86
|
+
trace<T> (name: string, fn: (span: Span) => T): T;
|
|
87
|
+
trace<T> (name: string, fn: (span: Span, done: (error?: Error) => void) => T): T;
|
|
88
|
+
trace<T> (name: string, options: TraceOptions & SpanOptions, fn: (span?: Span, done?: (error?: Error) => void) => T): T;
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
91
|
* Wrap a function to automatically create a span activated on its
|
|
@@ -883,7 +884,7 @@ interface Analyzable {
|
|
|
883
884
|
measured?: boolean | { [key: string]: boolean };
|
|
884
885
|
}
|
|
885
886
|
|
|
886
|
-
declare namespace plugins {
|
|
887
|
+
export declare namespace plugins {
|
|
887
888
|
/** @hidden */
|
|
888
889
|
interface Integration {
|
|
889
890
|
/**
|
|
@@ -1380,7 +1381,8 @@ declare namespace plugins {
|
|
|
1380
1381
|
*/
|
|
1381
1382
|
interface ioredis extends Instrumentation {
|
|
1382
1383
|
/**
|
|
1383
|
-
* List of commands that should be instrumented.
|
|
1384
|
+
* List of commands that should be instrumented. Commands must be in
|
|
1385
|
+
* lowercase for example 'xread'.
|
|
1384
1386
|
*
|
|
1385
1387
|
* @default /^.*$/
|
|
1386
1388
|
*/
|
|
@@ -1396,7 +1398,8 @@ declare namespace plugins {
|
|
|
1396
1398
|
|
|
1397
1399
|
/**
|
|
1398
1400
|
* List of commands that should not be instrumented. Takes precedence over
|
|
1399
|
-
* allowlist if a command matches an entry in both.
|
|
1401
|
+
* allowlist if a command matches an entry in both. Commands must be in
|
|
1402
|
+
* lowercase for example 'xread'.
|
|
1400
1403
|
*
|
|
1401
1404
|
* @default []
|
|
1402
1405
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -65,27 +65,27 @@
|
|
|
65
65
|
},
|
|
66
66
|
"homepage": "https://github.com/DataDog/dd-trace-js#readme",
|
|
67
67
|
"engines": {
|
|
68
|
-
"node": ">=
|
|
68
|
+
"node": ">=18"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@datadog/native-appsec": "6.0.0",
|
|
72
72
|
"@datadog/native-iast-rewriter": "2.2.2",
|
|
73
73
|
"@datadog/native-iast-taint-tracking": "1.6.4",
|
|
74
74
|
"@datadog/native-metrics": "^2.0.0",
|
|
75
|
-
"@datadog/pprof": "
|
|
75
|
+
"@datadog/pprof": "5.0.0",
|
|
76
76
|
"@datadog/sketches-js": "^2.1.0",
|
|
77
77
|
"@opentelemetry/api": "^1.0.0",
|
|
78
78
|
"@opentelemetry/core": "^1.14.0",
|
|
79
79
|
"crypto-randomuuid": "^1.0.0",
|
|
80
80
|
"dc-polyfill": "^0.1.2",
|
|
81
81
|
"ignore": "^5.2.4",
|
|
82
|
-
"import-in-the-middle": "^1.
|
|
82
|
+
"import-in-the-middle": "^1.7.1",
|
|
83
83
|
"int64-buffer": "^0.1.9",
|
|
84
84
|
"ipaddr.js": "^2.1.0",
|
|
85
85
|
"istanbul-lib-coverage": "3.2.0",
|
|
86
86
|
"jest-docblock": "^29.7.0",
|
|
87
87
|
"koalas": "^1.0.2",
|
|
88
|
-
"limiter": "
|
|
88
|
+
"limiter": "1.1.5",
|
|
89
89
|
"lodash.kebabcase": "^4.1.1",
|
|
90
90
|
"lodash.pick": "^4.4.0",
|
|
91
91
|
"lodash.sortby": "^4.7.0",
|
|
@@ -99,12 +99,12 @@
|
|
|
99
99
|
"path-to-regexp": "^0.1.2",
|
|
100
100
|
"pprof-format": "^2.0.7",
|
|
101
101
|
"protobufjs": "^7.2.5",
|
|
102
|
-
"tlhunter-sorted-set": "^0.1.0",
|
|
103
102
|
"retry": "^0.13.1",
|
|
104
|
-
"semver": "^7.5.4"
|
|
103
|
+
"semver": "^7.5.4",
|
|
104
|
+
"tlhunter-sorted-set": "^0.1.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@types/node": ">=
|
|
107
|
+
"@types/node": ">=18",
|
|
108
108
|
"autocannon": "^4.5.2",
|
|
109
109
|
"aws-sdk": "^2.1446.0",
|
|
110
110
|
"axios": "^0.21.2",
|
|
@@ -252,9 +252,10 @@ addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['^3.0.7', '^3.1.
|
|
|
252
252
|
return Cluster
|
|
253
253
|
})
|
|
254
254
|
|
|
255
|
-
// semver >=3.2.
|
|
255
|
+
// semver >=3.2.2
|
|
256
|
+
// NOTE: <3.2.2 segfaults on cluster.close() https://issues.couchbase.com/browse/JSCBC-936
|
|
256
257
|
|
|
257
|
-
addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.
|
|
258
|
+
addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.2'] }, collection => {
|
|
258
259
|
const Collection = collection.Collection
|
|
259
260
|
|
|
260
261
|
wrapAllNames(['upsert', 'insert', 'replace'], name => {
|
|
@@ -264,7 +265,7 @@ addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.0'] }
|
|
|
264
265
|
return collection
|
|
265
266
|
})
|
|
266
267
|
|
|
267
|
-
addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.
|
|
268
|
+
addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.2'] }, bucket => {
|
|
268
269
|
const Bucket = bucket.Bucket
|
|
269
270
|
shimmer.wrap(Bucket.prototype, 'collection', getCollection => {
|
|
270
271
|
return function () {
|
|
@@ -278,7 +279,7 @@ addHook({ name: 'couchbase', file: 'dist/bucket.js', versions: ['>=3.2.0'] }, bu
|
|
|
278
279
|
return bucket
|
|
279
280
|
})
|
|
280
281
|
|
|
281
|
-
addHook({ name: 'couchbase', file: 'dist/cluster.js', versions: ['
|
|
282
|
+
addHook({ name: 'couchbase', file: 'dist/cluster.js', versions: ['>=3.2.2'] }, (cluster) => {
|
|
282
283
|
const Cluster = cluster.Cluster
|
|
283
284
|
|
|
284
285
|
shimmer.wrap(Cluster.prototype, 'query', wrapV3Query)
|
|
@@ -38,6 +38,20 @@ class JestPlugin extends CiPlugin {
|
|
|
38
38
|
return 'jest'
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// The lists are the same for every test suite, so we can cache them
|
|
42
|
+
getUnskippableSuites (unskippableSuitesList) {
|
|
43
|
+
if (!this.unskippableSuites) {
|
|
44
|
+
this.unskippableSuites = JSON.parse(unskippableSuitesList)
|
|
45
|
+
}
|
|
46
|
+
return this.unskippableSuites
|
|
47
|
+
}
|
|
48
|
+
getForcedToRunSuites (forcedToRunSuitesList) {
|
|
49
|
+
if (!this.forcedToRunSuites) {
|
|
50
|
+
this.forcedToRunSuites = JSON.parse(forcedToRunSuitesList)
|
|
51
|
+
}
|
|
52
|
+
return this.forcedToRunSuites
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
constructor (...args) {
|
|
42
56
|
super(...args)
|
|
43
57
|
|
|
@@ -128,11 +142,17 @@ class JestPlugin extends CiPlugin {
|
|
|
128
142
|
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
|
|
129
143
|
|
|
130
144
|
if (_ddUnskippable) {
|
|
131
|
-
this.
|
|
132
|
-
|
|
145
|
+
const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
|
|
146
|
+
if (unskippableSuites[testSuite]) {
|
|
147
|
+
this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
148
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
149
|
+
}
|
|
133
150
|
if (_ddForcedToRun) {
|
|
134
|
-
this.
|
|
135
|
-
|
|
151
|
+
const forcedToRunSuites = this.getForcedToRunSuites(_ddForcedToRun)
|
|
152
|
+
if (forcedToRunSuites[testSuite]) {
|
|
153
|
+
this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
|
|
154
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
155
|
+
}
|
|
136
156
|
}
|
|
137
157
|
}
|
|
138
158
|
|
|
@@ -77,30 +77,52 @@ function isMarkedAsUnskippable (test) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
|
|
80
|
-
|
|
80
|
+
const unskippableSuites = {}
|
|
81
|
+
const forcedToRunSuites = {}
|
|
82
|
+
|
|
83
|
+
const skippedSuites = []
|
|
84
|
+
const suitesToRun = []
|
|
85
|
+
|
|
86
|
+
for (const test of originalTests) {
|
|
81
87
|
const relativePath = getTestSuitePath(test.path, rootDir)
|
|
82
88
|
const shouldBeSkipped = skippableSuites.includes(relativePath)
|
|
83
|
-
|
|
84
89
|
if (isMarkedAsUnskippable(test)) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (shouldBeSkipped) {
|
|
90
|
-
test.context.config.testEnvironmentOptions['_ddForcedToRun'] = true
|
|
91
|
-
acc.hasForcedToRunSuites = true
|
|
92
|
-
}
|
|
90
|
+
suitesToRun.push(test)
|
|
91
|
+
unskippableSuites[relativePath] = true
|
|
92
|
+
if (shouldBeSkipped) {
|
|
93
|
+
forcedToRunSuites[relativePath] = true
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
continue
|
|
95
96
|
}
|
|
96
|
-
|
|
97
97
|
if (shouldBeSkipped) {
|
|
98
|
-
|
|
98
|
+
skippedSuites.push(relativePath)
|
|
99
99
|
} else {
|
|
100
|
-
|
|
100
|
+
suitesToRun.push(test)
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
|
|
105
|
+
const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
|
|
106
|
+
|
|
107
|
+
if (originalTests.length) {
|
|
108
|
+
// The config object is shared by all tests, so we can just take the first one
|
|
109
|
+
const [test] = originalTests
|
|
110
|
+
if (test?.context?.config?.testEnvironmentOptions) {
|
|
111
|
+
if (hasUnskippableSuites) {
|
|
112
|
+
test.context.config.testEnvironmentOptions._ddUnskippable = JSON.stringify(unskippableSuites)
|
|
113
|
+
}
|
|
114
|
+
if (hasForcedToRunSuites) {
|
|
115
|
+
test.context.config.testEnvironmentOptions._ddForcedToRun = JSON.stringify(forcedToRunSuites)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
skippedSuites,
|
|
122
|
+
suitesToRun,
|
|
123
|
+
hasUnskippableSuites,
|
|
124
|
+
hasForcedToRunSuites
|
|
125
|
+
}
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }
|
|
@@ -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),
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const RateLimiter = require('./rate_limiter')
|
|
4
3
|
const Sampler = require('./sampler')
|
|
5
|
-
const ext = require('../../../ext')
|
|
6
4
|
const { setSamplingRules } = require('./startup-log')
|
|
5
|
+
const SamplingRule = require('./sampling_rule')
|
|
7
6
|
|
|
8
7
|
const {
|
|
9
8
|
SAMPLING_MECHANISM_DEFAULT,
|
|
@@ -16,14 +15,21 @@ const {
|
|
|
16
15
|
DECISION_MAKER_KEY
|
|
17
16
|
} = require('./constants')
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
const {
|
|
19
|
+
tags: {
|
|
20
|
+
MANUAL_KEEP,
|
|
21
|
+
MANUAL_DROP,
|
|
22
|
+
SAMPLING_PRIORITY,
|
|
23
|
+
SERVICE_NAME
|
|
24
|
+
},
|
|
25
|
+
priority: {
|
|
26
|
+
AUTO_REJECT,
|
|
27
|
+
AUTO_KEEP,
|
|
28
|
+
USER_REJECT,
|
|
29
|
+
USER_KEEP
|
|
30
|
+
}
|
|
31
|
+
} = require('../../../ext')
|
|
32
|
+
|
|
27
33
|
const DEFAULT_KEY = 'service:,env:'
|
|
28
34
|
|
|
29
35
|
const defaultSampler = new Sampler(AUTO_KEEP)
|
|
@@ -36,8 +42,7 @@ class PrioritySampler {
|
|
|
36
42
|
|
|
37
43
|
configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
|
|
38
44
|
this._env = env
|
|
39
|
-
this._rules = this._normalizeRules(rules, sampleRate)
|
|
40
|
-
this._limiter = new RateLimiter(rateLimit)
|
|
45
|
+
this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
|
|
41
46
|
|
|
42
47
|
setSamplingRules(this._rules)
|
|
43
48
|
}
|
|
@@ -104,7 +109,7 @@ class PrioritySampler {
|
|
|
104
109
|
|
|
105
110
|
_getPriorityFromAuto (span) {
|
|
106
111
|
const context = this._getContext(span)
|
|
107
|
-
const rule = this._findRule(
|
|
112
|
+
const rule = this._findRule(span)
|
|
108
113
|
|
|
109
114
|
return rule
|
|
110
115
|
? this._getPriorityByRule(context, rule)
|
|
@@ -131,15 +136,14 @@ class PrioritySampler {
|
|
|
131
136
|
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
132
137
|
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
const sampled = rule.sample()
|
|
140
|
+
const priority = sampled ? USER_KEEP : USER_REJECT
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
|
|
142
|
+
if (sampled) {
|
|
143
|
+
context._trace[SAMPLING_LIMIT_DECISION] = rule.effectiveRate
|
|
144
|
+
}
|
|
141
145
|
|
|
142
|
-
return
|
|
146
|
+
return priority
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
_getPriorityByAgent (context) {
|
|
@@ -172,33 +176,21 @@ class PrioritySampler {
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
_normalizeRules (rules, sampleRate) {
|
|
179
|
+
_normalizeRules (rules, sampleRate, rateLimit) {
|
|
176
180
|
rules = [].concat(rules || [])
|
|
177
181
|
|
|
178
182
|
return rules
|
|
179
|
-
.concat({ sampleRate })
|
|
183
|
+
.concat({ sampleRate, maxPerSecond: rateLimit })
|
|
180
184
|
.map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
|
|
181
185
|
.filter(rule => !isNaN(rule.sampleRate))
|
|
182
|
-
.map(
|
|
186
|
+
.map(SamplingRule.from)
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
_findRule (
|
|
186
|
-
for (
|
|
187
|
-
if (
|
|
189
|
+
_findRule (span) {
|
|
190
|
+
for (const rule of this._rules) {
|
|
191
|
+
if (rule.match(span)) return rule
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
|
-
|
|
191
|
-
_matchRule (context, rule) {
|
|
192
|
-
const name = context._name
|
|
193
|
-
const service = context._tags['service.name']
|
|
194
|
-
|
|
195
|
-
if (rule.name instanceof RegExp && !rule.name.test(name)) return false
|
|
196
|
-
if (typeof rule.name === 'string' && rule.name !== name) return false
|
|
197
|
-
if (rule.service instanceof RegExp && !rule.service.test(service)) return false
|
|
198
|
-
if (typeof rule.service === 'string' && rule.service !== service) return false
|
|
199
|
-
|
|
200
|
-
return true
|
|
201
|
-
}
|
|
202
194
|
}
|
|
203
195
|
|
|
204
196
|
function hasOwn (object, prop) {
|
|
@@ -35,6 +35,7 @@ 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,
|
|
@@ -91,14 +92,24 @@ class Config {
|
|
|
91
92
|
logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`)
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
// Profiler sampling contexts are not available on Windows, so features
|
|
96
|
+
// depending on those (code hotspots and endpoint collection) need to default
|
|
97
|
+
// to false on Windows.
|
|
98
|
+
const samplingContextsAvailable = process.platform !== 'win32'
|
|
99
|
+
function checkOptionAllowed (option, description) {
|
|
100
|
+
if (option && !samplingContextsAvailable) {
|
|
101
|
+
throw new Error(`${description} not supported on ${process.platform}.`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
94
104
|
this.flushInterval = flushInterval
|
|
95
105
|
this.uploadTimeout = uploadTimeout
|
|
96
106
|
this.sourceMap = sourceMap
|
|
97
107
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
98
108
|
this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
|
|
99
109
|
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
100
|
-
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED,
|
|
110
|
+
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable))
|
|
101
111
|
logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED')
|
|
112
|
+
checkOptionAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
|
|
102
113
|
|
|
103
114
|
this.pprofPrefix = pprofPrefix
|
|
104
115
|
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
@@ -147,8 +158,12 @@ class Config {
|
|
|
147
158
|
|
|
148
159
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
149
160
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
150
|
-
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
|
|
161
|
+
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
|
|
151
162
|
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
|
|
163
|
+
checkOptionAllowed(this.codeHotspotsEnabled, 'Code hotspots')
|
|
164
|
+
|
|
165
|
+
this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
|
|
166
|
+
DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, false))
|
|
152
167
|
|
|
153
168
|
this.profilers = ensureProfilers(profilers, this)
|
|
154
169
|
}
|
|
@@ -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
|
|
|
@@ -7,7 +7,7 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
|
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
9
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
10
|
-
const { END_TIMESTAMP_LABEL, getThreadLabels } = require('./shared')
|
|
10
|
+
const { END_TIMESTAMP_LABEL, getNonJSThreadsLabels, getThreadLabels } = require('./shared')
|
|
11
11
|
|
|
12
12
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
13
13
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
@@ -78,13 +78,15 @@ class NativeWallProfiler {
|
|
|
78
78
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
79
79
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
80
80
|
this._timelineEnabled = !!options.timelineEnabled
|
|
81
|
+
this._cpuProfilingEnabled = !!options.cpuProfilingEnabled
|
|
81
82
|
// We need to capture span data into the sample context for either code hotspots
|
|
82
83
|
// or endpoint collection.
|
|
83
84
|
this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
|
|
84
85
|
// We need to run the pprof wall profiler with sample contexts if we're either
|
|
85
86
|
// capturing span data or timeline is enabled (so we need sample timestamps, and for now
|
|
86
|
-
// timestamps require the sample contexts feature in the pprof wall profiler
|
|
87
|
-
|
|
87
|
+
// timestamps require the sample contexts feature in the pprof wall profiler), or
|
|
88
|
+
// cpu profiling is enabled.
|
|
89
|
+
this._withContexts = this._captureSpanData || this._timelineEnabled || this._cpuProfilingEnabled
|
|
88
90
|
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
89
91
|
this._mapper = undefined
|
|
90
92
|
this._pprof = undefined
|
|
@@ -131,7 +133,8 @@ class NativeWallProfiler {
|
|
|
131
133
|
sourceMapper: this._mapper,
|
|
132
134
|
withContexts: this._withContexts,
|
|
133
135
|
lineNumbers: false,
|
|
134
|
-
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
136
|
+
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled,
|
|
137
|
+
collectCpuTime: this._cpuProfilingEnabled
|
|
135
138
|
})
|
|
136
139
|
|
|
137
140
|
if (this._withContexts) {
|
|
@@ -220,22 +223,42 @@ class NativeWallProfiler {
|
|
|
220
223
|
|
|
221
224
|
_stop (restart) {
|
|
222
225
|
if (!this._started) return
|
|
226
|
+
|
|
223
227
|
if (this._captureSpanData) {
|
|
224
228
|
// update last sample context if needed
|
|
225
229
|
this._enter()
|
|
226
230
|
this._lastSampleCount = 0
|
|
227
231
|
}
|
|
228
232
|
const profile = this._pprof.time.stop(restart, this._generateLabels)
|
|
233
|
+
|
|
229
234
|
if (restart) {
|
|
230
235
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
231
236
|
if (v8BugDetected !== 0) {
|
|
232
237
|
this._reportV8bug(v8BugDetected === 1)
|
|
233
238
|
}
|
|
239
|
+
} else {
|
|
240
|
+
if (this._captureSpanData) {
|
|
241
|
+
beforeCh.unsubscribe(this._enter)
|
|
242
|
+
enterCh.unsubscribe(this._enter)
|
|
243
|
+
spanFinishCh.unsubscribe(this._spanFinished)
|
|
244
|
+
this._profilerState = undefined
|
|
245
|
+
this._lastSpan = undefined
|
|
246
|
+
this._lastStartedSpans = undefined
|
|
247
|
+
this._lastWebTags = undefined
|
|
248
|
+
}
|
|
249
|
+
this._started = false
|
|
234
250
|
}
|
|
251
|
+
|
|
235
252
|
return profile
|
|
236
253
|
}
|
|
237
254
|
|
|
238
|
-
_generateLabels (context) {
|
|
255
|
+
_generateLabels ({ node, context }) {
|
|
256
|
+
// check for special node that represents CPU time all non-JS threads.
|
|
257
|
+
// In that case only return a special thread name label since we cannot associate any timestamp/span/endpoint to it.
|
|
258
|
+
if (node.name === this._pprof.time.constants.NON_JS_THREADS_FUNCTION_NAME) {
|
|
259
|
+
return getNonJSThreadsLabels()
|
|
260
|
+
}
|
|
261
|
+
|
|
239
262
|
if (context == null) {
|
|
240
263
|
// generateLabels is also called for samples without context.
|
|
241
264
|
// In that case just return thread labels.
|
|
@@ -267,8 +290,8 @@ class NativeWallProfiler {
|
|
|
267
290
|
return labels
|
|
268
291
|
}
|
|
269
292
|
|
|
270
|
-
profile () {
|
|
271
|
-
return this._stop(
|
|
293
|
+
profile (restart) {
|
|
294
|
+
return this._stop(restart)
|
|
272
295
|
}
|
|
273
296
|
|
|
274
297
|
encode (profile) {
|
|
@@ -276,21 +299,11 @@ class NativeWallProfiler {
|
|
|
276
299
|
}
|
|
277
300
|
|
|
278
301
|
stop () {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const profile = this._stop(false)
|
|
282
|
-
if (this._captureSpanData) {
|
|
283
|
-
beforeCh.unsubscribe(this._enter)
|
|
284
|
-
enterCh.unsubscribe(this._enter)
|
|
285
|
-
spanFinishCh.unsubscribe(this._spanFinished)
|
|
286
|
-
this._profilerState = undefined
|
|
287
|
-
this._lastSpan = undefined
|
|
288
|
-
this._lastStartedSpans = undefined
|
|
289
|
-
this._lastWebTags = undefined
|
|
290
|
-
}
|
|
302
|
+
this._stop(false)
|
|
303
|
+
}
|
|
291
304
|
|
|
292
|
-
|
|
293
|
-
return
|
|
305
|
+
isStarted () {
|
|
306
|
+
return this._started
|
|
294
307
|
}
|
|
295
308
|
}
|
|
296
309
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { globMatch } = require('../src/util')
|
|
4
|
+
const RateLimiter = require('./rate_limiter')
|
|
5
|
+
const Sampler = require('./sampler')
|
|
6
|
+
|
|
7
|
+
class AlwaysMatcher {
|
|
8
|
+
match () {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class GlobMatcher {
|
|
14
|
+
constructor (pattern, locator) {
|
|
15
|
+
this.pattern = pattern
|
|
16
|
+
this.locator = locator
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
match (span) {
|
|
20
|
+
const subject = this.locator(span)
|
|
21
|
+
if (!subject) return false
|
|
22
|
+
return globMatch(this.pattern, subject)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class RegExpMatcher {
|
|
27
|
+
constructor (pattern, locator) {
|
|
28
|
+
this.pattern = pattern
|
|
29
|
+
this.locator = locator
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
match (span) {
|
|
33
|
+
const subject = this.locator(span)
|
|
34
|
+
if (!subject) return false
|
|
35
|
+
return this.pattern.test(subject)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function matcher (pattern, locator) {
|
|
40
|
+
if (pattern instanceof RegExp) {
|
|
41
|
+
return new RegExpMatcher(pattern, locator)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof pattern === 'string' && pattern !== '*') {
|
|
45
|
+
return new GlobMatcher(pattern, locator)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new AlwaysMatcher()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeTagLocator (tag) {
|
|
52
|
+
return (span) => span.context()._tags[tag]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function nameLocator (span) {
|
|
56
|
+
return span.context()._name
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function serviceLocator (span) {
|
|
60
|
+
const { _tags: tags } = span.context()
|
|
61
|
+
return tags.service ||
|
|
62
|
+
tags['service.name'] ||
|
|
63
|
+
span.tracer()._service
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class SamplingRule {
|
|
67
|
+
constructor ({ name, service, resource, tags, sampleRate = 1.0, maxPerSecond } = {}) {
|
|
68
|
+
this.matchers = []
|
|
69
|
+
|
|
70
|
+
if (name) {
|
|
71
|
+
this.matchers.push(matcher(name, nameLocator))
|
|
72
|
+
}
|
|
73
|
+
if (service) {
|
|
74
|
+
this.matchers.push(matcher(service, serviceLocator))
|
|
75
|
+
}
|
|
76
|
+
if (resource) {
|
|
77
|
+
this.matchers.push(matcher(resource, makeTagLocator('resource.name')))
|
|
78
|
+
}
|
|
79
|
+
for (const [key, value] of Object.entries(tags || {})) {
|
|
80
|
+
this.matchers.push(matcher(value, makeTagLocator(key)))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._sampler = new Sampler(sampleRate)
|
|
84
|
+
this._limiter = undefined
|
|
85
|
+
|
|
86
|
+
if (Number.isFinite(maxPerSecond)) {
|
|
87
|
+
this._limiter = new RateLimiter(maxPerSecond)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static from (config) {
|
|
92
|
+
return new SamplingRule(config)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get sampleRate () {
|
|
96
|
+
return this._sampler.rate()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get effectiveRate () {
|
|
100
|
+
return this._limiter && this._limiter.effectiveRate()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get maxPerSecond () {
|
|
104
|
+
return this._limiter && this._limiter._rateLimit
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
match (span) {
|
|
108
|
+
for (const matcher of this.matchers) {
|
|
109
|
+
if (!matcher.match(span)) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sample () {
|
|
118
|
+
if (!this._sampler.isSampled()) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this._limiter) {
|
|
123
|
+
return this._limiter.isAllowed()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = SamplingRule
|
|
@@ -1,67 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const { globMatch } = require('../src/util')
|
|
3
|
-
const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
|
|
4
|
-
const RateLimiter = require('./rate_limiter')
|
|
5
|
-
const Sampler = require('./sampler')
|
|
6
|
-
|
|
7
|
-
class SpanSamplingRule {
|
|
8
|
-
constructor ({ service, name, sampleRate = 1.0, maxPerSecond } = {}) {
|
|
9
|
-
this.service = service
|
|
10
|
-
this.name = name
|
|
11
|
-
|
|
12
|
-
this._sampler = new Sampler(sampleRate)
|
|
13
|
-
this._limiter = undefined
|
|
14
|
-
|
|
15
|
-
if (Number.isFinite(maxPerSecond)) {
|
|
16
|
-
this._limiter = new RateLimiter(maxPerSecond)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
get sampleRate () {
|
|
21
|
-
return this._sampler.rate()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get maxPerSecond () {
|
|
25
|
-
return this._limiter && this._limiter._rateLimit
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static from (config) {
|
|
29
|
-
return new SpanSamplingRule(config)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
match (service, name) {
|
|
33
|
-
if (this.service && !globMatch(this.service, service)) {
|
|
34
|
-
return false
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (this.name && !globMatch(this.name, name)) {
|
|
38
|
-
return false
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return true
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
sample () {
|
|
45
|
-
if (!this._sampler.isSampled()) {
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
2
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return true
|
|
54
|
-
}
|
|
55
|
-
}
|
|
3
|
+
const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
|
|
4
|
+
const SamplingRule = require('./sampling_rule')
|
|
56
5
|
|
|
57
6
|
class SpanSampler {
|
|
58
7
|
constructor ({ spanSamplingRules = [] } = {}) {
|
|
59
|
-
this._rules = spanSamplingRules.map(
|
|
8
|
+
this._rules = spanSamplingRules.map(SamplingRule.from)
|
|
60
9
|
}
|
|
61
10
|
|
|
62
|
-
findRule (
|
|
11
|
+
findRule (context) {
|
|
63
12
|
for (const rule of this._rules) {
|
|
64
|
-
if (rule.match(
|
|
13
|
+
if (rule.match(context)) {
|
|
65
14
|
return rule
|
|
66
15
|
}
|
|
67
16
|
}
|
|
@@ -73,14 +22,7 @@ class SpanSampler {
|
|
|
73
22
|
|
|
74
23
|
const { started } = spanContext._trace
|
|
75
24
|
for (const span of started) {
|
|
76
|
-
const
|
|
77
|
-
const tags = context._tags || {}
|
|
78
|
-
const name = context._name
|
|
79
|
-
const service = tags.service ||
|
|
80
|
-
tags['service.name'] ||
|
|
81
|
-
span.tracer()._service
|
|
82
|
-
|
|
83
|
-
const rule = this.findRule(service, name)
|
|
25
|
+
const rule = this.findRule(span)
|
|
84
26
|
if (rule && rule.sample()) {
|
|
85
27
|
span.context()._spanSampling = {
|
|
86
28
|
sampleRate: rule.sampleRate,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
const request = require('../exporters/common/request')
|
|
3
3
|
const log = require('../log')
|
|
4
|
+
|
|
4
5
|
let agentTelemetry = true
|
|
5
6
|
|
|
6
7
|
function getHeaders (config, application, reqType) {
|
|
@@ -15,9 +16,22 @@ function getHeaders (config, application, reqType) {
|
|
|
15
16
|
if (debug) {
|
|
16
17
|
headers['dd-telemetry-debug-enabled'] = 'true'
|
|
17
18
|
}
|
|
19
|
+
if (config.apiKey) {
|
|
20
|
+
headers['dd-api-key'] = config.apiKey
|
|
21
|
+
}
|
|
18
22
|
return headers
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
function getAgentlessTelemetryEndpoint (site) {
|
|
26
|
+
if (site === 'datad0g.com') { // staging
|
|
27
|
+
return 'https://all-http-intake.logs.datad0g.com'
|
|
28
|
+
}
|
|
29
|
+
if (site === 'datadoghq.eu') {
|
|
30
|
+
return 'https://instrumentation-telemetry-intake.eu1.datadoghq.com'
|
|
31
|
+
}
|
|
32
|
+
return `https://instrumentation-telemetry-intake.${site}`
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
let seqId = 0
|
|
22
36
|
|
|
23
37
|
function getPayload (payload) {
|
|
@@ -35,17 +49,33 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
|
|
|
35
49
|
const {
|
|
36
50
|
hostname,
|
|
37
51
|
port,
|
|
38
|
-
|
|
52
|
+
experimental,
|
|
53
|
+
isCiVisibility
|
|
39
54
|
} = config
|
|
40
55
|
|
|
56
|
+
let url = config.url
|
|
57
|
+
|
|
58
|
+
const isCiVisibilityAgentlessMode = isCiVisibility && experimental?.exporter === 'datadog'
|
|
59
|
+
|
|
60
|
+
if (isCiVisibilityAgentlessMode) {
|
|
61
|
+
try {
|
|
62
|
+
url = url || new URL(getAgentlessTelemetryEndpoint(config.site))
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log.error(err)
|
|
65
|
+
// No point to do the request if the URL is invalid
|
|
66
|
+
return cb(err, { payload, reqType })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
const options = {
|
|
42
71
|
url,
|
|
43
72
|
hostname,
|
|
44
73
|
port,
|
|
45
74
|
method: 'POST',
|
|
46
|
-
path: '/telemetry/proxy/api/v2/apmtelemetry',
|
|
75
|
+
path: isCiVisibilityAgentlessMode ? '/api/v2/apmtelemetry' : '/telemetry/proxy/api/v2/apmtelemetry',
|
|
47
76
|
headers: getHeaders(config, application, reqType)
|
|
48
77
|
}
|
|
78
|
+
|
|
49
79
|
const data = JSON.stringify({
|
|
50
80
|
api_version: 'v2',
|
|
51
81
|
naming_schema_version: config.spanAttributeSchema ? config.spanAttributeSchema : '',
|
|
@@ -65,24 +95,13 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
|
|
|
65
95
|
agentTelemetry = false
|
|
66
96
|
}
|
|
67
97
|
// figure out which data center to send to
|
|
68
|
-
|
|
69
|
-
const dataCenters = [
|
|
70
|
-
'datadoghq.com',
|
|
71
|
-
'us3.datadoghq.com',
|
|
72
|
-
'us5.datadoghq.com',
|
|
73
|
-
'ap1.datadoghq.com',
|
|
74
|
-
'eu1.datadoghq.com'
|
|
75
|
-
]
|
|
76
|
-
if (config.site === 'datad0g.com') { // staging
|
|
77
|
-
backendUrl = 'https://all-http-intake.logs.datad0g.com/api/v2/apmtelemetry'
|
|
78
|
-
} else if (dataCenters.includes(config.site)) {
|
|
79
|
-
backendUrl = 'https://instrumentation-telemetry-intake.' + config.site + '/api/v2/apmtelemetry'
|
|
80
|
-
}
|
|
98
|
+
const backendUrl = getAgentlessTelemetryEndpoint(config.site)
|
|
81
99
|
const backendHeader = { ...options.headers, 'DD-API-KEY': process.env.DD_API_KEY }
|
|
82
100
|
const backendOptions = {
|
|
83
101
|
...options,
|
|
84
102
|
url: backendUrl,
|
|
85
|
-
headers: backendHeader
|
|
103
|
+
headers: backendHeader,
|
|
104
|
+
path: '/api/v2/apmtelemetry'
|
|
86
105
|
}
|
|
87
106
|
if (backendUrl) {
|
|
88
107
|
request(data, backendOptions, (error) => { log.error(error) })
|