chore(agent-data-plane): register health API dynamically#1259
chore(agent-data-plane): register health API dynamically#1259tobz wants to merge 6 commits intotobz/dynamic-api-endpoint-routesfrom
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
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-healthcrate and rehomed health types undersaluki_core::health. - Added
HealthRegistryWorkerto run the health registry loop and dynamically publish the health HTTP routes. - Switched the agent-data-plane unprivileged API server to
DynamicAPIBuilderand updated dependencies accordingly (including enablingtonic’srouterfeature insaluki-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.
lib/saluki-core/src/health/worker.rs
Outdated
| 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()); | ||
|
|
There was a problem hiding this comment.
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.
| 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; |
lib/saluki-core/src/health/worker.rs
Outdated
| 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()); | ||
|
|
There was a problem hiding this comment.
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.
Binary Size Analysis (Agent Data Plane)Target: 5316221 (baseline) vs 7b7c39e (comparison) diff
|
| 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
Regression Detector (Agent Data Plane)Regression Detector ResultsRun ID: 841539ae-6e60-4510-b0fa-90c38d7032ec Baseline: 5316221 Optimization Goals: ✅ No significant changes detected
|
| 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:
-
Its estimated |Δ mean %| ≥ 5.00%, indicating the change is big enough to merit a closer look.
-
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.
-
Its configuration does not mark it "erratic".
372618f to
af14e67
Compare
1534b83 to
59dd8ff
Compare
af14e67 to
04f70b9
Compare
59dd8ff to
dfa31c4
Compare
4241ed5 to
78737f0
Compare
dfa31c4 to
03bc6f5
Compare
78737f0 to
2b2e54c
Compare
de87cba to
7182e6a
Compare
2b2e54c to
1030747
Compare
7182e6a to
bb8d32e
Compare
7e9b19a to
2a76a50
Compare
bb8d32e to
bf20e01
Compare
2a76a50 to
4324f1c
Compare
bf20e01 to
79fe48c
Compare
4324f1c to
e7a622f
Compare
8963add to
e3fbd14
Compare
5c33d3f to
c9a04bc
Compare
e3fbd14 to
5bc9b1c
Compare
8087921 to
37a1276
Compare
fc6377a to
b8b6053
Compare
37a1276 to
ad590c2
Compare
6e6af23 to
7b7c39e
Compare

Summary
Change Type
How did you test this PR?
References