dd-trace 6.20.1 → 6.22.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 +9 -14
- package/ci/init.js +1 -2
- package/ci/test-optimization-validation/ci-discovery.js +9 -4
- package/ci/test-optimization-validation/executable.js +1 -5
- package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
- package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
- package/ci/test-optimization-validation/generated-files.js +6 -4
- package/ci/test-optimization-validation/generated-test-contract.js +7 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
- package/ci/test-optimization-validation/manifest-schema.js +6 -7
- package/ci/test-optimization-validation/report-writer.js +6 -2
- package/ci/test-optimization-validation/runner-command.js +2 -4
- package/ci/test-optimization-validation/runner-contract.js +1 -2
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
- package/ci/vitest-no-worker-init-setup.mjs +10 -7
- package/index.d.ts +116 -17
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +13 -11
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- package/packages/datadog-instrumentations/src/aerospike.js +3 -3
- package/packages/datadog-instrumentations/src/ai.js +3 -3
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
- package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
- package/packages/datadog-instrumentations/src/cucumber.js +48 -33
- package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
- package/packages/datadog-instrumentations/src/electron.js +6 -6
- package/packages/datadog-instrumentations/src/fastify.js +13 -4
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
- package/packages/datadog-instrumentations/src/ioredis.js +7 -5
- package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
- package/packages/datadog-instrumentations/src/jest.js +122 -11
- package/packages/datadog-instrumentations/src/knex.js +9 -5
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
- package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
- package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
- package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +5 -6
- package/packages/datadog-instrumentations/src/playwright.js +3 -0
- package/packages/datadog-instrumentations/src/redis.js +27 -13
- package/packages/datadog-instrumentations/src/router.js +30 -0
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
- package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +2 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
- package/packages/datadog-instrumentations/src/ws.js +1 -4
- package/packages/datadog-plugin-amqplib/src/client.js +1 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
- package/packages/datadog-plugin-cypress/src/index.js +6 -6
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
- package/packages/datadog-plugin-cypress/src/support.js +1 -4
- package/packages/datadog-plugin-express/src/code_origin.js +1 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
- package/packages/datadog-plugin-graphql/src/execute.js +5 -5
- package/packages/datadog-plugin-grpc/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
- package/packages/datadog-plugin-mocha/src/index.js +223 -39
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
- package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/tracing.js +5 -2
- package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
- package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
- package/packages/datadog-plugin-postgres/src/index.js +24 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
- package/packages/datadog-plugin-vitest/src/index.js +5 -2
- package/packages/datadog-shimmer/src/shimmer.js +5 -4
- package/packages/datadog-webpack/index.js +55 -43
- package/packages/datadog-webpack/src/loader.js +56 -8
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
- package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
- package/packages/dd-trace/src/aiguard/redaction.js +8 -2
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
- package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
- package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
- package/packages/dd-trace/src/appsec/lambda.js +1 -3
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
- package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
- package/packages/dd-trace/src/appsec/reporter.js +21 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
- package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
- package/packages/dd-trace/src/config/config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/defaults.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +10 -9
- package/packages/dd-trace/src/config/normalize-service.js +1 -1
- package/packages/dd-trace/src/config/parsers.js +2 -2
- package/packages/dd-trace/src/config/supported-configurations.json +35 -0
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/debugger/index.js +24 -2
- package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
- package/packages/dd-trace/src/dogstatsd.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/request.js +2 -5
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +15 -5
- package/packages/dd-trace/src/lambda/handler.js +179 -19
- package/packages/dd-trace/src/lambda/index.js +15 -1
- package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
- package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
- package/packages/dd-trace/src/llmobs/tagger.js +56 -3
- package/packages/dd-trace/src/llmobs/util.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
- package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
- package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
- package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
- package/packages/dd-trace/src/otel-sampling.js +4 -5
- package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/util/git.js +9 -25
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/test.js +48 -4
- package/packages/dd-trace/src/priority_sampler.js +1 -2
- package/packages/dd-trace/src/profiling/profiler.js +4 -6
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +3 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +10 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/storage-channels.js +112 -46
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/util.js +29 -0
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('node:child_process')
|
|
4
|
+
const { mkdtempSync, rmSync, writeFileSync } = require('node:fs')
|
|
5
|
+
const { tmpdir } = require('node:os')
|
|
6
|
+
const { join } = require('node:path')
|
|
7
|
+
const { performance } = require('node:perf_hooks')
|
|
8
|
+
const { clearInterval, clearTimeout, setInterval, setTimeout } = require('node:timers')
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
VIDEO_UPLOAD_RESULT_ERROR,
|
|
12
|
+
VIDEO_UPLOAD_RESULT_UPLOADED,
|
|
13
|
+
} = require('../../dd-trace/src/ci-visibility/test-video')
|
|
14
|
+
const log = require('../../dd-trace/src/log')
|
|
15
|
+
|
|
16
|
+
const CAPTURE_INTERVAL_MS = 500
|
|
17
|
+
const CAPTURE_TIMEOUT_MS = 5000
|
|
18
|
+
const ENCODING_TIMEOUT_MS = 30_000
|
|
19
|
+
const MAX_PENDING_ENCODINGS = 32
|
|
20
|
+
const MAX_PIXELS = 16 * 1024 * 1024
|
|
21
|
+
const MAX_FRAME_BYTES = 200 * 1024 * 1024
|
|
22
|
+
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
|
23
|
+
const { queueMicrotask } = globalThis
|
|
24
|
+
const captureOwners = new WeakMap()
|
|
25
|
+
// A local timeout does not cancel the WebDriver request; keep its slot until it actually settles.
|
|
26
|
+
const pendingCaptures = new WeakSet()
|
|
27
|
+
const unsafePromptBrowsers = new WeakSet()
|
|
28
|
+
|
|
29
|
+
let ffmpegAvailable
|
|
30
|
+
// PNGs remain charged through encoding and upload, until their temporary files are removed.
|
|
31
|
+
let frameBytes = 0
|
|
32
|
+
|
|
33
|
+
/** @type {Array<() => void>} */
|
|
34
|
+
const encodingQueue = []
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Runs one encoder at a time across all attempts in this WDIO process. Uploads do not hold the slot.
|
|
38
|
+
*
|
|
39
|
+
* @param {{ directory: string, index: number, frames: number[], filePath: string, size: number[] }} recording
|
|
40
|
+
* @param {(error?: Error) => void} onDone
|
|
41
|
+
*/
|
|
42
|
+
function encodeVideo (recording, onDone) {
|
|
43
|
+
// Include the active encoder in the queue limit.
|
|
44
|
+
if (encodingQueue.length > MAX_PENDING_ENCODINGS) {
|
|
45
|
+
queueMicrotask(() => onDone(new Error('WebdriverIO video encoding queue is full')))
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
let encoder
|
|
49
|
+
let timeout
|
|
50
|
+
const complete = error => {
|
|
51
|
+
const index = encodingQueue.indexOf(start)
|
|
52
|
+
if (index === -1) return
|
|
53
|
+
encodingQueue.splice(index, 1)
|
|
54
|
+
// Defer the next start so repeated process startup failures cannot recurse through the queue.
|
|
55
|
+
if (encodingQueue.length) queueMicrotask(encodingQueue[0])
|
|
56
|
+
onDone(error)
|
|
57
|
+
}
|
|
58
|
+
const start = () => {
|
|
59
|
+
// A queued start may have been cancelled before its microtask runs.
|
|
60
|
+
if (encoder || !encodingQueue.includes(start)) return
|
|
61
|
+
try {
|
|
62
|
+
const { directory, index, frames, filePath, size: [width, height] } = recording
|
|
63
|
+
const manifestPath = join(directory, `${index}.ffconcat`)
|
|
64
|
+
let manifest = 'ffconcat version 1.0\n'
|
|
65
|
+
for (let frame = 0; frame < frames.length; frame++) {
|
|
66
|
+
// Command captures are irregular: fixed 2 fps stretches assertion polling into long videos.
|
|
67
|
+
const duration = frame + 1 < frames.length ? (frames[frame + 1] - frames[frame]) / 1000 : 0.04
|
|
68
|
+
manifest += `file ${index}-${frame}.png\nduration ${duration.toFixed(6)}\n`
|
|
69
|
+
}
|
|
70
|
+
// The PNG demuxer uses 25 Hz timestamps. Repeat the final image one tick later so it survives
|
|
71
|
+
// rounding when the last two captures are less than 40 ms apart. WebM preserves variable timing.
|
|
72
|
+
manifest += `file ${index}-${frames.length - 1}.png\n`
|
|
73
|
+
writeFileSync(manifestPath, manifest)
|
|
74
|
+
encoder = spawn('ffmpeg', [
|
|
75
|
+
'-hide_banner', '-loglevel', 'error', '-nostdin', '-y',
|
|
76
|
+
'-threads', '1', '-f', 'concat', '-i', manifestPath,
|
|
77
|
+
'-frames:v', String(frames.length + 1), '-an', '-c:v', 'libvpx', '-deadline', 'realtime',
|
|
78
|
+
'-threads', '1', '-filter_threads', '1', '-pix_fmt', 'yuv420p',
|
|
79
|
+
'-vf', `scale=${width}:${height},setsar=1`, filePath,
|
|
80
|
+
], { windowsHide: true, stdio: 'ignore' })
|
|
81
|
+
let encodingError
|
|
82
|
+
timeout = setTimeout(() => {
|
|
83
|
+
encodingError = new Error('WebdriverIO video encoding timed out')
|
|
84
|
+
// A kill request does not release the slot until the process actually closes.
|
|
85
|
+
encoder.kill('SIGKILL')
|
|
86
|
+
}, ENCODING_TIMEOUT_MS)
|
|
87
|
+
encoder.once('error', error => { encodingError = error })
|
|
88
|
+
encoder.once('close', code => {
|
|
89
|
+
clearTimeout(timeout)
|
|
90
|
+
if (code !== 0) encodingError ||= new Error(`WebdriverIO video encoder exited with code ${code}`)
|
|
91
|
+
complete(encodingError)
|
|
92
|
+
})
|
|
93
|
+
} catch (error) {
|
|
94
|
+
// Let the caller retain its cancellation callback even when process startup throws.
|
|
95
|
+
queueMicrotask(() => complete(error))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
encodingQueue.push(start)
|
|
99
|
+
if (encodingQueue.length === 1) start()
|
|
100
|
+
return () => {
|
|
101
|
+
const index = encodingQueue.indexOf(start)
|
|
102
|
+
if (index === -1) return
|
|
103
|
+
if (encoder) {
|
|
104
|
+
clearTimeout(timeout)
|
|
105
|
+
// Shutdown must not wait for a process that cannot terminate. Keep its slot until close.
|
|
106
|
+
encoder.unref?.()
|
|
107
|
+
if (!encoder.kill('SIGKILL')) log.error('Error stopping WebdriverIO video encoder')
|
|
108
|
+
} else {
|
|
109
|
+
encodingQueue.splice(index, 1)
|
|
110
|
+
if (index === 0 && encodingQueue.length) queueMicrotask(encodingQueue[0])
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Like wdio-video-reporter, collect WebDriver screenshots and encode only failed attempts.
|
|
116
|
+
// Encoding uses a customer-installed FFmpeg executable, isolated from the WDIO test process.
|
|
117
|
+
class WebdriverioVideo {
|
|
118
|
+
#browser
|
|
119
|
+
#directory
|
|
120
|
+
#frames = []
|
|
121
|
+
#sizes = []
|
|
122
|
+
#bytes = 0
|
|
123
|
+
#capturing = false
|
|
124
|
+
#finalCapture = false
|
|
125
|
+
#completeCapture
|
|
126
|
+
#disabled = false
|
|
127
|
+
#stopped = false
|
|
128
|
+
#captureTimeout
|
|
129
|
+
#interval
|
|
130
|
+
#onCapture
|
|
131
|
+
#error
|
|
132
|
+
#onResult
|
|
133
|
+
#cancelEncoding
|
|
134
|
+
#finished = false
|
|
135
|
+
#onDone
|
|
136
|
+
|
|
137
|
+
/** @param {object} browser - WebdriverIO browser or multiremote browser */
|
|
138
|
+
constructor (browser) {
|
|
139
|
+
this.#browser = browser
|
|
140
|
+
this.#directory = mkdtempSync(join(tmpdir(), 'dd-trace-wdio-video-'))
|
|
141
|
+
captureOwners.set(browser, this)
|
|
142
|
+
this.#onResult = /** @param {{command?: string, endpoint?: string}} event */ (event) => {
|
|
143
|
+
const { command, endpoint } = event
|
|
144
|
+
if (
|
|
145
|
+
command === 'takeScreenshot' || endpoint?.endsWith('/screenshot') ||
|
|
146
|
+
// WDIO looks up the context before BiDi navigation. Capturing in that gap can block navigation.
|
|
147
|
+
command === 'getWindowHandle' || command === 'getWindowHandles'
|
|
148
|
+
) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
this.capture()
|
|
152
|
+
}
|
|
153
|
+
browser.on?.('result', this.#onResult)
|
|
154
|
+
this.#interval = setInterval(() => this.capture(), CAPTURE_INTERVAL_MS)
|
|
155
|
+
this.#interval.unref?.()
|
|
156
|
+
this.capture()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Captures at most one in-flight screenshot, including all multiremote sessions. */
|
|
160
|
+
capture () {
|
|
161
|
+
if ((this.#stopped && !this.#finalCapture) || this.#capturing || this.#error || this.#disabled ||
|
|
162
|
+
pendingCaptures.has(this.#browser) || captureOwners.get(this.#browser) !== this) return
|
|
163
|
+
this.#finalCapture = false
|
|
164
|
+
this.#capturing = true
|
|
165
|
+
let completed = false
|
|
166
|
+
const complete = (error, screenshots, capturedAt) => {
|
|
167
|
+
if (completed || this.#finished) return
|
|
168
|
+
completed = true
|
|
169
|
+
this.#capturing = false
|
|
170
|
+
if (error) {
|
|
171
|
+
log.error('Error capturing WebdriverIO video frame: %s', error.message)
|
|
172
|
+
} else if (!this.#disabled && captureOwners.get(this.#browser) === this) {
|
|
173
|
+
try {
|
|
174
|
+
this.#writeFrames(screenshots, capturedAt)
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.#error = error
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (this.#finalCapture) {
|
|
180
|
+
this.capture()
|
|
181
|
+
if (this.#capturing) return
|
|
182
|
+
}
|
|
183
|
+
this.#finalCapture = false
|
|
184
|
+
clearTimeout(this.#captureTimeout)
|
|
185
|
+
this.#captureTimeout = undefined
|
|
186
|
+
this.#completeCapture = undefined
|
|
187
|
+
const onCapture = this.#onCapture
|
|
188
|
+
this.#onCapture = undefined
|
|
189
|
+
onCapture?.()
|
|
190
|
+
}
|
|
191
|
+
this.#completeCapture = complete
|
|
192
|
+
// An overlapping capture and its fresh final frame share the existing five-second deadline.
|
|
193
|
+
this.#captureTimeout ??= setTimeout(() => {
|
|
194
|
+
this.#completeCapture?.(new Error('WebdriverIO video screenshot capture timed out'))
|
|
195
|
+
}, CAPTURE_TIMEOUT_MS)
|
|
196
|
+
this.#captureTimeout.unref?.()
|
|
197
|
+
try {
|
|
198
|
+
if (!hasSafePromptPolicy(this.#browser)) {
|
|
199
|
+
this.#disabled = true
|
|
200
|
+
clearInterval(this.#interval)
|
|
201
|
+
this.#browser.removeListener?.('result', this.#onResult)
|
|
202
|
+
return complete()
|
|
203
|
+
}
|
|
204
|
+
if (this.#browser.isMultiremote || this.#browser.isMultiRemote) {
|
|
205
|
+
const names = this.#browser.instances
|
|
206
|
+
if (!names.length) return complete(new Error('WebdriverIO returned no browser sessions'))
|
|
207
|
+
const browsers = names.map(name => this.#browser.getInstance(name))
|
|
208
|
+
const frames = new Array(names.length)
|
|
209
|
+
const captureTimes = new Array(names.length)
|
|
210
|
+
let pending = names.length
|
|
211
|
+
let captureError
|
|
212
|
+
pendingCaptures.add(this.#browser)
|
|
213
|
+
for (let index = 0; index < names.length; index++) {
|
|
214
|
+
this.#captureBrowser(browsers[index], (error, frame, capturedAt) => {
|
|
215
|
+
captureError ||= error
|
|
216
|
+
frames[index] = frame
|
|
217
|
+
captureTimes[index] = capturedAt
|
|
218
|
+
if (--pending === 0) {
|
|
219
|
+
pendingCaptures.delete(this.#browser)
|
|
220
|
+
complete(captureError, frames, captureTimes)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
pendingCaptures.add(this.#browser)
|
|
226
|
+
this.#captureBrowser(this.#browser, (error, frame, capturedAt) => {
|
|
227
|
+
pendingCaptures.delete(this.#browser)
|
|
228
|
+
complete(error, frame, capturedAt)
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
complete(error)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @param {object} browser - A single WebDriver session, including sessions with BiDi enabled
|
|
238
|
+
* @param {(error?: Error, frame?: string, capturedAt?: number) => void} onDone
|
|
239
|
+
*/
|
|
240
|
+
#captureBrowser (browser, onDone) {
|
|
241
|
+
try {
|
|
242
|
+
// Use the session's WebDriver screenshot command. Direct BiDi captures can lose their
|
|
243
|
+
// browsing context during concurrent navigation and never return a frame.
|
|
244
|
+
// Timestamp each session before waiting for the rest of a multiremote batch.
|
|
245
|
+
browser.takeScreenshot().then(result => onDone(undefined, result, performance.now()), onDone)
|
|
246
|
+
} catch (error) {
|
|
247
|
+
onDone(error)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {string|string[]} screenshots - Base64 PNG screenshots, in session order
|
|
253
|
+
* @param {number|number[]} capturedAt - Completion timestamps in the same session order
|
|
254
|
+
*/
|
|
255
|
+
#writeFrames (screenshots, capturedAt) {
|
|
256
|
+
const frames = Array.isArray(screenshots) ? screenshots : [screenshots]
|
|
257
|
+
if (!frames.length) throw new Error('WebdriverIO returned no video frames')
|
|
258
|
+
for (let index = 0; index < frames.length; index++) {
|
|
259
|
+
if (typeof frames[index] !== 'string') throw new TypeError('WebdriverIO returned an invalid video frame')
|
|
260
|
+
if (Buffer.byteLength(frames[index], 'base64') > MAX_FRAME_BYTES - frameBytes) {
|
|
261
|
+
throw new Error('WebdriverIO video frames exceeded the 200 MiB worker limit')
|
|
262
|
+
}
|
|
263
|
+
const frame = Buffer.from(frames[index], 'base64')
|
|
264
|
+
if (frame.length < 24 || !frame.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
|
|
265
|
+
throw new Error('WebdriverIO returned an invalid PNG video frame')
|
|
266
|
+
}
|
|
267
|
+
const width = frame.readUInt32BE(16)
|
|
268
|
+
const height = frame.readUInt32BE(20)
|
|
269
|
+
if (!width || !height || width * height > MAX_PIXELS) {
|
|
270
|
+
throw new Error('WebdriverIO video frame exceeds the 16 megapixel limit or has invalid dimensions')
|
|
271
|
+
}
|
|
272
|
+
if (!this.#sizes[index]) {
|
|
273
|
+
// Keep a fixed, even output size across viewport changes.
|
|
274
|
+
const scale = Math.min(1, 1280 / width, 720 / height)
|
|
275
|
+
this.#sizes[index] = [
|
|
276
|
+
Math.max(2, Math.floor(width * scale / 2) * 2),
|
|
277
|
+
Math.max(2, Math.floor(height * scale / 2) * 2),
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
const timestamps = this.#frames[index] ||= []
|
|
281
|
+
const frameNumber = timestamps.length
|
|
282
|
+
// Reserve before writing so a partial write stays accounted for until cleanup.
|
|
283
|
+
frameBytes += frame.length
|
|
284
|
+
this.#bytes += frame.length
|
|
285
|
+
writeFileSync(join(this.#directory, `${index}-${frameNumber}.png`), frame)
|
|
286
|
+
timestamps.push(Array.isArray(capturedAt) ? capturedAt[index] : capturedAt)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Stops capture, uploads failed attempts, and removes all owned files after upload completion.
|
|
292
|
+
*
|
|
293
|
+
* @param {boolean} failed - Whether this attempt failed
|
|
294
|
+
* @param {(filePath: string, index: number, onDone: (error?: Error) => void) => void} upload
|
|
295
|
+
* @param {(result?: string) => void} onDone
|
|
296
|
+
*/
|
|
297
|
+
finish (failed, upload, onDone) {
|
|
298
|
+
if (this.#stopped) return
|
|
299
|
+
this.#onDone = onDone
|
|
300
|
+
if (failed) {
|
|
301
|
+
if (this.#capturing) this.#finalCapture = true
|
|
302
|
+
else this.capture()
|
|
303
|
+
}
|
|
304
|
+
this.#stopped = true
|
|
305
|
+
clearInterval(this.#interval)
|
|
306
|
+
this.#browser.removeListener?.('result', this.#onResult)
|
|
307
|
+
const finish = () => {
|
|
308
|
+
if (this.#finished) return
|
|
309
|
+
if (!failed || this.#disabled) return this.#cleanup(undefined, onDone)
|
|
310
|
+
if (this.#error || !this.#frames.length) {
|
|
311
|
+
log.error('Error recording WebdriverIO failure video: %s', this.#error?.message || 'No frames captured')
|
|
312
|
+
return this.#cleanup(VIDEO_UPLOAD_RESULT_ERROR, onDone)
|
|
313
|
+
}
|
|
314
|
+
this.#encode(0, upload, false, onDone)
|
|
315
|
+
}
|
|
316
|
+
if (this.#capturing) this.#onCapture = finish
|
|
317
|
+
else finish()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** @param {() => void} onDone - Releases a retry once this attempt's final capture settles. */
|
|
321
|
+
waitForCapture (onDone) {
|
|
322
|
+
if (!this.#capturing || this.#finished) return onDone()
|
|
323
|
+
const previous = this.#onCapture
|
|
324
|
+
this.#onCapture = () => {
|
|
325
|
+
previous?.()
|
|
326
|
+
onDone()
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Cancels a finishing attempt at the worker's final-flush deadline. */
|
|
331
|
+
cancel () {
|
|
332
|
+
if (this.#finished) return
|
|
333
|
+
this.#cancelEncoding?.()
|
|
334
|
+
this.#cleanup(VIDEO_UPLOAD_RESULT_ERROR, this.#onDone)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Encodes multiremote sessions sequentially to bound encoder resource use.
|
|
339
|
+
*
|
|
340
|
+
* @param {number} index
|
|
341
|
+
* @param {(filePath: string, index: number, onDone: (error?: Error) => void) => void} upload
|
|
342
|
+
* @param {boolean} hasError
|
|
343
|
+
* @param {(result?: string) => void} onDone
|
|
344
|
+
*/
|
|
345
|
+
#encode (index, upload, hasError, onDone) {
|
|
346
|
+
if (index === this.#frames.length) {
|
|
347
|
+
return this.#cleanup(hasError ? VIDEO_UPLOAD_RESULT_ERROR : VIDEO_UPLOAD_RESULT_UPLOADED, onDone)
|
|
348
|
+
}
|
|
349
|
+
const filePath = join(this.#directory, `${index}.webm`)
|
|
350
|
+
let completed = false
|
|
351
|
+
const next = error => {
|
|
352
|
+
if (completed) return
|
|
353
|
+
completed = true
|
|
354
|
+
// Cancellation may try cleanup while encoding or upload still holds files open.
|
|
355
|
+
if (this.#finished) return this.#cleanup(undefined, () => {})
|
|
356
|
+
if (error) log.error('Error uploading WebdriverIO failure video: %s', error.message)
|
|
357
|
+
this.#encode(index + 1, upload, hasError || Boolean(error), onDone)
|
|
358
|
+
}
|
|
359
|
+
const recording = {
|
|
360
|
+
directory: this.#directory,
|
|
361
|
+
index,
|
|
362
|
+
frames: this.#frames[index],
|
|
363
|
+
filePath,
|
|
364
|
+
size: this.#sizes[index],
|
|
365
|
+
}
|
|
366
|
+
this.#cancelEncoding = encodeVideo(recording, error => {
|
|
367
|
+
if (this.#finished || error) return next(error)
|
|
368
|
+
try {
|
|
369
|
+
upload(filePath, index, next)
|
|
370
|
+
} catch (error) {
|
|
371
|
+
next(error)
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @param {string|undefined} result
|
|
378
|
+
* @param {(result?: string) => void} onDone
|
|
379
|
+
*/
|
|
380
|
+
#cleanup (result, onDone) {
|
|
381
|
+
this.#finished = true
|
|
382
|
+
if (captureOwners.get(this.#browser) === this) captureOwners.delete(this.#browser)
|
|
383
|
+
clearTimeout(this.#captureTimeout)
|
|
384
|
+
this.#completeCapture = undefined
|
|
385
|
+
const onCapture = this.#onCapture
|
|
386
|
+
this.#onCapture = undefined
|
|
387
|
+
onCapture?.()
|
|
388
|
+
try {
|
|
389
|
+
rmSync(this.#directory, { recursive: true, force: true })
|
|
390
|
+
frameBytes -= this.#bytes
|
|
391
|
+
this.#bytes = 0
|
|
392
|
+
} catch (error) {
|
|
393
|
+
log.error('Error removing WebdriverIO video files: %s', error.message)
|
|
394
|
+
}
|
|
395
|
+
onDone(result)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** @param {object} browser - Current WebdriverIO browser or multiremote browser */
|
|
400
|
+
function hasSafePromptPolicy (browser) {
|
|
401
|
+
const sessions = (browser.isMultiremote || browser.isMultiRemote)
|
|
402
|
+
? browser.instances.map(name => browser.getInstance(name))
|
|
403
|
+
: [browser]
|
|
404
|
+
for (const session of sessions) {
|
|
405
|
+
const behavior = session.capabilities?.unhandledPromptBehavior
|
|
406
|
+
if (behavior === 'ignore' || (behavior?.default === 'ignore' &&
|
|
407
|
+
Object.values(behavior).every(value => value === 'ignore'))) continue
|
|
408
|
+
if (!unsafePromptBrowsers.has(browser)) {
|
|
409
|
+
unsafePromptBrowsers.add(browser)
|
|
410
|
+
log.warn('WebdriverIO failure video recording is disabled: every session must use %s to preserve open dialogs.',
|
|
411
|
+
'unhandledPromptBehavior: "ignore"')
|
|
412
|
+
}
|
|
413
|
+
return false
|
|
414
|
+
}
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Checks FFmpeg once in the worker, before configuring browser sessions or recording attempts. */
|
|
419
|
+
function isFfmpegAvailable () {
|
|
420
|
+
if (ffmpegAvailable !== undefined) return ffmpegAvailable
|
|
421
|
+
ffmpegAvailable = false
|
|
422
|
+
try {
|
|
423
|
+
const result = spawnSync('ffmpeg', ['-hide_banner', '-encoders'], {
|
|
424
|
+
timeout: 5000, killSignal: 'SIGKILL', windowsHide: true, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
425
|
+
})
|
|
426
|
+
ffmpegAvailable = !result.error && result.status === 0 && /^[ \t]*V\S{5}[ \t]+libvpx[ \t]/m.test(result.stdout)
|
|
427
|
+
} catch (error) {
|
|
428
|
+
log.error('Error checking WebdriverIO video FFmpeg availability: %s', error.message)
|
|
429
|
+
}
|
|
430
|
+
if (!ffmpegAvailable) {
|
|
431
|
+
log.warn('WebdriverIO failure video recording requires FFmpeg on PATH with the libvpx encoder.')
|
|
432
|
+
}
|
|
433
|
+
return ffmpegAvailable
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Creates a recorder when a browser is available. The encoder is loaded only for failed attempts.
|
|
438
|
+
*
|
|
439
|
+
* @param {object} browser
|
|
440
|
+
* @returns {WebdriverioVideo|void}
|
|
441
|
+
*/
|
|
442
|
+
function createWebdriverioVideo (browser) {
|
|
443
|
+
if (typeof browser?.takeScreenshot !== 'function') return
|
|
444
|
+
try {
|
|
445
|
+
if (ffmpegAvailable === false || !hasSafePromptPolicy(browser) || !isFfmpegAvailable()) return
|
|
446
|
+
return new WebdriverioVideo(browser)
|
|
447
|
+
} catch (error) {
|
|
448
|
+
log.error('Error starting WebdriverIO video recording: %s', error.message)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
module.exports = createWebdriverioVideo
|
|
453
|
+
module.exports.isFfmpegAvailable = isFfmpegAvailable
|
|
@@ -181,10 +181,9 @@ function canStringifyDirectWalk (value, depth) {
|
|
|
181
181
|
child._bsontype !== undefined ||
|
|
182
182
|
isRegExp(child) ||
|
|
183
183
|
isMap(child) ||
|
|
184
|
-
typeof child.toJSON === 'function') {
|
|
184
|
+
typeof child.toJSON === 'function' || !canStringifyDirectWalk(child, depth + 1)) {
|
|
185
185
|
return false
|
|
186
186
|
}
|
|
187
|
-
if (!canStringifyDirectWalk(child, depth + 1)) return false
|
|
188
187
|
}
|
|
189
188
|
return true
|
|
190
189
|
}
|
|
@@ -322,9 +321,13 @@ function buildRedact (value, ancestors) {
|
|
|
322
321
|
// `value === original` short-circuit.
|
|
323
322
|
if (typeof value.toJSON === 'function') {
|
|
324
323
|
const json = value.toJSON()
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
if (
|
|
325
|
+
typeof json !== 'object' || json === null || json === value ||
|
|
326
|
+
// Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
|
|
327
|
+
ArrayBuffer.isView(json) || json._bsontype === 'Binary'
|
|
328
|
+
) {
|
|
329
|
+
return REDACT_LEAF
|
|
330
|
+
}
|
|
328
331
|
value = json
|
|
329
332
|
} else if (bsontype !== undefined) {
|
|
330
333
|
return REDACT_LEAF
|
|
@@ -107,8 +107,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
107
107
|
const { req, res, nextRequest = {} } = ctx
|
|
108
108
|
const store = ctx.currentStore ?? storage('legacy').getStore()
|
|
109
109
|
|
|
110
|
-
if (!store) return
|
|
111
|
-
if (reusedNextRequestStores.has(store)) return
|
|
110
|
+
if (!store || reusedNextRequestStores.has(store)) return
|
|
112
111
|
|
|
113
112
|
const span = store.span
|
|
114
113
|
const error = ctx.error ?? span.context().getTag('error')
|
|
@@ -295,8 +295,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
sendLog (methodName, span, tags, openaiStore, error) {
|
|
298
|
-
if (!openaiStore) return
|
|
299
|
-
if (!this.sampler.isSampled(span)) return
|
|
298
|
+
if (!openaiStore || !this.sampler.isSampled(span)) return
|
|
300
299
|
|
|
301
300
|
const log = {
|
|
302
301
|
status: error ? 'error' : 'info',
|
|
@@ -334,6 +333,10 @@ function normalizeMethodName (methodName) {
|
|
|
334
333
|
case 'responses.create':
|
|
335
334
|
return 'createResponse'
|
|
336
335
|
|
|
336
|
+
// decisions
|
|
337
|
+
case 'decisions.create':
|
|
338
|
+
return 'createDecision'
|
|
339
|
+
|
|
337
340
|
// files
|
|
338
341
|
case 'files.create':
|
|
339
342
|
return 'createFile'
|
|
@@ -460,8 +460,12 @@ class OpenAIAgentsIntegration {
|
|
|
460
460
|
*/
|
|
461
461
|
#llmSpanParentAgentName (oaiSpan) {
|
|
462
462
|
const traceInfo = this.#traceInfo.get(oaiSpan.traceId)
|
|
463
|
-
if (
|
|
464
|
-
|
|
463
|
+
if (
|
|
464
|
+
!traceInfo?.currentTopLevelAgentSpanId ||
|
|
465
|
+
!this.#hasUntracedPathToAncestor(oaiSpan.parentId, traceInfo.currentTopLevelAgentSpanId)
|
|
466
|
+
) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
465
469
|
return traceInfo.currentTopLevelAgentName
|
|
466
470
|
}
|
|
467
471
|
|
|
@@ -64,8 +64,7 @@ class DDOpenAIAgentsProcessor {
|
|
|
64
64
|
|
|
65
65
|
onSpanStart (oaiSpan) {
|
|
66
66
|
const integration = this._getIntegration()
|
|
67
|
-
if (!integration?.enabled) return RESOLVED
|
|
68
|
-
if (!oaiSpan?.spanData) return RESOLVED // guard NoopSpan
|
|
67
|
+
if (!integration?.enabled || !oaiSpan?.spanData) return RESOLVED // guard NoopSpan
|
|
69
68
|
const type = oaiSpan.spanData.type
|
|
70
69
|
const kind = SPAN_KIND_BY_TYPE[type]
|
|
71
70
|
try {
|
|
@@ -84,8 +83,7 @@ class DDOpenAIAgentsProcessor {
|
|
|
84
83
|
|
|
85
84
|
onSpanEnd (oaiSpan) {
|
|
86
85
|
const integration = this._getIntegration()
|
|
87
|
-
if (!integration?.enabled) return RESOLVED
|
|
88
|
-
if (!oaiSpan?.spanData) return RESOLVED
|
|
86
|
+
if (!integration?.enabled || !oaiSpan?.spanData) return RESOLVED
|
|
89
87
|
const type = oaiSpan.spanData.type
|
|
90
88
|
const kind = SPAN_KIND_BY_TYPE[type]
|
|
91
89
|
try {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { performance } = require('node:perf_hooks')
|
|
4
|
+
|
|
3
5
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
4
6
|
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
5
7
|
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
6
8
|
|
|
9
|
+
const ACQUIRE_START_CHANNEL = 'tracing:orchestrion:postgres:query:acquire:start'
|
|
7
10
|
const PREPARE_START_CHANNEL = 'tracing:orchestrion:postgres:query:prepare:start'
|
|
8
11
|
|
|
9
12
|
/**
|
|
@@ -14,6 +17,8 @@ const PREPARE_START_CHANNEL = 'tracing:orchestrion:postgres:query:prepare:start'
|
|
|
14
17
|
* @property {string} [host]
|
|
15
18
|
* @property {number} [pid]
|
|
16
19
|
* @property {number} [port]
|
|
20
|
+
* @property {boolean} [poolWaitFinished]
|
|
21
|
+
* @property {number} [poolWaitStartMs]
|
|
17
22
|
* @property {PostgresQuery} query
|
|
18
23
|
* @property {string} [statement]
|
|
19
24
|
* @property {string} user
|
|
@@ -63,12 +68,16 @@ class PostgresPlugin extends DatabasePlugin {
|
|
|
63
68
|
/** @type {WeakMap<PostgresQuery, import('../../..').Span>} */
|
|
64
69
|
#spans = new WeakMap()
|
|
65
70
|
|
|
71
|
+
/** @type {WeakMap<PostgresQuery, PostgresContext>} */
|
|
72
|
+
#waitContexts = new WeakMap()
|
|
73
|
+
|
|
66
74
|
/**
|
|
67
75
|
* @param {object} tracer
|
|
68
76
|
* @param {import('../../dd-trace/src/config/config-base')} tracerConfig
|
|
69
77
|
*/
|
|
70
78
|
constructor (tracer, tracerConfig) {
|
|
71
79
|
super(tracer, tracerConfig)
|
|
80
|
+
this.addSub(ACQUIRE_START_CHANNEL, ctx => this.#finishPoolWait(ctx.query))
|
|
72
81
|
this.#preparation = new PostgresPreparationPlugin(this.#prepare.bind(this), tracer, tracerConfig)
|
|
73
82
|
}
|
|
74
83
|
|
|
@@ -112,12 +121,26 @@ class PostgresPlugin extends DatabasePlugin {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
this.#spans.set(query, span)
|
|
124
|
+
this.#waitContexts.set(query, ctx)
|
|
115
125
|
if (this.#dbmEnabled) {
|
|
116
126
|
this.#dbmQueries.set(query, { service: service.name })
|
|
117
127
|
}
|
|
118
128
|
return ctx.currentStore
|
|
119
129
|
}
|
|
120
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param {PostgresQuery} query
|
|
133
|
+
*/
|
|
134
|
+
#finishPoolWait (query) {
|
|
135
|
+
const ctx = this.#waitContexts.get(query)
|
|
136
|
+
if (ctx === undefined) return
|
|
137
|
+
|
|
138
|
+
this.#waitContexts.delete(query)
|
|
139
|
+
ctx.poolWaitFinished = true
|
|
140
|
+
const waitTime = ctx.poolWaitStartMs === undefined ? 0 : performance.now() - ctx.poolWaitStartMs
|
|
141
|
+
ctx.currentStore.span.setTag('db.pool.wait_time_ms', waitTime)
|
|
142
|
+
}
|
|
143
|
+
|
|
121
144
|
/**
|
|
122
145
|
* @param {PostgresPreparationContext} ctx
|
|
123
146
|
*/
|
|
@@ -152,6 +175,7 @@ class PostgresPlugin extends DatabasePlugin {
|
|
|
152
175
|
this.#dbmQueries.delete(query)
|
|
153
176
|
if (span === undefined) return
|
|
154
177
|
|
|
178
|
+
this.#finishPoolWait(query)
|
|
155
179
|
this.#spans.delete(query)
|
|
156
180
|
|
|
157
181
|
const statement = dbmState?.statement ?? result.statement
|
|
@@ -144,12 +144,8 @@ class SchemaExtractor {
|
|
|
144
144
|
span.setTag(SCHEMA_NAME, removeLeadingPeriod(descriptor.fullName))
|
|
145
145
|
span.setTag(SCHEMA_OPERATION, operation)
|
|
146
146
|
|
|
147
|
-
if (!tracer._dataStreamsProcessor.canSampleSchema(operation)) {
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
147
|
// if the span is unsampled, do not sample the schema
|
|
152
|
-
if (!tracer._prioritySampler.isSampled(span)) {
|
|
148
|
+
if (!tracer._dataStreamsProcessor.canSampleSchema(operation) || !tracer._prioritySampler.isSampled(span)) {
|
|
153
149
|
return
|
|
154
150
|
}
|
|
155
151
|
|
|
@@ -52,10 +52,7 @@ class RheaConsumerPlugin extends ConsumerPlugin {
|
|
|
52
52
|
|
|
53
53
|
function getResourceNameFromMessage (msgObj) {
|
|
54
54
|
let resourceName = 'amq.topic'
|
|
55
|
-
|
|
56
|
-
if (msgObj.receiver && msgObj.receiver.options) {
|
|
57
|
-
options = msgObj.receiver.options
|
|
58
|
-
}
|
|
55
|
+
const options = msgObj.receiver && msgObj.receiver.options ? msgObj.receiver.options : {}
|
|
59
56
|
if (options.source && options.source.address) {
|
|
60
57
|
resourceName = options.source.address
|
|
61
58
|
}
|
|
@@ -354,6 +354,8 @@ class VitestPlugin extends CiPlugin {
|
|
|
354
354
|
browserProjectName,
|
|
355
355
|
testExecutionId,
|
|
356
356
|
testStartLine,
|
|
357
|
+
startTime,
|
|
358
|
+
duration,
|
|
357
359
|
}) => {
|
|
358
360
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
359
361
|
const extraTags = {
|
|
@@ -380,10 +382,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
380
382
|
testSuite,
|
|
381
383
|
testSuiteSpan || this.testSuiteSpan,
|
|
382
384
|
extraTags,
|
|
383
|
-
testExecutionId
|
|
385
|
+
testExecutionId,
|
|
386
|
+
startTime
|
|
384
387
|
)
|
|
385
388
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
|
|
386
|
-
testSpan.finish()
|
|
389
|
+
testSpan.finish(Number.isFinite(duration) && duration >= 0 ? testSpan._startTime + duration : undefined)
|
|
387
390
|
})
|
|
388
391
|
|
|
389
392
|
this.addBind('ci:vitest:test-suite:start', (ctx) => {
|