Skip to content

Commit b042647

Browse files
committed
enhancement(core): add unified emitter for diagnostics
1 parent 1e66828 commit b042647

10 files changed

Lines changed: 468 additions & 80 deletions

File tree

bin/agent-data-plane/src/internal/env/workload/api.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use saluki_context::{
1414
tags::{SharedTagSet, TagSet},
1515
};
1616
use saluki_core::{
17-
diagnostic::DiagnosticHandle,
17+
diagnostic::DiagnosticsEmitter,
1818
runtime::{state::DataspaceRegistry, InitializationError, Supervisable, SupervisorFuture},
19+
support::SubsystemIdentifier,
1920
};
2021
use saluki_env::workload::{
2122
entity::HighestPrecedenceEntityIdRef,
@@ -210,21 +211,23 @@ impl Supervisable for RemoteAgentWorkloadAPIWorker {
210211
async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
211212
let workload_route = DynamicRoute::http(EndpointType::Privileged, &self.handler);
212213

213-
let state = self.handler.state.clone();
214-
let tags_handle = DiagnosticHandle::new("workload-tags-dump.json", move || state.tags_dump_json().into_bytes());
215-
216-
let state = self.handler.state.clone();
217-
let eds_handle = DiagnosticHandle::new("workload-external-data-dump.json", move || {
218-
state.eds_dump_json().into_bytes()
219-
});
214+
let tags_state = self.handler.state.clone();
215+
let eds_state = self.handler.state.clone();
220216

221217
Ok(Box::pin(async move {
222218
let dataspace =
223219
DataspaceRegistry::try_current().ok_or_else(|| generic_error!("Dataspace not available."))?;
224220

225221
dataspace.assert(workload_route, "workload-api");
226-
dataspace.assert(tags_handle, "diag-workload-tags");
227-
dataspace.assert(eds_handle, "diag-workload-eds");
222+
223+
let diagnostics =
224+
DiagnosticsEmitter::from_dataspace(SubsystemIdentifier::from_segments(["workload-api"]), dataspace);
225+
diagnostics.register_collector("workload-tags-dump.json", move || {
226+
tags_state.tags_dump_json().into_bytes()
227+
});
228+
diagnostics.register_collector("workload-external-data-dump.json", move || {
229+
eds_state.eds_dump_json().into_bytes()
230+
});
228231

229232
process_shutdown.await;
230233
Ok(())

bin/agent-data-plane/src/internal/remote_agent.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use saluki_common::sync::shutdown::ShutdownHandle;
2020
use saluki_common::task::spawn_traced_named;
2121
use saluki_config::{dynamic::ConfigUpdate, upsert, GenericConfiguration};
2222
use saluki_core::{
23-
diagnostic::DiagnosticHandle,
23+
diagnostic::DiagnosticCollector,
2424
observability::metrics::{get_shared_metrics_state, AggregatedMetricsProcessor, Reflector, TelemetryProcessor},
2525
runtime::{
2626
state::{DataspaceRegistry, IdentifierFilter},
@@ -578,7 +578,7 @@ impl FlareProvider for RemoteAgentImpl {
578578

579579
// Grab and collect all asserted diagnostic handles
580580
if let Some(dataspace) = self.dataspace.get() {
581-
let handles = dataspace.current_values::<DiagnosticHandle>(IdentifierFilter::all());
581+
let handles = dataspace.current_values::<DiagnosticCollector>(IdentifierFilter::all());
582582
let total_handles = handles.len();
583583
let deadline = tokio::time::Instant::now() + DIAGNOSTIC_COLLECT_TIMEOUT;
584584

lib/saluki-app/src/accounting.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use saluki_core::accounting::{
1212
ComponentBounds, ComponentRegistry, ComponentRegistryHandle, MemoryGrant, MemoryLimiter,
1313
};
1414
use saluki_core::{
15-
diagnostic::DiagnosticHandle,
15+
diagnostic::DiagnosticsEmitter,
1616
runtime::{state::DataspaceRegistry, InitializationError, Supervisable, SupervisorFuture},
17+
support::SubsystemIdentifier,
1718
};
1819
use saluki_error::{generic_error, ErrorContext as _, GenericError};
1920
use serde::Deserialize;
@@ -330,17 +331,22 @@ impl Supervisable for ResourceTelemetryWorker {
330331
let memory_routes = DynamicRoute::http(EndpointType::Unprivileged, self.component_registry.api_handler());
331332

332333
let component_registry = self.component_registry.clone();
333-
let flare_handle = DiagnosticHandle::new("memory_status.json", move || {
334-
component_registry.memory_snapshot_json().into_bytes()
335-
});
336334

337335
Ok(Box::pin(async move {
338336
let dataspace =
339337
DataspaceRegistry::try_current().ok_or_else(|| generic_error!("Dataspace not available."))?;
340338

341-
// Register our API routes and diagnostic handle before we actually start running.
339+
// Register our API routes before we actually start running.
342340
dataspace.assert(memory_routes, "resource-telemetry-api");
343-
dataspace.assert(flare_handle, "diag-memory");
341+
342+
// Expose our diagnostic artifact via the diagnostics control surface.
343+
let diagnostics = DiagnosticsEmitter::from_dataspace(
344+
SubsystemIdentifier::from_segments(["resource-telemetry"]),
345+
dataspace,
346+
);
347+
diagnostics.register_collector("memory_status.json", move || {
348+
component_registry.memory_snapshot_json().into_bytes()
349+
});
344350

345351
select! {
346352
_ = process_shutdown => {},

lib/saluki-app/src/config.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use saluki_api::{
1111
use saluki_common::sync::shutdown::ShutdownHandle;
1212
use saluki_config::GenericConfiguration;
1313
use saluki_core::{
14-
diagnostic::DiagnosticHandle,
14+
diagnostic::DiagnosticsEmitter,
1515
runtime::{state::DataspaceRegistry, InitializationError, Supervisable, SupervisorFuture},
16+
support::SubsystemIdentifier,
1617
};
1718
use saluki_error::generic_error;
1819
use serde_json::Value;
@@ -91,19 +92,21 @@ impl Supervisable for ConfigWorker {
9192
let config_route = DynamicRoute::http(EndpointType::Privileged, &self.handler);
9293

9394
let config = self.handler.state.config.clone();
94-
let flare_handle = DiagnosticHandle::new("runtime_config_dump.yaml", move || {
95-
config
96-
.as_typed::<serde_json::Value>()
97-
.map(|v| serde_json::to_vec_pretty(&v).unwrap_or_default())
98-
.unwrap_or_default()
99-
});
10095

10196
Ok(Box::pin(async move {
10297
let dataspace =
10398
DataspaceRegistry::try_current().ok_or_else(|| generic_error!("Dataspace not available."))?;
10499

105100
dataspace.assert(config_route, "config-api");
106-
dataspace.assert(flare_handle, "diag-config");
101+
102+
let diagnostics =
103+
DiagnosticsEmitter::from_dataspace(SubsystemIdentifier::from_segments(["config-api"]), dataspace);
104+
diagnostics.register_collector("runtime_config_dump.yaml", move || {
105+
config
106+
.as_typed::<serde_json::Value>()
107+
.map(|v| serde_json::to_vec_pretty(&v).unwrap_or_default())
108+
.unwrap_or_default()
109+
});
107110

108111
process_shutdown.await;
109112
Ok(())

lib/saluki-core/src/diagnostic.rs

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! On-demand diagnostic artifact collection.
2+
3+
use std::sync::Arc;
4+
5+
/// A named, on-demand producer of diagnostic artifact bytes.
6+
///
7+
/// A collector pairs an artifact name with a synchronous closure that produces the artifact's bytes when
8+
/// invoked. Collectors are registered through a [`DiagnosticsEmitter`][super::DiagnosticsEmitter] and gathered on
9+
/// demand by whichever subsystem is responsible for assembling diagnostic artifacts.
10+
///
11+
/// The artifact name is suitable, but not guaranteed, to be used as a filename.
12+
#[derive(Clone)]
13+
pub struct DiagnosticCollector {
14+
artifact_name: String,
15+
collect_fn: Arc<dyn Fn() -> Vec<u8> + Send + Sync>,
16+
}
17+
18+
impl DiagnosticCollector {
19+
/// Creates a new collector with the given artifact name and collection closure.
20+
///
21+
/// `collect_fn` runs synchronously and must return promptly, as it can delay the collection of artifacts for the
22+
/// whole system.
23+
pub fn new(artifact_name: impl Into<String>, collect_fn: impl Fn() -> Vec<u8> + Send + Sync + 'static) -> Self {
24+
Self {
25+
artifact_name: artifact_name.into(),
26+
collect_fn: Arc::new(collect_fn),
27+
}
28+
}
29+
30+
/// Returns the artifact name.
31+
pub fn artifact_name(&self) -> &str {
32+
&self.artifact_name
33+
}
34+
35+
/// Collects and returns the artifact bytes.
36+
pub fn collect(&self) -> Vec<u8> {
37+
(self.collect_fn)()
38+
}
39+
}

0 commit comments

Comments
 (0)