dd-trace 4.13.1 → 4.14.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/package.json +2 -2
- package/packages/datadog-instrumentations/src/mocha.js +3 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +32 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
- package/packages/dd-trace/src/appsec/recommended.json +107 -8
- package/packages/dd-trace/src/dogstatsd.js +45 -5
- package/packages/dd-trace/src/plugins/util/test.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
- package/packages/dd-trace/src/proxy.js +1 -11
- package/packages/dd-trace/src/runtime_metrics.js +1 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@datadog/native-appsec": "^3.2.0",
|
|
71
|
-
"@datadog/native-iast-rewriter": "2.
|
|
71
|
+
"@datadog/native-iast-rewriter": "2.1.3",
|
|
72
72
|
"@datadog/native-iast-taint-tracking": "1.5.0",
|
|
73
73
|
"@datadog/native-metrics": "^2.0.0",
|
|
74
74
|
"@datadog/pprof": "3.2.0",
|
|
@@ -441,6 +441,9 @@ addHook({
|
|
|
441
441
|
file: 'lib/cli/run-helpers.js'
|
|
442
442
|
}, (run) => {
|
|
443
443
|
shimmer.wrap(run, 'runMocha', runMocha => async function () {
|
|
444
|
+
if (!testStartCh.hasSubscribers) {
|
|
445
|
+
return runMocha.apply(this, arguments)
|
|
446
|
+
}
|
|
444
447
|
const mocha = arguments[0]
|
|
445
448
|
/**
|
|
446
449
|
* This attaches `run` to the global context, which we'll call after
|
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
26
26
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
27
27
|
const log = require('../../dd-trace/src/log')
|
|
28
|
+
const NoopTracer = require('../../dd-trace/src/noop/tracer')
|
|
28
29
|
|
|
29
30
|
const TEST_FRAMEWORK_NAME = 'cypress'
|
|
30
31
|
|
|
@@ -119,10 +120,32 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
|
119
120
|
})
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
const noopTask = {
|
|
124
|
+
'dd:testSuiteStart': () => {
|
|
125
|
+
return null
|
|
126
|
+
},
|
|
127
|
+
'dd:beforeEach': () => {
|
|
128
|
+
return {}
|
|
129
|
+
},
|
|
130
|
+
'dd:afterEach': () => {
|
|
131
|
+
return null
|
|
132
|
+
},
|
|
133
|
+
'dd:addTags': () => {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
module.exports = (on, config) => {
|
|
123
139
|
let isTestsSkipped = false
|
|
124
140
|
const skippedTests = []
|
|
125
141
|
const tracer = require('../../dd-trace')
|
|
142
|
+
|
|
143
|
+
// The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
|
|
144
|
+
if (tracer._tracer instanceof NoopTracer) {
|
|
145
|
+
// We still need to register these tasks or the support file will fail
|
|
146
|
+
return on('task', noopTask)
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
127
150
|
|
|
128
151
|
const {
|
|
@@ -328,12 +351,16 @@ module.exports = (on, config) => {
|
|
|
328
351
|
}
|
|
329
352
|
|
|
330
353
|
return new Promise(resolve => {
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
const exporter = tracer._tracer._exporter
|
|
355
|
+
if (!exporter) {
|
|
356
|
+
return resolve(null)
|
|
357
|
+
}
|
|
358
|
+
if (exporter.flush) {
|
|
359
|
+
exporter.flush(() => {
|
|
333
360
|
resolve(null)
|
|
334
361
|
})
|
|
335
|
-
} else {
|
|
336
|
-
|
|
362
|
+
} else if (exporter._writer) {
|
|
363
|
+
exporter._writer.flush(() => {
|
|
337
364
|
resolve(null)
|
|
338
365
|
})
|
|
339
366
|
}
|
|
@@ -375,7 +402,7 @@ module.exports = (on, config) => {
|
|
|
375
402
|
'dd:afterEach': ({ test, coverage }) => {
|
|
376
403
|
const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
|
|
377
404
|
if (activeSpan) {
|
|
378
|
-
if (coverage && tracer._tracer._exporter
|
|
405
|
+
if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
|
|
379
406
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
380
407
|
const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
|
|
381
408
|
const { _traceId, _spanId } = testSuiteSpan.context()
|
|
@@ -6,6 +6,7 @@ const { addVulnerability } = require('../vulnerability-reporter')
|
|
|
6
6
|
const { getIastContext } = require('../iast-context')
|
|
7
7
|
const overheadController = require('../overhead-controller')
|
|
8
8
|
const { SinkIastPlugin } = require('../iast-plugin')
|
|
9
|
+
const { getOriginalPathAndLineFromSourceMap } = require('../taint-tracking/rewriter')
|
|
9
10
|
|
|
10
11
|
class Analyzer extends SinkIastPlugin {
|
|
11
12
|
constructor (type) {
|
|
@@ -25,8 +26,9 @@ class Analyzer extends SinkIastPlugin {
|
|
|
25
26
|
const evidence = this._getEvidence(value, context)
|
|
26
27
|
const location = this._getLocation(value)
|
|
27
28
|
if (!this._isExcluded(location)) {
|
|
29
|
+
const locationSourceMap = this._replaceLocationFromSourceMap(location)
|
|
28
30
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
29
|
-
const vulnerability = this._createVulnerability(this._type, evidence, spanId,
|
|
31
|
+
const vulnerability = this._createVulnerability(this._type, evidence, spanId, locationSourceMap)
|
|
30
32
|
addVulnerability(context, vulnerability)
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -47,6 +49,22 @@ class Analyzer extends SinkIastPlugin {
|
|
|
47
49
|
return getFirstNonDDPathAndLine(this._getExcludedPaths())
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
_replaceLocationFromSourceMap (location) {
|
|
53
|
+
if (location) {
|
|
54
|
+
const { path, line, column } = getOriginalPathAndLineFromSourceMap(location)
|
|
55
|
+
if (path) {
|
|
56
|
+
location.path = path
|
|
57
|
+
}
|
|
58
|
+
if (line) {
|
|
59
|
+
location.line = line
|
|
60
|
+
}
|
|
61
|
+
if (column) {
|
|
62
|
+
location.column = column
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return location
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
_getExcludedPaths () {}
|
|
51
69
|
|
|
52
70
|
_isInvalidContext (store, iastContext) {
|
|
@@ -47,6 +47,7 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
|
|
|
47
47
|
return {
|
|
48
48
|
path: path.relative(process.cwd(), filepath),
|
|
49
49
|
line: callsite.getLineNumber(),
|
|
50
|
+
column: callsite.getColumnNumber(),
|
|
50
51
|
isInternal: !path.isAbsolute(filepath)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -10,12 +10,51 @@ const { getRewriteFunction } = require('./rewriter-telemetry')
|
|
|
10
10
|
|
|
11
11
|
let rewriter
|
|
12
12
|
let getPrepareStackTrace
|
|
13
|
+
|
|
14
|
+
let getRewriterOriginalPathAndLineFromSourceMap = function (path, line, column) {
|
|
15
|
+
return { path, line, column }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isFlagPresent (flag) {
|
|
19
|
+
return process.env.NODE_OPTIONS?.includes(flag) ||
|
|
20
|
+
process.execArgv?.some(arg => arg.includes(flag))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getGetOriginalPathAndLineFromSourceMapFunction (chainSourceMap, getOriginalPathAndLineFromSourceMap) {
|
|
24
|
+
if (chainSourceMap) {
|
|
25
|
+
return function (path, line, column) {
|
|
26
|
+
// if --enable-source-maps is present stacktraces of the rewritten files contain the original path, file and
|
|
27
|
+
// column because the sourcemap chaining is done during the rewriting process so we can skip it
|
|
28
|
+
if (isPrivateModule(path) && isNotLibraryFile(path)) {
|
|
29
|
+
return { path, line, column }
|
|
30
|
+
} else {
|
|
31
|
+
return getOriginalPathAndLineFromSourceMap(path, line, column)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
return getOriginalPathAndLineFromSourceMap
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
function getRewriter (telemetryVerbosity) {
|
|
14
40
|
if (!rewriter) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
try {
|
|
42
|
+
const iastRewriter = require('@datadog/native-iast-rewriter')
|
|
43
|
+
const Rewriter = iastRewriter.Rewriter
|
|
44
|
+
getPrepareStackTrace = iastRewriter.getPrepareStackTrace
|
|
45
|
+
|
|
46
|
+
const chainSourceMap = isFlagPresent('--enable-source-maps')
|
|
47
|
+
const getOriginalPathAndLineFromSourceMap = iastRewriter.getOriginalPathAndLineFromSourceMap
|
|
48
|
+
if (getOriginalPathAndLineFromSourceMap) {
|
|
49
|
+
getRewriterOriginalPathAndLineFromSourceMap =
|
|
50
|
+
getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity), chainSourceMap })
|
|
54
|
+
} catch (e) {
|
|
55
|
+
iastLog.error('Unable to initialize TaintTracking Rewriter')
|
|
56
|
+
.errorAndPublish(e)
|
|
57
|
+
}
|
|
19
58
|
}
|
|
20
59
|
return rewriter
|
|
21
60
|
}
|
|
@@ -74,6 +113,10 @@ function disableRewriter () {
|
|
|
74
113
|
Error.prepareStackTrace = originalPrepareStackTrace
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
function getOriginalPathAndLineFromSourceMap ({ path, line, column }) {
|
|
117
|
+
return getRewriterOriginalPathAndLineFromSourceMap(path, line, column)
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
module.exports = {
|
|
78
|
-
enableRewriter, disableRewriter
|
|
121
|
+
enableRewriter, disableRewriter, getOriginalPathAndLineFromSourceMap
|
|
79
122
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "2.2",
|
|
3
3
|
"metadata": {
|
|
4
|
-
"rules_version": "1.7.
|
|
4
|
+
"rules_version": "1.7.2"
|
|
5
5
|
},
|
|
6
6
|
"rules": [
|
|
7
7
|
{
|
|
@@ -1743,7 +1743,10 @@
|
|
|
1743
1743
|
"sys/hypervisor",
|
|
1744
1744
|
"sys/kernel",
|
|
1745
1745
|
"sys/module",
|
|
1746
|
-
"sys/power"
|
|
1746
|
+
"sys/power",
|
|
1747
|
+
"windows\\win.ini",
|
|
1748
|
+
"default\\ntuser.dat",
|
|
1749
|
+
"/var/run/secrets/kubernetes.io/serviceaccount"
|
|
1747
1750
|
]
|
|
1748
1751
|
},
|
|
1749
1752
|
"operator": "phrase_match"
|
|
@@ -2312,7 +2315,8 @@
|
|
|
2312
2315
|
}
|
|
2313
2316
|
],
|
|
2314
2317
|
"transformers": [
|
|
2315
|
-
"lowercase"
|
|
2318
|
+
"lowercase",
|
|
2319
|
+
"cmdLine"
|
|
2316
2320
|
]
|
|
2317
2321
|
},
|
|
2318
2322
|
{
|
|
@@ -2950,7 +2954,7 @@
|
|
|
2950
2954
|
"address": "grpc.server.request.message"
|
|
2951
2955
|
}
|
|
2952
2956
|
],
|
|
2953
|
-
"regex": "
|
|
2957
|
+
"regex": "\\bon(?:d(?:r(?:ag(?:en(?:ter|d)|leave|start|over)?|op)|urationchange|blclick)|s(?:e(?:ek(?:ing|ed)|arch|lect)|u(?:spend|bmit)|talled|croll|how)|m(?:ouse(?:(?:lea|mo)ve|o(?:ver|ut)|enter|down|up)|essage)|p(?:a(?:ge(?:hide|show)|(?:st|us)e)|lay(?:ing)?|rogress|aste|ointer(?:cancel|down|enter|leave|move|out|over|rawupdate|up))|c(?:anplay(?:through)?|o(?:ntextmenu|py)|hange|lick|ut)|a(?:nimation(?:iteration|start|end)|(?:fterprin|bor)t|uxclick|fterscriptexecute)|t(?:o(?:uch(?:cancel|start|move|end)|ggle)|imeupdate)|f(?:ullscreen(?:change|error)|ocus(?:out|in)?|inish)|(?:(?:volume|hash)chang|o(?:ff|n)lin)e|b(?:efore(?:unload|print)|lur)|load(?:ed(?:meta)?data|start|end)?|r(?:es(?:ize|et)|atechange)|key(?:press|down|up)|w(?:aiting|heel)|in(?:valid|put)|e(?:nded|rror)|unload)[\\s\\x0B\\x09\\x0C\\x3B\\x2C\\x28\\x3B]*?=[^=]",
|
|
2954
2958
|
"options": {
|
|
2955
2959
|
"min_length": 8
|
|
2956
2960
|
}
|
|
@@ -4636,6 +4640,46 @@
|
|
|
4636
4640
|
],
|
|
4637
4641
|
"transformers": []
|
|
4638
4642
|
},
|
|
4643
|
+
{
|
|
4644
|
+
"id": "dog-913-008",
|
|
4645
|
+
"name": "Netsparker OOB domain",
|
|
4646
|
+
"tags": {
|
|
4647
|
+
"type": "commercial_scanner",
|
|
4648
|
+
"category": "attack_attempt",
|
|
4649
|
+
"tool_name": "Netsparker",
|
|
4650
|
+
"confidence": "0"
|
|
4651
|
+
},
|
|
4652
|
+
"conditions": [
|
|
4653
|
+
{
|
|
4654
|
+
"parameters": {
|
|
4655
|
+
"inputs": [
|
|
4656
|
+
{
|
|
4657
|
+
"address": "server.request.query"
|
|
4658
|
+
},
|
|
4659
|
+
{
|
|
4660
|
+
"address": "server.request.body"
|
|
4661
|
+
},
|
|
4662
|
+
{
|
|
4663
|
+
"address": "server.request.path_params"
|
|
4664
|
+
},
|
|
4665
|
+
{
|
|
4666
|
+
"address": "server.request.headers.no_cookies"
|
|
4667
|
+
},
|
|
4668
|
+
{
|
|
4669
|
+
"address": "grpc.server.request.message"
|
|
4670
|
+
}
|
|
4671
|
+
],
|
|
4672
|
+
"regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b",
|
|
4673
|
+
"options": {
|
|
4674
|
+
"case_sensitive": false,
|
|
4675
|
+
"min_length": 7
|
|
4676
|
+
}
|
|
4677
|
+
},
|
|
4678
|
+
"operator": "match_regex"
|
|
4679
|
+
}
|
|
4680
|
+
],
|
|
4681
|
+
"transformers": []
|
|
4682
|
+
},
|
|
4639
4683
|
{
|
|
4640
4684
|
"id": "dog-931-001",
|
|
4641
4685
|
"name": "RFI: URL Payload to well known RFI target",
|
|
@@ -4699,6 +4743,56 @@
|
|
|
4699
4743
|
],
|
|
4700
4744
|
"transformers": []
|
|
4701
4745
|
},
|
|
4746
|
+
{
|
|
4747
|
+
"id": "dog-941-001",
|
|
4748
|
+
"name": "XSS in source property",
|
|
4749
|
+
"tags": {
|
|
4750
|
+
"type": "xss",
|
|
4751
|
+
"category": "attack_attempt",
|
|
4752
|
+
"confidence": "0"
|
|
4753
|
+
},
|
|
4754
|
+
"conditions": [
|
|
4755
|
+
{
|
|
4756
|
+
"parameters": {
|
|
4757
|
+
"inputs": [
|
|
4758
|
+
{
|
|
4759
|
+
"address": "server.request.headers.no_cookies",
|
|
4760
|
+
"key_path": [
|
|
4761
|
+
"user-agent"
|
|
4762
|
+
]
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
"address": "server.request.headers.no_cookies",
|
|
4766
|
+
"key_path": [
|
|
4767
|
+
"referer"
|
|
4768
|
+
]
|
|
4769
|
+
},
|
|
4770
|
+
{
|
|
4771
|
+
"address": "server.request.query"
|
|
4772
|
+
},
|
|
4773
|
+
{
|
|
4774
|
+
"address": "server.request.body"
|
|
4775
|
+
},
|
|
4776
|
+
{
|
|
4777
|
+
"address": "server.request.path_params"
|
|
4778
|
+
},
|
|
4779
|
+
{
|
|
4780
|
+
"address": "grpc.server.request.message"
|
|
4781
|
+
}
|
|
4782
|
+
],
|
|
4783
|
+
"regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|//)[^\\s'\"]+['\"]?",
|
|
4784
|
+
"options": {
|
|
4785
|
+
"min_length": 14
|
|
4786
|
+
}
|
|
4787
|
+
},
|
|
4788
|
+
"operator": "match_regex"
|
|
4789
|
+
}
|
|
4790
|
+
],
|
|
4791
|
+
"transformers": [
|
|
4792
|
+
"removeNulls",
|
|
4793
|
+
"urlDecodeUni"
|
|
4794
|
+
]
|
|
4795
|
+
},
|
|
4702
4796
|
{
|
|
4703
4797
|
"id": "dog-942-001",
|
|
4704
4798
|
"name": "Blind XSS callback domains",
|
|
@@ -5428,12 +5522,14 @@
|
|
|
5428
5522
|
"address": "grpc.server.request.message"
|
|
5429
5523
|
}
|
|
5430
5524
|
],
|
|
5431
|
-
"regex": "(?i)[&|]\\s*cat\\s
|
|
5525
|
+
"regex": "(?i)[&|]\\s*cat\\s*\\/etc\\/[\\w\\.\\/]*passwd\\s*[&|]"
|
|
5432
5526
|
},
|
|
5433
5527
|
"operator": "match_regex"
|
|
5434
5528
|
}
|
|
5435
5529
|
],
|
|
5436
|
-
"transformers": [
|
|
5530
|
+
"transformers": [
|
|
5531
|
+
"cmdLine"
|
|
5532
|
+
]
|
|
5437
5533
|
},
|
|
5438
5534
|
{
|
|
5439
5535
|
"id": "sqr-000-010",
|
|
@@ -7014,7 +7110,10 @@
|
|
|
7014
7110
|
]
|
|
7015
7111
|
}
|
|
7016
7112
|
],
|
|
7017
|
-
"regex": "mozilla/4\\.0 \\(compatible(; msie 6\\.0; win32)?\\)"
|
|
7113
|
+
"regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; win32|4\\.0; Windows NT))?\\)",
|
|
7114
|
+
"options": {
|
|
7115
|
+
"case_sensitive": false
|
|
7116
|
+
}
|
|
7018
7117
|
},
|
|
7019
7118
|
"operator": "match_regex"
|
|
7020
7119
|
}
|
|
@@ -7076,4 +7175,4 @@
|
|
|
7076
7175
|
"transformers": []
|
|
7077
7176
|
}
|
|
7078
7177
|
]
|
|
7079
|
-
}
|
|
7178
|
+
}
|
|
@@ -5,6 +5,7 @@ const request = require('./exporters/common/request')
|
|
|
5
5
|
const dgram = require('dgram')
|
|
6
6
|
const isIP = require('net').isIP
|
|
7
7
|
const log = require('./log')
|
|
8
|
+
const { URL, format } = require('url')
|
|
8
9
|
|
|
9
10
|
const MAX_BUFFER_SIZE = 1024 // limit from the agent
|
|
10
11
|
|
|
@@ -13,9 +14,7 @@ const TYPE_GAUGE = 'g'
|
|
|
13
14
|
const TYPE_DISTRIBUTION = 'd'
|
|
14
15
|
|
|
15
16
|
class DogStatsDClient {
|
|
16
|
-
constructor (options) {
|
|
17
|
-
options = options || {}
|
|
18
|
-
|
|
17
|
+
constructor (options = {}) {
|
|
19
18
|
if (options.metricsProxyUrl) {
|
|
20
19
|
this._httpOptions = {
|
|
21
20
|
url: options.metricsProxyUrl.toString(),
|
|
@@ -50,6 +49,8 @@ class DogStatsDClient {
|
|
|
50
49
|
flush () {
|
|
51
50
|
const queue = this._enqueue()
|
|
52
51
|
|
|
52
|
+
log.debug(`Flushing ${queue.length} metrics via ${this._httpOptions ? 'HTTP' : 'UDP'}`)
|
|
53
|
+
|
|
53
54
|
if (this._queue.length === 0) return
|
|
54
55
|
|
|
55
56
|
this._queue = []
|
|
@@ -141,6 +142,44 @@ class DogStatsDClient {
|
|
|
141
142
|
|
|
142
143
|
return socket
|
|
143
144
|
}
|
|
145
|
+
|
|
146
|
+
static generateClientConfig (config = {}) {
|
|
147
|
+
const tags = []
|
|
148
|
+
|
|
149
|
+
if (config.tags) {
|
|
150
|
+
Object.keys(config.tags)
|
|
151
|
+
.filter(key => typeof config.tags[key] === 'string')
|
|
152
|
+
.filter(key => {
|
|
153
|
+
// Skip runtime-id unless enabled as cardinality may be too high
|
|
154
|
+
if (key !== 'runtime-id') return true
|
|
155
|
+
return (config.experimental && config.experimental.runtimeId)
|
|
156
|
+
})
|
|
157
|
+
.forEach(key => {
|
|
158
|
+
// https://docs.datadoghq.com/tagging/#defining-tags
|
|
159
|
+
const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
|
|
160
|
+
|
|
161
|
+
tags.push(`${key}:${value}`)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clientConfig = {
|
|
166
|
+
host: config.dogstatsd.hostname,
|
|
167
|
+
port: config.dogstatsd.port,
|
|
168
|
+
tags
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (config.url) {
|
|
172
|
+
clientConfig.metricsProxyUrl = config.url
|
|
173
|
+
} else if (config.port) {
|
|
174
|
+
clientConfig.metricsProxyUrl = new URL(format({
|
|
175
|
+
protocol: 'http:',
|
|
176
|
+
hostname: config.hostname || 'localhost',
|
|
177
|
+
port: config.port
|
|
178
|
+
}))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return clientConfig
|
|
182
|
+
}
|
|
144
183
|
}
|
|
145
184
|
|
|
146
185
|
class NoopDogStatsDClient {
|
|
@@ -155,8 +194,9 @@ class NoopDogStatsDClient {
|
|
|
155
194
|
|
|
156
195
|
// This is a simplified user-facing proxy to the underlying DogStatsDClient instance
|
|
157
196
|
class CustomMetrics {
|
|
158
|
-
constructor (
|
|
159
|
-
|
|
197
|
+
constructor (config) {
|
|
198
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
199
|
+
this.dogstatsd = new DogStatsDClient(clientConfig)
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
increment (stat, value = 1, tags) {
|
|
@@ -139,13 +139,13 @@ function removeInvalidMetadata (metadata) {
|
|
|
139
139
|
return Object.keys(metadata).reduce((filteredTags, tag) => {
|
|
140
140
|
if (tag === GIT_REPOSITORY_URL) {
|
|
141
141
|
if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
|
|
142
|
-
log.error(
|
|
142
|
+
log.error(`Repository URL is not a valid repository URL: ${metadata[GIT_REPOSITORY_URL]}.`)
|
|
143
143
|
return filteredTags
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
if (tag === GIT_COMMIT_SHA) {
|
|
147
147
|
if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
|
|
148
|
-
log.error(
|
|
148
|
+
log.error(`Git commit SHA must be a full-length git SHA: ${metadata[GIT_COMMIT_SHA]}.`)
|
|
149
149
|
return filteredTags
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -6,9 +6,11 @@ const dc = require('../../../../diagnostics_channel')
|
|
|
6
6
|
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
|
+
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
10
|
|
|
10
11
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
11
12
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
13
|
+
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
12
14
|
|
|
13
15
|
let kSampleCount
|
|
14
16
|
|
|
@@ -168,6 +170,16 @@ class NativeWallProfiler {
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
_reportV8bug (maybeBug) {
|
|
174
|
+
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
|
|
175
|
+
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
176
|
+
this._logger?.warn(`Wall profiler: ${maybeBug ? 'possible ' : ''}v8 profiler stuck event loop detected.`)
|
|
177
|
+
// report as runtime metric (can be removed in the future when telemetry is mature)
|
|
178
|
+
runtimeMetrics.increment(`runtime.node.profiler.${metric}`, tag, true)
|
|
179
|
+
// report as telemetry metric
|
|
180
|
+
profilerTelemetryMetrics.count(metric, [tag]).inc()
|
|
181
|
+
}
|
|
182
|
+
|
|
171
183
|
_stop (restart) {
|
|
172
184
|
if (!this._started) return
|
|
173
185
|
if (this._codeHotspotsEnabled) {
|
|
@@ -178,12 +190,8 @@ class NativeWallProfiler {
|
|
|
178
190
|
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
|
|
179
191
|
if (restart) {
|
|
180
192
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
181
|
-
if (v8BugDetected
|
|
182
|
-
this.
|
|
183
|
-
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_maybe_stuck_event_loop', undefined, true)
|
|
184
|
-
} else if (v8BugDetected === 2) {
|
|
185
|
-
this._logger?.warn('Wall profiler: v8 profiler stuck event loop detected.')
|
|
186
|
-
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_stuck_event_loop', undefined, true)
|
|
193
|
+
if (v8BugDetected !== 0) {
|
|
194
|
+
this._reportV8bug(v8BugDetected === 1)
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
return profile
|
|
@@ -30,17 +30,7 @@ class Tracer extends NoopProxy {
|
|
|
30
30
|
|
|
31
31
|
if (config.dogstatsd) {
|
|
32
32
|
// Custom Metrics
|
|
33
|
-
this.dogstatsd = new dogstatsd.CustomMetrics(
|
|
34
|
-
host: config.dogstatsd.hostname,
|
|
35
|
-
port: config.dogstatsd.port,
|
|
36
|
-
tags: [
|
|
37
|
-
// these are the Runtime Metrics default tags
|
|
38
|
-
// Python also uses these as default Custom Metrics tags
|
|
39
|
-
`service:${config.tags.service}`,
|
|
40
|
-
`env:${config.tags.env}`,
|
|
41
|
-
`version:${config.tags.version}`
|
|
42
|
-
]
|
|
43
|
-
})
|
|
33
|
+
this.dogstatsd = new dogstatsd.CustomMetrics(config)
|
|
44
34
|
|
|
45
35
|
setInterval(() => {
|
|
46
36
|
this.dogstatsd.flush()
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// TODO: capture every second and flush every 10 seconds
|
|
4
4
|
|
|
5
|
-
const { URL, format } = require('url')
|
|
6
5
|
const v8 = require('v8')
|
|
7
6
|
const os = require('os')
|
|
8
7
|
const { DogStatsDClient } = require('./dogstatsd')
|
|
@@ -27,21 +26,7 @@ reset()
|
|
|
27
26
|
|
|
28
27
|
module.exports = {
|
|
29
28
|
start (config) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
Object.keys(config.tags)
|
|
33
|
-
.filter(key => typeof config.tags[key] === 'string')
|
|
34
|
-
.filter(key => {
|
|
35
|
-
// Skip runtime-id unless enabled as cardinality may be too high
|
|
36
|
-
if (key !== 'runtime-id') return true
|
|
37
|
-
return (config.experimental && config.experimental.runtimeId)
|
|
38
|
-
})
|
|
39
|
-
.forEach(key => {
|
|
40
|
-
// https://docs.datadoghq.com/tagging/#defining-tags
|
|
41
|
-
const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
|
|
42
|
-
|
|
43
|
-
tags.push(`${key}:${value}`)
|
|
44
|
-
})
|
|
29
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
45
30
|
|
|
46
31
|
try {
|
|
47
32
|
nativeMetrics = require('@datadog/native-metrics')
|
|
@@ -51,22 +36,6 @@ module.exports = {
|
|
|
51
36
|
nativeMetrics = null
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
const clientConfig = {
|
|
55
|
-
host: config.dogstatsd.hostname,
|
|
56
|
-
port: config.dogstatsd.port,
|
|
57
|
-
tags
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (config.url) {
|
|
61
|
-
clientConfig.metricsProxyUrl = config.url
|
|
62
|
-
} else if (config.port) {
|
|
63
|
-
clientConfig.metricsProxyUrl = new URL(format({
|
|
64
|
-
protocol: 'http:',
|
|
65
|
-
hostname: config.hostname || 'localhost',
|
|
66
|
-
port: config.port
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
39
|
client = new DogStatsDClient(clientConfig)
|
|
71
40
|
|
|
72
41
|
time = process.hrtime()
|