use super::*;
pub(super) fn build_snapshot_aggregator(snapshot: &StatusSnapshot) -> MetricsAggregator {
let mut aggregator = MetricsAggregator::new();
let topic_pending_total = snapshot.topics.iter().map(|item| item.pending as f64).sum();
let topic_depth_total = snapshot
.topics
.iter()
.map(|item| item.max_depth as f64)
.sum();
let service_pending_requests_total = snapshot
.services
.iter()
.map(|item| item.pending_requests as f64)
.sum();
let service_pending_responses_total = snapshot
.services
.iter()
.map(|item| item.pending_responses as f64)
.sum();
let loaded_plugins = snapshot
.plugins
.iter()
.filter(|plugin| plugin.loaded)
.count() as f64;
let runtime_status = snapshot
.health
.iter()
.find(|item| item.component == "runtime")
.map(health_status_from_item)
.unwrap_or(HealthStatus::Healthy);
let runtime_metrics = vec![
MetricsSnapshot::gauge("nodes.count", snapshot.nodes.len() as f64, ""),
MetricsSnapshot::gauge("topics.count", snapshot.topics.len() as f64, ""),
MetricsSnapshot::gauge("topics.pending_total", topic_pending_total, ""),
MetricsSnapshot::gauge("topics.max_depth_total", topic_depth_total, ""),
MetricsSnapshot::gauge("services.count", snapshot.services.len() as f64, ""),
MetricsSnapshot::gauge(
"services.pending_requests_total",
service_pending_requests_total,
"",
),
MetricsSnapshot::gauge(
"services.pending_responses_total",
service_pending_responses_total,
"",
),
MetricsSnapshot::gauge("actions.count", snapshot.actions.len() as f64, ""),
MetricsSnapshot::gauge("missions.count", snapshot.missions.len() as f64, ""),
MetricsSnapshot::gauge("plugins.loaded", loaded_plugins, ""),
MetricsSnapshot::gauge("graph.edges", snapshot.edges.len() as f64, ""),
];
aggregator.register(
"runtime",
Box::new(SnapshotProvider::new(runtime_status, runtime_metrics)),
);
for item in &snapshot.health {
if item.component == "runtime" {
continue;
}
aggregator.register(
item.component.clone(),
Box::new(SnapshotProvider::new(
health_status_from_item(item),
Vec::new(),
)),
);
}
aggregator
}
pub(super) fn build_alert_template(kind: &str) -> serde_json::Value {
match kind {
"none" => serde_json::Value::Null,
"basic" => serde_json::Value::String(
"groups:\n - name: robotrt-basic\n rules:\n - alert: RobotRTUnhealthy\n expr: robotrt_health_overall > 0\n for: 2m\n labels:\n severity: critical\n annotations:\n summary: \"RobotRT health degraded or unhealthy\"\n\n - alert: RobotRTHighQueuePressure\n expr: robotrt_runtime_topics_pending_total > 100\n for: 3m\n labels:\n severity: warning\n annotations:\n summary: \"RobotRT topic queue pressure is high\""
.to_string(),
),
_ => serde_json::Value::Null,
}
}
fn health_status_from_item(item: &HealthStatusItem) -> HealthStatus {
match item.status.to_ascii_lowercase().as_str() {
"healthy" => HealthStatus::Healthy,
"degraded" => HealthStatus::Degraded {
reason: item
.reason
.clone()
.unwrap_or_else(|| "reported degraded status".to_string()),
},
_ => HealthStatus::Unhealthy {
reason: item
.reason
.clone()
.unwrap_or_else(|| format!("reported status={}", item.status)),
},
}
}