Skip to main content

nexus_memory_web/api/
stats.rs

1//! Statistics API endpoints
2
3use axum::{
4    extract::{Path, State},
5    Json,
6};
7use serde_json::json;
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12use crate::{
13    error::Result,
14    models::{AgentStats, StatsResponse, SystemInfo},
15    state::AppState,
16};
17
18/// Get global statistics
19pub async fn get_stats(State(state): State<Arc<RwLock<AppState>>>) -> Result<Json<StatsResponse>> {
20    let state = state.read().await;
21
22    // Get all namespaces
23    let namespaces = state.namespace_repo.list_all().await?;
24
25    let mut total_memories: i64 = 0;
26    let mut active_memories: i64 = 0;
27    let mut archived_memories: i64 = 0;
28    let mut categories: HashMap<String, i64> = HashMap::new();
29    let mut agent_stats: Vec<AgentStats> = Vec::new();
30
31    for namespace in &namespaces {
32        // Count memories using unfiltered queries for accurate totals
33        let ns_total = state
34            .memory_repo
35            .count_all_by_namespace(namespace.id)
36            .await?;
37        let ns_active = state.memory_repo.count_by_namespace(namespace.id).await?;
38        let ns_archived = state
39            .memory_repo
40            .count_archived_by_namespace(namespace.id)
41            .await?;
42
43        // Get active memories for category breakdown
44        let memories = state
45            .memory_repo
46            .search_by_namespace(namespace.id, 10000, 0)
47            .await?;
48
49        // Count categories from active memories
50        let mut ns_categories: HashMap<String, i64> = HashMap::new();
51        for memory in &memories {
52            let cat = memory.category.to_string();
53            *ns_categories.entry(cat.clone()).or_insert(0) += 1;
54            *categories.entry(cat).or_insert(0) += 1;
55        }
56
57        // Find oldest and newest memories
58        let oldest = memories.iter().map(|m| m.created_at).min();
59        let newest = memories.iter().map(|m| m.created_at).max();
60
61        agent_stats.push(AgentStats {
62            agent_type: namespace.agent_type.clone(),
63            namespace_name: namespace.name.clone(),
64            total_memories: ns_total,
65            active_memories: ns_active,
66            archived_memories: ns_archived,
67            categories: json!(ns_categories),
68            oldest_memory: oldest.map(|d| d.to_rfc3339()),
69            newest_memory: newest.map(|d| d.to_rfc3339()),
70        });
71
72        total_memories += ns_total;
73        active_memories += ns_active;
74        archived_memories += ns_archived;
75    }
76
77    let system_info = SystemInfo {
78        version: env!("CARGO_PKG_VERSION").to_string(),
79        uptime_seconds: state.uptime_seconds(),
80        active_sessions: state.orchestrator.active_session_count().await,
81    };
82
83    Ok(Json(StatsResponse {
84        success: true,
85        total_memories,
86        active_memories,
87        archived_memories,
88        categories: json!(categories),
89        agents: agent_stats,
90        system_info: Some(system_info),
91    }))
92}
93
94/// Get statistics for a specific agent
95pub async fn get_agent_stats(
96    State(state): State<Arc<RwLock<AppState>>>,
97    Path(agent_type): Path<String>,
98) -> Result<Json<AgentStats>> {
99    let state = state.read().await;
100
101    // Get namespace for this agent
102    let namespace = state
103        .namespace_repo
104        .get_or_create(&agent_type, &agent_type)
105        .await?;
106
107    // Count memories using unfiltered queries for accurate totals
108    let count = state
109        .memory_repo
110        .count_all_by_namespace(namespace.id)
111        .await?;
112    let active = state.memory_repo.count_by_namespace(namespace.id).await?;
113    let archived = state
114        .memory_repo
115        .count_archived_by_namespace(namespace.id)
116        .await?;
117
118    // Get active memories for category breakdown
119    let memories = state
120        .memory_repo
121        .search_by_namespace(namespace.id, 10000, 0)
122        .await?;
123
124    // Count categories
125    let mut ns_categories: HashMap<String, i64> = HashMap::new();
126    for memory in &memories {
127        let cat = memory.category.to_string();
128        *ns_categories.entry(cat).or_insert(0) += 1;
129    }
130
131    // Find oldest and newest memories
132    let oldest = memories.iter().map(|m| m.created_at).min();
133    let newest = memories.iter().map(|m| m.created_at).max();
134
135    Ok(Json(AgentStats {
136        agent_type: namespace.agent_type,
137        namespace_name: namespace.name,
138        total_memories: count,
139        active_memories: active,
140        archived_memories: archived,
141        categories: json!(ns_categories),
142        oldest_memory: oldest.map(|d| d.to_rfc3339()),
143        newest_memory: newest.map(|d| d.to_rfc3339()),
144    }))
145}