DI: coordinated sampling for Live Debugger snapshots (runtime_id, trace_id_source) - #6118
DI: coordinated sampling for Live Debugger snapshots (runtime_id, trace_id_source)#6118p-datadog wants to merge 13 commits into
Conversation
…ing gate Coordinated sampling for Live Debugger snapshots: one emit/drop decision per execution unit (active APM trace, or a task-scoped correlation id read from fiber-local storage), shared across every probe in the unit so related snapshots stop fragmenting under independent per-probe sampling. Within an emitting unit a per-probe-per-span cap bounds each probe to one snapshot. The decision reuses the probe's existing rate limiter (first probe in a unit decides, siblings inherit), introducing no new sampling-rate wire or config contract. Decision and cap state live in bounded LRU maps. Includes RBS signatures and unit tests.
…apshots
Gap 1 (process identity): emit the per-process runtimeId in the snapshot
envelope, distinguishing snapshots from before and after a restart inside the
same container. Same value already sent in probe status diagnostics.
envelope-source: emit trace_id_source ("apm" | "task" | "none") so a consumer
knows when the trace id in the envelope is a valid join key to APM. Mirrors the
correlation gate's tier ordering using in-process reads only.
Wire Correlation into Component and Instrumenter. Replace the two per-probe rate-limiter checks (method-probe and line-probe paths) with a single emit? gate that delegates to Correlation#gate, so probes in one execution unit share the emit/drop decision and a per-probe-per-span cap bounds each probe. emit? fails open: when correlation is absent or the gate raises (outside propagate_all_exceptions), it falls back to the probe's own rate limiter so a correlation bug cannot silence all snapshots. Adds an integration test covering tier-1 coordination, per-span cap, tier-2 task units, runtime id on the wire, and fail-open.
Typing analysisNote: Ignored files are excluded from the next sections.
|
|
BenchmarksBenchmark execution time: 2026-07-29 18:04:12 Comparing candidate commit af741e4 in PR branch Found 1 performance improvements and 0 performance regressions! Performance is the same for 47 metrics, 1 unstable metrics.
|
Extract execution-unit identity (APM trace / task boundary / individual hit) into Datadog::DI::ExecutionUnit, the single source of truth for tiering used by both the sampler and the snapshot envelope. Correlation becomes a sampler with one responsibility: emit?(probe, unit) decides once per unit, shares the decision across sibling probes, and caps each probe once per scope. Drops the unused decisions_made/last_decision_at counters.
Correlation used none of settings/logger/telemetry; remove them and their attr_readers rather than document dead surface. Constructor now takes only max_entries. Add brief describing docstrings to the constructors.
The requirements decision permits only existing tracer context and prohibits new context mechanisms; with no active context the hit is not correlated. Remove the task tier from ExecutionUnit: TASK_KEY, the task branch in .current, and the .bracket/.open/.close boundary API (a new fiber-local context mechanism). Units now resolve to :apm (active trace) or :none. Correlation and the notification builder need no change: Correlation is unit-agnostic (nil key -> per-probe, i.e. not correlated), and trace_id_source serializes the unit source, which now yields only "apm" or "none". Drop the tier-2 tests and RBS entries.
Replace untyped in the ExecutionUnit and Correlation signatures with concrete types: unit key/scope are Integer? (trace/span ids); the decision and cap maps are Hash[Integer, bool] and Hash[Integer, Set[String]]; probe ids are String; evict is generic over [K, V]. Bind unit.key/unit.scope to locals in emit? so the nil-guard narrows them to Integer for the map operations. Steep clean, DI correlation specs green.
Rename the unit that groups related probe hits from 'execution unit' to 'sampling unit': class ExecutionUnit -> SamplingUnit (file, sig, spec), the Correlation param unit -> sampling_unit, the @unit_decisions map and unit_decision method -> @sampling_unit_decisions / sampling_unit_decision, and the prose throughout. Also drop a stale 'task' mention left in the trace_id_source envelope comment. Behavior unchanged. Steep clean, DI correlation specs green.
What does this PR do?
Adds coordinated sampling for Live Debugger snapshots, plus two snapshot-envelope
fields, on the DI producer side.
Datadog::DI::ExecutionUnit+Datadog::DI::Correlation):one emit/drop decision per execution unit — the active APM trace — shared
across every probe that fires within that unit. Within an emitting unit, a
per-probe-per-span cap bounds each probe to one snapshot so a probe inside a
high-iteration loop cannot starve sibling probes. When no trace is active the
hit is not correlated (an independent per-probe decision). The gate runs at
the top of the probe-hit path, before any capture work, so a dropped probe
costs one lookup.
runtimeIdin the snapshot envelope: distinguishes snapshots from before andafter a restart inside the same container (host/container tags cannot). Same
value already sent in probe status diagnostics.
trace_id_source("apm"|"none"): tells a consumer when the trace id inthe envelope is a valid join key to APM.
The unit is resolved from existing tracer context only (the active trace/span);
no new context mechanism is introduced. The sampling decision reuses the probe's
existing rate limiter (the first probe in a unit decides, siblings inherit), so
this introduces no new sampling-rate contract on the wire or in configuration.
The gate fails open to the previous per-probe rate-limiting behavior if
correlation is unavailable or errors, so a correlation bug cannot silence all
snapshots.
Motivation:
Independent per-probe sampling fragments chains of related snapshots: the system
can emit one probe's snapshot while dropping a sibling, parent, or child probe
that would explain it, with no signal to the user. This implements the
producer-side track of the "Improving Correlation for Live Debugger Snapshots"
RFC for Ruby.
Change log entry
Yes. Live Debugger snapshots from probes in the same trace now share one sampling
decision so related snapshots arrive together instead of fragmenting, and each
snapshot carries the process runtime id.
Additional Notes:
Correlation uses existing tracer context only; there is deliberately no new
context mechanism, so probes firing outside an active trace are not correlated.
Deferred (not in this PR): emitting a non-null
thread_idand the associatedgeneration token (Ruby emits
thread_id: nulltoday); and a global per-sessionevents/sec ceiling.
How to test the change?
spec/datadog/di/execution_unit_spec.rb— unit resolution from the activetrace/span, and the no-trace (
:none) case.spec/datadog/di/correlation_spec.rb— the sampler: coordinated decision,per-span cap, LRU eviction.
spec/datadog/di/integration/correlation_integration_spec.rb— end-to-endthrough real instrumentation: tier-1 coordination, per-span cap, no-trace
independent decision, runtime id on the wire, and fail-open.
spec/datadog/di/probe_notification_builder_spec.rb— envelope fields.Full
rake spec:di:di_with_extis green (1069 examples). Standard and Steep areclean.