Skip to content

feat(otlp): semantic registry provider for embedded or live registries - #2718

Open
webern wants to merge 2 commits into
mainfrom
m/otlp-semreg
Open

webern wants to merge 2 commits into
mainfrom
m/otlp-semreg

Conversation

@webern

@webern webern commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Human Summary

Adds a SemanticRegistryProvider to provide either the embedded or live semantic registry. This PR doesn't actually wire up the provider to the remote configuration client yet, so it would always return embedded. In the next PR we wire it up to return the registry updates.

#2719 adds the semantic RC decoder and subscription method; #2724 connects that subscription during ADP startup.

AI Summary

A span's HTTP status may appear as http.response.status_code or http.status_code. The semantic registry maps concepts like HTTP status code to ordered attribute-name fallbacks, expected types, and conditions. OTLP trace translation uses these mappings to read and normalize spans; APM stats uses the registry's peer-attribute names to group stats by the service or resource a span calls. The embedded mappings.json provides the default registry. Remote Configuration can supply a complete replacement, as in the Agent’s semantic registry.

This PR gives those consumers a SemanticRegistryProvider. It follows Saluki's pattern of passing providers through component builders: the OTLP source, OTLP decoder, and APM stats builder pass a handle to the translator or stats concentrator instead of having either consumer find a process-wide registry. The handle wraps an existing Remote Configuration Subscription, not a separate registry store or background task. The RC client publishes accepted snapshots to the subscription; provider clones see the same publications. With RC off (an inert subscription), or before any snapshot is accepted, snapshot() returns the embedded registry. A rejected update leaves the last accepted registry in place.

  • The translator pins one Arc<Registry> per ResourceSpans and passes &Registry through the span lookups. An update arriving mid-batch takes effect on a later batch, without a subscription read per span.
  • Before each input event buffer, APM stats checks the provider and updates its peer-tag key set if the registry changed. That set combines the registry's peer-attribute names with configured custom tags; it determines which span attributes distinguish stats groups. Existing aggregated stats are not rewritten.
  • The embedded file must parse without skipped entries. Remote JSON keeps the concepts and fallbacks this binary understands and reports skipped entries; malformed documents or missing required registry metadata are rejected. As in the Agent, a fingerprint of the payload bytes tracks changes, rather than the producer-supplied metadata.content_hash label.

This PR makes the consumers ready for a subscribed provider; decoding APM_SEMANTIC_CORE_DD and wiring the subscription into ADP follow separately. Lookups that still use literal attribute keys, such as env and container ID, are unchanged.

Change Type

  • New feature

How did you test this PR?

  • make fmt, cargo check --workspace, cargo check --workspace --tests, make check-docs, make check-clippy at the stack tip.
  • Filtered saluki-components tests: 549 passed at the stack tip.

References

@webern
webern added this pull request to stack #2721 September 30, 2026 11:18
@dd-octo-sts dd-octo-sts Bot added area/components Sources, transforms, and destinations. transform/apm-stats APM Stats transform. labels Sep 30, 2026
@webern webern added the changelog/no-changelog No changelog entry needed label Sep 30, 2026
@pr-commenter

pr-commenter Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Binary Size Analysis (Agent Data Plane)

Baseline: 216e499 · Comparison: b286fb2 · diff
Analysis Configuration: stripped binaries · Pass/Fail Threshold: +5%
Sizes: 38.27 MiB (baseline) vs 38.24 MiB (comparison)
Size Change: -28.50 KiB (-0.07%)

✅ Binary size difference within threshold

Changes by Module
Module File Size Symbols
core +69.92 KiB 8990
tonic_prost +38.64 KiB 42
prost -38.46 KiB 311
&mut serde_json -28.30 KiB 94
saluki_metrics::mapped::MappedMetric<H> -24.51 KiB 6
axum -24.42 KiB 404
saluki_components::destinations::dogstatsd_client_telemetry +22.75 KiB 31
serde_json +16.46 KiB 459
anon.7bcce2bce1d7289ca2b67a9dabbae1a7.13.llvm.8132447645249006792 +16.18 KiB 1
anon.87e7c38bfd928d17fa28187081c51bf0.2.llvm.240572210426274404 -16.09 KiB 1
anon.87e7c38bfd928d17fa28187081c51bf0.768.llvm.240572210426274404 -14.62 KiB 1
anon.7bcce2bce1d7289ca2b67a9dabbae1a7.477.llvm.8132447645249006792 +14.62 KiB 1
otlp_protos::otlp_include::opentelemetry -14.03 KiB 178
saluki_components::sources::otlp -13.08 KiB 267
anon.87e7c38bfd928d17fa28187081c51bf0.766.llvm.240572210426274404 -12.84 KiB 1
anon.7bcce2bce1d7289ca2b67a9dabbae1a7.6.llvm.8132447645249006792 +12.83 KiB 1
quick_cache +12.40 KiB 109
alloc -12.18 KiB 1425
anon.9d1057ec6821611763596b0232be6ef0.693.llvm.6678353967787269721 -11.13 KiB 1
tonic +11.12 KiB 324
Detailed Symbol Changes
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  [NEW] +61.1Ki  [NEW] +61.0Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::ha38a49b72e023545
  [NEW] +42.2Ki  [NEW] +42.0Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::hb872765f165820b0
  [NEW] +40.6Ki  [NEW] +40.5Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h75562c7ea09bcbff
  [NEW] +37.4Ki  [NEW] +37.2Ki    _<saluki_components::transforms::apm_stats::ApmStats as saluki_core::components::transforms::Transform>::run::_{{closure}}::h79ca62828ea1b270
  [NEW] +34.5Ki  [NEW] +34.4Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h0f7a307e9cc0a16a
  [NEW] +31.0Ki  [NEW] +30.8Ki    agent_data_plane::cli::dogstatsd::run_dogstatsd_command::_{{closure}}::hdfcd4810bc0a3053
  [NEW] +29.0Ki  [NEW] +28.8Ki    datadog_agent_commons::ipc::client::RemoteAgentClient::connect::_{{closure}}::_{{closure}}::_{{closure}}::h0a198a9356f00b58
  [NEW] +28.6Ki  [NEW] +28.5Ki    agent_data_plane::dogstatsd_contexts::artifact::for_each_record::h1b4e32864ddc5f08
  [NEW] +26.4Ki  [NEW] +26.3Ki    saluki_components::destinations::dogstatsd_client_telemetry::DogStatsDClientTelemetry::record_metric::h6e61a932a14c058b
  [NEW] +25.6Ki  [NEW] +25.4Ki    agent_data_plane::internal::remote_agent::run_remote_agent_registration_loop::_{{closure}}::h044bf8d66771780c
  [DEL] -25.7Ki  [DEL] -25.6Ki    core::ops::function::FnOnce::call_once::h4d6ad556f476b8c9
  [DEL] -28.4Ki  [DEL] -28.2Ki    datadog_agent_commons::ipc::client::RemoteAgentClient::connect::_{{closure}}::_{{closure}}::_{{closure}}::h1bd6a026eeafe819
  [DEL] -28.7Ki  [DEL] -28.6Ki    agent_data_plane::dogstatsd_contexts::artifact::for_each_record::hb60d5262ffff75a0
  [DEL] -31.4Ki  [DEL] -31.2Ki    agent_data_plane::cli::dogstatsd::run_dogstatsd_command::_{{closure}}::h2f5246c806e7f392
  [DEL] -34.9Ki  [DEL] -34.7Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::haa79196f741be08a
  [DEL] -35.8Ki  [DEL] -35.6Ki    _<saluki_components::transforms::apm_stats::ApmStats as saluki_core::components::transforms::Transform>::run::_{{closure}}::h8bffda5d06bacbfd
  [DEL] -40.5Ki  [DEL] -40.4Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h02ed82f16b54dbc0
  [DEL] -44.7Ki  [DEL] -44.5Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h252ca254b106e4c3
  [DEL] -58.6Ki  [DEL] -58.5Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h15a965fc1b5a6f1e
  -0.4% -56.3Ki  -0.7% -71.7Ki    [28806 Others]
  [ = ]       0 -87.5% -1.75Mi    _rjem_je_arena_emap_global
  -0.1% -28.5Ki  -5.5% -1.79Mi    TOTAL

@pr-commenter

pr-commenter Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Regression Detector (Agent Data Plane)

Run ID: eef15432-dfd0-459d-84e4-5095cabeaa15
Baseline: 216e499a · Comparison: b286fb2f · diff

Optimization Goals: ✅ No significant changes detected

Fine details of change detection per experiment (5)

Experiments configured erratic: true are tagged (ignored) and skipped when determining which experiments regressed or improved. Experiments which are detected as erratic at runtime are tagged (erratic) to flag that the run's sample dispersion was high, but their regression / improvement signal still counts.

experiment goal Δ mean % links
quality_gates_rss_dsd_ultraheavy memory ⚪ +0.59 metrics profiles logs
quality_gates_rss_dsd_low memory ⚪ +0.26 metrics profiles logs
quality_gates_rss_dsd_medium memory ⚪ -0.00 metrics profiles logs
quality_gates_rss_dsd_heavy memory ⚪ -0.07 metrics profiles logs
quality_gates_rss_idle memory ⚪ -0.62 metrics profiles logs
Bounds Checks: ✅ Passed (5)
experiment check replicates observed links
quality_gates_rss_dsd_heavy memory_usage 10/10 ✅ 230 MiB ≤ 250 MiB metrics profiles logs
quality_gates_rss_dsd_low memory_usage 10/10 ✅ 51.8 MiB ≤ 60 MiB metrics profiles logs
quality_gates_rss_dsd_medium memory_usage 10/10 ✅ 91.3 MiB ≤ 100 MiB metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 ✅ 386 MiB ≤ 420 MiB metrics profiles logs
quality_gates_rss_idle memory_usage 10/10 ✅ 33.2 MiB ≤ 40 MiB metrics profiles logs
Explanation

A change is flagged as a regression when |Δ mean %| > 5.00% in the regressing direction for its optimization goal AND SMP marks the experiment as a regression (is_regression: true). Improvements use the matching criteria for the improving direction. Experiments configured erratic: true (tagged (ignored)) are skipped outright; experiments detected as erratic at runtime (tagged (erratic)) still count, since that flag describes sample dispersion rather than directional certainty. The Δ mean % cell is colored accordingly: 🟢 = improvement, 🔴 = regression, ⚪ = neutral. Reduction in CPU or memory is an improvement; reduction in ingress throughput is a regression. Experiments tagged (no analysis) show ⚠️ n/a: SMP ran them but produced no analysis, usually because a replicate failed and exhausted its retries. Check the SMP report for that experiment's replicate failures.

@webern
webern marked this pull request as ready for review September 30, 2026 11:50
@webern
webern requested a review from a team as a code owner September 30, 2026 11:50

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

The translator snapshots the live registry per ResourceSpans batch, while APM stats refresh peer-tag keys per event buffer; the completed static review found no concrete regression in these replacement boundaries.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 028e9d9 · @DataDog review to ask questions

Base automatically changed from m/otlp-rcclient to main September 30, 2026 14:00

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: FAIL

An Unimplemented response can leave previously published remote configuration active indefinitely after the Agent disables Remote Configuration.

Open Bits AI session

🤖 Bits Code Review · Commit 3a398d6 · @DataDog review to ask questions


Findings that could not be posted inline

lib/datadog-agent/remote-config/src/worker.rs:152

P2 Withdraw assignments when Remote Configuration is disabled

After configurations have been published, an Unimplemented response—such as when Remote Configuration is disabled at runtime—only changes the retry schedule. The repository retains its files and subscribers continue using the last snapshot indefinitely instead of receiving an empty assignment, leaving disabled configuration active.

Was this helpful? React 👍 or 👎
🤖 Bits Code Review · @DataDog review to ask questions · Open Bits AI session

@webern

webern commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

AI

Re the Bits finding "Withdraw assignments when Remote Configuration is disabled" (lib/datadog-agent/remote-config/src/worker.rs:152): I think this is intended, and it matches the Agent's own client. On an update error, including Unimplemented, the Go client keeps its configurations and only tells listeners that connectivity changed (pkg/config/remote/client/client.go:468-479 at 17ecddf4e3e); it withdraws nothing. The trace-agent therefore keeps its last applied rates too. Keeping the last accepted snapshot when the Agent stops serving is the client's last-known-good rule. The client only withdraws configurations when the Agent reports its cache as expired, which is the case the Agent itself withdraws on. The client is also no longer part of this PR's diff: it merged in #2713.

Comment thread lib/saluki-components/src/common/otlp/semantics/accessor.rs Outdated
@webern
webern marked this pull request as draft October 1, 2026 12:36
@webern
webern force-pushed the m/otlp-semreg branch 2 times, most recently from 53d27cb to dc703fe Compare October 1, 2026 17:31
@webern webern changed the title feat(otlp): make the semantic registry replaceable at runtime feat(otlp): select the semantic registry per component Oct 1, 2026
@webern webern changed the title feat(otlp): select the semantic registry per component feat(otlp): semantic registry provider for embedded or live registries Oct 2, 2026
Replace direct reads of the global semantic registry with a
SemanticRegistryProvider passed through component builders. Each
provider reads the last accepted subscription snapshot and falls back to
the embedded registry until an update arrives. The default provider
holds an inert subscription.

The OTLP translator pins one registry per ResourceSpans. APM stats reads
one per event buffer and rebuilds peer tag keys when the registry
fingerprint changes. Components select their own snapshots without a
background applier.

Parse remote registries permissively, skipping unsupported concepts and
fallback entries while keeping supported mappings. Keep embedded parsing
strict. Return parse errors without payload contents, and report skipped
entries to the caller.

Use a fingerprint of the payload bytes to detect changes. Keep
metadata.content_hash as a label because changed mappings can carry the
same declared hash.

Add tests for parsing, embedded fallback, independent subscriptions,
pinned snapshots, translation and peer tag refresh through the component
builders.
Comment thread lib/saluki-components/src/common/otlp/semantics/provider.rs Outdated
Comment thread lib/saluki-components/src/common/otlp/semantics/provider.rs Outdated
Comment thread lib/saluki-components/src/common/otlp/semantics/accessor.rs Outdated
Clarify embedded-only operation when Remote Configuration is disabled,
initial fallback before an accepted update, and stable batch snapshots.
Explain registry parsing and peer tag refreshes for unfamiliar readers.
Limit changes to comment blocks already added or changed by this PR.
@webern
webern marked this pull request as ready for review October 2, 2026 13:43

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

Registry snapshots remain consistent within each trace batch, while APM stats refreshes peer-tag keys between input buffers; no concrete regression was identified.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit b286fb2 · @DataDog review to ask questions

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/components Sources, transforms, and destinations. changelog/no-changelog No changelog entry needed decoders/otlp source/otlp OTLP source. transform/apm-stats APM Stats transform.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Traces(otlp+dd): Make the semantic registry replaceable at runtime

2 participants