1use crate::storage::{backend::MetricsBackend, memory_backend::InMemoryBackend};
18use khora_core::telemetry::metrics::{Metric, MetricId, MetricType, MetricsError, MetricsResult};
19use std::sync::Arc;
20
21#[derive(Debug)]
28pub struct MetricsRegistry {
29 backend: Arc<dyn MetricsBackend>,
30}
31
32impl MetricsRegistry {
33 pub fn new() -> Self {
35 Self {
36 backend: Arc::new(InMemoryBackend::new()),
37 }
38 }
39
40 pub fn with_backend(backend: Arc<dyn MetricsBackend>) -> Self {
42 Self { backend }
43 }
44
45 pub fn register_counter(
47 &self,
48 namespace: impl Into<String>,
49 name: impl Into<String>,
50 description: impl Into<String>,
51 ) -> MetricsResult<CounterHandle> {
52 let id = MetricId::new(namespace, name);
53 let metric = Metric::new_counter(id.clone(), description, 0);
54 self.backend.put_metric(metric)?;
55 Ok(CounterHandle::new(id, self.backend.clone()))
56 }
57
58 pub fn register_counter_with_labels(
60 &self,
61 namespace: impl Into<String>,
62 name: impl Into<String>,
63 description: impl Into<String>,
64 labels: Vec<(String, String)>,
65 ) -> MetricsResult<CounterHandle> {
66 let mut id = MetricId::new(namespace, name);
67 for (key, value) in labels {
68 id = id.with_label(key, value);
69 }
70 let metric = Metric::new_counter(id.clone(), description, 0);
71 self.backend.put_metric(metric)?;
72 Ok(CounterHandle::new(id, self.backend.clone()))
73 }
74
75 pub fn register_gauge(
77 &self,
78 namespace: impl Into<String>,
79 name: impl Into<String>,
80 description: impl Into<String>,
81 unit: impl Into<String>,
82 ) -> MetricsResult<GaugeHandle> {
83 let id = MetricId::new(namespace, name);
84 let metric = Metric::new_gauge(id.clone(), description, unit, 0.0);
85 self.backend.put_metric(metric)?;
86 Ok(GaugeHandle::new(id, self.backend.clone()))
87 }
88
89 pub fn register_gauge_with_labels(
91 &self,
92 namespace: impl Into<String>,
93 name: impl Into<String>,
94 description: impl Into<String>,
95 unit: impl Into<String>,
96 labels: Vec<(String, String)>,
97 ) -> MetricsResult<GaugeHandle> {
98 let mut id = MetricId::new(namespace, name);
99 for (key, value) in labels {
100 id = id.with_label(key, value);
101 }
102 let metric = Metric::new_gauge(id.clone(), description, unit, 0.0);
103 self.backend.put_metric(metric)?;
104 Ok(GaugeHandle::new(id, self.backend.clone()))
105 }
106
107 pub fn register_histogram(
109 &self,
110 namespace: impl Into<String>,
111 name: impl Into<String>,
112 description: impl Into<String>,
113 unit: impl Into<String>,
114 buckets: Vec<f64>,
115 ) -> MetricsResult<HistogramHandle> {
116 let id = MetricId::new(namespace, name);
117 let metric = Metric::new_histogram(id.clone(), description, unit, buckets);
118 self.backend.put_metric(metric)?;
119 Ok(HistogramHandle::new(id, self.backend.clone()))
120 }
121
122 pub fn get_metric(&self, id: &MetricId) -> MetricsResult<Metric> {
124 self.backend.get_metric(id)
125 }
126
127 pub fn contains_metric(&self, id: &MetricId) -> bool {
129 self.backend.contains_metric(id)
130 }
131
132 pub fn get_namespace_metrics(&self, namespace: &str) -> Vec<Metric> {
134 if let Some(memory_backend) = self
136 .backend
137 .as_ref()
138 .as_any()
139 .downcast_ref::<InMemoryBackend>()
140 {
141 memory_backend.get_metrics_by_namespace(namespace)
142 } else {
143 self.backend
145 .list_all_metrics()
146 .into_iter()
147 .filter(|m| m.metadata.id.namespace == namespace)
148 .collect()
149 }
150 }
151
152 pub fn get_all_counters(&self) -> Vec<Metric> {
154 if let Some(memory_backend) = self
155 .backend
156 .as_ref()
157 .as_any()
158 .downcast_ref::<InMemoryBackend>()
159 {
160 memory_backend.get_metrics_by_type(MetricType::Counter)
161 } else {
162 self.backend
163 .list_all_metrics()
164 .into_iter()
165 .filter(|m| m.metadata.metric_type == MetricType::Counter)
166 .collect()
167 }
168 }
169
170 pub fn get_all_gauges(&self) -> Vec<Metric> {
172 if let Some(memory_backend) = self
173 .backend
174 .as_ref()
175 .as_any()
176 .downcast_ref::<InMemoryBackend>()
177 {
178 memory_backend.get_metrics_by_type(MetricType::Gauge)
179 } else {
180 self.backend
181 .list_all_metrics()
182 .into_iter()
183 .filter(|m| m.metadata.metric_type == MetricType::Gauge)
184 .collect()
185 }
186 }
187
188 pub fn metric_count(&self) -> usize {
190 self.backend.metric_count()
191 }
192
193 pub fn clear_all(&self) -> MetricsResult<()> {
195 self.backend.clear_all()
196 }
197
198 pub fn backend(&self) -> &Arc<dyn MetricsBackend> {
200 &self.backend
201 }
202}
203
204impl Default for MetricsRegistry {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210#[derive(Debug, Clone)]
212pub struct CounterHandle {
213 id: MetricId,
214 backend: Arc<dyn MetricsBackend>,
215}
216
217impl CounterHandle {
218 fn new(id: MetricId, backend: Arc<dyn MetricsBackend>) -> Self {
219 Self { id, backend }
220 }
221
222 pub fn increment(&self) -> MetricsResult<u64> {
224 self.backend.increment_counter(&self.id, 1)
225 }
226
227 pub fn increment_by(&self, amount: u64) -> MetricsResult<u64> {
229 self.backend.increment_counter(&self.id, amount)
230 }
231
232 pub fn get(&self) -> MetricsResult<u64> {
234 let metric = self.backend.get_metric(&self.id)?;
235 metric
236 .value
237 .as_counter()
238 .ok_or_else(|| MetricsError::TypeMismatch {
239 expected: MetricType::Counter,
240 found: metric.value.metric_type(),
241 })
242 }
243
244 pub fn id(&self) -> &MetricId {
246 &self.id
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct GaugeHandle {
253 id: MetricId,
254 backend: Arc<dyn MetricsBackend>,
255}
256
257impl GaugeHandle {
258 fn new(id: MetricId, backend: Arc<dyn MetricsBackend>) -> Self {
259 Self { id, backend }
260 }
261
262 pub fn set(&self, value: f64) -> MetricsResult<()> {
264 self.backend.set_gauge(&self.id, value)
265 }
266
267 pub fn add(&self, delta: f64) -> MetricsResult<f64> {
269 let current = self.get()?;
270 let new_value = current + delta;
271 self.set(new_value)?;
272 Ok(new_value)
273 }
274
275 pub fn sub(&self, delta: f64) -> MetricsResult<f64> {
277 self.add(-delta)
278 }
279
280 pub fn get(&self) -> MetricsResult<f64> {
282 let metric = self.backend.get_metric(&self.id)?;
283 metric
284 .value
285 .as_gauge()
286 .ok_or_else(|| MetricsError::TypeMismatch {
287 expected: MetricType::Gauge,
288 found: metric.value.metric_type(),
289 })
290 }
291
292 pub fn id(&self) -> &MetricId {
294 &self.id
295 }
296}
297
298#[derive(Debug, Clone)]
300pub struct HistogramHandle {
301 id: MetricId,
302 backend: Arc<dyn MetricsBackend>,
303}
304
305impl HistogramHandle {
306 fn new(id: MetricId, backend: Arc<dyn MetricsBackend>) -> Self {
307 Self { id, backend }
308 }
309
310 pub fn observe(&self, value: f64) -> MetricsResult<()> {
312 self.backend.record_histogram_sample(&self.id, value)
313 }
314
315 pub fn id(&self) -> &MetricId {
317 &self.id
318 }
319
320 pub fn get_metric(&self) -> MetricsResult<Metric> {
322 self.backend.get_metric(&self.id)
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_registry_creation() {
332 let registry = MetricsRegistry::new();
333 assert_eq!(registry.metric_count(), 0);
334 }
335
336 #[test]
337 fn test_counter_registration_and_operations() {
338 let registry = MetricsRegistry::new();
339
340 let counter = registry
341 .register_counter("engine", "frame_count", "Total number of frames rendered")
342 .unwrap();
343
344 assert_eq!(counter.increment().unwrap(), 1);
346 assert_eq!(counter.increment_by(5).unwrap(), 6);
347 assert_eq!(counter.get().unwrap(), 6);
348
349 assert!(registry.contains_metric(counter.id()));
351 assert_eq!(registry.metric_count(), 1);
352 }
353
354 #[test]
355 fn test_gauge_registration_and_operations() {
356 let registry = MetricsRegistry::new();
357
358 let gauge = registry
359 .register_gauge("memory", "heap_usage", "Current heap usage", "MB")
360 .unwrap();
361
362 gauge.set(100.5).unwrap();
364 assert_eq!(gauge.get().unwrap(), 100.5);
365
366 assert_eq!(gauge.add(50.0).unwrap(), 150.5);
367 assert_eq!(gauge.sub(25.0).unwrap(), 125.5);
368
369 assert!(registry.contains_metric(gauge.id()));
371 }
372
373 #[test]
374 fn test_histogram_registration_and_operations() {
375 let registry = MetricsRegistry::new();
376
377 let histogram = registry
378 .register_histogram(
379 "renderer",
380 "frame_time",
381 "Frame rendering time distribution",
382 "ms",
383 vec![1.0, 5.0, 10.0, 50.0, 100.0],
384 )
385 .unwrap();
386
387 histogram.observe(2.5).unwrap();
389 histogram.observe(15.0).unwrap();
390 histogram.observe(75.0).unwrap();
391
392 assert!(registry.contains_metric(histogram.id()));
394
395 let metric = histogram.get_metric().unwrap();
396 if let khora_core::telemetry::metrics::MetricValue::Histogram { samples, .. } = metric.value
397 {
398 assert_eq!(samples.len(), 3);
399 assert!(samples.contains(&2.5));
400 assert!(samples.contains(&15.0));
401 assert!(samples.contains(&75.0));
402 } else {
403 panic!("Expected histogram metric");
404 }
405 }
406
407 #[test]
408 fn test_metrics_with_labels() {
409 let registry = MetricsRegistry::new();
410
411 let counter = registry
412 .register_counter_with_labels(
413 "renderer",
414 "triangles_rendered",
415 "Number of triangles rendered",
416 vec![
417 ("quality".to_string(), "high".to_string()),
418 ("pass".to_string(), "main".to_string()),
419 ],
420 )
421 .unwrap();
422
423 counter.increment_by(1000).unwrap();
424
425 let id_str = counter.id().to_string_formatted();
426 assert!(id_str.contains("quality=high"));
427 assert!(id_str.contains("pass=main"));
428 }
429
430 #[test]
431 fn test_namespace_filtering() {
432 let registry = MetricsRegistry::new();
433
434 registry
435 .register_counter("engine", "frames", "Frame count")
436 .unwrap();
437 registry
438 .register_counter("engine", "updates", "Update count")
439 .unwrap();
440 registry
441 .register_counter("renderer", "draws", "Draw calls")
442 .unwrap();
443 registry
444 .register_gauge("memory", "heap", "Heap usage", "MB")
445 .unwrap();
446
447 let engine_metrics = registry.get_namespace_metrics("engine");
448 assert_eq!(engine_metrics.len(), 2);
449
450 let renderer_metrics = registry.get_namespace_metrics("renderer");
451 assert_eq!(renderer_metrics.len(), 1);
452
453 let memory_metrics = registry.get_namespace_metrics("memory");
454 assert_eq!(memory_metrics.len(), 1);
455 }
456
457 #[test]
458 fn test_type_filtering() {
459 let registry = MetricsRegistry::new();
460
461 registry
462 .register_counter("test", "c1", "Counter 1")
463 .unwrap();
464 registry
465 .register_counter("test", "c2", "Counter 2")
466 .unwrap();
467 registry
468 .register_gauge("test", "g1", "Gauge 1", "unit")
469 .unwrap();
470
471 let counters = registry.get_all_counters();
472 assert_eq!(counters.len(), 2);
473
474 let gauges = registry.get_all_gauges();
475 assert_eq!(gauges.len(), 1);
476 }
477
478 #[test]
479 fn test_clear_all() {
480 let registry = MetricsRegistry::new();
481
482 registry
483 .register_counter("test", "counter", "Test counter")
484 .unwrap();
485 registry
486 .register_gauge("test", "gauge", "Test gauge", "unit")
487 .unwrap();
488
489 assert_eq!(registry.metric_count(), 2);
490
491 registry.clear_all().unwrap();
492 assert_eq!(registry.metric_count(), 0);
493 }
494}