dd-trace 6.9.0 → 6.11.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 +0 -17
- package/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +150 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +14 -10
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +26 -18
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
- package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +606 -40
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +14 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +24 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +14 -3
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +16 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +5 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
- package/packages/dd-trace/src/plugins/util/test.js +93 -14
- package/packages/dd-trace/src/plugins/util/web.js +56 -2
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -8,17 +8,18 @@ const { getLageTestSessionName } = require('../../ci-visibility/lage')
|
|
|
8
8
|
const log = require('../../log')
|
|
9
9
|
const { getEnvironmentVariable, getValueFromEnvSources } = require('../../config/helper')
|
|
10
10
|
const { getSegment } = require('../../util')
|
|
11
|
+
const { parse } = require('../../../../../vendor/dist/jest-docblock')
|
|
11
12
|
const satisfies = require('../../../../../vendor/dist/semifies')
|
|
12
13
|
|
|
13
14
|
const istanbul = require('../../../../../vendor/dist/istanbul-lib-coverage')
|
|
14
15
|
|
|
16
|
+
const { writeDatadogParentId, writeDatadogTraceId } = require('../../carrier')
|
|
15
17
|
const id = require('../../id')
|
|
16
18
|
const {
|
|
17
19
|
incrementCountMetric,
|
|
18
20
|
TELEMETRY_GIT_COMMIT_SHA_DISCREPANCY,
|
|
19
21
|
TELEMETRY_GIT_SHA_MATCH,
|
|
20
22
|
} = require('../../ci-visibility/telemetry')
|
|
21
|
-
|
|
22
23
|
const { SPAN_TYPE, RESOURCE_NAME, SAMPLING_PRIORITY } = require('../../../../../ext/tags')
|
|
23
24
|
const { SAMPLING_RULE_DECISION } = require('../../constants')
|
|
24
25
|
const { AUTO_KEEP } = require('../../../../../ext/priority')
|
|
@@ -195,6 +196,8 @@ const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
|
|
|
195
196
|
const VITEST_WORKER_COVERAGE_PAYLOAD_CODE = 101
|
|
196
197
|
const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
|
|
197
198
|
const VITEST_WORKER_TELEMETRY_PAYLOAD_CODE = 103
|
|
199
|
+
const VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE = 104
|
|
200
|
+
const VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE = 105
|
|
198
201
|
|
|
199
202
|
const TEST_IS_TEST_FRAMEWORK_WORKER = 'test.is_test_framework_worker'
|
|
200
203
|
|
|
@@ -489,6 +492,8 @@ module.exports = {
|
|
|
489
492
|
VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
490
493
|
VITEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
491
494
|
VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
495
|
+
VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE,
|
|
496
|
+
VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE,
|
|
492
497
|
TEST_IS_TEST_FRAMEWORK_WORKER,
|
|
493
498
|
TEST_SOURCE_START,
|
|
494
499
|
TEST_SKIPPED_BY_ITR,
|
|
@@ -504,6 +509,7 @@ module.exports = {
|
|
|
504
509
|
getTestParametersString,
|
|
505
510
|
finishAllTraceSpans,
|
|
506
511
|
getTestParentSpan,
|
|
512
|
+
isMarkedAsUnskippable,
|
|
507
513
|
getTestSuitePath,
|
|
508
514
|
getCodeOwnersFileEntries,
|
|
509
515
|
getCodeOwnersForFilename,
|
|
@@ -544,6 +550,7 @@ module.exports = {
|
|
|
544
550
|
removeInvalidMetadata,
|
|
545
551
|
parseAnnotations,
|
|
546
552
|
getIsFaultyEarlyFlakeDetection,
|
|
553
|
+
getFailedTestReplayPromise,
|
|
547
554
|
TEST_BROWSER_DRIVER,
|
|
548
555
|
TEST_BROWSER_DRIVER_VERSION,
|
|
549
556
|
TEST_BROWSER_NAME,
|
|
@@ -844,7 +851,7 @@ function getTestTypeFromFramework (testFramework) {
|
|
|
844
851
|
*/
|
|
845
852
|
function finishAllTraceSpans (span) {
|
|
846
853
|
for (const traceSpan of span.context()._trace.started) {
|
|
847
|
-
if (traceSpan !== span) {
|
|
854
|
+
if (traceSpan !== span && traceSpan._duration === undefined) {
|
|
848
855
|
traceSpan.finish()
|
|
849
856
|
}
|
|
850
857
|
}
|
|
@@ -901,10 +908,10 @@ function setRumTestCorrelation (context, activeSpan) {
|
|
|
901
908
|
* @returns {import('../../opentracing/span_context')}
|
|
902
909
|
*/
|
|
903
910
|
function getTestParentSpan (tracer, testExecutionId) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
911
|
+
const carrier = /** @type {Record<string, unknown>} */ ({})
|
|
912
|
+
writeDatadogTraceId(carrier, testExecutionId || id().toString(10))
|
|
913
|
+
writeDatadogParentId(carrier, '0000000000000000')
|
|
914
|
+
return tracer.extract('text_map', carrier)
|
|
908
915
|
}
|
|
909
916
|
|
|
910
917
|
function getTestCommonTags (name, suite, version, testFramework) {
|
|
@@ -941,6 +948,62 @@ function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
|
|
|
941
948
|
return testSuitePath.replaceAll(path.sep, '/')
|
|
942
949
|
}
|
|
943
950
|
|
|
951
|
+
const globalDocblockRegExp = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/
|
|
952
|
+
const MAX_COMMENTS_CHECKED = 10
|
|
953
|
+
|
|
954
|
+
function isMarkedAsUnskippable (test) {
|
|
955
|
+
let testSource
|
|
956
|
+
|
|
957
|
+
try {
|
|
958
|
+
testSource = fs.readFileSync(test.path, 'utf8')
|
|
959
|
+
} catch {
|
|
960
|
+
return false
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const re = globalDocblockRegExp
|
|
964
|
+
re.lastIndex = 0
|
|
965
|
+
let commentsChecked = 0
|
|
966
|
+
|
|
967
|
+
while (testSource.length) {
|
|
968
|
+
const match = re.exec(testSource)
|
|
969
|
+
if (!match) break
|
|
970
|
+
const comment = match[1]
|
|
971
|
+
|
|
972
|
+
let docblocks
|
|
973
|
+
try {
|
|
974
|
+
docblocks = parse(comment)
|
|
975
|
+
} catch {
|
|
976
|
+
// Skip unparsable comment and continue scanning
|
|
977
|
+
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
|
|
978
|
+
return false
|
|
979
|
+
}
|
|
980
|
+
continue
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (docblocks?.datadog) {
|
|
984
|
+
try {
|
|
985
|
+
// @ts-expect-error The datadog type is defined by us and may only be a string.
|
|
986
|
+
return JSON.parse(docblocks.datadog).unskippable
|
|
987
|
+
} catch {
|
|
988
|
+
// If the @datadog block comment is present but malformed, we'll run the suite
|
|
989
|
+
log.warn('@datadog block comment is malformed.')
|
|
990
|
+
return true
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
|
|
995
|
+
return false
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// To stop as soon as no doc blocks are found, slice the source. That way the
|
|
999
|
+
// regexp works by using the `^` anchor. Without it, it would continue
|
|
1000
|
+
// scanning the rest of the file.
|
|
1001
|
+
testSource = testSource.slice(match[0].length)
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return false
|
|
1005
|
+
}
|
|
1006
|
+
|
|
944
1007
|
const POSSIBLE_CODEOWNERS_LOCATIONS = [
|
|
945
1008
|
'CODEOWNERS',
|
|
946
1009
|
'.github/CODEOWNERS',
|
|
@@ -1376,10 +1439,6 @@ function addSkippedCoverageToMap (skippedCoverage, targetMap) {
|
|
|
1376
1439
|
}
|
|
1377
1440
|
}
|
|
1378
1441
|
|
|
1379
|
-
function hasSkippedCoverage (skippedCoverage) {
|
|
1380
|
-
return skippedCoverage && typeof skippedCoverage === 'object' && Object.keys(skippedCoverage).length > 0
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
1442
|
function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
|
|
1384
1443
|
const executableLinesByFile = new Map()
|
|
1385
1444
|
const coveredLinesByFile = new Map()
|
|
@@ -1433,10 +1492,10 @@ function applySkippedCoverageToFileCoverage (fileCoverage, skippedBitmap) {
|
|
|
1433
1492
|
* @returns {boolean}
|
|
1434
1493
|
*/
|
|
1435
1494
|
function applySkippedCoverageToCoverage (coverage, skippedCoverage, rootDir) {
|
|
1436
|
-
|
|
1495
|
+
const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
|
|
1496
|
+
if (skippedCoverageByFilename.size === 0) return false
|
|
1437
1497
|
|
|
1438
1498
|
const coverageMap = getCoverageMap(coverage)
|
|
1439
|
-
const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
|
|
1440
1499
|
let matched = false
|
|
1441
1500
|
|
|
1442
1501
|
for (const filename of coverageMap.files()) {
|
|
@@ -1542,6 +1601,22 @@ function parseAnnotations (annotations) {
|
|
|
1542
1601
|
}, {})
|
|
1543
1602
|
}
|
|
1544
1603
|
|
|
1604
|
+
/**
|
|
1605
|
+
* Combines the optional Failed Test Replay operations that must settle before a retry.
|
|
1606
|
+
*
|
|
1607
|
+
* @param {{setProbePromise?: Promise<void>, finishTestPromise?: Promise<void>}} promises
|
|
1608
|
+
* @returns {Promise<void>|undefined}
|
|
1609
|
+
*/
|
|
1610
|
+
function getFailedTestReplayPromise (promises) {
|
|
1611
|
+
if (promises.setProbePromise && promises.finishTestPromise) {
|
|
1612
|
+
return Promise.all([
|
|
1613
|
+
promises.setProbePromise,
|
|
1614
|
+
promises.finishTestPromise,
|
|
1615
|
+
]).then(() => {})
|
|
1616
|
+
}
|
|
1617
|
+
return promises.setProbePromise || promises.finishTestPromise
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1545
1620
|
function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
|
|
1546
1621
|
let newSuites = 0
|
|
1547
1622
|
for (const suite of projectSuites) {
|
|
@@ -1741,6 +1816,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1741
1816
|
}
|
|
1742
1817
|
|
|
1743
1818
|
const metrics = {}
|
|
1819
|
+
let hasMetrics = false
|
|
1744
1820
|
for (const candidate of candidateBranches) {
|
|
1745
1821
|
// Find common ancestor
|
|
1746
1822
|
const baseSha = getMergeBase(candidate, sourceBranch)
|
|
@@ -1759,6 +1835,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1759
1835
|
ahead,
|
|
1760
1836
|
baseSha,
|
|
1761
1837
|
}
|
|
1838
|
+
hasMetrics = true
|
|
1762
1839
|
}
|
|
1763
1840
|
|
|
1764
1841
|
function isDefaultBranch (branch) {
|
|
@@ -1767,7 +1844,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1767
1844
|
)
|
|
1768
1845
|
}
|
|
1769
1846
|
|
|
1770
|
-
if (
|
|
1847
|
+
if (!hasMetrics) {
|
|
1771
1848
|
return null
|
|
1772
1849
|
}
|
|
1773
1850
|
// Find branch with smallest "ahead" value, preferring default branch on tie
|
|
@@ -1793,6 +1870,7 @@ function getPullRequestDiff (baseCommit, targetCommit) {
|
|
|
1793
1870
|
function getModifiedFilesFromDiff (diff) {
|
|
1794
1871
|
if (!diff) return null
|
|
1795
1872
|
const result = {}
|
|
1873
|
+
let hasModifiedFiles = false
|
|
1796
1874
|
|
|
1797
1875
|
const filesRegex = /^diff --git a\/(?<file>.+) b\/(?<file2>.+)$/g
|
|
1798
1876
|
const linesRegex = /^@@ -\d+(,\d+)? \+(?<start>\d+)(,(?<count>\d+))? @@/g
|
|
@@ -1807,6 +1885,7 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1807
1885
|
if (fileMatch && fileMatch.groups.file) {
|
|
1808
1886
|
currentFile = fileMatch.groups.file
|
|
1809
1887
|
result[currentFile] = []
|
|
1888
|
+
hasModifiedFiles = true
|
|
1810
1889
|
continue
|
|
1811
1890
|
}
|
|
1812
1891
|
|
|
@@ -1825,7 +1904,7 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1825
1904
|
linesRegex.lastIndex = 0
|
|
1826
1905
|
}
|
|
1827
1906
|
|
|
1828
|
-
if (
|
|
1907
|
+
if (!hasModifiedFiles) {
|
|
1829
1908
|
return null
|
|
1830
1909
|
}
|
|
1831
1910
|
return result
|
|
@@ -35,6 +35,11 @@ const MANUAL_DROP = tags.MANUAL_DROP
|
|
|
35
35
|
|
|
36
36
|
const contexts = new WeakMap()
|
|
37
37
|
const requests = new WeakMap()
|
|
38
|
+
const statusCodeRangesPattern = /^[1-5]\d{2}(?:-[1-5]\d{2})?(?:,[1-5]\d{2}(?:-[1-5]\d{2})?)*$/
|
|
39
|
+
const whitespacePattern = /\s/g
|
|
40
|
+
const MAX_HTTP_STATUS_CODE = 599
|
|
41
|
+
|
|
42
|
+
/** @typedef {(code: number) => boolean} StatusValidator */
|
|
38
43
|
|
|
39
44
|
// TODO: change this to no longer rely on creating a dummy plugin to be able to access startSpan
|
|
40
45
|
function createWebPlugin (tracer, config = {}) {
|
|
@@ -371,6 +376,9 @@ function addAllowHeaders (req, res, headers) {
|
|
|
371
376
|
const allowHeaders = splitHeader(headers['access-control-allow-headers'])
|
|
372
377
|
const requestHeaders = splitHeader(req.headers['access-control-request-headers'])
|
|
373
378
|
const contextHeaders = [
|
|
379
|
+
'baggage',
|
|
380
|
+
'traceparent',
|
|
381
|
+
'tracestate',
|
|
374
382
|
'x-datadog-origin',
|
|
375
383
|
'x-datadog-parent-id',
|
|
376
384
|
'x-datadog-sampled', // Deprecated, but still accept it in case it's sent.
|
|
@@ -559,13 +567,59 @@ function isNot500ErrorCode (code) {
|
|
|
559
567
|
return code < 500
|
|
560
568
|
}
|
|
561
569
|
|
|
570
|
+
/**
|
|
571
|
+
* @param {{ validateStatus?: unknown, DD_TRACE_HTTP_SERVER_ERROR_STATUSES?: unknown }} config
|
|
572
|
+
* @returns {StatusValidator}
|
|
573
|
+
*/
|
|
562
574
|
function getStatusValidator (config) {
|
|
563
575
|
if (typeof config.validateStatus === 'function') {
|
|
564
|
-
return config.validateStatus
|
|
576
|
+
return /** @type {StatusValidator} */ (config.validateStatus)
|
|
565
577
|
} else if (config.hasOwnProperty('validateStatus')) {
|
|
566
578
|
log.error('Expected `validateStatus` to be a function.')
|
|
567
579
|
}
|
|
568
|
-
|
|
580
|
+
|
|
581
|
+
const { DD_TRACE_HTTP_SERVER_ERROR_STATUSES } = config
|
|
582
|
+
if (DD_TRACE_HTTP_SERVER_ERROR_STATUSES === undefined) return isNot500ErrorCode
|
|
583
|
+
if (typeof DD_TRACE_HTTP_SERVER_ERROR_STATUSES !== 'string') {
|
|
584
|
+
log.error('Expected `DD_TRACE_HTTP_SERVER_ERROR_STATUSES` to be a string.')
|
|
585
|
+
return isNot500ErrorCode
|
|
586
|
+
}
|
|
587
|
+
if (DD_TRACE_HTTP_SERVER_ERROR_STATUSES === '500-599') return isNot500ErrorCode
|
|
588
|
+
|
|
589
|
+
const normalized = DD_TRACE_HTTP_SERVER_ERROR_STATUSES.replaceAll(whitespacePattern, '')
|
|
590
|
+
if (normalized === '500-599') return isNot500ErrorCode
|
|
591
|
+
if (!statusCodeRangesPattern.test(normalized)) {
|
|
592
|
+
log.error(
|
|
593
|
+
'`DD_TRACE_HTTP_SERVER_ERROR_STATUSES` must contain comma-separated status codes or ranges from 100 to 599.'
|
|
594
|
+
)
|
|
595
|
+
return isNot500ErrorCode
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const errorStatusCodes = new Uint8Array(MAX_HTTP_STATUS_CODE + 1)
|
|
599
|
+
const ranges = normalized.split(',')
|
|
600
|
+
for (const range of ranges) {
|
|
601
|
+
const separator = range.indexOf('-')
|
|
602
|
+
if (separator === -1) {
|
|
603
|
+
errorStatusCodes[Number(range)] = 1
|
|
604
|
+
continue
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const first = Number(range.slice(0, separator))
|
|
608
|
+
const second = Number(range.slice(separator + 1))
|
|
609
|
+
const start = Math.min(first, second)
|
|
610
|
+
const end = Math.max(first, second)
|
|
611
|
+
errorStatusCodes.fill(1, start, end + 1)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* @param {number} code
|
|
616
|
+
* @returns {boolean}
|
|
617
|
+
*/
|
|
618
|
+
function isValidStatusCode (code) {
|
|
619
|
+
return errorStatusCodes[code] !== 1
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return isValidStatusCode
|
|
569
623
|
}
|
|
570
624
|
|
|
571
625
|
const noop = () => {}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dc = require('dc-polyfill')
|
|
4
|
-
|
|
5
|
-
const { storage } = require('../../../../datadog-core')
|
|
6
3
|
const log = require('../../log')
|
|
7
4
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
8
5
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
|
-
const {
|
|
6
|
+
const { endpointNameFromTags, finalEndpoint, getStartedSpans } = require('../webspan-utils')
|
|
10
7
|
const { SAMPLING_INTERVAL } = require('../constants')
|
|
8
|
+
const {
|
|
9
|
+
enterCh,
|
|
10
|
+
beforeCh,
|
|
11
|
+
spanFinishCh,
|
|
12
|
+
getActiveSpan,
|
|
13
|
+
ensureChannelsActivated,
|
|
14
|
+
} = require('../../storage-channels')
|
|
15
|
+
const webTagsCache = require('../../web-tags-cache')
|
|
11
16
|
|
|
12
17
|
const {
|
|
13
18
|
END_TIMESTAMP_LABEL,
|
|
@@ -21,21 +26,12 @@ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
|
|
|
21
26
|
|
|
22
27
|
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
23
28
|
|
|
24
|
-
let beforeCh
|
|
25
|
-
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
26
|
-
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
27
|
-
const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
|
|
28
29
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
29
30
|
|
|
30
31
|
const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')
|
|
31
32
|
|
|
32
33
|
let kSampleCount
|
|
33
34
|
|
|
34
|
-
function getActiveSpan () {
|
|
35
|
-
const store = storage('legacy').getStore()
|
|
36
|
-
return store && store.span
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
function toBigInt (spanId) {
|
|
40
36
|
return spanId !== null && typeof spanId === 'object' ? spanId.toBigInt() : spanId
|
|
41
37
|
}
|
|
@@ -53,58 +49,13 @@ function updateContext (context) {
|
|
|
53
49
|
}
|
|
54
50
|
if (context.webTags !== undefined && context.endpoint === undefined) {
|
|
55
51
|
// endpoint may not be determined yet, but keep it as fallback
|
|
56
|
-
// if tags are not available anymore during serialization
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function ensureChannelsActivated (asyncContextFrameEnabled) {
|
|
63
|
-
if (channelsActivated) return
|
|
64
|
-
|
|
65
|
-
const shimmer = require('../../../../datadog-shimmer')
|
|
66
|
-
|
|
67
|
-
// We need to instrument enterWith() on the legacy storage — that's the storage
|
|
68
|
-
// carrying span data and the only one the profiler cares about.
|
|
69
|
-
const legacyStorage = storage('legacy')
|
|
70
|
-
let inRun = false
|
|
71
|
-
shimmer.wrap(legacyStorage, 'enterWith', function (original) {
|
|
72
|
-
return function (store) {
|
|
73
|
-
const retVal = original.call(this, store)
|
|
74
|
-
if (!inRun) enterCh.publish()
|
|
75
|
-
return retVal
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// When not using AsyncContextFrame, we need additional instrumentation.
|
|
80
|
-
if (!asyncContextFrameEnabled) {
|
|
81
|
-
// We need async_hooks.createHook to create a "before" callback.
|
|
82
|
-
const { createHook } = require('async_hooks')
|
|
83
|
-
beforeCh = dc.channel('dd-trace:storage:before')
|
|
84
|
-
createHook({ before: () => beforeCh.publish() }).enable()
|
|
85
|
-
|
|
86
|
-
// In ACF-based implementation run() delegates to enterWith() so it doesn't
|
|
87
|
-
// need to be separately instrumented. in non-ACF implementation run()
|
|
88
|
-
// doesn't delegate to enterWith(), so separate instrumentation is necessary.
|
|
89
|
-
shimmer.wrap(legacyStorage, 'run', function (original) {
|
|
90
|
-
return function (store, callback, ...args) {
|
|
91
|
-
const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
|
|
92
|
-
inRun = false
|
|
93
|
-
enterCh.publish()
|
|
94
|
-
const retVal = cb.apply(this, args)
|
|
95
|
-
inRun = true
|
|
96
|
-
return retVal
|
|
97
|
-
})
|
|
98
|
-
inRun = true
|
|
99
|
-
const retVal = original.call(this, store, wrappedCb, ...args)
|
|
100
|
-
enterCh.publish()
|
|
101
|
-
inRun = false
|
|
102
|
-
return retVal
|
|
103
|
-
}
|
|
104
|
-
})
|
|
52
|
+
// if tags are not available anymore during serialization. Only snapshot a
|
|
53
|
+
// settled value: routing tags land over the course of the request, and this
|
|
54
|
+
// field is never refreshed once set, so capturing an interim placeholder
|
|
55
|
+
// (a bare `GET`) would pin the fallback to it. Staying undefined until then
|
|
56
|
+
// just means the next sample rotation tries again.
|
|
57
|
+
context.endpoint = finalEndpoint(context.webTags)
|
|
105
58
|
}
|
|
106
|
-
|
|
107
|
-
channelsActivated = true
|
|
108
59
|
}
|
|
109
60
|
|
|
110
61
|
class NativeWallProfiler {
|
|
@@ -216,7 +167,13 @@ class NativeWallProfiler {
|
|
|
216
167
|
enterCh.subscribe(this.#boundEnter)
|
|
217
168
|
spanFinishCh.subscribe(this.#boundSpanFinished)
|
|
218
169
|
if (this.#endpointCollectionEnabled) {
|
|
219
|
-
|
|
170
|
+
// Web-tags cache publishes once per span at the moment its
|
|
171
|
+
// walk-result transitions from undefined to a real value —
|
|
172
|
+
// exactly when we need to refresh the ProfilingContext snapshot.
|
|
173
|
+
webTagsCache.resolvedCh.subscribe(this.#boundSpanTagsUpdated)
|
|
174
|
+
// Turn on the cache's own tagsUpdate subscription — it's
|
|
175
|
+
// ref-counted, so this composes with any other active consumer.
|
|
176
|
+
webTagsCache.activate()
|
|
220
177
|
}
|
|
221
178
|
}
|
|
222
179
|
}
|
|
@@ -299,32 +256,20 @@ class NativeWallProfiler {
|
|
|
299
256
|
let profilingContext = span[ProfilingContext]
|
|
300
257
|
if (profilingContext === undefined) {
|
|
301
258
|
const context = span.context()
|
|
302
|
-
const startedSpans = getStartedSpans(context)
|
|
303
259
|
|
|
304
260
|
let spanId
|
|
305
261
|
let rootSpanId
|
|
306
262
|
if (this.#codeHotspotsEnabled) {
|
|
263
|
+
const startedSpans = getStartedSpans(context)
|
|
307
264
|
spanId = context._spanId
|
|
308
265
|
rootSpanId = startedSpans.length ? startedSpans[0].context()._spanId : context._spanId
|
|
309
266
|
}
|
|
310
267
|
|
|
311
|
-
|
|
312
|
-
if
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
} else {
|
|
317
|
-
// Get parent's context's web tags
|
|
318
|
-
const parentId = context._parentId
|
|
319
|
-
for (let i = startedSpans.length; --i >= 0;) {
|
|
320
|
-
const ispan = startedSpans[i]
|
|
321
|
-
if (ispan.context()._spanId === parentId) {
|
|
322
|
-
webTags = this.#getProfilingContext(ispan).webTags
|
|
323
|
-
break
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
268
|
+
// webTags is snapshotted into the sample context at getProfilingContext
|
|
269
|
+
// time; if the answer turns out to be undefined and the span later gets
|
|
270
|
+
// web-server tags, #spanTagsUpdated refreshes this field via the shared
|
|
271
|
+
// cache (see web-tags-cache.js).
|
|
272
|
+
const webTags = this.#endpointCollectionEnabled ? webTagsCache.getCachedWebTags(span) : undefined
|
|
328
273
|
|
|
329
274
|
profilingContext = { spanId, rootSpanId, webTags }
|
|
330
275
|
span[ProfilingContext] = profilingContext
|
|
@@ -345,14 +290,15 @@ class NativeWallProfiler {
|
|
|
345
290
|
}
|
|
346
291
|
}
|
|
347
292
|
|
|
293
|
+
// Invoked (via webTagsCache.resolvedCh) once per span at the moment the
|
|
294
|
+
// shared cache promotes a previously-undefined webTags answer into a
|
|
295
|
+
// real value. Refresh the ProfilingContext snapshot so future samples
|
|
296
|
+
// pick it up.
|
|
348
297
|
#spanTagsUpdated (span) {
|
|
349
298
|
if (!this.#started) return
|
|
350
299
|
const profilingContext = span[ProfilingContext]
|
|
351
|
-
if (profilingContext === undefined
|
|
352
|
-
|
|
353
|
-
if (isWebServerSpan(tags)) {
|
|
354
|
-
profilingContext.webTags = tags
|
|
355
|
-
}
|
|
300
|
+
if (profilingContext === undefined) return
|
|
301
|
+
profilingContext.webTags = webTagsCache.getCachedWebTags(span)
|
|
356
302
|
}
|
|
357
303
|
|
|
358
304
|
#reportV8bug (maybeBug) {
|
|
@@ -401,7 +347,8 @@ class NativeWallProfiler {
|
|
|
401
347
|
enterCh.unsubscribe(this.#boundEnter)
|
|
402
348
|
spanFinishCh.unsubscribe(this.#boundSpanFinished)
|
|
403
349
|
if (this.#endpointCollectionEnabled) {
|
|
404
|
-
|
|
350
|
+
webTagsCache.resolvedCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
351
|
+
webTagsCache.deactivate()
|
|
405
352
|
}
|
|
406
353
|
this.#profilerState = undefined
|
|
407
354
|
}
|
|
@@ -470,7 +417,7 @@ class NativeWallProfiler {
|
|
|
470
417
|
if (rootSpanId !== undefined) {
|
|
471
418
|
labels[LOCAL_ROOT_SPAN_ID_LABEL] = toBigInt(rootSpanId)
|
|
472
419
|
}
|
|
473
|
-
if (webTags !== undefined
|
|
420
|
+
if (webTags !== undefined) {
|
|
474
421
|
labels[TRACE_ENDPOINT_LABEL] = endpointNameFromTags(webTags)
|
|
475
422
|
} else if (endpoint) {
|
|
476
423
|
// fallback to endpoint computed when sample was taken
|
|
@@ -14,6 +14,50 @@ function endpointNameFromTags (tags) {
|
|
|
14
14
|
].filter(Boolean).join(' ')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// The endpoint name a web-server span's tag bag yields changes over the span's
|
|
18
|
+
// lifetime: plugins set `http.method` when the request arrives and only add
|
|
19
|
+
// `http.route` (and often `resource.name`) once framework routing has resolved
|
|
20
|
+
// the URL. Returns the endpoint name once its value looks stable, or undefined
|
|
21
|
+
// while it is still an interim placeholder.
|
|
22
|
+
//
|
|
23
|
+
// Consumers that read the tag bag lazily (the wall profiler resolves endpoint
|
|
24
|
+
// labels at serialization time) see the settled value for free and only need
|
|
25
|
+
// this for fallbacks captured mid-request. Consumers that have to materialize
|
|
26
|
+
// the value early — the OTEP-4947 thread-context writer writes it into a record
|
|
27
|
+
// an out-of-process reader samples at any moment — need it to decide when
|
|
28
|
+
// publishing is safe.
|
|
29
|
+
//
|
|
30
|
+
// The test is on the computed value rather than on which tags are present,
|
|
31
|
+
// because endpointNameFromTags prefers `resource.name` over
|
|
32
|
+
// `http.method` + `http.route` and some plugins seed `resource.name` with the
|
|
33
|
+
// bare request method before routing resolves: datadog-plugin-next starts its
|
|
34
|
+
// request span with `resource.name: req.method` and only replaces it with
|
|
35
|
+
// `${req.method} ${page}` once the page is known. A value equal to the bare
|
|
36
|
+
// method is therefore a placeholder no matter which tags produced it.
|
|
37
|
+
function finalEndpoint (tags) {
|
|
38
|
+
if (tags == null) return
|
|
39
|
+
// Presence gate first: nothing but these two can contribute routing
|
|
40
|
+
// information, and checking them keeps the common not-yet-routed case free of
|
|
41
|
+
// the string building inside endpointNameFromTags.
|
|
42
|
+
if (tags[RESOURCE_NAME] == null && tags[HTTP_ROUTE] == null) return
|
|
43
|
+
const endpoint = endpointNameFromTags(tags)
|
|
44
|
+
if (!endpoint || endpoint === tags[HTTP_METHOD]) return
|
|
45
|
+
return endpoint
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The trace's started-spans list, whose first entry is the local root span by
|
|
49
|
+
// repo-wide convention — priority_sampler, span_format, the wall profiler's
|
|
50
|
+
// local-root-span-id label, the event plugins and the OTEP-4947 writer all read
|
|
51
|
+
// it that way, and web-tags-cache walks it to find a span's parent.
|
|
52
|
+
//
|
|
53
|
+
// Partial flush weakens that convention for every one of them alike:
|
|
54
|
+
// span_processor's _erase() replaces the list with the still-active spans only,
|
|
55
|
+
// so once a trace crosses DD_TRACE_PARTIAL_FLUSH_MIN_SPANS a local root that has
|
|
56
|
+
// already finished drops out, and the first entry becomes merely the oldest span
|
|
57
|
+
// still running — while a finished web-server ancestor stops being reachable for
|
|
58
|
+
// the parent-chain walk. Correcting it needs a local-root reference the tracer
|
|
59
|
+
// core keeps across flushes; until there is one, consumers share the same skew
|
|
60
|
+
// on large traces rather than each compensating differently.
|
|
17
61
|
function getStartedSpans (context) {
|
|
18
62
|
return context._trace.started
|
|
19
63
|
}
|
|
@@ -21,5 +65,6 @@ function getStartedSpans (context) {
|
|
|
21
65
|
module.exports = {
|
|
22
66
|
isWebServerSpan,
|
|
23
67
|
endpointNameFromTags,
|
|
68
|
+
finalEndpoint,
|
|
24
69
|
getStartedSpans,
|
|
25
70
|
}
|