Skip to content

chore(agent-data-plane): register health API dynamically#1259

Draft
tobz wants to merge 6 commits intotobz/dynamic-api-endpoint-routesfrom
tobz/health-registry-runtime-refactor
Draft

chore(agent-data-plane): register health API dynamically#1259
tobz wants to merge 6 commits intotobz/dynamic-api-endpoint-routesfrom
tobz/health-registry-runtime-refactor

Conversation

@tobz
Copy link
Copy Markdown
Member

@tobz tobz commented Mar 25, 2026

Summary

Change Type

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

How did you test this PR?

References

Copy link
Copy Markdown

Copilot AI left a comment

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 moves the health registry functionality into saluki-core and updates the agent data plane to expose the health API via the dynamic API routing mechanism (dataspace-published routes).

Changes:

  • Removed the standalone saluki-health crate and rehomed health types under saluki_core::health.
  • Added HealthRegistryWorker to run the health registry loop and dynamically publish the health HTTP routes.
  • Switched the agent-data-plane unprivileged API server to DynamicAPIBuilder and updated dependencies accordingly (including enabling tonic’s router feature in saluki-api).

Reviewed changes

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

Show a summary per file
File Description
lib/saluki-core/src/health/worker.rs New worker that runs the health registry and publishes health routes dynamically via DataspaceRegistry.
bin/agent-data-plane/src/internal/control_plane.rs Replaces the unprivileged API worker with DynamicAPIBuilder and wires in the new health worker.
lib/saluki-core/src/health/mod.rs Exposes health module + worker API; removes direct spawn API and adjusts tests/visibility.
lib/saluki-api/Cargo.toml Enables tonic router feature to support route-to-axum conversion used by dynamic APIs.
lib/saluki-env/, bin/agent-data-plane/, lib/saluki-core/** Updates imports/dependencies from saluki_health::* to saluki_core::health::*.
Cargo.toml, Cargo.lock Removes saluki-health from workspace/deps and updates lockfile.

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

Comment on lines +39 to +46
let dataspace_registry = DataspaceRegistry::global();
dataspace_registry.assert(health_routes, Handle::current_process());

runner.run(process_shutdown).await;

// TODO: OK, yeah, this is clunky. We should just make it a drop guard.
dataspace_registry.retract::<DynamicRoute>(Handle::current_process());

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

DynamicRoute is asserted but only retracted on the normal completion path. If runner.run(process_shutdown).await panics (or this future is cancelled), the route can remain published in the DataspaceRegistry, leaving the dynamic API serving a stale health handler even though the registry loop is no longer running. Consider using an RAII/drop guard (or scopeguard) so the retraction happens on all exits, including unwinding/cancellation.

Suggested change
let dataspace_registry = DataspaceRegistry::global();
dataspace_registry.assert(health_routes, Handle::current_process());
runner.run(process_shutdown).await;
// TODO: OK, yeah, this is clunky. We should just make it a drop guard.
dataspace_registry.retract::<DynamicRoute>(Handle::current_process());
//
// Use an RAII guard so the route is retracted even if this future is cancelled
// or unwinds due to a panic while awaiting `runner.run`.
struct RouteRetractionGuard<'a> {
dataspace_registry: &'a DataspaceRegistry,
handle: Handle,
}
impl<'a> Drop for RouteRetractionGuard<'a> {
fn drop(&mut self) {
self.dataspace_registry.retract::<DynamicRoute>(self.handle);
}
}
let dataspace_registry = DataspaceRegistry::global();
dataspace_registry.assert(health_routes, Handle::current_process());
let _route_guard = RouteRetractionGuard {
dataspace_registry: &dataspace_registry,
handle: Handle::current_process(),
};
runner.run(process_shutdown).await;

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +46
let health_routes = DynamicRoute::http(EndpointType::Unprivileged, self.health_registry.api_handler());

Ok(Box::pin(async move {
// Publish our health endpoint route and retract it after the registry task completes,
// regardless of whether it failed or not.
let dataspace_registry = DataspaceRegistry::global();
dataspace_registry.assert(health_routes, Handle::current_process());

runner.run(process_shutdown).await;

// TODO: OK, yeah, this is clunky. We should just make it a drop guard.
dataspace_registry.retract::<DynamicRoute>(Handle::current_process());

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Using Handle::current_process() for the DynamicRoute assertion means this process can only safely publish a single DynamicRoute value at a time (same type+handle key). If any other dynamic route is asserted under the same process handle later, it will overwrite this one and retract::<DynamicRoute>(Handle::current_process()) would retract whichever route is currently stored. Consider using a dedicated Handle::new_global() stored in the worker so the health route has a stable, collision-free key.

Copilot uses AI. Check for mistakes.
@pr-commenter
Copy link
Copy Markdown

pr-commenter bot commented Mar 25, 2026

Binary Size Analysis (Agent Data Plane)

Target: 5316221 (baseline) vs 7b7c39e (comparison) diff
Analysis Type: Stripped binaries (debug symbols excluded)
Baseline Size: 26.48 MiB
Comparison Size: 27.06 MiB
Size Change: +597.06 KiB (+2.20%)
Pass/Fail Threshold: +5%
Result: PASSED ✅

Changes by Module

Module File Size Symbols
h2 +142.82 KiB 352
core +84.93 KiB 9230
hyper +70.83 KiB 346
tokio +68.94 KiB 2349
hyper_util +59.19 KiB 87
saluki_app::dynamic_api::DynamicAPIBuilder +46.26 KiB 6
[sections] +46.26 KiB 7
saluki_health -36.51 KiB 32
agent_data_plane::internal::control_plane -30.67 KiB 22
saluki_core::health::worker +27.96 KiB 4
saluki_io::net::server +24.65 KiB 39
saluki_app::dynamic_api::rebuild_router +10.60 KiB 5
http +10.59 KiB 329
saluki_core::health::Runner +9.15 KiB 19
tokio_rustls +8.86 KiB 29
saluki_common::task::instrument +8.53 KiB 87
tracing_core +7.25 KiB 489
rustls +6.48 KiB 35
alloc +5.25 KiB 822
saluki_core::health::Health +5.23 KiB 2

Detailed Symbol Changes

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  [NEW] +1.79Mi  [NEW] +1.79Mi    std::thread::local::LocalKey<T>::with::hc1e1f1345a93b9e0
  +4.6%  +596Ki  +4.8%  +522Ki    [23146 Others]
  [NEW]  +119Ki  [NEW]  +119Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h2b59fb7f941acebf
  [NEW] +64.2Ki  [NEW] +64.1Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::ha2646192447fe834
  [NEW] +62.1Ki  [NEW] +62.0Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::he12c422fd3b6039e
  [NEW] +58.8Ki  [NEW] +58.6Ki    _<agent_data_plane::internal::control_plane::PrivilegedApiWorker as saluki_core::runtime::supervisor::Supervisable>::initialize::_{{closure}}::h96c20cd7d9569a03
  [NEW] +49.6Ki  [NEW] +49.4Ki    saluki_app::bootstrap::AppBootstrapper::bootstrap::_{{closure}}::h80b1643494b8efe1
  [NEW] +46.4Ki  [NEW] +46.3Ki    h2::proto::connection::Connection<T,P,B>::poll::hfd143450fe97d30f
  [NEW] +46.2Ki  [NEW] +46.0Ki    _<saluki_components::destinations::prometheus::Prometheus as saluki_core::components::destinations::Destination>::run::_{{closure}}::haa7e79f2a1f7706d
  [NEW] +46.1Ki  [NEW] +45.9Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h2fbbc5d173683ac9
  [NEW] +44.1Ki  [NEW] +43.9Ki    saluki_env::workload::providers::remote_agent::RemoteAgentWorkloadProvider::from_configuration::_{{closure}}::h9faa80f7dbf804c2
  [DEL] -44.1Ki  [DEL] -43.9Ki    saluki_env::workload::providers::remote_agent::RemoteAgentWorkloadProvider::from_configuration::_{{closure}}::hfc80d52378eff90b
  [DEL] -46.2Ki  [DEL] -46.0Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h9e6a07604104748f
  [DEL] -46.2Ki  [DEL] -46.0Ki    _<saluki_components::destinations::prometheus::Prometheus as saluki_core::components::destinations::Destination>::run::_{{closure}}::h804b0075faebe88b
  [DEL] -46.4Ki  [DEL] -46.3Ki    h2::proto::connection::Connection<T,P,B>::poll::hded4fb5f8a002638
  [DEL] -49.6Ki  [DEL] -49.4Ki    saluki_app::bootstrap::AppBootstrapper::bootstrap::_{{closure}}::hd83d25f4c7e8a1ae
  [DEL] -58.7Ki  [DEL] -58.4Ki    _<agent_data_plane::internal::control_plane::PrivilegedApiWorker as saluki_core::runtime::supervisor::Supervisable>::initialize::_{{closure}}::hf1e132bb3e8bfeaf
  [DEL] -61.9Ki  [DEL] -61.8Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::hbff4cb15be99fca3
  [DEL] -64.2Ki  [DEL] -64.1Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h7847418cb5d4cb46
  [DEL]  -119Ki  [DEL]  -119Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h5c132aa500e2a433
  [DEL] -1.79Mi  [DEL] -1.79Mi    std::thread::local::LocalKey<T>::with::hddbf5b47575f6048
  +2.2%  +597Ki  +2.2%  +522Ki    TOTAL

@pr-commenter
Copy link
Copy Markdown

pr-commenter bot commented Mar 25, 2026

Regression Detector (Agent Data Plane)

Regression Detector Results

Run ID: 841539ae-6e60-4510-b0fa-90c38d7032ec

Baseline: 5316221
Comparison: 7b7c39e
Diff

Optimization Goals: ✅ No significant changes detected

Experiments ignored for regressions

Regressions in experiments with settings containing erratic: true are ignored.

perf experiment goal Δ mean % Δ mean % CI trials links
otlp_ingest_logs_5mb_cpu % cpu utilization +1.13 [-3.86, +6.11] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_throughput ingress throughput +0.00 [-0.12, +0.13] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_memory memory utilization -6.14 [-6.78, -5.50] 1 (metrics) (profiles) (logs)

Fine details of change detection per experiment

perf experiment goal Δ mean % Δ mean % CI trials links
dsd_uds_500mb_3k_contexts_throughput ingress throughput +4.78 [+4.65, +4.92] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_cpu % cpu utilization +4.28 [+1.69, +6.87] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_memory memory utilization +2.09 [+1.92, +2.27] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_low memory utilization +1.90 [+1.71, +2.09] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_memory memory utilization +1.71 [+1.53, +1.89] 1 (metrics) (profiles) (logs)
dsd_uds_500mb_3k_contexts_cpu % cpu utilization +1.67 [+0.21, +3.13] 1 (metrics) (profiles) (logs)
quality_gates_rss_idle memory utilization +1.50 [+1.47, +1.53] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_memory memory utilization +1.37 [+1.12, +1.62] 1 (metrics) (profiles) (logs)
dsd_uds_512kb_3k_contexts_memory memory utilization +1.32 [+1.15, +1.50] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_memory memory utilization +1.28 [+1.10, +1.46] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_cpu % cpu utilization +1.13 [-3.86, +6.11] 1 (metrics) (profiles) (logs)
dsd_uds_500mb_3k_contexts_memory memory utilization +1.12 [+0.95, +1.28] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_memory memory utilization +0.88 [+0.54, +1.22] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_memory memory utilization +0.88 [+0.63, +1.12] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_cpu % cpu utilization +0.69 [-6.14, +7.52] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_medium memory utilization +0.68 [+0.49, +0.87] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_ultraheavy memory utilization +0.31 [+0.18, +0.44] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_heavy memory utilization +0.12 [-0.02, +0.27] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_throughput ingress throughput +0.01 [-0.03, +0.04] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_throughput ingress throughput +0.00 [-0.12, +0.13] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_throughput ingress throughput +0.00 [-0.05, +0.06] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_throughput ingress throughput +0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_throughput ingress throughput -0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_throughput ingress throughput -0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
dsd_uds_512kb_3k_contexts_throughput ingress throughput -0.00 [-0.05, +0.05] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_throughput ingress throughput -0.01 [-0.15, +0.14] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_throughput ingress throughput -0.01 [-0.13, +0.11] 1 (metrics) (profiles) (logs)
dsd_uds_512kb_3k_contexts_cpu % cpu utilization -0.38 [-57.48, +56.72] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_cpu % cpu utilization -0.51 [-2.72, +1.71] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_cpu % cpu utilization -1.41 [-3.63, +0.81] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_cpu % cpu utilization -1.44 [-32.28, +29.40] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_memory memory utilization -2.08 [-2.27, -1.88] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_cpu % cpu utilization -2.26 [-8.22, +3.70] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_memory memory utilization -6.14 [-6.78, -5.50] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_cpu % cpu utilization -8.35 [-60.09, +43.39] 1 (metrics) (profiles) (logs)

Bounds Checks: ✅ Passed

perf experiment bounds_check_name replicates_passed observed_value links
quality_gates_rss_dsd_heavy memory_usage 10/10 116.55MiB ≤ 140MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_low memory_usage 10/10 34.77MiB ≤ 50MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_medium memory_usage 10/10 54.49MiB ≤ 75MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 170.96MiB ≤ 200MiB (metrics) (profiles) (logs)
quality_gates_rss_idle memory_usage 10/10 21.67MiB ≤ 40MiB (metrics) (profiles) (logs)

Explanation

Confidence level: 90.00%
Effect size tolerance: |Δ mean %| ≥ 5.00%

Performance changes are noted in the perf column of each table:

  • ✅ = significantly better comparison variant performance
  • ❌ = significantly worse comparison variant performance
  • ➖ = no significant change in performance

A regression test is an A/B test of target performance in a repeatable rig, where "performance" is measured as "comparison variant minus baseline variant" for an optimization goal (e.g., ingress throughput). Due to intrinsic variability in measuring that goal, we can only estimate its mean value for each experiment; we report uncertainty in that value as a 90.00% confidence interval denoted "Δ mean % CI".

For each experiment, we decide whether a change in performance is a "regression" -- a change worth investigating further -- if all of the following criteria are true:

  1. Its estimated |Δ mean %| ≥ 5.00%, indicating the change is big enough to merit a closer look.

  2. Its 90.00% confidence interval "Δ mean % CI" does not contain zero, indicating that if our statistical model is accurate, there is at least a 90.00% chance there is a difference in performance between baseline and comparison variants.

  3. Its configuration does not mark it "erratic".

@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 372618f to af14e67 Compare March 27, 2026 19:35
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch 2 times, most recently from 1534b83 to 59dd8ff Compare March 30, 2026 17:46
Copilot AI review requested due to automatic review settings March 30, 2026 17:46
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from af14e67 to 04f70b9 Compare March 30, 2026 17:46
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from 59dd8ff to dfa31c4 Compare April 2, 2026 17:53
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch 2 times, most recently from 4241ed5 to 78737f0 Compare April 3, 2026 13:41
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from dfa31c4 to 03bc6f5 Compare April 3, 2026 13:41
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 78737f0 to 2b2e54c Compare April 3, 2026 18:31
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch 2 times, most recently from de87cba to 7182e6a Compare April 3, 2026 18:41
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 2b2e54c to 1030747 Compare April 3, 2026 18:41
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from 7182e6a to bb8d32e Compare April 6, 2026 17:23
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch 2 times, most recently from 7e9b19a to 2a76a50 Compare April 7, 2026 01:00
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from bb8d32e to bf20e01 Compare April 7, 2026 01:00
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 2a76a50 to 4324f1c Compare April 7, 2026 17:49
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from bf20e01 to 79fe48c Compare April 7, 2026 17:49
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 4324f1c to e7a622f Compare April 9, 2026 13:14
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch 2 times, most recently from 8963add to e3fbd14 Compare April 9, 2026 20:28
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch 2 times, most recently from 5c33d3f to c9a04bc Compare April 9, 2026 22:57
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from e3fbd14 to 5bc9b1c Compare April 9, 2026 22:57
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch 2 times, most recently from 8087921 to 37a1276 Compare April 10, 2026 12:40
@tobz tobz force-pushed the tobz/dynamic-api-endpoint-routes branch from fc6377a to b8b6053 Compare April 10, 2026 16:04
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 37a1276 to ad590c2 Compare April 10, 2026 16:04
@dd-octo-sts dd-octo-sts bot added the area/observability Internal observability of ADP and Saluki. label Apr 10, 2026
@tobz tobz force-pushed the tobz/health-registry-runtime-refactor branch from 6e6af23 to 7b7c39e Compare April 10, 2026 19:33
@dd-octo-sts dd-octo-sts bot removed the area/observability Internal observability of ADP and Saluki. label Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Configuration. area/core Core functionality, event model, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants