cstats-core 0.1.1

Core library for cstats - statistical analysis and metrics collection
Documentation
//! Statistics collection functionality

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::Mutex;
use tracing::{debug, warn};

use crate::{
    api::{ApiClient, MetricValue},
    cache::FileCache,
    config::{Config, StatsConfig},
    Result,
};

use super::{StatsRecord, SystemStats, Timer};

/// Statistics collector for gathering and storing metrics
#[derive(Debug, Clone)]
pub struct StatsCollector {
    #[allow(dead_code)] // Will be used for future configuration features
    config: StatsConfig,
    api_client: Option<ApiClient>,
    pub cache: Option<FileCache>,
    active_timers: Arc<Mutex<HashMap<String, Timer>>>,
}

impl StatsCollector {
    /// Create a new statistics collector
    pub fn new(config: StatsConfig) -> Self {
        Self {
            config,
            api_client: None,
            cache: None,
            active_timers: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Create a new statistics collector from the main config
    pub fn from_config(config: &Config) -> Result<Self> {
        let mut collector = Self::new(config.stats.clone());

        // Set up API client if configured
        if config.api.base_url.is_some() {
            let api_client = ApiClient::new(config.api.clone())?;
            collector = collector.with_api_client(api_client);
        }

        // Set up cache
        let cache = FileCache::new(config.cache.clone());
        collector = collector.with_cache(cache);

        Ok(collector)
    }

    /// Add an API client to the collector
    pub fn with_api_client(mut self, api_client: ApiClient) -> Self {
        self.api_client = Some(api_client);
        self
    }

    /// Add a cache to the collector
    pub fn with_cache(mut self, cache: FileCache) -> Self {
        self.cache = Some(cache);
        self
    }

    /// Start a timer with the given name
    pub async fn start_timer(&self, name: impl Into<String>) -> Result<String> {
        let name = name.into();
        let timer = Timer::start(name.clone());

        let mut timers = self.active_timers.lock().await;
        timers.insert(name.clone(), timer);

        debug!("Started timer: {}", name);
        Ok(name)
    }

    /// Stop a timer and record the duration
    pub async fn stop_timer(&self, name: &str) -> Result<Option<Duration>> {
        let mut timers = self.active_timers.lock().await;

        if let Some(timer) = timers.remove(name) {
            let (_, duration) = timer.stop();
            debug!("Stopped timer '{}': {:?}", name, duration);
            Ok(Some(duration))
        } else {
            warn!("Timer '{}' not found", name);
            Ok(None)
        }
    }

    /// Record a metric value
    pub async fn record_metric(
        &self,
        source: impl Into<String>,
        name: impl Into<String>,
        value: MetricValue,
    ) -> Result<()> {
        let mut record = StatsRecord::new(source);
        record.add_metric(name, value);

        self.store_record(record).await
    }

    /// Record multiple metrics at once
    pub async fn record_metrics(
        &self,
        source: impl Into<String>,
        metrics: HashMap<String, MetricValue>,
    ) -> Result<()> {
        let mut record = StatsRecord::new(source);
        for (name, value) in metrics {
            record.add_metric(name, value);
        }

        self.store_record(record).await
    }

    /// Collect system statistics
    pub async fn collect_system_stats(&self, source: impl Into<String>) -> Result<StatsRecord> {
        let mut record = StatsRecord::new(source);

        // Collect basic system metrics
        let system_stats = self.get_system_stats().await?;

        if let Some(memory) = system_stats.memory {
            record.add_metric(
                "memory_current_bytes",
                MetricValue::Integer(memory.current_bytes as i64),
            );
            record.add_metric(
                "memory_peak_bytes",
                MetricValue::Integer(memory.peak_bytes as i64),
            );
            record.add_metric(
                "memory_allocations",
                MetricValue::Integer(memory.allocations as i64),
            );
        }

        if let Some(cpu) = system_stats.cpu {
            record.add_metric("cpu_usage_percent", MetricValue::Float(cpu.usage_percent));
            record.add_metric(
                "cpu_user_time_ms",
                MetricValue::Integer(cpu.user_time_ms as i64),
            );
            record.add_metric(
                "cpu_system_time_ms",
                MetricValue::Integer(cpu.system_time_ms as i64),
            );
        }

        if let Some(load_avg) = system_stats.load_avg {
            record.add_metric("load_avg_1min", MetricValue::Float(load_avg[0]));
            record.add_metric("load_avg_5min", MetricValue::Float(load_avg[1]));
            record.add_metric("load_avg_15min", MetricValue::Float(load_avg[2]));
        }

        if let Some(uptime) = system_stats.uptime_seconds {
            record.add_metric("uptime_seconds", MetricValue::Integer(uptime as i64));
        }

        Ok(record)
    }

    /// Store a statistics record
    pub async fn store_record(&self, record: StatsRecord) -> Result<()> {
        // Store in cache if available
        if let Some(cache) = &self.cache {
            let cache_key = format!("stats_record_{}", record.id);
            if let Err(e) = cache.store(&cache_key, &record).await {
                warn!("Failed to cache statistics record: {}", e);
            }
        }

        // Submit to API if available and configured
        if let Some(api_client) = &self.api_client {
            let stats_data = record.clone().into();
            if let Err(e) = api_client.submit_statistics(&stats_data).await {
                warn!("Failed to submit statistics to API: {}", e);
            }
        }

        debug!("Stored statistics record: {}", record.id);
        Ok(())
    }

    /// Get current system statistics
    async fn get_system_stats(&self) -> Result<SystemStats> {
        // This would typically use a library like `sysinfo` or platform-specific APIs
        // For now, return minimal stats
        Ok(SystemStats {
            memory: None,
            cpu: None,
            load_avg: None,
            uptime_seconds: None,
        })
    }
}

impl From<StatsRecord> for crate::api::StatisticsData {
    fn from(record: StatsRecord) -> Self {
        Self {
            id: record.id,
            timestamp: record.timestamp,
            source: record.source,
            metrics: record.metrics,
            metadata: Some(record.metadata),
        }
    }
}