brainos_httpadapter/
metrics.rs1use std::sync::atomic::{AtomicU64, Ordering};
4
5#[derive(Default)]
7pub struct GaugeCache {
8 pub fact_count: i64,
9 pub episode_count: i64,
10 pub last_updated_ms: u64,
11}
12
13const GAUGE_TTL_MS: u64 = 10_000;
14
15#[derive(Default)]
17pub struct Metrics {
18 pub signals_total: AtomicU64,
20 pub signals_ok: AtomicU64,
22 pub signals_error: AtomicU64,
24 pub search_total: AtomicU64,
26 pub facts_total: AtomicU64,
28 pub signals_latency_ms_total: AtomicU64,
30 pub gauge_cache: std::sync::Mutex<GaugeCache>,
32}
33
34impl Metrics {
35 pub fn refresh_gauges(&self, processor: &signal::SignalProcessor) {
37 let now_ms = std::time::SystemTime::now()
38 .duration_since(std::time::UNIX_EPOCH)
39 .unwrap_or_default()
40 .as_millis() as u64;
41 let mut guard = self.gauge_cache.lock().unwrap_or_else(|e| e.into_inner());
42 if now_ms.saturating_sub(guard.last_updated_ms) < GAUGE_TTL_MS {
43 return;
44 }
45 let pool = processor.episodic().pool();
46 let fact_count = pool
47 .with_conn(|conn| {
48 let mut stmt = conn.prepare("SELECT COUNT(*) FROM semantic_facts")?;
49 let row: i64 = stmt.query_row([], |r| r.get(0))?;
50 Ok(row)
51 })
52 .unwrap_or(0);
53 let episode_count = pool
54 .with_conn(|conn| {
55 let mut stmt = conn.prepare("SELECT COUNT(*) FROM episodes")?;
56 let row: i64 = stmt.query_row([], |r| r.get(0))?;
57 Ok(row)
58 })
59 .unwrap_or(0);
60 guard.fact_count = fact_count;
61 guard.episode_count = episode_count;
62 guard.last_updated_ms = now_ms;
63 }
64
65 pub fn render(&self, subsystems: &metrics::SubsystemMetrics) -> String {
67 let signals_total = self.signals_total.load(Ordering::Relaxed);
68 let signals_ok = self.signals_ok.load(Ordering::Relaxed);
69 let signals_error = self.signals_error.load(Ordering::Relaxed);
70 let search_total = self.search_total.load(Ordering::Relaxed);
71 let facts_total = self.facts_total.load(Ordering::Relaxed);
72 let latency_ms = self.signals_latency_ms_total.load(Ordering::Relaxed);
73 let sub = subsystems.snapshot();
74 let (fact_count, episode_count) = {
75 let g = self.gauge_cache.lock().unwrap_or_else(|e| e.into_inner());
76 (g.fact_count, g.episode_count)
77 };
78
79 format!(
80 "# HELP brain_signals_total Total signal requests received.\n\
81 # TYPE brain_signals_total counter\n\
82 brain_signals_total {signals_total}\n\
83 # HELP brain_signals_ok_total Successful signal requests.\n\
84 # TYPE brain_signals_ok_total counter\n\
85 brain_signals_ok_total {signals_ok}\n\
86 # HELP brain_signals_error_total Failed signal requests (5xx).\n\
87 # TYPE brain_signals_error_total counter\n\
88 brain_signals_error_total {signals_error}\n\
89 # HELP brain_search_total Total memory search requests.\n\
90 # TYPE brain_search_total counter\n\
91 brain_search_total {search_total}\n\
92 # HELP brain_facts_total Total memory facts requests.\n\
93 # TYPE brain_facts_total counter\n\
94 brain_facts_total {facts_total}\n\
95 # HELP brain_signals_latency_ms_total Cumulative signal processing latency in ms.\n\
96 # TYPE brain_signals_latency_ms_total counter\n\
97 brain_signals_latency_ms_total {latency_ms}\n\
98 # HELP brain_facts_count Total semantic facts currently stored.\n\
99 # TYPE brain_facts_count gauge\n\
100 brain_facts_count {fact_count}\n\
101 # HELP brain_episodes_count Total episodes currently stored.\n\
102 # TYPE brain_episodes_count gauge\n\
103 brain_episodes_count {episode_count}\n\
104 # HELP brain_embedding_requests_total Total embedding requests.\n\
105 # TYPE brain_embedding_requests_total counter\n\
106 brain_embedding_requests_total {embed_req}\n\
107 # HELP brain_embedding_fallbacks_total Times deterministic fallback vectors were used.\n\
108 # TYPE brain_embedding_fallbacks_total counter\n\
109 brain_embedding_fallbacks_total {embed_fb}\n\
110 # HELP brain_consolidation_runs_total Number of consolidation cycles completed.\n\
111 # TYPE brain_consolidation_runs_total counter\n\
112 brain_consolidation_runs_total {cons_runs}\n\
113 # HELP brain_consolidation_pruned_total Episodes pruned during consolidation.\n\
114 # TYPE brain_consolidation_pruned_total counter\n\
115 brain_consolidation_pruned_total {cons_pruned}\n\
116 # HELP brain_consolidation_promoted_total Episodes promoted to semantic facts.\n\
117 # TYPE brain_consolidation_promoted_total counter\n\
118 brain_consolidation_promoted_total {cons_promoted}\n\
119 # HELP brain_circuit_open_total Times a circuit breaker opened.\n\
120 # TYPE brain_circuit_open_total counter\n\
121 brain_circuit_open_total {cb_open}\n\
122 # HELP brain_circuit_resets_total Times a circuit breaker reset on success.\n\
123 # TYPE brain_circuit_resets_total counter\n\
124 brain_circuit_resets_total {cb_reset}\n\
125 # HELP brain_intent_classifications_total Total intent classifications.\n\
126 # TYPE brain_intent_classifications_total counter\n\
127 brain_intent_classifications_total {intents}\n\
128 # HELP brain_intent_llm_fallbacks_total Intents resolved via LLM fallback.\n\
129 # TYPE brain_intent_llm_fallbacks_total counter\n\
130 brain_intent_llm_fallbacks_total {intent_llm}\n",
131 embed_req = sub.embedding_requests_total,
132 embed_fb = sub.embedding_fallbacks_total,
133 cons_runs = sub.consolidation_runs_total,
134 cons_pruned = sub.consolidation_pruned_total,
135 cons_promoted = sub.consolidation_promoted_total,
136 cb_open = sub.circuit_open_total,
137 cb_reset = sub.circuit_resets_total,
138 intents = sub.intent_classifications_total,
139 intent_llm = sub.intent_llm_fallbacks_total,
140 )
141 }
142}