robotrt-cli 0.1.0-beta.1

RobotRT modular robotics runtime and middleware components.
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)),
        },
    }
}