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};
#[derive(Debug, Clone)]
pub struct StatsCollector {
#[allow(dead_code)] config: StatsConfig,
api_client: Option<ApiClient>,
pub cache: Option<FileCache>,
active_timers: Arc<Mutex<HashMap<String, Timer>>>,
}
impl StatsCollector {
pub fn new(config: StatsConfig) -> Self {
Self {
config,
api_client: None,
cache: None,
active_timers: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn from_config(config: &Config) -> Result<Self> {
let mut collector = Self::new(config.stats.clone());
if config.api.base_url.is_some() {
let api_client = ApiClient::new(config.api.clone())?;
collector = collector.with_api_client(api_client);
}
let cache = FileCache::new(config.cache.clone());
collector = collector.with_cache(cache);
Ok(collector)
}
pub fn with_api_client(mut self, api_client: ApiClient) -> Self {
self.api_client = Some(api_client);
self
}
pub fn with_cache(mut self, cache: FileCache) -> Self {
self.cache = Some(cache);
self
}
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)
}
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)
}
}
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
}
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
}
pub async fn collect_system_stats(&self, source: impl Into<String>) -> Result<StatsRecord> {
let mut record = StatsRecord::new(source);
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)
}
pub async fn store_record(&self, record: StatsRecord) -> Result<()> {
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);
}
}
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(())
}
async fn get_system_stats(&self) -> Result<SystemStats> {
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),
}
}
}