dd-trace 5.116.0 → 5.118.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/ci/diagnose.js +162 -21
- package/ci/init.js +0 -1
- package/ci/runbook.md +187 -180
- package/ci/test-optimization-validation/approval-artifacts.js +3 -1
- package/ci/test-optimization-validation/approval.js +52 -15
- package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
- package/ci/test-optimization-validation/ci-discovery.js +1 -1
- package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
- package/ci/test-optimization-validation/ci-remediation.js +112 -39
- package/ci/test-optimization-validation/cli.js +516 -681
- package/ci/test-optimization-validation/command-blocker.js +188 -33
- package/ci/test-optimization-validation/command-output-policy.js +3 -26
- package/ci/test-optimization-validation/command-runner.js +189 -245
- package/ci/test-optimization-validation/environment.js +90 -0
- package/ci/test-optimization-validation/executable.js +159 -388
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
- package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
- package/ci/test-optimization-validation/generated-files.js +75 -13
- package/ci/test-optimization-validation/generated-test-contract.js +283 -0
- package/ci/test-optimization-validation/generated-verifier.js +49 -16
- package/ci/test-optimization-validation/literal-environment.js +57 -0
- package/ci/test-optimization-validation/manifest-loader.js +2 -22
- package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
- package/ci/test-optimization-validation/manifest-schema.js +494 -702
- package/ci/test-optimization-validation/offline-fixtures.js +4 -1
- package/ci/test-optimization-validation/plan-writer.js +243 -984
- package/ci/test-optimization-validation/preflight-runner.js +49 -55
- package/ci/test-optimization-validation/redaction.js +2 -1
- package/ci/test-optimization-validation/report-writer.js +391 -1357
- package/ci/test-optimization-validation/result-semantics.js +86 -0
- package/ci/test-optimization-validation/runner-command.js +249 -0
- package/ci/test-optimization-validation/runner-contract.js +664 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
- package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
- package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +7 -0
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/cucumber.js +11 -12
- package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/jest.js +18 -17
- package/packages/datadog-instrumentations/src/mocha/main.js +79 -30
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-instrumentations/src/playwright.js +3 -24
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main.js +14 -26
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +2 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -1
- package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
- package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
- package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/datadog-plugin-vitest/src/index.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/llmobs/index.js +9 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
- package/packages/dd-trace/src/llmobs/tagger.js +23 -3
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
- package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/ci/test-optimization-validation/command-suitability.js +0 -471
- package/ci/test-optimization-validation/init-probe-preload.js +0 -163
- package/ci/test-optimization-validation/init-probe.js +0 -246
- package/ci/test-optimization-validation/late-initialization.js +0 -63
- package/ci/test-optimization-validation/local-command.js +0 -163
- package/ci/test-optimization-validation/setup-runner.js +0 -97
- package/ci/test-optimization-validation-manifest.schema.json +0 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
4
|
+
|
|
5
|
+
const SPAN_KIND_BY_TYPE = {
|
|
6
|
+
agent: 'agent',
|
|
7
|
+
function: 'tool',
|
|
8
|
+
handoff: 'tool',
|
|
9
|
+
guardrail: 'task',
|
|
10
|
+
custom: 'task',
|
|
11
|
+
generation: 'llm',
|
|
12
|
+
response: 'llm',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Lifecycle methods are awaited by agents-core. Share one resolved Promise
|
|
16
|
+
// across every callback so we don't allocate per span event.
|
|
17
|
+
const RESOLVED = Promise.resolve()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* dd-trace-js implementation of the agents-core `TracingProcessor` interface.
|
|
21
|
+
* Registered via `addTraceProcessor(new DDOpenAIAgentsProcessor(integration))` inside
|
|
22
|
+
* the `@openai/agents` module load hook. Mirrors Python's LLMObsTraceProcessor.
|
|
23
|
+
*
|
|
24
|
+
* Each agents-core Span / Trace lifecycle event turns into a dd-trace span
|
|
25
|
+
* (APM + LLMObs-annotated) keyed off the agents-core spanId / traceId. Parent
|
|
26
|
+
* hierarchy is resolved through the agents-core parentId chain, which gives us
|
|
27
|
+
* correct multi-agent handoff nesting that ctx-argument capture cannot provide.
|
|
28
|
+
*
|
|
29
|
+
* agents-core awaits the lifecycle methods, so each one returns a settled
|
|
30
|
+
* Promise even though the work is synchronous.
|
|
31
|
+
*/
|
|
32
|
+
class DDOpenAIAgentsProcessor {
|
|
33
|
+
/**
|
|
34
|
+
* @param {() => (import('./integration').OpenAIAgentsIntegration | undefined)} getIntegration - Lazy accessor for the
|
|
35
|
+
* current OpenAIAgentsIntegration singleton. Read on each lifecycle event
|
|
36
|
+
* so re-instantiating the plugin doesn't strand the processor against an
|
|
37
|
+
* old integration reference inside agents-core.
|
|
38
|
+
*/
|
|
39
|
+
constructor (getIntegration) {
|
|
40
|
+
this._getIntegration = getIntegration
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onTraceStart (oaiTrace) {
|
|
44
|
+
const integration = this._getIntegration()
|
|
45
|
+
if (!integration?.enabled) return RESOLVED
|
|
46
|
+
try {
|
|
47
|
+
integration.startTrace(oaiTrace)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
log.warn('[openai-agents] onTraceStart failed: %s', err)
|
|
50
|
+
}
|
|
51
|
+
return RESOLVED
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onTraceEnd (oaiTrace) {
|
|
55
|
+
const integration = this._getIntegration()
|
|
56
|
+
if (!integration?.enabled) return RESOLVED
|
|
57
|
+
try {
|
|
58
|
+
integration.endTrace(oaiTrace)
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.warn('[openai-agents] onTraceEnd failed: %s', err)
|
|
61
|
+
}
|
|
62
|
+
return RESOLVED
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onSpanStart (oaiSpan) {
|
|
66
|
+
const integration = this._getIntegration()
|
|
67
|
+
if (!integration?.enabled) return RESOLVED
|
|
68
|
+
if (!oaiSpan?.spanData) return RESOLVED // guard NoopSpan
|
|
69
|
+
const kind = SPAN_KIND_BY_TYPE[oaiSpan.spanData.type]
|
|
70
|
+
if (!kind) return RESOLVED // span types without an LLMObs kind are not traced
|
|
71
|
+
try {
|
|
72
|
+
integration.startSpan(oaiSpan, kind)
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log.warn('[openai-agents] onSpanStart failed: %s', err)
|
|
75
|
+
}
|
|
76
|
+
return RESOLVED
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onSpanEnd (oaiSpan) {
|
|
80
|
+
const integration = this._getIntegration()
|
|
81
|
+
if (!integration?.enabled) return RESOLVED
|
|
82
|
+
if (!oaiSpan?.spanData) return RESOLVED
|
|
83
|
+
try {
|
|
84
|
+
integration.endSpan(oaiSpan)
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log.warn('[openai-agents] onSpanEnd failed: %s', err)
|
|
87
|
+
}
|
|
88
|
+
return RESOLVED
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
forceFlush () {
|
|
92
|
+
// dd-trace exports on its own schedule; nothing to force here.
|
|
93
|
+
return RESOLVED
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
shutdown () {
|
|
97
|
+
const integration = this._getIntegration()
|
|
98
|
+
if (!integration) return RESOLVED
|
|
99
|
+
try {
|
|
100
|
+
integration.clearState()
|
|
101
|
+
} catch (err) {
|
|
102
|
+
log.warn('[openai-agents] shutdown cleanup failed: %s', err)
|
|
103
|
+
}
|
|
104
|
+
return RESOLVED
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { DDOpenAIAgentsProcessor }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// agents-core's `error` is a plain `{ message, data }` object, not a JS Error
|
|
4
|
+
// — there's no constructor to name and no stack. We tag a stable type constant
|
|
5
|
+
// and stringify `data` into the message so the LLMObs error shape stays
|
|
6
|
+
// consistent with other integrations.
|
|
7
|
+
const AGENTS_ERROR_TYPE = 'AgentsCoreError'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build the dd-trace span name from an agents-core oai-span. Handoffs collapse
|
|
11
|
+
* the target agent name into snake_case under a `transfer_to_` prefix; other
|
|
12
|
+
* span types use the SDK-provided name, falling back to
|
|
13
|
+
* `openai_agents.<type>` (or `openai_agents.request` when even the type is
|
|
14
|
+
* missing).
|
|
15
|
+
*
|
|
16
|
+
* @param {object} oaiSpan
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function deriveSpanName (oaiSpan) {
|
|
20
|
+
const spanData = oaiSpan.spanData
|
|
21
|
+
if (spanData?.type === 'handoff') {
|
|
22
|
+
const toAgent = spanData.to_agent || ''
|
|
23
|
+
if (toAgent) return `transfer_to_${toAgent.replaceAll(' ', '_').toLowerCase()}`
|
|
24
|
+
}
|
|
25
|
+
if (spanData?.name) return spanData.name
|
|
26
|
+
return spanData?.type ? `openai_agents.${spanData.type}` : 'openai_agents.request'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Apply agents-core's error shape onto a dd-trace span. No-op when the
|
|
31
|
+
* oai-span has no error attached.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} ddSpan
|
|
34
|
+
* @param {object} oaiSpan
|
|
35
|
+
*/
|
|
36
|
+
function applyError (ddSpan, oaiSpan) {
|
|
37
|
+
const err = oaiSpan.error
|
|
38
|
+
if (!err) return
|
|
39
|
+
|
|
40
|
+
ddSpan.setTag('error', true)
|
|
41
|
+
|
|
42
|
+
let errorMessage = err.message || 'Error'
|
|
43
|
+
if (err.data) {
|
|
44
|
+
try {
|
|
45
|
+
errorMessage = JSON.stringify(err.data)
|
|
46
|
+
} catch {
|
|
47
|
+
// circular / non-serializable — fall back to the raw message
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ddSpan.setTag('error.type', AGENTS_ERROR_TYPE)
|
|
52
|
+
ddSpan.setTag('error.message', errorMessage)
|
|
53
|
+
ddSpan.setTag('error.stack', err.stack || '')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
AGENTS_ERROR_TYPE,
|
|
58
|
+
deriveSpanName,
|
|
59
|
+
applyError,
|
|
60
|
+
}
|
|
@@ -400,7 +400,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
400
400
|
return ctx.currentStore
|
|
401
401
|
})
|
|
402
402
|
|
|
403
|
-
this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush,
|
|
403
|
+
this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onDone }) => {
|
|
404
404
|
if (testSuiteSpan) {
|
|
405
405
|
testSuiteSpan.setTag(TEST_STATUS, status)
|
|
406
406
|
testSuiteSpan.finish()
|
|
@@ -408,10 +408,10 @@ class VitestPlugin extends CiPlugin {
|
|
|
408
408
|
}
|
|
409
409
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
410
410
|
if (deferFlush) {
|
|
411
|
-
|
|
411
|
+
onDone()
|
|
412
412
|
return
|
|
413
413
|
}
|
|
414
|
-
this.tracer._exporter.flush(
|
|
414
|
+
this.tracer._exporter.flush(onDone)
|
|
415
415
|
if (this.runningTestProbe) {
|
|
416
416
|
this.removeDiProbe(this.runningTestProbe)
|
|
417
417
|
}
|
|
@@ -442,7 +442,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
442
442
|
requestErrorTags,
|
|
443
443
|
vitestPool,
|
|
444
444
|
isVitestNoWorkerInitActive,
|
|
445
|
-
|
|
445
|
+
onDone,
|
|
446
446
|
}) => {
|
|
447
447
|
for (const [tag, value] of Object.entries(requestErrorTags || {})) {
|
|
448
448
|
this.testSessionSpan.setTag(tag, value)
|
|
@@ -481,7 +481,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
481
481
|
provider: this.ciProviderName,
|
|
482
482
|
autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
|
|
483
483
|
})
|
|
484
|
-
this.tracer._exporter.flush(
|
|
484
|
+
this.tracer._exporter.flush(onDone)
|
|
485
485
|
})
|
|
486
486
|
|
|
487
487
|
this.addSub('ci:vitest:coverage-report', ({ rootDir, onDone }) => {
|
|
@@ -20,6 +20,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags'
|
|
|
20
20
|
const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
|
|
21
21
|
|
|
22
22
|
const hostname = getHostname()
|
|
23
|
+
const EMPTY_SETTINGS = Object.freeze({})
|
|
23
24
|
|
|
24
25
|
function getTestConfigurationTags (tags) {
|
|
25
26
|
if (!tags) {
|
|
@@ -308,60 +309,58 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
// Takes into account potential kill switches
|
|
311
|
-
filterConfiguration (remoteConfiguration) {
|
|
312
|
-
|
|
313
|
-
return {}
|
|
314
|
-
}
|
|
312
|
+
filterConfiguration (remoteConfiguration = EMPTY_SETTINGS) {
|
|
313
|
+
const { testOptimization } = this._config
|
|
315
314
|
const {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED: isEarlyFlakeDetectionAllowed,
|
|
316
|
+
DD_CIVISIBILITY_FLAKY_RETRY_COUNT: flakyTestRetriesCount = 0,
|
|
317
|
+
DD_CIVISIBILITY_FLAKY_RETRY_ENABLED: isFlakyTestRetriesAllowed,
|
|
318
|
+
DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED: isImpactedTestsAllowed,
|
|
319
|
+
DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: earlyFlakeDetectionRetryCount,
|
|
320
|
+
DD_TEST_FAILED_TEST_REPLAY_ENABLED: isFailedTestReplayAllowed,
|
|
321
|
+
DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: configuredAttemptToFixRetries = 0,
|
|
322
|
+
DD_TEST_MANAGEMENT_ENABLED: isTestManagementAllowed,
|
|
323
|
+
} = testOptimization
|
|
324
|
+
const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
|
|
325
|
+
const configuredSlowTestRetries = remoteConfiguration.earlyFlakeDetectionSlowTestRetries ?? EMPTY_SETTINGS
|
|
326
|
+
const earlyFlakeDetectionSlowTestRetries = hasEarlyFlakeDetectionRetryCount
|
|
327
|
+
? Object.freeze({
|
|
328
|
+
'5s': earlyFlakeDetectionRetryCount,
|
|
329
|
+
'10s': earlyFlakeDetectionRetryCount,
|
|
330
|
+
'30s': earlyFlakeDetectionRetryCount,
|
|
331
|
+
'5m': earlyFlakeDetectionRetryCount,
|
|
332
|
+
})
|
|
333
|
+
: Object.isFrozen(configuredSlowTestRetries)
|
|
334
|
+
? configuredSlowTestRetries
|
|
335
|
+
: Object.freeze({ ...configuredSlowTestRetries })
|
|
336
|
+
const earlyFlakeDetectionNumRetries = hasEarlyFlakeDetectionRetryCount
|
|
337
|
+
? earlyFlakeDetectionRetryCount
|
|
338
|
+
: remoteConfiguration.earlyFlakeDetectionNumRetries ?? 0
|
|
339
|
+
const testManagementAttemptToFixRetries =
|
|
340
|
+
remoteConfiguration.testManagementAttemptToFixRetries ?? configuredAttemptToFixRetries
|
|
341
|
+
|
|
342
|
+
return Object.freeze({
|
|
343
|
+
isCodeCoverageEnabled: remoteConfiguration.isCodeCoverageEnabled === true,
|
|
344
|
+
isSuitesSkippingEnabled: remoteConfiguration.isSuitesSkippingEnabled === true,
|
|
345
|
+
isItrEnabled: remoteConfiguration.isItrEnabled === true,
|
|
346
|
+
requireGit: remoteConfiguration.requireGit === true,
|
|
347
|
+
isEarlyFlakeDetectionEnabled:
|
|
348
|
+
remoteConfiguration.isEarlyFlakeDetectionEnabled === true && isEarlyFlakeDetectionAllowed === true,
|
|
321
349
|
earlyFlakeDetectionNumRetries,
|
|
322
350
|
earlyFlakeDetectionSlowTestRetries,
|
|
323
|
-
earlyFlakeDetectionFaultyThreshold,
|
|
324
|
-
isFlakyTestRetriesEnabled
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
351
|
+
earlyFlakeDetectionFaultyThreshold: remoteConfiguration.earlyFlakeDetectionFaultyThreshold ?? 30,
|
|
352
|
+
isFlakyTestRetriesEnabled:
|
|
353
|
+
remoteConfiguration.isFlakyTestRetriesEnabled === true && isFlakyTestRetriesAllowed === true,
|
|
354
|
+
flakyTestRetriesCount,
|
|
355
|
+
isDiEnabled: remoteConfiguration.isDiEnabled === true && isFailedTestReplayAllowed === true,
|
|
356
|
+
isKnownTestsEnabled: remoteConfiguration.isKnownTestsEnabled === true,
|
|
357
|
+
isTestManagementEnabled:
|
|
358
|
+
remoteConfiguration.isTestManagementEnabled === true && isTestManagementAllowed === true,
|
|
328
359
|
testManagementAttemptToFixRetries,
|
|
329
|
-
isImpactedTestsEnabled,
|
|
330
|
-
isCoverageReportUploadEnabled,
|
|
331
|
-
} = remoteConfiguration
|
|
332
|
-
const { testOptimization } = this._config
|
|
333
|
-
const earlyFlakeDetectionRetryCount =
|
|
334
|
-
testOptimization.DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT
|
|
335
|
-
const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
|
|
336
|
-
return {
|
|
337
|
-
isCodeCoverageEnabled,
|
|
338
|
-
isSuitesSkippingEnabled,
|
|
339
|
-
isItrEnabled,
|
|
340
|
-
requireGit,
|
|
341
|
-
isEarlyFlakeDetectionEnabled:
|
|
342
|
-
isEarlyFlakeDetectionEnabled && testOptimization.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
|
|
343
|
-
earlyFlakeDetectionNumRetries:
|
|
344
|
-
hasEarlyFlakeDetectionRetryCount ? earlyFlakeDetectionRetryCount : earlyFlakeDetectionNumRetries,
|
|
345
|
-
earlyFlakeDetectionSlowTestRetries: hasEarlyFlakeDetectionRetryCount
|
|
346
|
-
? {
|
|
347
|
-
'5s': earlyFlakeDetectionRetryCount,
|
|
348
|
-
'10s': earlyFlakeDetectionRetryCount,
|
|
349
|
-
'30s': earlyFlakeDetectionRetryCount,
|
|
350
|
-
'5m': earlyFlakeDetectionRetryCount,
|
|
351
|
-
}
|
|
352
|
-
: earlyFlakeDetectionSlowTestRetries,
|
|
353
|
-
earlyFlakeDetectionFaultyThreshold,
|
|
354
|
-
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
|
|
355
|
-
flakyTestRetriesCount: testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
|
|
356
|
-
isDiEnabled: isDiEnabled && testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED,
|
|
357
|
-
isKnownTestsEnabled,
|
|
358
|
-
isTestManagementEnabled: isTestManagementEnabled && testOptimization.DD_TEST_MANAGEMENT_ENABLED,
|
|
359
|
-
testManagementAttemptToFixRetries:
|
|
360
|
-
testManagementAttemptToFixRetries ?? testOptimization.DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES,
|
|
361
360
|
isImpactedTestsEnabled:
|
|
362
|
-
isImpactedTestsEnabled &&
|
|
363
|
-
isCoverageReportUploadEnabled,
|
|
364
|
-
}
|
|
361
|
+
remoteConfiguration.isImpactedTestsEnabled === true && isImpactedTestsAllowed === true,
|
|
362
|
+
isCoverageReportUploadEnabled: remoteConfiguration.isCoverageReportUploadEnabled === true,
|
|
363
|
+
})
|
|
365
364
|
}
|
|
366
365
|
|
|
367
366
|
sendGitMetadata (repositoryUrl) {
|
|
@@ -12,12 +12,145 @@ const {
|
|
|
12
12
|
TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE,
|
|
13
13
|
} = require('../telemetry')
|
|
14
14
|
const { writeSettingsToCache } = require('../test-optimization-cache')
|
|
15
|
-
const { validateSettingsResponse } = require('../test-optimization-http-cache-schema')
|
|
15
|
+
const { MAX_RETRIES, validateSettingsResponse } = require('../test-optimization-http-cache-schema')
|
|
16
16
|
const request = require('./request')
|
|
17
17
|
|
|
18
18
|
const DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES = 2
|
|
19
|
-
const DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES = {
|
|
19
|
+
const DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES = Object.freeze({
|
|
20
|
+
'5s': 10,
|
|
21
|
+
'10s': 5,
|
|
22
|
+
'30s': 3,
|
|
23
|
+
'5m': 2,
|
|
24
|
+
})
|
|
20
25
|
const DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD = 30
|
|
26
|
+
const EARLY_FLAKE_DETECTION_RETRY_BUCKETS = Object.keys(DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} EarlyFlakeDetectionSettings
|
|
30
|
+
* @property {boolean} enabled
|
|
31
|
+
* @property {number} numRetries
|
|
32
|
+
* @property {Readonly<Record<string, number>>} slowTestRetries
|
|
33
|
+
* @property {number} faultyThreshold
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} TestManagementSettings
|
|
38
|
+
* @property {boolean} enabled
|
|
39
|
+
* @property {number|undefined} attemptToFixRetries
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {unknown} value
|
|
44
|
+
* @returns {value is Record<string, unknown>}
|
|
45
|
+
*/
|
|
46
|
+
function isRecord (value) {
|
|
47
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {unknown} value
|
|
52
|
+
* @returns {value is number}
|
|
53
|
+
*/
|
|
54
|
+
function isNonNegativeInteger (value) {
|
|
55
|
+
return Number.isInteger(value) && value >= 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {unknown} value
|
|
60
|
+
* @returns {value is number}
|
|
61
|
+
*/
|
|
62
|
+
function isRetryCount (value) {
|
|
63
|
+
return isNonNegativeInteger(value) && value <= MAX_RETRIES
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown} value
|
|
68
|
+
* @returns {Readonly<Record<string, number>>|undefined}
|
|
69
|
+
*/
|
|
70
|
+
function parseSlowTestRetries (value) {
|
|
71
|
+
if (!isRecord(value)) return
|
|
72
|
+
|
|
73
|
+
const slowTestRetries = {}
|
|
74
|
+
for (const bucket of EARLY_FLAKE_DETECTION_RETRY_BUCKETS) {
|
|
75
|
+
if (!Object.hasOwn(value, bucket)) continue
|
|
76
|
+
|
|
77
|
+
const retries = value[bucket]
|
|
78
|
+
if (!isRetryCount(retries)) return
|
|
79
|
+
slowTestRetries[bucket] = retries
|
|
80
|
+
}
|
|
81
|
+
return Object.freeze(slowTestRetries)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {unknown} value
|
|
86
|
+
* @param {boolean} isKnownTestsEnabled
|
|
87
|
+
* @returns {EarlyFlakeDetectionSettings}
|
|
88
|
+
*/
|
|
89
|
+
function parseEarlyFlakeDetectionSettings (value, isKnownTestsEnabled) {
|
|
90
|
+
if (!isRecord(value) || value.enabled !== true) {
|
|
91
|
+
return {
|
|
92
|
+
enabled: false,
|
|
93
|
+
numRetries: DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
|
|
94
|
+
slowTestRetries: DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
95
|
+
faultyThreshold: DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let isValid = true
|
|
100
|
+
let numRetries = DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES
|
|
101
|
+
let slowTestRetries = DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES
|
|
102
|
+
if (Object.hasOwn(value, 'slow_test_retries')) {
|
|
103
|
+
const parsedSlowTestRetries = parseSlowTestRetries(value.slow_test_retries)
|
|
104
|
+
if (parsedSlowTestRetries) {
|
|
105
|
+
slowTestRetries = parsedSlowTestRetries
|
|
106
|
+
numRetries = parsedSlowTestRetries['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES
|
|
107
|
+
} else {
|
|
108
|
+
isValid = false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let faultyThreshold = DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD
|
|
113
|
+
if (Object.hasOwn(value, 'faulty_session_threshold')) {
|
|
114
|
+
if (isNonNegativeInteger(value.faulty_session_threshold) && value.faulty_session_threshold <= 100) {
|
|
115
|
+
faultyThreshold = value.faulty_session_threshold
|
|
116
|
+
} else {
|
|
117
|
+
isValid = false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
enabled: isKnownTestsEnabled && isValid,
|
|
123
|
+
numRetries,
|
|
124
|
+
slowTestRetries,
|
|
125
|
+
faultyThreshold,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {unknown} value
|
|
131
|
+
* @returns {TestManagementSettings}
|
|
132
|
+
*/
|
|
133
|
+
function parseTestManagementSettings (value) {
|
|
134
|
+
if (!isRecord(value) || value.enabled !== true) {
|
|
135
|
+
return {
|
|
136
|
+
enabled: false,
|
|
137
|
+
attemptToFixRetries: undefined,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const attemptToFixRetries = value.attempt_to_fix_retries
|
|
142
|
+
if (attemptToFixRetries !== undefined && !isRetryCount(attemptToFixRetries)) {
|
|
143
|
+
return {
|
|
144
|
+
enabled: false,
|
|
145
|
+
attemptToFixRetries: undefined,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
enabled: true,
|
|
151
|
+
attemptToFixRetries,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
21
154
|
|
|
22
155
|
function parseJsonResponse (rawJson) {
|
|
23
156
|
return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
|
|
@@ -28,40 +161,35 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
28
161
|
if (options.validateRequiredFields) {
|
|
29
162
|
validateSettingsResponse(parsedResponse, options)
|
|
30
163
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} = parsedResponse?.data?.attributes ?? parsedResponse
|
|
164
|
+
const parsedAttributes = parsedResponse?.data?.attributes ?? parsedResponse
|
|
165
|
+
if (!isRecord(parsedAttributes)) {
|
|
166
|
+
throw new TypeError('Invalid settings response: attributes must be an object')
|
|
167
|
+
}
|
|
168
|
+
const attributes = parsedAttributes
|
|
169
|
+
const isKnownTestsEnabled = attributes.known_tests_enabled === true
|
|
170
|
+
const isFlakyTestRetriesEnabled = attributes.flaky_test_retries_enabled === true
|
|
171
|
+
const earlyFlakeDetection = parseEarlyFlakeDetectionSettings(
|
|
172
|
+
attributes.early_flake_detection,
|
|
173
|
+
isKnownTestsEnabled
|
|
174
|
+
)
|
|
175
|
+
const testManagement = parseTestManagementSettings(attributes.test_management)
|
|
44
176
|
|
|
45
177
|
const settings = {
|
|
46
|
-
isCodeCoverageEnabled,
|
|
47
|
-
isSuitesSkippingEnabled,
|
|
48
|
-
isItrEnabled,
|
|
49
|
-
requireGit,
|
|
50
|
-
isEarlyFlakeDetectionEnabled:
|
|
51
|
-
earlyFlakeDetectionNumRetries:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
55
|
-
earlyFlakeDetectionFaultyThreshold:
|
|
56
|
-
earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
|
|
178
|
+
isCodeCoverageEnabled: attributes.code_coverage === true,
|
|
179
|
+
isSuitesSkippingEnabled: attributes.tests_skipping === true,
|
|
180
|
+
isItrEnabled: attributes.itr_enabled === true,
|
|
181
|
+
requireGit: attributes.require_git === true,
|
|
182
|
+
isEarlyFlakeDetectionEnabled: earlyFlakeDetection.enabled,
|
|
183
|
+
earlyFlakeDetectionNumRetries: earlyFlakeDetection.numRetries,
|
|
184
|
+
earlyFlakeDetectionSlowTestRetries: earlyFlakeDetection.slowTestRetries,
|
|
185
|
+
earlyFlakeDetectionFaultyThreshold: earlyFlakeDetection.faultyThreshold,
|
|
57
186
|
isFlakyTestRetriesEnabled,
|
|
58
|
-
isDiEnabled:
|
|
187
|
+
isDiEnabled: attributes.di_enabled === true && isFlakyTestRetriesEnabled,
|
|
59
188
|
isKnownTestsEnabled,
|
|
60
|
-
isTestManagementEnabled:
|
|
61
|
-
testManagementAttemptToFixRetries:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
isCoverageReportUploadEnabled: isCoverageReportUploadEnabled ?? false,
|
|
189
|
+
isTestManagementEnabled: testManagement.enabled,
|
|
190
|
+
testManagementAttemptToFixRetries: testManagement.attemptToFixRetries,
|
|
191
|
+
isImpactedTestsEnabled: attributes.impacted_tests_enabled === true,
|
|
192
|
+
isCoverageReportUploadEnabled: attributes.coverage_report_upload_enabled === true,
|
|
65
193
|
}
|
|
66
194
|
|
|
67
195
|
log.debug('Remote settings: %j', settings)
|
|
@@ -82,7 +210,7 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
82
210
|
log.debug('Code coverage report upload was disabled by the environment variable')
|
|
83
211
|
}
|
|
84
212
|
|
|
85
|
-
return settings
|
|
213
|
+
return Object.freeze(settings)
|
|
86
214
|
}
|
|
87
215
|
|
|
88
216
|
function getLibraryConfiguration ({
|
|
@@ -30,7 +30,7 @@ const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
|
|
|
30
30
|
const MAX_VALIDATION_MODULES = 1000
|
|
31
31
|
const MAX_VALIDATION_SUITES = 10_000
|
|
32
32
|
const MAX_VALIDATION_TESTS = 100_000
|
|
33
|
-
const
|
|
33
|
+
const MAX_RETRIES = 100
|
|
34
34
|
const MAX_VALIDATION_STRING_BYTES = 4096
|
|
35
35
|
|
|
36
36
|
function isObject (value) {
|
|
@@ -75,7 +75,8 @@ function validateSettingsResponse (response, options = {}) {
|
|
|
75
75
|
assertBooleanFields(attributes.test_management, ['enabled'], 'settings test_management')
|
|
76
76
|
assertOptionalBoundedNumber(
|
|
77
77
|
attributes.early_flake_detection.faulty_session_threshold,
|
|
78
|
-
'settings early_flake_detection faulty_session_threshold'
|
|
78
|
+
'settings early_flake_detection faulty_session_threshold',
|
|
79
|
+
{ integer: true }
|
|
79
80
|
)
|
|
80
81
|
assertRetryMap(attributes.early_flake_detection.slow_test_retries)
|
|
81
82
|
assertOptionalBoundedNumber(
|
|
@@ -216,8 +217,8 @@ function assertBooleanFields (object, keys, endpoint) {
|
|
|
216
217
|
function assertOptionalBoundedNumber (value, endpoint, { integer = false } = {}) {
|
|
217
218
|
if (value === undefined) return
|
|
218
219
|
if (!Number.isFinite(value) || (integer && !Number.isInteger(value)) ||
|
|
219
|
-
value < 0 || value >
|
|
220
|
-
throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${
|
|
220
|
+
value < 0 || value > MAX_RETRIES) {
|
|
221
|
+
throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_RETRIES}`)
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -249,6 +250,7 @@ function assertValidationString (value, field) {
|
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
module.exports = {
|
|
253
|
+
MAX_RETRIES,
|
|
252
254
|
validateKnownTestsResponse,
|
|
253
255
|
validateSettingsResponse,
|
|
254
256
|
validateSkippableTestsResponse,
|
|
@@ -333,6 +333,7 @@ export interface GeneratedConfig {
|
|
|
333
333
|
DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
|
|
334
334
|
DD_TRACE_NYC_ENABLED: boolean;
|
|
335
335
|
DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
|
|
336
|
+
DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
|
|
336
337
|
DD_TRACE_OPENAI_ENABLED: boolean;
|
|
337
338
|
DD_TRACE_OPENSEARCH_ENABLED: boolean;
|
|
338
339
|
DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
|
|
@@ -1028,6 +1029,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
1028
1029
|
DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
|
|
1029
1030
|
DD_TRACE_NYC_ENABLED: boolean;
|
|
1030
1031
|
DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
|
|
1032
|
+
DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
|
|
1031
1033
|
DD_TRACE_OPENAI_ENABLED: boolean;
|
|
1032
1034
|
DD_TRACE_OPENSEARCH_ENABLED: boolean;
|
|
1033
1035
|
DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
|
|
@@ -3452,6 +3452,13 @@
|
|
|
3452
3452
|
"default": "true"
|
|
3453
3453
|
}
|
|
3454
3454
|
],
|
|
3455
|
+
"DD_TRACE_OPENAI_AGENTS_ENABLED": [
|
|
3456
|
+
{
|
|
3457
|
+
"implementation": "A",
|
|
3458
|
+
"type": "boolean",
|
|
3459
|
+
"default": "true"
|
|
3460
|
+
}
|
|
3461
|
+
],
|
|
3455
3462
|
"DD_TRACE_OPENSEARCH_ENABLED": [
|
|
3456
3463
|
{
|
|
3457
3464
|
"implementation": "A",
|
|
@@ -31,7 +31,7 @@ class SpanStatsEncoder extends AgentEncoder {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
_encodeStat (bytes, stat) {
|
|
34
|
-
bytes.writeMapPrefix(
|
|
34
|
+
bytes.writeMapPrefix(17)
|
|
35
35
|
|
|
36
36
|
this._encodeString(bytes, 'Service')
|
|
37
37
|
const service = stat.Service || DEFAULT_SERVICE_NAME
|
|
@@ -79,6 +79,12 @@ class SpanStatsEncoder extends AgentEncoder {
|
|
|
79
79
|
|
|
80
80
|
this._encodeString(bytes, 'srv_src')
|
|
81
81
|
this._encodeString(bytes, stat.srv_src || '')
|
|
82
|
+
|
|
83
|
+
this._encodeString(bytes, 'SpanKind')
|
|
84
|
+
this._encodeString(bytes, stat.SpanKind || '')
|
|
85
|
+
|
|
86
|
+
this._encodeString(bytes, 'GRPCStatusCode')
|
|
87
|
+
this._encodeString(bytes, stat.GRPCStatusCode || '')
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
_encodeBucket (bytes, bucket) {
|