Skip to content

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

Draft
tobz wants to merge 2 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 2 commits intotobz/dynamic-api-endpoint-routesfrom
tobz/health-registry-runtime-refactor

Conversation

@tobz
Copy link
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

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

pr-commenter bot commented Mar 25, 2026

Binary Size Analysis (Agent Data Plane)

Target: 568e97e (baseline) vs 372618f (comparison) diff
Analysis Type: Stripped binaries (debug symbols excluded)
Baseline Size: 26.17 MiB
Comparison Size: 26.84 MiB
Size Change: +688.36 KiB (+2.57%)
Pass/Fail Threshold: +5%
Result: PASSED ✅

Changes by Module

Module File Size Symbols
h2 +142.85 KiB 355
core +133.06 KiB 8969
hyper +70.77 KiB 347
saluki_core::runtime::supervisor +69.31 KiB 66
tokio +59.85 KiB 2352
hyper_util +59.22 KiB 80
[sections] +49.96 KiB 9
agent_data_plane::internal::control_plane -42.83 KiB 16
saluki_app::dynamic_api::DynamicAPIBuilder +37.41 KiB 6
saluki_health -30.01 KiB 24
saluki_core::health::worker +25.30 KiB 4
saluki_io::net::server +25.02 KiB 39
agent_data_plane::internal::initialize_and_launch_runtime -18.37 KiB 1
agent_data_plane::internal::create_internal_supervisor +16.49 KiB 1
saluki_app::memory::MemoryBoundsConfiguration -13.70 KiB 5
std -10.77 KiB 211
http +10.59 KiB 339
tracing_core +9.24 KiB 475
tokio_rustls +8.86 KiB 29
saluki_common::task::instrument +8.57 KiB 91

Detailed Symbol Changes

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  [NEW] +1.79Mi  [NEW] +1.79Mi    std::thread::local::LocalKey<T>::with::hb17e908acf04879d
  +5.6%  +710Ki  +5.8%  +610Ki    [22642 Others]
  [NEW]  +114Ki  [NEW]  +114Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::ha8f300467dd1d69a
  [NEW] +64.1Ki  [NEW] +64.0Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::hb106ea2c152b09e3
  [NEW] +63.6Ki  [NEW] +63.4Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h2d979578028f7e69
  [NEW] +58.9Ki  [NEW] +58.6Ki    _<agent_data_plane::internal::control_plane::PrivilegedApiWorker as saluki_core::runtime::supervisor::Supervisable>::initialize::_{{closure}}::h41b755b25f8fd10b
  [NEW] +49.5Ki  [NEW] +49.3Ki    saluki_app::bootstrap::AppBootstrapper::bootstrap::_{{closure}}::h160d59e5f17d49c0
  [NEW] +46.4Ki  [NEW] +46.3Ki    h2::proto::connection::Connection<T,P,B>::poll::h70805a8e402f8fb4
  [NEW] +46.2Ki  [NEW] +46.0Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h13de10e3cdb6b0d8
  [NEW] +45.7Ki  [NEW] +45.5Ki    _<saluki_components::destinations::prometheus::Prometheus as saluki_core::components::destinations::Destination>::run::_{{closure}}::hf2c7a477f8eea3fb
  [NEW] +44.3Ki  [NEW] +44.1Ki    _<saluki_components::transforms::aggregate::Aggregate as saluki_core::components::transforms::Transform>::run::_{{closure}}::h58d13fc6b4dd4d3f
  [DEL] -44.4Ki  [DEL] -44.2Ki    _<saluki_components::transforms::aggregate::Aggregate as saluki_core::components::transforms::Transform>::run::_{{closure}}::h4183d8718b35855c
  [DEL] -45.7Ki  [DEL] -45.5Ki    _<saluki_components::destinations::prometheus::Prometheus as saluki_core::components::destinations::Destination>::run::_{{closure}}::he34c6c99f535251c
  [DEL] -46.1Ki  [DEL] -45.9Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h3fc056b8c3b1ef5a
  [DEL] -46.4Ki  [DEL] -46.3Ki    h2::proto::connection::Connection<T,P,B>::poll::h882eac246abc63c7
  [DEL] -49.5Ki  [DEL] -49.3Ki    saluki_app::bootstrap::AppBootstrapper::bootstrap::_{{closure}}::he5d18b68ad8674e3
  [DEL] -59.9Ki  [DEL] -59.7Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h18a09eec15b3cc76
  [DEL] -64.2Ki  [DEL] -64.0Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::hbbac7c9eda9e917d
  [DEL] -84.6Ki  [DEL] -84.5Ki    agent_data_plane::internal::control_plane::spawn_control_plane::_{{closure}}::h10ad1a803e89e510
  [DEL]  -114Ki  [DEL]  -114Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::hd2940cf47ffc187f
  [DEL] -1.79Mi  [DEL] -1.79Mi    std::thread::local::LocalKey<T>::with::h9949df9476fbdca7
  +2.6%  +688Ki  +2.5%  +588Ki    TOTAL

@pr-commenter
Copy link

pr-commenter bot commented Mar 25, 2026

Regression Detector (Agent Data Plane)

Regression Detector Results

Run ID: 6adc7a0d-44df-4f5d-b332-10fdb87c3c5f

Baseline: 568e97e
Comparison: 372618f
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_throughput ingress throughput +0.00 [-0.12, +0.13] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_cpu % cpu utilization -0.06 [-5.19, +5.07] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_memory memory utilization -3.20 [-3.96, -2.43] 1 (metrics) (profiles) (logs)

Fine details of change detection per experiment

perf experiment goal Δ mean % Δ mean % CI trials links
dsd_uds_512kb_3k_contexts_cpu % cpu utilization +7.81 [-52.15, +67.77] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_cpu % cpu utilization +4.65 [-3.10, +12.41] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_memory memory utilization +2.30 [+2.06, +2.55] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_cpu % cpu utilization +1.68 [-53.80, +57.17] 1 (metrics) (profiles) (logs)
dsd_uds_500mb_3k_contexts_memory memory utilization +0.98 [+0.82, +1.14] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_low memory utilization +0.97 [+0.78, +1.16] 1 (metrics) (profiles) (logs)
dsd_uds_512kb_3k_contexts_memory memory utilization +0.70 [+0.52, +0.87] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_memory memory utilization +0.64 [+0.45, +0.82] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_medium memory utilization +0.56 [+0.37, +0.76] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_cpu % cpu utilization +0.45 [-31.89, +32.80] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_memory memory utilization +0.39 [+0.05, +0.73] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_memory memory utilization +0.38 [+0.19, +0.56] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_ultraheavy memory utilization +0.34 [+0.20, +0.47] 1 (metrics) (profiles) (logs)
dsd_uds_500mb_3k_contexts_cpu % cpu utilization +0.31 [-1.10, +1.73] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_memory memory utilization +0.25 [+0.07, +0.42] 1 (metrics) (profiles) (logs)
quality_gates_rss_dsd_heavy memory utilization +0.18 [+0.04, +0.32] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_memory memory utilization +0.10 [-0.15, +0.35] 1 (metrics) (profiles) (logs)
quality_gates_rss_idle memory utilization +0.04 [-0.01, +0.09] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_cpu % cpu utilization +0.03 [-6.17, +6.23] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_throughput ingress throughput +0.00 [-0.12, +0.13] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_throughput ingress throughput +0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
dsd_uds_1mb_3k_contexts_throughput ingress throughput -0.00 [-0.06, +0.06] 1 (metrics) (profiles) (logs)
dsd_uds_512kb_3k_contexts_throughput ingress throughput -0.00 [-0.05, +0.05] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_throughput ingress throughput -0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_throughput ingress throughput -0.00 [-0.02, +0.02] 1 (metrics) (profiles) (logs)
dsd_uds_100mb_3k_contexts_throughput ingress throughput -0.01 [-0.04, +0.03] 1 (metrics) (profiles) (logs)
dsd_uds_10mb_3k_contexts_throughput ingress throughput -0.01 [-0.15, +0.13] 1 (metrics) (profiles) (logs)
otlp_ingest_metrics_5mb_throughput ingress throughput -0.03 [-0.15, +0.10] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_cpu % cpu utilization -0.06 [-5.19, +5.07] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_memory memory utilization -0.34 [-0.60, -0.09] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_transform_5mb_cpu % cpu utilization -1.42 [-3.62, +0.78] 1 (metrics) (profiles) (logs)
dsd_uds_500mb_3k_contexts_throughput ingress throughput -1.95 [-2.07, -1.83] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_ottl_filtering_5mb_cpu % cpu utilization -2.12 [-4.53, +0.28] 1 (metrics) (profiles) (logs)
otlp_ingest_traces_5mb_cpu % cpu utilization -2.56 [-4.67, -0.46] 1 (metrics) (profiles) (logs)
otlp_ingest_logs_5mb_memory memory utilization -3.20 [-3.96, -2.43] 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 112.30MiB ≤ 140MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_low memory_usage 10/10 33.95MiB ≤ 50MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_medium memory_usage 10/10 53.29MiB ≤ 75MiB (metrics) (profiles) (logs)
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 167.71MiB ≤ 200MiB (metrics) (profiles) (logs)
quality_gates_rss_idle memory_usage 10/10 21.42MiB ≤ 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".

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