Skip to content

chore(core): unify how we define identifiers for subsystems/components - #2029

Merged
tobz merged 5 commits into
mainfrom
tobz/component-identity-unified
Jul 8, 2026
Merged

chore(core): unify how we define identifiers for subsystems/components#2029
tobz merged 5 commits into
mainfrom
tobz/component-identity-unified

Conversation

@tobz

@tobz tobz commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

This PR attempts to lay the groundwork to unify how we define the unique identifier for a given component/subsystem within the entire process to drive towards avoiding mismatched identifiers for the same component between different registries.

In Saluki, we have a number of "components" -- topology components, yes, but also more "generic" components like standalone subsystems... think the environment provider, specific background tasks, and so on -- that all end up using some sort of unique name within the process to identify themselves for the purpose of health checking or resource accounting and so on. Ideally, these identifiers would always be the same across every possible registry that wants such an identifier for a single component... but currently that's not the case. This is a problem because it makes it hard to aggregate things across registries: imagine trying to get the live usage of a component (resource accounting) while figuring out how much of its memory bounds that usage represents. Without a stable/consistent identifier between both registries, it becomes hard to do and, ultimately, ends up becoming very fragile.

This PR introduces the concept of SubsystemIdentifier (don't loveeee the name, might change it) which is meant to represent this shared identifier that a component uses across various registries. It's designed to be built in a hierarchical fashion, so that we define base prefixes that can then be used consistently to build the version of the identifier for each child component and so on.

With this, every "component" -- a specific metadata collector in the environment provider, the Aggregate transform in the metrics portion of the topology, etc -- gets a unique name/identifier that is used everywhere: memory bounds, resource accounting, health registry, supervisor worker names, and so on. Most importantly, we accomplish this through a specialized type that ensures the name/identifier is sanitized and normalized, and acts as a way to gate the usages of this name/identifier by requiring the new type at the API boundary instead of any old plain string

This PR doesn't move everything over to using SubsystemIdentifier just yet, only the topology components. The rest will happen in a follow-up PR. However, we did a large amount of simplification related to ComponentContext: as it now carries the identifier from the topology, it's somewhat easier to generate it once and use it in places that previously used ComponentId, which was the case in TopologyBlueprint, BuiltTopology, and ComponentInterconnects, which have now all been simplified to taken advantage of these changes. Like SubsystemIdentifier, I have more changes planned in this area in the future.

Change Type

  • Bug fix
  • New feature
  • Non-functional (chore, refactoring, docs)
  • Performance

How did you test this PR?

  • Existing unit/integration/correctness tests.
  • (manual) Ensure there are no straggler references to hand-written topology component identifiers.
  • (manual) Ensure resource accounting data is still present for topology components and non-topology components.

References

DADP-2

@pr-commenter

pr-commenter Bot commented Jul 6, 2026

Copy link
Copy Markdown

Binary Size Analysis (Agent Data Plane)

Baseline: 4c0e98b · Comparison: d4129ca · diff
Analysis Configuration: stripped binaries · Pass/Fail Threshold: +5%
Sizes: 41.59 MiB (baseline) vs 41.66 MiB (comparison)
Size Change: +78.23 KiB (+0.18%)

✅ Binary size difference within threshold

Changes by Module
Module File Size Symbols
tonic +26.33 KiB 75
http_body_util -20.67 KiB 42
hashbrown +17.86 KiB 186
tokio +15.12 KiB 345
piecemeal -13.04 KiB 25
otlp_protos::otlp_include::opentelemetry -11.99 KiB 14
smallvec +11.68 KiB 24
serde_core +11.66 KiB 81
datadog_protos::trace_piecemeal_include::datadog +11.19 KiB 16
saluki_components::common::datadog -10.86 KiB 48
anon.9e7fe21941eb14193087d4323bb508b7.51.llvm.18246841136514536931 -10.51 KiB 1
anon.9d74b0aa49281f31c2e13ca4fdb52337.65.llvm.9026379143731643429 +10.33 KiB 1
tokio_rustls -9.18 KiB 4
core +8.97 KiB 2266
rustls +8.71 KiB 11
[sections] +7.63 KiB 7
saluki_core::topology::blueprint +7.42 KiB 37
std -7.38 KiB 92
quick_cache -7.19 KiB 19
tower -7.10 KiB 57
Detailed Symbol Changes
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.7% +63.5Ki  +0.8% +56.8Ki    [12365 Others]
  [NEW] +56.7Ki  [NEW] +56.5Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h14ea2d62dcf96940
  [NEW] +27.2Ki  [NEW] +27.1Ki    saluki_core::topology::built::BuiltTopology::spawn_inner::_{{closure}}::h0d4bfa1c2bddc708
  [NEW] +25.9Ki  [NEW] +25.8Ki    saluki_core::topology::blueprint::TopologyBuildState::build::_{{closure}}::hb65140384bee32f9
  [NEW] +22.9Ki  [NEW] +22.7Ki    _<agent_data_plane::components::host_tags::HostTagsConfiguration as saluki_core::components::transforms::builder::SynchronousTransformBuilder>::build::_{{closure}}::h96fd3c0ffa4e0596
  [NEW] +21.8Ki  [NEW] +21.6Ki    _<saluki_components::sources::dogstatsd::DogStatsDConfiguration as saluki_core::components::sources::builder::SourceBuilder>::build::_{{closure}}::h31b1e6024d10a067
  [NEW] +19.1Ki  [NEW] +19.0Ki    saluki_core::topology::built::ComponentInterconnects::from_graph::hcf7c6c14f657d489
  +292% +12.7Ki  +297% +12.7Ki    prost::message::Message::encode::h4efca10136c534fb
  [NEW] +10.8Ki  [NEW] +10.7Ki    _<tracing::instrument::Instrumented<T> as core::future::future::Future>::poll::h7d0bcbca7ea71a41
  [NEW] +10.3Ki  [NEW]     +79    anon.9d74b0aa49281f31c2e13ca4fdb52337.65.llvm.9026379143731643429
  +185% +10.3Ki  +190% +10.3Ki    saluki_components::transforms::trace_obfuscation::sql::obfuscate_sql_string::hdb39a0c7eb881eec
  [DEL] -10.4Ki  [DEL] -10.3Ki    _<tracing::instrument::Instrumented<T> as core::future::future::Future>::poll::hd591eb081a589172
  [DEL] -10.5Ki  [DEL]     -79    anon.9e7fe21941eb14193087d4323bb508b7.51.llvm.18246841136514536931
  [DEL] -10.8Ki  [DEL] -10.7Ki    saluki_components::sources::otlp::logs::transform::transform_log_record::hfdd9b0df97194e85
  [DEL] -11.4Ki  [DEL] -11.2Ki    _<saluki_components::forwarders::datadog::DatadogForwarderConfiguration as saluki_core::components::forwarders::builder::ForwarderBuilder>::build::_{{closure}}::h68f4c84d42f9ef20
  [DEL] -16.7Ki  [DEL] -16.6Ki    saluki_core::topology::built::ComponentInterconnects::from_graph::h7d7d410df94f8613
  [DEL] -18.5Ki  [DEL] -18.3Ki    saluki_core::topology::blueprint::TopologyBuildState::build::_{{closure}}::hf42a2e2f3b7ef6e1
  [DEL] -21.8Ki  [DEL] -21.6Ki    _<saluki_components::sources::dogstatsd::DogStatsDConfiguration as saluki_core::components::sources::builder::SourceBuilder>::build::_{{closure}}::h5d4b7e2a823fc59a
  [DEL] -22.4Ki  [DEL] -22.2Ki    _<agent_data_plane::components::host_tags::HostTagsConfiguration as saluki_core::components::transforms::builder::SynchronousTransformBuilder>::build::_{{closure}}::h64642649d7aa39ca
  [DEL] -24.4Ki  [DEL] -24.3Ki    saluki_core::topology::built::BuiltTopology::spawn_inner::_{{closure}}::h04838737207cae8e
  [DEL] -56.2Ki  [DEL] -56.0Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h71f8185a6fdfc24a
  +0.2% +78.2Ki  +0.2% +72.0Ki    TOTAL

@pr-commenter

pr-commenter Bot commented Jul 6, 2026

Copy link
Copy Markdown

Regression Detector (Agent Data Plane)

Run ID: c271404f-dbb0-4c5d-b3b2-ee9266d97eb2
Baseline: 4c0e98b4 · Comparison: d4129ca7 · 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_medium memory ⚪ +0.58 metrics profiles logs
quality_gates_rss_dsd_low memory ⚪ +0.20 metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory ⚪ +0.04 metrics profiles logs
quality_gates_rss_dsd_heavy memory ⚪ +0.01 metrics profiles logs
quality_gates_rss_idle memory ⚪ -0.26 metrics profiles logs
Bounds Checks: ✅ Passed (5)
experiment check replicates observed links
quality_gates_rss_dsd_heavy memory_usage 10/10 ✅ 131 MiB ≤ 140 MiB metrics profiles logs
quality_gates_rss_dsd_low memory_usage 10/10 ✅ 43.1 MiB ≤ 50 MiB metrics profiles logs
quality_gates_rss_dsd_medium memory_usage 10/10 ✅ 65.2 MiB ≤ 75 MiB metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 ✅ 192 MiB ≤ 200 MiB metrics profiles logs
quality_gates_rss_idle memory_usage 10/10 ✅ 29.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.

@tobz tobz added the type/chore Updates to dependencies or general "administrative" tasks necessary to maintain the codebase/repo. label Jul 7, 2026
@tobz
tobz marked this pull request as ready for review July 7, 2026 14:22
@tobz
tobz requested a review from a team as a code owner July 7, 2026 14:22
Copilot AI review requested due to automatic review settings July 7, 2026 14:22
@tobz
tobz force-pushed the tobz/component-supervision-trees branch from d16e35a to 3cad967 Compare July 7, 2026 14:23
@tobz
tobz force-pushed the tobz/component-identity-unified branch from 2ee39ae to 65f7e30 Compare July 7, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR lays groundwork for a single canonical identifier for “components/subsystems” so health registration, resource-accounting paths, and supervisor/process-tree names stay byte-identical (starting with topology components).

Changes:

  • Introduces SubsystemIdentifier and a shared topology_root() to derive canonical dotted identifiers (topology.<name>...) across subsystems.
  • Updates topology component contexts, health registration, supervision naming, and resource-accounting registry paths to use the canonical identity.
  • Adjusts runtime process-name sanitization to preserve dotted scoping and updates tests/usages across crates accordingly.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/saluki-core/src/topology/mod.rs Defines topology_root() returning a SubsystemIdentifier as the single topology identity root.
lib/saluki-core/src/topology/interconnect/consumer.rs Updates tests to build ComponentContext with an explicit topology name.
lib/saluki-core/src/topology/ids.rs Threads topology name into TypedComponentIdComponentContext creation.
lib/saluki-core/src/topology/context.rs Stores topology name in TopologyContext for consistent context construction.
lib/saluki-core/src/topology/component_worker.rs Removes explicit per-worker resource tracking, relying on supervisor/process naming.
lib/saluki-core/src/topology/built.rs Aligns supervisor names + health registration with canonical identity; passes topology name into interconnect builder.
lib/saluki-core/src/topology/blueprint.rs Aligns resource-accounting registry structure with canonical identity; adds invariant test.
lib/saluki-core/src/support.rs Adds SubsystemIdentifier type for sanitized, hierarchical identifiers.
lib/saluki-core/src/runtime/process.rs Preserves dotted scoping in Name sanitization; changes name storage to MetaString.
lib/saluki-core/src/runtime/mod.rs Re-exports get_sanitized_name (and Name for tests).
lib/saluki-core/src/lib.rs Exposes new support module.
lib/saluki-core/src/components/mod.rs Extends ComponentContext to include topology root and expose canonical identity().
lib/saluki-components/src/transforms/dogstatsd_mapper/mod.rs Updates tests for new ComponentContext constructors.
lib/saluki-components/src/transforms/aggregate/mod.rs Updates tests for new ComponentContext constructors.
lib/saluki-components/src/sources/dogstatsd/mod.rs Updates tests for new ComponentContext constructors.
lib/saluki-components/src/sources/dogstatsd/metrics.rs Updates tests for new ComponentContext constructors.
lib/saluki-components/src/common/otlp/mod.rs Updates tests for new ComponentContext constructors.
lib/saluki-components/src/common/datadog/io.rs Updates tests for new ComponentContext constructors.
lib/resource-accounting/src/registry.rs Adds ComponentRegistry::full_name() to support asserting canonical naming.
bin/agent-data-plane/src/components/ottl_transform_processor/mod.rs Updates tests for new ComponentContext constructors.
bin/agent-data-plane/src/components/ottl_filter_processor/mod.rs Updates tests for new ComponentContext constructors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +295 to 319
/// Sanitizes a (possibly dotted) name into a dotted string of process-safe segments.
///
/// Periods are treated as segment separators: the input is split on `.`, each segment is sanitized via
/// [`get_sanitized_name`], and the results are rejoined with `.`. Segments that are empty (or sanitize to empty) are
/// dropped. Returns `None` if nothing remains.
fn sanitize_scoped_name(name: &str) -> Option<MetaString> {
let mut rendered = String::new();
for segment in name.split('.') {
let sanitized = get_sanitized_name(segment);
if sanitized.is_empty() {
continue;
}

if !rendered.is_empty() {
rendered.push('.');
}
rendered.push_str(&sanitized);
}

if rendered.is_empty() {
None
} else {
Some(rendered.into())
}
}
Comment on lines +32 to +48
pub fn from_segments<I, S>(segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Self {
segments: segments.into_iter().map(|s| get_sanitized_name(s.as_ref())).collect(),
}
}

/// Consumes the identifier and returns a new one with the given segment appended.
///
/// The segment is sanitized/normalized first.
pub fn child<S: AsRef<str>>(mut self, segment: S) -> Self {
self.segments.push(get_sanitized_name(segment.as_ref()));
self
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ee39ae4fd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

self.topology_root
.clone()
.child(self.component_type.as_category())
.child(&*self.component_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve uniqueness when canonicalizing component IDs

Because ComponentId accepts both hyphens and underscores, routing the raw ID through SubsystemIdentifier::child makes distinct valid same-type components such as foo-bar and foo_bar both render as foo_bar. In a topology that contains both IDs, the second component collides in health registration (and shares the same resource/process identity), so startup fails or accounting merges unrelated components. Consider rejecting IDs that collide after normalization or encoding the raw ID losslessly instead of sanitizing it in-place.

Useful? React with 👍 / 👎.

@datadog-prod-us1-4

This comment has been minimized.

Base automatically changed from tobz/component-supervision-trees to main July 7, 2026 19:57
@tobz
tobz force-pushed the tobz/component-identity-unified branch from 3ba932e to 6f97b88 Compare July 7, 2026 20:03
@tobz
tobz force-pushed the tobz/component-identity-unified branch from 2096156 to d4129ca Compare July 8, 2026 18:58
@aqian01

aqian01 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Similar to the codex comment

I think two different topology names can turn into the same internal ID,
Example:

  • primary-a becomes topology.primary_a
  • primary_a also becomes topology.primary_a

Had codex make a quick test to check for this

#[cfg(test)]
  61 +mod tests {
  62 +    use super::topology_identifier;
  63 +
  64 +    #[test]
  65 +    fn topology_identifier_keeps_distinct_raw_names_distinct() {
  66 +        let hyphenated = topology_identifier("primary-a");
  67 +        let underscored = topology_identifier("primary_a");
  68 +
  69 +        assert_ne!(
  70 +            hyphenated.to_string(),
  71 +            underscored.to_string(),
  72 +            "distinct topology names must not collapse to the same identifier"
  73 +        );
  74 +    }
  75 +}

@tobz

tobz commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Similar to the codex comment

I think two different topology names can turn into the same internal ID, Example:

@aqian01 Mmmm, yes. That's suboptimal, although luckily not an issue right now since we never create more than one topology at a time.

I'll approach this as follow-up PR since we'll need to make TopologyBlueprint::new fallible so that we can do validation on the name... or maybe I can figure out a better solution to this problem that doesn't involve having to always validate at every individual point. 😅

@tobz
tobz merged commit dbece44 into main Jul 8, 2026
83 checks passed
@tobz
tobz deleted the tobz/component-identity-unified branch July 8, 2026 19:58
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. area/core Core functionality, event model, etc. area/memory Memory bounds and memory management. source/dogstatsd DogStatsD source. transform/aggregate Aggregate transform. transform/dogstatsd-mapper DogStatsD Mapper synchronous transform. type/chore Updates to dependencies or general "administrative" tasks necessary to maintain the codebase/repo.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants