use async_trait::async_trait;
use serde_json::Value;
use super::{HostProbe, ProbeContext, ProbeError, ProbeReading};
pub const PROBE_SOURCE: &str = "host.fc_metrics";
#[derive(Debug, Clone)]
pub struct FcMetricsProbe {
pub endpoint: String,
}
impl FcMetricsProbe {
pub fn new(endpoint: impl Into<String>) -> Self {
Self {
endpoint: endpoint.into(),
}
}
}
#[async_trait]
impl HostProbe for FcMetricsProbe {
fn probe_name(&self) -> &'static str {
"fc_metrics"
}
async fn read(&self, _ctx: &ProbeContext) -> Result<ProbeReading, ProbeError> {
let raw = read_metrics_document(&self.endpoint).await?;
let parsed: Value = serde_json::from_str(&raw)
.map_err(|e| ProbeError::Parse(format!("fc /metrics not JSON: {e}")))?;
let inputs = serde_json::json!({
"endpoint": self.endpoint,
"kind": "firecracker.metrics",
});
let output = serde_json::json!({
"metrics": parsed,
});
Ok(ProbeReading::new(PROBE_SOURCE, inputs, output))
}
}
#[cfg(target_os = "linux")]
async fn read_metrics_document(_endpoint: &str) -> Result<String, ProbeError> {
Err(ProbeError::Io(
"fc /metrics socket scrape not yet wired (Phase F1a — pending supervisor integration)"
.into(),
))
}
#[cfg(not(target_os = "linux"))]
async fn read_metrics_document(_endpoint: &str) -> Result<String, ProbeError> {
Err(ProbeError::PlatformUnsupported)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use cellos_core::ports::EventSink;
use super::super::test_support::CapturingSink;
use super::super::{emit_reading, ProbeContext, ProbeReading};
use super::PROBE_SOURCE;
#[tokio::test]
async fn emitted_event_carries_d12_fields() {
let reading = ProbeReading::new(
PROBE_SOURCE,
serde_json::json!({
"endpoint": "unix:///run/firecracker/test.sock/metrics",
"kind": "firecracker.metrics",
}),
serde_json::json!({
"metrics": {"vmm": {"reads": 7, "writes": 2}},
}),
);
let ctx = ProbeContext::new("cell-fc-1", "run-fc-1", "sha256:fc");
let snapshot_handle = Arc::new(CapturingSink::new());
let probe_sink: Arc<dyn EventSink> = snapshot_handle.clone();
emit_reading(&probe_sink, &ctx, reading)
.await
.expect("emit");
let events = snapshot_handle.snapshot().await;
assert_eq!(events.len(), 1);
let data = events[0].data.as_ref().expect("data");
assert_eq!(data["probeSource"], PROBE_SOURCE);
assert_eq!(data["cellId"], "cell-fc-1");
assert_eq!(data["runId"], "run-fc-1");
assert!(data["hostReceivedAt"].is_string());
assert!(data["inputs"]["endpoint"].is_string());
assert!(data["output"]["metrics"].is_object());
assert!(events[0]
.ty
.ends_with(".cell.observability.host.v1.fc_metrics"));
}
}