nexus_memory_web/api/
stats.rs1use 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
18pub async fn get_stats(State(state): State<Arc<RwLock<AppState>>>) -> Result<Json<StatsResponse>> {
20 let state = state.read().await;
21
22 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 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 let memories = state
45 .memory_repo
46 .search_by_namespace(namespace.id, 10000, 0)
47 .await?;
48
49 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 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
94pub 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 let namespace = state
103 .namespace_repo
104 .get_or_create(&agent_type, &agent_type)
105 .await?;
106
107 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 let memories = state
120 .memory_repo
121 .search_by_namespace(namespace.id, 10000, 0)
122 .await?;
123
124 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 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}