dd-trace 6.19.0 → 6.20.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/index.d.ts +163 -22
- package/package.json +2 -2
- package/packages/datadog-esbuild/index.js +9 -6
- package/packages/datadog-instrumentations/src/ai.js +26 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
- package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/remote_config.js +12 -0
- package/packages/dd-trace/src/config/supported-configurations.json +21 -0
- package/packages/dd-trace/src/debugger/constants.js +13 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
- package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
- package/packages/dd-trace/src/debugger/index.js +60 -28
- package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
- package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
- package/packages/dd-trace/src/debugger/redaction.js +139 -0
- package/packages/dd-trace/src/knuth-hash.js +17 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
- package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
- package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
- package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
- package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +2 -2
- package/packages/dd-trace/src/llmobs/sdk.js +19 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +42 -26
- package/packages/dd-trace/src/llmobs/tagger.js +35 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
- package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
- package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
- package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
- package/packages/dd-trace/src/opentracing/span.js +14 -9
- package/packages/dd-trace/src/opentracing/tracer.js +10 -3
- package/packages/dd-trace/src/otel-sampling.js +171 -0
- package/packages/dd-trace/src/plugins/index.js +0 -1
- package/packages/dd-trace/src/priority_sampler.js +155 -33
- package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
- package/packages/dd-trace/src/sampler.js +2 -5
- package/packages/dd-trace/src/sampling_rule.js +4 -8
- package/packages/dd-trace/src/span_format.js +6 -0
- package/packages/dd-trace/src/span_processor.js +26 -2
- package/packages/dd-trace/src/standalone/index.js +5 -4
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
- package/packages/dd-trace/src/telemetry/metrics.js +4 -3
- package/packages/datadog-instrumentations/src/postgres.js +0 -7
- package/packages/datadog-instrumentations/src/supabase.js +0 -15
|
@@ -20,28 +20,62 @@ const ddTraceGlobal = /** @type {Record<symbol, SharedArrayBuffer | object | und
|
|
|
20
20
|
/** @type {Record<symbol, unknown>} */ (globalThis)[Symbol.for(DD_TRACE_SYMBOL)]
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} ProbeThrottle
|
|
25
|
+
* @property {bigint} untilNs - The probe is skipped at entry until this point in time
|
|
26
|
+
* @property {boolean} timedOut - Whether the throttle was caused by an evaluation exceeding its time budget
|
|
27
|
+
* @property {string | undefined} error - A condition error not yet handed over to the worker
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} ProbeSamplerConfig
|
|
32
|
+
* @property {{ DD_DYNAMIC_INSTRUMENTATION_EVALUATION_TIMEOUT_MS: number }} dynamicInstrumentation
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
let evaluationTimeoutNs = 0n
|
|
36
|
+
|
|
23
37
|
module.exports = {
|
|
38
|
+
configureProbeSampler,
|
|
24
39
|
installProbeSampler,
|
|
25
40
|
uninstallProbeSampler,
|
|
26
41
|
}
|
|
27
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Apply the evaluation time budget from the tracer configuration.
|
|
45
|
+
*
|
|
46
|
+
* @param {ProbeSamplerConfig} config - The tracer configuration.
|
|
47
|
+
*/
|
|
48
|
+
function configureProbeSampler (config) {
|
|
49
|
+
evaluationTimeoutNs = BigInt(config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_EVALUATION_TIMEOUT_MS) *
|
|
50
|
+
1_000_000n
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
/**
|
|
29
54
|
* Install the runtime sampler in the debuggee context.
|
|
30
55
|
*
|
|
31
56
|
* @param {import('./guardrail-metrics').GuardrailMetrics} guardrailMetrics - Counters for skipped probe hits.
|
|
57
|
+
* @param {ProbeSamplerConfig} config - The tracer configuration.
|
|
32
58
|
* @returns {SharedArrayBuffer} The shared sampler buffer to pass to the debugger worker.
|
|
33
59
|
*/
|
|
34
|
-
function installProbeSampler (guardrailMetrics) {
|
|
60
|
+
function installProbeSampler (guardrailMetrics, config) {
|
|
61
|
+
configureProbeSampler(config)
|
|
62
|
+
|
|
35
63
|
const buffer = createProbeSamplerBuffer()
|
|
36
64
|
|
|
37
65
|
const lastCaptureNsByProbeId = new Map()
|
|
38
66
|
/**
|
|
39
|
-
* Probes
|
|
40
|
-
*
|
|
67
|
+
* Probes that are skipped at entry because a recent evaluation failed or exceeded its time budget. One error result
|
|
68
|
+
* is reported per throttle window, so the probe stays visible without repeatedly paying for the evaluation.
|
|
41
69
|
*
|
|
42
|
-
* @type {Map<string,
|
|
70
|
+
* @type {Map<string, ProbeThrottle>}
|
|
71
|
+
*/
|
|
72
|
+
const throttleByProbeId = new Map()
|
|
73
|
+
/**
|
|
74
|
+
* When the condition currently being evaluated started. V8 doesn't evaluate breakpoint conditions re-entrantly, and
|
|
75
|
+
* the conditions of probes at the same location are evaluated one after another, so one start time is enough. It is
|
|
76
|
+
* kept here rather than in the compiled condition so the condition can't shadow a variable of the paused frame.
|
|
43
77
|
*/
|
|
44
|
-
|
|
78
|
+
let conditionStartNs = 0n
|
|
45
79
|
const sampledProbeIndexes = new Int32Array(buffer)
|
|
46
80
|
Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_COUNT_INDEX, 0)
|
|
47
81
|
Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_OVERFLOW_INDEX, 0)
|
|
@@ -52,7 +86,7 @@ function installProbeSampler (guardrailMetrics) {
|
|
|
52
86
|
|
|
53
87
|
ddTraceGlobal[Symbol.for(PROBE_SAMPLER_SYMBOL)] = {
|
|
54
88
|
/**
|
|
55
|
-
* Decide if a probe should be sampled and store sampled probe indexes for the debugger worker.
|
|
89
|
+
* Decide if a probe without a condition should be sampled and store sampled probe indexes for the debugger worker.
|
|
56
90
|
*
|
|
57
91
|
* @param {number} probeIndex - The worker-side probe sampling index.
|
|
58
92
|
* @param {string} probeId - The probe id.
|
|
@@ -61,55 +95,46 @@ function installProbeSampler (guardrailMetrics) {
|
|
|
61
95
|
*/
|
|
62
96
|
makeSampleDecision (probeIndex, probeId, nsBetweenSampling, isSnapshotProducingProbe) {
|
|
63
97
|
const now = process.hrtime.bigint()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
SKIPPED_REASON.RATE_LIMIT_PROBE,
|
|
68
|
-
isSnapshotProducingProbe === true ? EVENT_TYPE.SNAPSHOT : EVENT_TYPE.LOG
|
|
69
|
-
)
|
|
70
|
-
return false
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let shouldResetGlobalSnapshotRateWindow = false
|
|
74
|
-
if (isSnapshotProducingProbe === true) {
|
|
75
|
-
if (now - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
|
|
76
|
-
shouldResetGlobalSnapshotRateWindow = true
|
|
77
|
-
} else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
|
|
78
|
-
guardrailMetrics.eventSkipped(SKIPPED_REASON.RATE_LIMIT_GLOBAL, EVENT_TYPE.SNAPSHOT)
|
|
79
|
-
return false
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!storeSampledProbeIndex(probeIndex)) return false
|
|
84
|
-
|
|
85
|
-
if (isSnapshotProducingProbe === true) {
|
|
86
|
-
if (shouldResetGlobalSnapshotRateWindow === true) {
|
|
87
|
-
snapshotsSampledWithinTheLastSecond = 1
|
|
88
|
-
globalSnapshotSamplingRateWindowStart = now
|
|
89
|
-
} else {
|
|
90
|
-
snapshotsSampledWithinTheLastSecond++
|
|
91
|
-
}
|
|
92
|
-
}
|
|
98
|
+
if (isThrottled(probeId, now, isSnapshotProducingProbe)) return false
|
|
99
|
+
return sample(probeIndex, probeId, now, nsBetweenSampling, isSnapshotProducingProbe)
|
|
100
|
+
},
|
|
93
101
|
|
|
94
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Decide if a probe's condition should be evaluated, or skipped because a recent evaluation error throttled it. If
|
|
104
|
+
* it should be evaluated, this starts timing the evaluation against the evaluation time budget.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} probeId - The probe id.
|
|
107
|
+
* @param {boolean} isSnapshotProducingProbe - Whether this probe produces snapshots.
|
|
108
|
+
*/
|
|
109
|
+
shouldEvaluateCondition (probeId, isSnapshotProducingProbe) {
|
|
110
|
+
const now = process.hrtime.bigint()
|
|
111
|
+
if (isThrottled(probeId, now, isSnapshotProducingProbe)) return false
|
|
112
|
+
conditionStartNs = now
|
|
95
113
|
return true
|
|
96
114
|
},
|
|
97
115
|
|
|
98
116
|
/**
|
|
99
|
-
* Decide if a probe
|
|
117
|
+
* Decide if a probe should be sampled now that its condition has been evaluated. A condition that exceeded the
|
|
118
|
+
* evaluation time budget is reported like a condition error instead, regardless of its result.
|
|
100
119
|
*
|
|
120
|
+
* @param {number} probeIndex - The worker-side probe sampling index.
|
|
101
121
|
* @param {string} probeId - The probe id.
|
|
122
|
+
* @param {boolean} matched - Whether the condition evaluated to `true`.
|
|
123
|
+
* @param {bigint} nsBetweenSampling - Minimum nanoseconds between samples for this probe.
|
|
124
|
+
* @param {boolean} isSnapshotProducingProbe - Whether this probe counts toward the global snapshot sample limit.
|
|
102
125
|
*/
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
conditionEvaluated (probeIndex, probeId, matched, nsBetweenSampling, isSnapshotProducingProbe) {
|
|
127
|
+
const now = process.hrtime.bigint()
|
|
128
|
+
const elapsedNs = now - conditionStartNs
|
|
129
|
+
if (elapsedNs > evaluationTimeoutNs) {
|
|
130
|
+
return recordConditionError(probeIndex, probeId, now, describeTimeout(elapsedNs), true)
|
|
131
|
+
}
|
|
132
|
+
return matched === true && sample(probeIndex, probeId, now, nsBetweenSampling, isSnapshotProducingProbe)
|
|
109
133
|
},
|
|
110
134
|
|
|
111
135
|
/**
|
|
112
|
-
* Record that a probe's condition threw, throttle the probe, and request a pause if the error can be reported.
|
|
136
|
+
* Record that a probe's condition threw, throttle the probe, and request a pause if the error can be reported. A
|
|
137
|
+
* condition that exceeded the evaluation time budget before throwing is reported as a timeout instead.
|
|
113
138
|
*
|
|
114
139
|
* Error results bypass the per-probe and global rate limits: they are rate limited by the throttle instead, which
|
|
115
140
|
* allows one error result per probe per window. If the shared buffer is full, the error is dropped but the throttle
|
|
@@ -120,12 +145,12 @@ function installProbeSampler (guardrailMetrics) {
|
|
|
120
145
|
* @param {unknown} error - The value thrown by the condition.
|
|
121
146
|
*/
|
|
122
147
|
conditionError (probeIndex, probeId, error) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
return
|
|
148
|
+
const now = process.hrtime.bigint()
|
|
149
|
+
const elapsedNs = now - conditionStartNs
|
|
150
|
+
if (elapsedNs > evaluationTimeoutNs) {
|
|
151
|
+
return recordConditionError(probeIndex, probeId, now, describeTimeout(elapsedNs), true)
|
|
152
|
+
}
|
|
153
|
+
return recordConditionError(probeIndex, probeId, now, describeError(error), false)
|
|
129
154
|
},
|
|
130
155
|
|
|
131
156
|
/**
|
|
@@ -135,13 +160,28 @@ function installProbeSampler (guardrailMetrics) {
|
|
|
135
160
|
* @returns {string | undefined} The error description, if any.
|
|
136
161
|
*/
|
|
137
162
|
takeConditionError (probeId) {
|
|
138
|
-
const state =
|
|
163
|
+
const state = throttleByProbeId.get(probeId)
|
|
139
164
|
if (state === undefined) return
|
|
140
165
|
const { error } = state
|
|
141
166
|
state.error = undefined
|
|
142
167
|
return error
|
|
143
168
|
},
|
|
144
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Throttle a probe whose evaluation exceeded its time budget in the worker, e.g. while evaluating its template.
|
|
172
|
+
* Called by the worker after the result has been reported.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} probeId - The probe id.
|
|
175
|
+
*/
|
|
176
|
+
evaluationTimedOut (probeId) {
|
|
177
|
+
throttleByProbeId.set(probeId, {
|
|
178
|
+
untilNs: process.hrtime.bigint() + CONDITION_ERROR_THROTTLE_NS,
|
|
179
|
+
timedOut: true,
|
|
180
|
+
// A hit that raced this request may have recorded a condition error that its pause has yet to report
|
|
181
|
+
error: throttleByProbeId.get(probeId)?.error,
|
|
182
|
+
})
|
|
183
|
+
},
|
|
184
|
+
|
|
145
185
|
/**
|
|
146
186
|
* Remove cached sampling state for a probe.
|
|
147
187
|
*
|
|
@@ -149,10 +189,100 @@ function installProbeSampler (guardrailMetrics) {
|
|
|
149
189
|
*/
|
|
150
190
|
remove (probeId) {
|
|
151
191
|
lastCaptureNsByProbeId.delete(probeId)
|
|
152
|
-
|
|
192
|
+
throttleByProbeId.delete(probeId)
|
|
153
193
|
},
|
|
154
194
|
}
|
|
155
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Check if a probe is skipped at entry because of a recent evaluation error, recording the skip when the error was
|
|
198
|
+
* an exceeded time budget.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} probeId - The probe id.
|
|
201
|
+
* @param {bigint | undefined} now - The current time, if already available.
|
|
202
|
+
* @param {boolean} isSnapshotProducingProbe - Whether this probe produces snapshots.
|
|
203
|
+
*/
|
|
204
|
+
function isThrottled (probeId, now, isSnapshotProducingProbe) {
|
|
205
|
+
const state = throttleByProbeId.get(probeId)
|
|
206
|
+
if (state === undefined) return false
|
|
207
|
+
now ??= process.hrtime.bigint()
|
|
208
|
+
if (now >= state.untilNs) {
|
|
209
|
+
throttleByProbeId.delete(probeId)
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
if (state.timedOut) {
|
|
213
|
+
guardrailMetrics.eventSkipped(
|
|
214
|
+
SKIPPED_REASON.EVALUATION_TIMEOUT,
|
|
215
|
+
isSnapshotProducingProbe === true ? EVENT_TYPE.SNAPSHOT : EVENT_TYPE.LOG
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Apply the per-probe and global rate limits and store the sampled probe index for the debugger worker.
|
|
223
|
+
*
|
|
224
|
+
* @param {number} probeIndex - The worker-side probe sampling index.
|
|
225
|
+
* @param {string} probeId - The probe id.
|
|
226
|
+
* @param {bigint} now - The current time.
|
|
227
|
+
* @param {bigint} nsBetweenSampling - Minimum nanoseconds between samples for this probe.
|
|
228
|
+
* @param {boolean} isSnapshotProducingProbe - Whether this probe counts toward the global snapshot sample limit.
|
|
229
|
+
*/
|
|
230
|
+
function sample (probeIndex, probeId, now, nsBetweenSampling, isSnapshotProducingProbe) {
|
|
231
|
+
const lastCaptureNs = lastCaptureNsByProbeId.get(probeId)
|
|
232
|
+
if (lastCaptureNs !== undefined && now - lastCaptureNs < nsBetweenSampling) {
|
|
233
|
+
guardrailMetrics.eventSkipped(
|
|
234
|
+
SKIPPED_REASON.RATE_LIMIT_PROBE,
|
|
235
|
+
isSnapshotProducingProbe === true ? EVENT_TYPE.SNAPSHOT : EVENT_TYPE.LOG
|
|
236
|
+
)
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let shouldResetGlobalSnapshotRateWindow = false
|
|
241
|
+
if (isSnapshotProducingProbe === true) {
|
|
242
|
+
if (now - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
|
|
243
|
+
shouldResetGlobalSnapshotRateWindow = true
|
|
244
|
+
} else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
|
|
245
|
+
guardrailMetrics.eventSkipped(SKIPPED_REASON.RATE_LIMIT_GLOBAL, EVENT_TYPE.SNAPSHOT)
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!storeSampledProbeIndex(probeIndex)) return false
|
|
251
|
+
|
|
252
|
+
if (isSnapshotProducingProbe === true) {
|
|
253
|
+
if (shouldResetGlobalSnapshotRateWindow === true) {
|
|
254
|
+
snapshotsSampledWithinTheLastSecond = 1
|
|
255
|
+
globalSnapshotSamplingRateWindowStart = now
|
|
256
|
+
} else {
|
|
257
|
+
snapshotsSampledWithinTheLastSecond++
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
lastCaptureNsByProbeId.set(probeId, now)
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Throttle a probe whose condition failed and request a pause so the error can be reported. Error results bypass the
|
|
267
|
+
* per-probe and global rate limits: they are rate limited by the throttle instead, which allows one error result per
|
|
268
|
+
* probe per window.
|
|
269
|
+
*
|
|
270
|
+
* @param {number} probeIndex - The worker-side probe sampling index.
|
|
271
|
+
* @param {string} probeId - The probe id.
|
|
272
|
+
* @param {bigint} now - The current time.
|
|
273
|
+
* @param {string} error - The error description.
|
|
274
|
+
* @param {boolean} timedOut - Whether the error is an exceeded time budget.
|
|
275
|
+
*/
|
|
276
|
+
function recordConditionError (probeIndex, probeId, now, error, timedOut) {
|
|
277
|
+
const stored = storeSampledProbeIndex(probeIndex | CONDITION_ERROR_FLAG)
|
|
278
|
+
throttleByProbeId.set(probeId, {
|
|
279
|
+
untilNs: now + CONDITION_ERROR_THROTTLE_NS,
|
|
280
|
+
timedOut,
|
|
281
|
+
error: stored ? error : undefined,
|
|
282
|
+
})
|
|
283
|
+
return stored
|
|
284
|
+
}
|
|
285
|
+
|
|
156
286
|
/**
|
|
157
287
|
* Hand a sampled probe index over to the worker for the upcoming pause.
|
|
158
288
|
*
|
|
@@ -224,6 +354,16 @@ function getErrorStringProperty (error, property) {
|
|
|
224
354
|
}
|
|
225
355
|
}
|
|
226
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Describe a condition evaluation that exceeded its time budget.
|
|
359
|
+
*
|
|
360
|
+
* @param {bigint} elapsedNs - The time the evaluation took.
|
|
361
|
+
*/
|
|
362
|
+
function describeTimeout (elapsedNs) {
|
|
363
|
+
return `Condition evaluation exceeded its time budget of ${evaluationTimeoutNs / 1_000_000n}ms ` +
|
|
364
|
+
`(took ${(Number(elapsedNs) / 1_000_000).toFixed(1)}ms)`
|
|
365
|
+
}
|
|
366
|
+
|
|
227
367
|
/**
|
|
228
368
|
* Create the shared buffer used to hand sampled probe indexes from breakpoint conditions to the debugger worker.
|
|
229
369
|
*
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Used by both the main thread, which renders log template values, and the devtools client worker, which captures
|
|
4
|
+
// snapshots and compiles probe expressions, so that both redact the same identifiers.
|
|
5
|
+
|
|
6
|
+
// What a redacted value is rendered as in a log message.
|
|
7
|
+
const REDACTED_PLACEHOLDER = '{redacted}'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_REDACTED_IDENTIFIERS = [
|
|
10
|
+
'2fa',
|
|
11
|
+
'_csrf',
|
|
12
|
+
'_csrf_token',
|
|
13
|
+
'_session',
|
|
14
|
+
'_xsrf',
|
|
15
|
+
'access_token',
|
|
16
|
+
'aiohttp_session',
|
|
17
|
+
'api_key',
|
|
18
|
+
'apisecret',
|
|
19
|
+
'apisignature',
|
|
20
|
+
'applicationkey',
|
|
21
|
+
'appkey',
|
|
22
|
+
'auth',
|
|
23
|
+
'authtoken',
|
|
24
|
+
'authorization',
|
|
25
|
+
'cc_number',
|
|
26
|
+
'certificatepin',
|
|
27
|
+
'cipher',
|
|
28
|
+
'client_secret',
|
|
29
|
+
'clientid',
|
|
30
|
+
'connect.sid',
|
|
31
|
+
'connectionstring',
|
|
32
|
+
'cookie',
|
|
33
|
+
'credentials',
|
|
34
|
+
'creditcard',
|
|
35
|
+
'csrf',
|
|
36
|
+
'csrf_token',
|
|
37
|
+
'cvv',
|
|
38
|
+
'databaseurl',
|
|
39
|
+
'db_url',
|
|
40
|
+
'encryption_key',
|
|
41
|
+
'encryptionkeyid',
|
|
42
|
+
'geo_location',
|
|
43
|
+
'gpg_key',
|
|
44
|
+
'ip_address',
|
|
45
|
+
'jti',
|
|
46
|
+
'jwt',
|
|
47
|
+
'license_key',
|
|
48
|
+
'masterkey',
|
|
49
|
+
'mysql_pwd',
|
|
50
|
+
'nonce',
|
|
51
|
+
'oauth',
|
|
52
|
+
'oauthtoken',
|
|
53
|
+
'otp',
|
|
54
|
+
'passhash',
|
|
55
|
+
'passwd',
|
|
56
|
+
'password',
|
|
57
|
+
'passwordb',
|
|
58
|
+
'pem_file',
|
|
59
|
+
'pgp_key',
|
|
60
|
+
'PHPSESSID',
|
|
61
|
+
'pin',
|
|
62
|
+
'pincode',
|
|
63
|
+
'pkcs8',
|
|
64
|
+
'private_key',
|
|
65
|
+
'publickey',
|
|
66
|
+
'pwd',
|
|
67
|
+
'recaptcha_key',
|
|
68
|
+
'refresh_token',
|
|
69
|
+
'routingnumber',
|
|
70
|
+
'salt',
|
|
71
|
+
'secret',
|
|
72
|
+
'secretKey',
|
|
73
|
+
'secrettoken',
|
|
74
|
+
'securitycode',
|
|
75
|
+
'security_answer',
|
|
76
|
+
'security_question',
|
|
77
|
+
'serviceaccountcredentials',
|
|
78
|
+
'session',
|
|
79
|
+
'sessionid',
|
|
80
|
+
'sessionkey',
|
|
81
|
+
'set_cookie',
|
|
82
|
+
'signature',
|
|
83
|
+
'signaturekey',
|
|
84
|
+
'ssh_key',
|
|
85
|
+
'ssn',
|
|
86
|
+
'symfony',
|
|
87
|
+
'token',
|
|
88
|
+
'transactionid',
|
|
89
|
+
'twilio_token',
|
|
90
|
+
'user_session',
|
|
91
|
+
'voterid',
|
|
92
|
+
'x-auth-token',
|
|
93
|
+
'x_api_key',
|
|
94
|
+
'x_csrftoken',
|
|
95
|
+
'x_forwarded_for',
|
|
96
|
+
'x_real_ip',
|
|
97
|
+
'XSRF-TOKEN',
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
createIsRedactedIdentifier,
|
|
102
|
+
REDACTED_PLACEHOLDER,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a predicate for whether the value of an identifier must be redacted, based on the default list of redacted
|
|
107
|
+
* identifiers and the user's configuration.
|
|
108
|
+
*
|
|
109
|
+
* @param {object} config - The `dynamicInstrumentation` config.
|
|
110
|
+
* @param {string[]} config.DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS - Identifiers to redact in addition to the
|
|
111
|
+
* defaults.
|
|
112
|
+
* @param {string[]} config.DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS - Identifiers to never redact.
|
|
113
|
+
* @returns {(name: string, isSymbol?: boolean) => boolean} Whether the identifier must be redacted. If `isSymbol` is
|
|
114
|
+
* `true`, `name` is a `Symbol(...)` description.
|
|
115
|
+
*/
|
|
116
|
+
function createIsRedactedIdentifier ({
|
|
117
|
+
DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS: redactedIdentifiers,
|
|
118
|
+
DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS: excludedIdentifiers,
|
|
119
|
+
}) {
|
|
120
|
+
const excluded = new Set(excludedIdentifiers.map((name) => normalizeName(name)))
|
|
121
|
+
const redacted = new Set()
|
|
122
|
+
for (const name of [...DEFAULT_REDACTED_IDENTIFIERS, ...redactedIdentifiers]) {
|
|
123
|
+
const normalized = normalizeName(name)
|
|
124
|
+
if (!excluded.has(normalized)) redacted.add(normalized)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return function isRedactedIdentifier (name, isSymbol) {
|
|
128
|
+
return redacted.has(normalizeName(name, isSymbol))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} name - The identifier name.
|
|
134
|
+
* @param {boolean} [isSymbol] - Whether the name is a `Symbol(...)` description.
|
|
135
|
+
*/
|
|
136
|
+
function normalizeName (name, isSymbol) {
|
|
137
|
+
if (isSymbol) name = name.slice(7, -1) // Remove `Symbol(` and `)`
|
|
138
|
+
return name.toLowerCase().replaceAll(/[-_@$.]/g, '')
|
|
139
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const UINT64_MODULO = 2n ** 64n
|
|
4
|
+
|
|
5
|
+
// Knuth's factor for the sampling algorithm shared across Datadog tracers.
|
|
6
|
+
const SAMPLING_KNUTH_FACTOR = 1_111_111_111_111_111_111n
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hashes the lower 64 bits of a trace ID for deterministic trace sampling.
|
|
10
|
+
*
|
|
11
|
+
* @param {bigint} traceId
|
|
12
|
+
*/
|
|
13
|
+
function knuthHash (traceId) {
|
|
14
|
+
return (traceId * SAMPLING_KNUTH_FACTOR) % UINT64_MODULO
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = knuthHash
|
|
@@ -54,11 +54,13 @@ module.exports = {
|
|
|
54
54
|
DEFAULT_MODEL: 'custom',
|
|
55
55
|
|
|
56
56
|
INPUT_DOCUMENTS: '_ml_obs.meta.input.documents',
|
|
57
|
+
EXPERIMENT_INPUT: '_ml_obs.meta.input',
|
|
57
58
|
INPUT_MESSAGES: '_ml_obs.meta.input.messages',
|
|
58
59
|
INPUT_VALUE: '_ml_obs.meta.input.value',
|
|
59
60
|
INPUT_PROMPT: '_ml_obs.meta.input.prompt',
|
|
60
61
|
|
|
61
62
|
OUTPUT_DOCUMENTS: '_ml_obs.meta.output.documents',
|
|
63
|
+
EXPERIMENT_OUTPUT: '_ml_obs.meta.output',
|
|
62
64
|
OUTPUT_MESSAGES: '_ml_obs.meta.output.messages',
|
|
63
65
|
OUTPUT_VALUE: '_ml_obs.meta.output.value',
|
|
64
66
|
|
|
@@ -5,8 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
const { Dataset, DatasetRecord } = require('./dataset')
|
|
7
7
|
const { ExperimentResult } = require('./result')
|
|
8
|
+
const { RemoteEvaluatorError } = require('./remote-evaluator')
|
|
8
9
|
|
|
9
10
|
const API_BASE_PATH = '/api/v2/llm-obs/v1'
|
|
11
|
+
const EVALUATOR_INFERENCE_API_BASE_PATH = '/api/unstable/llm-obs/v1'
|
|
12
|
+
|
|
13
|
+
class ExperimentsRequestError extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} message
|
|
16
|
+
* @param {number} statusCode
|
|
17
|
+
* @param {object} responseBody
|
|
18
|
+
*/
|
|
19
|
+
constructor (message, statusCode, responseBody) {
|
|
20
|
+
super(message)
|
|
21
|
+
this.statusCode = statusCode
|
|
22
|
+
this.responseBody = responseBody
|
|
23
|
+
}
|
|
24
|
+
}
|
|
10
25
|
|
|
11
26
|
// Control-plane host for a Datadog site, e.g.
|
|
12
27
|
// datadoghq.com -> api.datadoghq.com
|
|
@@ -151,7 +166,15 @@ class ExperimentsClient {
|
|
|
151
166
|
|
|
152
167
|
const text = await response.text()
|
|
153
168
|
if (!response.ok) {
|
|
154
|
-
|
|
169
|
+
let responseBody = {}
|
|
170
|
+
try {
|
|
171
|
+
responseBody = text ? JSON.parse(text) : {}
|
|
172
|
+
} catch {}
|
|
173
|
+
throw new ExperimentsRequestError(
|
|
174
|
+
`${method} ${path} failed: HTTP ${response.status} ${text}`,
|
|
175
|
+
response.status,
|
|
176
|
+
responseBody
|
|
177
|
+
)
|
|
155
178
|
}
|
|
156
179
|
return text ? JSON.parse(text) : {}
|
|
157
180
|
}
|
|
@@ -247,6 +270,41 @@ class ExperimentsClient {
|
|
|
247
270
|
return experimentFromResource(this, response?.data ?? null)
|
|
248
271
|
}
|
|
249
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @param {string} evalName
|
|
275
|
+
* @param {object} context
|
|
276
|
+
*/
|
|
277
|
+
evaluatorInfer (evalName, context) {
|
|
278
|
+
const encodedName = encodeURIComponent(evalName)
|
|
279
|
+
const path = `${EVALUATOR_INFERENCE_API_BASE_PATH}/evaluators/${encodedName}/infer`
|
|
280
|
+
return this.jsonApiRequest('POST', path, 'evaluator_inference', { context }).then(response => {
|
|
281
|
+
const attributes = response?.data?.attributes ?? {}
|
|
282
|
+
return {
|
|
283
|
+
value: attributes.value,
|
|
284
|
+
reasoning: attributes.reasoning,
|
|
285
|
+
assessment: attributes.assessment,
|
|
286
|
+
status: attributes.status,
|
|
287
|
+
}
|
|
288
|
+
}).catch(err => {
|
|
289
|
+
const attributes = err.responseBody?.data?.attributes ?? {}
|
|
290
|
+
const backendError = attributes.error
|
|
291
|
+
if (backendError !== null && typeof backendError === 'object' && !Array.isArray(backendError)) {
|
|
292
|
+
const fallback = err.statusCode === undefined ? err.message : `HTTP ${err.statusCode}`
|
|
293
|
+
throw new RemoteEvaluatorError(
|
|
294
|
+
`Remote evaluator '${evalName}' failed: ${backendError.message ?? fallback}`,
|
|
295
|
+
{ status: attributes.status, backendError }
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const apiError = Array.isArray(err.responseBody?.errors) ? err.responseBody.errors[0] : undefined
|
|
300
|
+
const detail = apiError !== null && typeof apiError === 'object'
|
|
301
|
+
? apiError.detail ?? apiError.title
|
|
302
|
+
: undefined
|
|
303
|
+
const fallback = err.statusCode === undefined ? err.message : `HTTP ${err.statusCode}`
|
|
304
|
+
throw new Error(`Failed to call evaluator '${evalName}': ${detail ?? fallback}`)
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
|
|
250
308
|
postExperimentEvents (experimentId, attributes) {
|
|
251
309
|
return this.jsonApiRequest(
|
|
252
310
|
'POST',
|
|
@@ -278,4 +336,10 @@ class ExperimentsClient {
|
|
|
278
336
|
}
|
|
279
337
|
}
|
|
280
338
|
|
|
281
|
-
module.exports = {
|
|
339
|
+
module.exports = {
|
|
340
|
+
ExperimentsClient,
|
|
341
|
+
apiHost,
|
|
342
|
+
appHost,
|
|
343
|
+
API_BASE_PATH,
|
|
344
|
+
EVALUATOR_INFERENCE_API_BASE_PATH,
|
|
345
|
+
}
|