1use std::sync::atomic::{AtomicU64, Ordering};
4
5pub struct Metrics {
7 pub writes_total: AtomicU64,
9 pub reads_total: AtomicU64,
11 pub deletes_total: AtomicU64,
13 pub cache_hits: AtomicU64,
15 pub cache_misses: AtomicU64,
17 pub wal_syncs: AtomicU64,
19 pub context_assemblies: AtomicU64,
21 pub mql_queries_parsed: AtomicU64,
23 pub contradictions_detected: AtomicU64,
25 pub beliefs_propagated: AtomicU64,
27 pub speculative_hits: AtomicU64,
29 pub speculative_misses: AtomicU64,
31 pub write_latency_us_sum: AtomicU64,
33 pub write_latency_count: AtomicU64,
35 pub read_latency_us_sum: AtomicU64,
37 pub read_latency_count: AtomicU64,
39}
40
41#[derive(Debug, Clone)]
43pub struct MetricsSnapshot {
44 pub writes_total: u64,
46 pub reads_total: u64,
48 pub deletes_total: u64,
50 pub cache_hit_rate: f64,
52 pub avg_write_latency_us: f64,
54 pub avg_read_latency_us: f64,
56 pub contradictions_detected: u64,
58 pub speculative_hit_rate: f64,
60}
61
62impl Metrics {
63 pub fn new() -> Self {
65 Self {
66 writes_total: AtomicU64::new(0),
67 reads_total: AtomicU64::new(0),
68 deletes_total: AtomicU64::new(0),
69 cache_hits: AtomicU64::new(0),
70 cache_misses: AtomicU64::new(0),
71 wal_syncs: AtomicU64::new(0),
72 context_assemblies: AtomicU64::new(0),
73 mql_queries_parsed: AtomicU64::new(0),
74 contradictions_detected: AtomicU64::new(0),
75 beliefs_propagated: AtomicU64::new(0),
76 speculative_hits: AtomicU64::new(0),
77 speculative_misses: AtomicU64::new(0),
78 write_latency_us_sum: AtomicU64::new(0),
79 write_latency_count: AtomicU64::new(0),
80 read_latency_us_sum: AtomicU64::new(0),
81 read_latency_count: AtomicU64::new(0),
82 }
83 }
84
85 pub fn inc_writes(&self) {
87 self.writes_total.fetch_add(1, Ordering::Relaxed);
88 }
89
90 pub fn inc_reads(&self) {
92 self.reads_total.fetch_add(1, Ordering::Relaxed);
93 }
94
95 pub fn inc_deletes(&self) {
97 self.deletes_total.fetch_add(1, Ordering::Relaxed);
98 }
99
100 pub fn inc_cache_hits(&self) {
102 self.cache_hits.fetch_add(1, Ordering::Relaxed);
103 }
104
105 pub fn inc_cache_misses(&self) {
107 self.cache_misses.fetch_add(1, Ordering::Relaxed);
108 }
109
110 pub fn record_write_latency(&self, microseconds: u64) {
112 self.write_latency_us_sum
113 .fetch_add(microseconds, Ordering::Relaxed);
114 self.write_latency_count.fetch_add(1, Ordering::Relaxed);
115 }
116
117 pub fn record_read_latency(&self, microseconds: u64) {
119 self.read_latency_us_sum
120 .fetch_add(microseconds, Ordering::Relaxed);
121 self.read_latency_count.fetch_add(1, Ordering::Relaxed);
122 }
123
124 pub fn export_prometheus(&self) -> String {
126 let mut out = String::with_capacity(2048);
127
128 let counters = [
129 (
130 "mentedb_writes_total",
131 "Total write operations",
132 &self.writes_total,
133 ),
134 (
135 "mentedb_reads_total",
136 "Total read operations",
137 &self.reads_total,
138 ),
139 (
140 "mentedb_deletes_total",
141 "Total delete operations",
142 &self.deletes_total,
143 ),
144 (
145 "mentedb_cache_hits_total",
146 "Total cache hits",
147 &self.cache_hits,
148 ),
149 (
150 "mentedb_cache_misses_total",
151 "Total cache misses",
152 &self.cache_misses,
153 ),
154 (
155 "mentedb_wal_syncs_total",
156 "Total WAL sync operations",
157 &self.wal_syncs,
158 ),
159 (
160 "mentedb_context_assemblies_total",
161 "Total context assemblies",
162 &self.context_assemblies,
163 ),
164 (
165 "mentedb_mql_queries_parsed_total",
166 "Total MQL queries parsed",
167 &self.mql_queries_parsed,
168 ),
169 (
170 "mentedb_contradictions_detected_total",
171 "Total contradictions detected",
172 &self.contradictions_detected,
173 ),
174 (
175 "mentedb_beliefs_propagated_total",
176 "Total beliefs propagated",
177 &self.beliefs_propagated,
178 ),
179 (
180 "mentedb_speculative_hits_total",
181 "Speculative cache hits",
182 &self.speculative_hits,
183 ),
184 (
185 "mentedb_speculative_misses_total",
186 "Speculative cache misses",
187 &self.speculative_misses,
188 ),
189 ];
190
191 for (name, help, counter) in &counters {
192 out.push_str(&format!(
193 "# HELP {name} {help}\n# TYPE {name} counter\n{name} {}\n",
194 counter.load(Ordering::Relaxed)
195 ));
196 }
197
198 let wl_sum = self.write_latency_us_sum.load(Ordering::Relaxed);
200 let wl_count = self.write_latency_count.load(Ordering::Relaxed);
201 out.push_str(&format!(
202 "# HELP mentedb_write_latency_us Write latency in microseconds\n\
203 # TYPE mentedb_write_latency_us summary\n\
204 mentedb_write_latency_us_sum {wl_sum}\n\
205 mentedb_write_latency_us_count {wl_count}\n"
206 ));
207
208 let rl_sum = self.read_latency_us_sum.load(Ordering::Relaxed);
209 let rl_count = self.read_latency_count.load(Ordering::Relaxed);
210 out.push_str(&format!(
211 "# HELP mentedb_read_latency_us Read latency in microseconds\n\
212 # TYPE mentedb_read_latency_us summary\n\
213 mentedb_read_latency_us_sum {rl_sum}\n\
214 mentedb_read_latency_us_count {rl_count}\n"
215 ));
216
217 out
218 }
219
220 pub fn export_json(&self) -> String {
222 let snap = self.snapshot();
223 format!(
224 concat!(
225 "{{",
226 "\"writes_total\":{},",
227 "\"reads_total\":{},",
228 "\"deletes_total\":{},",
229 "\"cache_hit_rate\":{:.4},",
230 "\"avg_write_latency_us\":{:.2},",
231 "\"avg_read_latency_us\":{:.2},",
232 "\"contradictions_detected\":{},",
233 "\"speculative_hit_rate\":{:.4},",
234 "\"wal_syncs\":{},",
235 "\"context_assemblies\":{},",
236 "\"mql_queries_parsed\":{},",
237 "\"beliefs_propagated\":{}",
238 "}}"
239 ),
240 snap.writes_total,
241 snap.reads_total,
242 snap.deletes_total,
243 snap.cache_hit_rate,
244 snap.avg_write_latency_us,
245 snap.avg_read_latency_us,
246 snap.contradictions_detected,
247 snap.speculative_hit_rate,
248 self.wal_syncs.load(Ordering::Relaxed),
249 self.context_assemblies.load(Ordering::Relaxed),
250 self.mql_queries_parsed.load(Ordering::Relaxed),
251 self.beliefs_propagated.load(Ordering::Relaxed),
252 )
253 }
254
255 pub fn snapshot(&self) -> MetricsSnapshot {
257 let hits = self.cache_hits.load(Ordering::Relaxed);
258 let misses = self.cache_misses.load(Ordering::Relaxed);
259 let cache_total = hits + misses;
260 let cache_hit_rate = if cache_total > 0 {
261 hits as f64 / cache_total as f64
262 } else {
263 0.0
264 };
265
266 let wl_sum = self.write_latency_us_sum.load(Ordering::Relaxed);
267 let wl_count = self.write_latency_count.load(Ordering::Relaxed);
268 let avg_write = if wl_count > 0 {
269 wl_sum as f64 / wl_count as f64
270 } else {
271 0.0
272 };
273
274 let rl_sum = self.read_latency_us_sum.load(Ordering::Relaxed);
275 let rl_count = self.read_latency_count.load(Ordering::Relaxed);
276 let avg_read = if rl_count > 0 {
277 rl_sum as f64 / rl_count as f64
278 } else {
279 0.0
280 };
281
282 let spec_hits = self.speculative_hits.load(Ordering::Relaxed);
283 let spec_misses = self.speculative_misses.load(Ordering::Relaxed);
284 let spec_total = spec_hits + spec_misses;
285 let speculative_hit_rate = if spec_total > 0 {
286 spec_hits as f64 / spec_total as f64
287 } else {
288 0.0
289 };
290
291 MetricsSnapshot {
292 writes_total: self.writes_total.load(Ordering::Relaxed),
293 reads_total: self.reads_total.load(Ordering::Relaxed),
294 deletes_total: self.deletes_total.load(Ordering::Relaxed),
295 cache_hit_rate,
296 avg_write_latency_us: avg_write,
297 avg_read_latency_us: avg_read,
298 contradictions_detected: self.contradictions_detected.load(Ordering::Relaxed),
299 speculative_hit_rate,
300 }
301 }
302}
303
304impl Default for Metrics {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn increment_and_read() {
316 let m = Metrics::new();
317 m.inc_writes();
318 m.inc_writes();
319 m.inc_reads();
320 m.inc_cache_hits();
321 m.inc_cache_hits();
322 m.inc_cache_misses();
323 m.record_write_latency(100);
324 m.record_write_latency(200);
325
326 let snap = m.snapshot();
327 assert_eq!(snap.writes_total, 2);
328 assert_eq!(snap.reads_total, 1);
329 assert!((snap.cache_hit_rate - 2.0 / 3.0).abs() < 0.01);
330 assert!((snap.avg_write_latency_us - 150.0).abs() < 0.01);
331 }
332
333 #[test]
334 fn prometheus_format() {
335 let m = Metrics::new();
336 m.inc_writes();
337 m.inc_reads();
338 m.inc_reads();
339
340 let prom = m.export_prometheus();
341 assert!(prom.contains("# HELP mentedb_writes_total Total write operations"));
342 assert!(prom.contains("# TYPE mentedb_writes_total counter"));
343 assert!(prom.contains("mentedb_writes_total 1"));
344 assert!(prom.contains("mentedb_reads_total 2"));
345 }
346
347 #[test]
348 fn json_format() {
349 let m = Metrics::new();
350 m.inc_writes();
351 m.inc_deletes();
352 m.record_read_latency(500);
353
354 let json = m.export_json();
355 assert!(json.starts_with('{'));
357 assert!(json.ends_with('}'));
358 assert!(json.contains("\"writes_total\":1"));
359 assert!(json.contains("\"deletes_total\":1"));
360 assert!(json.contains("\"avg_read_latency_us\":500.00"));
361 }
362}