robotrt-obs-core 0.1.0-beta.2

RobotRT modular robotics runtime and middleware components.
Documentation
use core_types::{HealthStatus, MetricsProvider, MetricsSnapshot};

/// A simple in-memory time series: stores the last `capacity` snapshots for
/// a single metric name.
pub struct MetricTimeSeries {
    name: String,
    capacity: usize,
    samples: std::collections::VecDeque<f64>,
}

impl MetricTimeSeries {
    pub fn new(name: impl Into<String>, capacity: usize) -> Self {
        Self {
            name: name.into(),
            capacity: capacity.max(1),
            samples: std::collections::VecDeque::new(),
        }
    }

    pub fn push(&mut self, value: f64) {
        if self.samples.len() >= self.capacity {
            self.samples.pop_front();
        }
        self.samples.push_back(value);
    }

    pub fn name(&self) -> &str {
        &self.name
    }
    pub fn len(&self) -> usize {
        self.samples.len()
    }
    pub fn is_empty(&self) -> bool {
        self.samples.is_empty()
    }
    pub fn last(&self) -> Option<f64> {
        self.samples.back().copied()
    }

    pub fn avg(&self) -> Option<f64> {
        if self.samples.is_empty() {
            return None;
        }
        Some(self.samples.iter().sum::<f64>() / self.samples.len() as f64)
    }

    pub fn max(&self) -> Option<f64> {
        self.samples.iter().cloned().reduce(f64::max)
    }

    pub fn min(&self) -> Option<f64> {
        self.samples.iter().cloned().reduce(f64::min)
    }
}

/// A `MetricsProvider` that wraps a single `MetricTimeSeries` and a threshold
/// for degraded/unhealthy detection.
///
/// Useful for tracking bounded scalar metrics (queue depth, CPU%, latency ms).
pub struct ThresholdGauge {
    series: MetricTimeSeries,
    unit: &'static str,
    warn_threshold: f64,
    crit_threshold: f64,
}

impl ThresholdGauge {
    /// Create a gauge.  `warn` is the degraded threshold, `crit` is unhealthy.
    pub fn new(name: impl Into<String>, unit: &'static str, warn: f64, crit: f64) -> Self {
        Self {
            series: MetricTimeSeries::new(name, 60),
            unit,
            warn_threshold: warn,
            crit_threshold: crit,
        }
    }

    pub fn push(&mut self, value: f64) {
        self.series.push(value);
    }
}

impl MetricsProvider for ThresholdGauge {
    fn collect(&self) -> Vec<MetricsSnapshot> {
        let mut out = Vec::new();
        if let Some(v) = self.series.last() {
            out.push(MetricsSnapshot::gauge(
                self.series.name().to_string(),
                v,
                self.unit,
            ));
        }
        if let Some(avg) = self.series.avg() {
            out.push(MetricsSnapshot::gauge(
                format!("{}.avg", self.series.name()),
                avg,
                self.unit,
            ));
        }
        out
    }

    fn health(&self) -> HealthStatus {
        match self.series.last() {
            None => HealthStatus::Healthy,
            Some(v) if v >= self.crit_threshold => HealthStatus::Unhealthy {
                reason: format!(
                    "{} = {:.2} >= crit {:.2}",
                    self.series.name(),
                    v,
                    self.crit_threshold
                ),
            },
            Some(v) if v >= self.warn_threshold => HealthStatus::Degraded {
                reason: format!(
                    "{} = {:.2} >= warn {:.2}",
                    self.series.name(),
                    v,
                    self.warn_threshold
                ),
            },
            _ => HealthStatus::Healthy,
        }
    }
}