1use crate::common::ApiState;
12use crate::common::{ApiError, ApiResult, Json, Query, State};
13use serde::Deserialize;
14use serde_json::{json, Value};
15use std::collections::HashMap;
16use utoipa::IntoParams;
17
18#[utoipa::path(
24 get,
25 path = "/v1/monitoring/status",
26 tag = "monitoring",
27 responses(
28 (status = 200, description = "Monitoring status", body = HashMap<String, serde_json::Value>),
29 (status = 500, description = "Internal server error")
30 )
31)]
32pub async fn get_status(State(state): State<ApiState>) -> ApiResult<Json<HashMap<String, Value>>> {
33 let analytics_service = state.analytics_service.as_ref();
35
36 let health = analytics_service
38 .get_system_health()
39 .await
40 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
41
42 let mut response = HashMap::new();
43 response.insert("enabled".to_string(), json!(true));
44 response.insert("metrics_collected".to_string(), json!(5)); response.insert("brain_readiness".to_string(), json!(health.brain_readiness));
46 response.insert(
47 "burst_engine_active".to_string(),
48 json!(health.burst_engine_active),
49 );
50
51 Ok(Json(response))
52}
53
54#[utoipa::path(
56 get,
57 path = "/v1/monitoring/metrics",
58 tag = "monitoring",
59 responses(
60 (status = 200, description = "System metrics", body = HashMap<String, serde_json::Value>),
61 (status = 500, description = "Internal server error")
62 )
63)]
64pub async fn get_metrics(State(state): State<ApiState>) -> ApiResult<Json<HashMap<String, Value>>> {
65 let runtime_service = state.runtime_service.as_ref();
67 let analytics_service = state.analytics_service.as_ref();
68
69 let runtime_status = runtime_service
70 .get_status()
71 .await
72 .map_err(|e| ApiError::internal(format!("Failed to get runtime status: {}", e)))?;
73
74 let health = analytics_service
75 .get_system_health()
76 .await
77 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
78
79 let mut response = HashMap::new();
80 response.insert(
81 "burst_frequency_hz".to_string(),
82 json!(runtime_status.frequency_hz),
83 );
84 response.insert("burst_count".to_string(), json!(runtime_status.burst_count));
85 response.insert("neuron_count".to_string(), json!(health.neuron_count));
86 response.insert(
87 "cortical_area_count".to_string(),
88 json!(health.cortical_area_count),
89 );
90 response.insert("brain_readiness".to_string(), json!(health.brain_readiness));
91 response.insert(
92 "burst_engine_active".to_string(),
93 json!(health.burst_engine_active),
94 );
95
96 Ok(Json(response))
97}
98
99#[utoipa::path(
101 get,
102 path = "/v1/monitoring/data",
103 tag = "monitoring",
104 responses(
105 (status = 200, description = "Monitoring data", body = HashMap<String, serde_json::Value>),
106 (status = 500, description = "Internal server error")
107 )
108)]
109pub async fn get_data(State(state): State<ApiState>) -> ApiResult<Json<HashMap<String, Value>>> {
110 let analytics_service = state.analytics_service.as_ref();
112
113 let health = analytics_service
114 .get_system_health()
115 .await
116 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
117
118 let mut data = HashMap::new();
120 data.insert("neuron_count".to_string(), json!(health.neuron_count));
121 data.insert(
122 "cortical_area_count".to_string(),
123 json!(health.cortical_area_count),
124 );
125 data.insert("burst_count".to_string(), json!(health.burst_count));
126 data.insert("brain_readiness".to_string(), json!(health.brain_readiness));
127 data.insert(
128 "burst_engine_active".to_string(),
129 json!(health.burst_engine_active),
130 );
131
132 let mut response = HashMap::new();
133 response.insert("data".to_string(), json!(data));
134 response.insert(
135 "timestamp".to_string(),
136 json!(chrono::Utc::now().to_rfc3339()),
137 );
138
139 Ok(Json(response))
140}
141
142#[utoipa::path(
144 get,
145 path = "/v1/monitoring/performance",
146 tag = "monitoring",
147 responses(
148 (status = 200, description = "Performance metrics", body = HashMap<String, serde_json::Value>),
149 (status = 500, description = "Internal server error")
150 )
151)]
152pub async fn get_performance(
153 State(_state): State<ApiState>,
154) -> ApiResult<Json<HashMap<String, Value>>> {
155 let mut response = HashMap::new();
156 response.insert("cpu_usage".to_string(), json!(0.0));
157 response.insert("memory_usage".to_string(), json!(0.0));
158
159 Ok(Json(response))
160}
161
162#[derive(Debug, Deserialize, IntoParams)]
167#[into_params(parameter_in = Query)]
168pub struct CorticalActivityQuery {
169 pub area: String,
171 #[serde(default = "default_duration")]
173 pub duration: f32,
174}
175
176fn default_duration() -> f32 {
177 1.0
178}
179
180#[utoipa::path(
182 get,
183 path = "/v1/monitoring/cortical_activity",
184 tag = "monitoring",
185 params(CorticalActivityQuery),
186 responses(
187 (status = 200, description = "Cortical area firing activity", body = HashMap<String, serde_json::Value>),
188 (status = 404, description = "Cortical area not found"),
189 (status = 500, description = "Internal server error")
190 )
191)]
192pub async fn get_cortical_activity(
193 State(state): State<ApiState>,
194 Query(params): Query<CorticalActivityQuery>,
195) -> ApiResult<Json<HashMap<String, Value>>> {
196 use tracing::info;
197
198 let runtime_service = state.runtime_service.as_ref();
199 let connectome_service = state.connectome_service.as_ref();
200
201 info!(
202 target: "feagi-api",
203 "Monitoring cortical activity for area={}, duration={}s",
204 params.area,
205 params.duration
206 );
207
208 let area = connectome_service
209 .get_cortical_area(¶ms.area)
210 .await
211 .map_err(|_| ApiError::not_found("cortical area", ¶ms.area))?;
212
213 let cortical_idx = area.cortical_idx;
214 let area_name = area.name.clone();
215
216 let start_burst = runtime_service
217 .get_burst_count()
218 .await
219 .map_err(|e| ApiError::internal(format!("Failed to get burst count: {}", e)))?;
220
221 let sample_interval_ms = 50;
222 let num_samples = ((params.duration * 1000.0) / sample_interval_ms as f32).ceil() as usize;
223
224 let mut spike_history = Vec::new();
225 let mut neuron_fire_counts: HashMap<u32, u32> = HashMap::new();
226 let mut total_membrane_potential = 0.0;
227 let mut potential_samples = 0;
228
229 for _ in 0..num_samples {
230 tokio::time::sleep(tokio::time::Duration::from_millis(sample_interval_ms)).await;
231
232 let fq_sample = runtime_service
233 .get_fire_queue_sample()
234 .await
235 .map_err(|e| ApiError::internal(format!("Failed to get fire queue: {}", e)))?;
236
237 if let Some((neuron_ids, _x_coords, _y_coords, _z_coords, potentials)) =
238 fq_sample.get(&cortical_idx)
239 {
240 let current_burst = runtime_service
241 .get_burst_count()
242 .await
243 .map_err(|e| ApiError::internal(format!("Failed to get burst count: {}", e)))?;
244
245 for (i, &neuron_id) in neuron_ids.iter().enumerate() {
246 *neuron_fire_counts.entry(neuron_id).or_insert(0) += 1;
247
248 let potential = potentials.get(i).copied().unwrap_or(0.0);
249 total_membrane_potential += potential;
250 potential_samples += 1;
251
252 spike_history.push(json!({
253 "burst": current_burst,
254 "neuron_id": neuron_id,
255 "potential": potential
256 }));
257 }
258 }
259 }
260
261 let end_burst = runtime_service
262 .get_burst_count()
263 .await
264 .map_err(|e| ApiError::internal(format!("Failed to get burst count: {}", e)))?;
265
266 let total_spikes = spike_history.len();
267 let firing_rate_hz = if params.duration > 0.0 {
268 total_spikes as f32 / params.duration
269 } else {
270 0.0
271 };
272
273 let active_neurons: Vec<u32> = neuron_fire_counts.keys().copied().collect();
274 let peak_firing_neuron = neuron_fire_counts
275 .iter()
276 .max_by_key(|(_, &count)| count)
277 .map(|(&neuron_id, _)| neuron_id);
278
279 let avg_membrane_potential = if potential_samples > 0 {
280 total_membrane_potential / potential_samples as f32
281 } else {
282 0.0
283 };
284
285 info!(
286 target: "feagi-api",
287 "Activity monitoring complete: {} spikes, {} active neurons, {:.2} Hz",
288 total_spikes,
289 active_neurons.len(),
290 firing_rate_hz
291 );
292
293 Ok(Json(HashMap::from([
294 ("area_id".to_string(), json!(params.area)),
295 ("area_name".to_string(), json!(area_name)),
296 ("duration_ms".to_string(), json!(params.duration * 1000.0)),
297 (
298 "burst_count_sampled".to_string(),
299 json!(end_burst - start_burst),
300 ),
301 (
302 "firing_statistics".to_string(),
303 json!({
304 "total_spikes": total_spikes,
305 "firing_rate_hz": firing_rate_hz,
306 "active_neurons": active_neurons,
307 "active_neuron_count": active_neurons.len(),
308 "peak_firing_neuron": peak_firing_neuron,
309 "avg_membrane_potential": avg_membrane_potential,
310 }),
311 ),
312 ("spike_history".to_string(), json!(spike_history)),
313 ])))
314}