use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tracing::{debug, info};
use trusty_common::host_metrics::{HostMetrics, HostSampler};
#[derive(Clone, Debug)]
pub struct HostMetricsCache {
inner: Arc<RwLock<Option<HostMetrics>>>,
}
impl Default for HostMetricsCache {
fn default() -> Self {
Self::new()
}
}
impl HostMetricsCache {
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(None)),
}
}
pub async fn get(&self) -> Option<HostMetrics> {
self.inner.read().await.clone()
}
pub async fn set(&self, metrics: HostMetrics) {
*self.inner.write().await = Some(metrics);
}
}
pub fn start(cache: HostMetricsCache, interval: Duration) {
tokio::spawn(async move {
info!(
"host_status: starting host-metrics sampler (interval={}s)",
interval.as_secs()
);
let mut sampler = HostSampler::new();
loop {
let metrics = sampler.sample();
debug!(
overall = ?metrics.overall_pressure,
cpu_pct = metrics.cpu.usage_pct,
mem_pct = metrics.memory.usage_pct,
"host_status: sampled host metrics"
);
cache.set(metrics).await;
tokio::time::sleep(interval).await;
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn cache_initialises_empty() {
let cache = HostMetricsCache::new();
assert!(cache.get().await.is_none(), "cache must start empty");
}
#[tokio::test]
async fn cache_write_read_roundtrip() {
let cache = HostMetricsCache::new();
let metrics = HostSampler::new().sample();
let cores = metrics.cpu.logical_cores;
cache.set(metrics).await;
let got = cache.get().await.expect("must have a snapshot after set");
assert_eq!(got.cpu.logical_cores, cores);
}
}