mod backend;
pub use backend::{MonitoringStorageBackend, SimpleMonitoringStorage};
use super::types::{AgentMetrics, ExecutionRecord, TaskMetrics};
use crate::Result;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MonitoringAnalytics {
pub total_executions: u64,
pub success_rate: f64,
pub avg_duration_secs: f64,
pub top_performing_agents: Vec<(String, f64)>,
pub recent_failures: Vec<ExecutionRecord>,
}
impl Default for MonitoringAnalytics {
fn default() -> Self {
Self {
total_executions: 0,
success_rate: 0.0,
avg_duration_secs: 0.0,
top_performing_agents: Vec::new(),
recent_failures: Vec::new(),
}
}
}
pub struct MonitoringStorage {
durable_storage: Option<Arc<dyn backend::MonitoringStorageBackend>>,
cache_storage: Option<Arc<dyn backend::MonitoringStorageBackend>>,
}
impl Default for MonitoringStorage {
fn default() -> Self {
Self::new()
}
}
impl MonitoringStorage {
#[must_use]
pub fn new() -> Self {
Self {
durable_storage: None,
cache_storage: None,
}
}
pub fn with_durable(durable: Arc<dyn backend::MonitoringStorageBackend>) -> Self {
Self {
durable_storage: Some(durable),
cache_storage: None,
}
}
pub fn with_backends(
durable: Arc<dyn backend::MonitoringStorageBackend>,
cache: Arc<dyn backend::MonitoringStorageBackend>,
) -> Self {
Self {
durable_storage: Some(durable),
cache_storage: Some(cache),
}
}
#[must_use]
pub fn has_durable_storage(&self) -> bool {
self.durable_storage.is_some()
}
#[must_use]
pub fn has_cache_storage(&self) -> bool {
self.cache_storage.is_some()
}
pub async fn store_execution_record(&self, record: &ExecutionRecord) -> Result<()> {
if let Some(storage) = &self.durable_storage {
storage.store_execution_record(record).await?;
}
if let Some(storage) = &self.cache_storage {
storage.store_execution_record(record).await?;
}
Ok(())
}
pub async fn store_agent_metrics(&self, metrics: &AgentMetrics) -> Result<()> {
if let Some(storage) = &self.durable_storage {
storage.store_agent_metrics(metrics).await?;
}
if let Some(storage) = &self.cache_storage {
storage.store_agent_metrics(metrics).await?;
}
Ok(())
}
pub async fn store_task_metrics(&self, metrics: &TaskMetrics) -> Result<()> {
if let Some(storage) = &self.durable_storage {
storage.store_task_metrics(metrics).await?;
}
if let Some(storage) = &self.cache_storage {
storage.store_task_metrics(metrics).await?;
}
Ok(())
}
pub async fn load_agent_metrics(&self, agent_name: &str) -> Result<Option<AgentMetrics>> {
if let Some(storage) = &self.cache_storage {
if let Some(metrics) = storage.load_agent_metrics(agent_name).await? {
return Ok(Some(metrics));
}
}
if let Some(storage) = &self.durable_storage {
return storage.load_agent_metrics(agent_name).await;
}
Ok(None)
}
pub async fn load_execution_records(
&self,
agent_name: Option<&str>,
limit: usize,
) -> Result<Vec<ExecutionRecord>> {
if let Some(storage) = &self.cache_storage {
let records = storage.load_execution_records(agent_name, limit).await?;
if !records.is_empty() {
return Ok(records);
}
}
if let Some(storage) = &self.durable_storage {
return storage.load_execution_records(agent_name, limit).await;
}
Ok(Vec::new())
}
pub async fn load_task_metrics(&self, task_type: &str) -> Result<Option<TaskMetrics>> {
if let Some(storage) = &self.cache_storage {
if let Some(metrics) = storage.load_task_metrics(task_type).await? {
return Ok(Some(metrics));
}
}
if let Some(storage) = &self.durable_storage {
return storage.load_task_metrics(task_type).await;
}
Ok(None)
}
pub async fn get_analytics(&self) -> Result<MonitoringAnalytics> {
let records = self
.load_execution_records(None, 1000)
.await
.unwrap_or_default();
if records.is_empty() {
return Ok(MonitoringAnalytics::default());
}
let total_executions = records.len() as u64;
let successful = records.iter().filter(|r| r.success).count() as u64;
let success_rate = if total_executions > 0 {
successful as f64 / total_executions as f64
} else {
0.0
};
let total_duration: std::time::Duration = records.iter().map(|r| r.duration).sum();
let avg_duration_secs = total_duration.as_secs_f64() / total_executions as f64;
let mut agent_stats: HashMap<String, (u64, u64)> = HashMap::new();
for r in &records {
let entry = agent_stats.entry(r.agent_name.clone()).or_default();
entry.1 += 1;
if r.success {
entry.0 += 1;
}
}
let top_agents: Vec<(String, f64)> = agent_stats
.into_iter()
.filter_map(|(name, (succ, total))| {
if total > 0 {
Some((name, succ as f64 / total as f64))
} else {
None
}
})
.filter(|&(_, rate)| rate > 0.5)
.collect();
let mut top_performing_agents = top_agents;
top_performing_agents
.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let top_performing_agents = top_performing_agents.into_iter().take(5).collect();
let recent_failures: Vec<ExecutionRecord> = records
.into_iter()
.filter(|r| !r.success)
.take(10)
.collect();
Ok(MonitoringAnalytics {
total_executions,
success_rate,
avg_duration_secs,
top_performing_agents,
recent_failures,
})
}
}