khora_telemetry/storage/
backend.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Storage backends for telemetry data.
16
17use khora_core::telemetry::{
18    metrics::MetricType, Metric, MetricId, MetricValue, MetricsError, MetricsResult,
19};
20use std::fmt::Debug;
21
22/// Trait defining the interface for metrics storage backends
23pub trait MetricsBackend: Send + Sync + Debug + 'static {
24    /// Get a reference to this object as Any for downcasting
25    fn as_any(&self) -> &dyn std::any::Any;
26    /// Store or update a metric
27    fn put_metric(&self, metric: Metric) -> MetricsResult<()>;
28
29    /// Retrieve a metric by ID
30    fn get_metric(&self, id: &MetricId) -> MetricsResult<Metric>;
31
32    /// Check if a metric exists
33    fn contains_metric(&self, id: &MetricId) -> bool;
34
35    /// Remove a metric
36    fn remove_metric(&self, id: &MetricId) -> MetricsResult<()>;
37
38    /// Get all metric IDs currently stored
39    fn list_metric_ids(&self) -> Vec<MetricId>;
40
41    /// Get all metrics (potentially expensive operation)
42    fn list_all_metrics(&self) -> Vec<Metric>;
43
44    /// Clear all metrics
45    fn clear_all(&self) -> MetricsResult<()>;
46
47    /// Get the number of metrics stored
48    fn metric_count(&self) -> usize;
49
50    // Convenience methods for common operations
51
52    /// Increment a counter by the given amount
53    fn increment_counter(&self, id: &MetricId, delta: u64) -> MetricsResult<u64> {
54        let mut metric = self.get_metric(id)?;
55
56        match metric.value {
57            MetricValue::Counter(ref mut value) => {
58                *value = value.saturating_add(delta);
59                metric.metadata.update_timestamp();
60                let result = *value;
61                self.put_metric(metric)?;
62                Ok(result)
63            }
64            _ => Err(MetricsError::TypeMismatch {
65                expected: MetricType::Counter,
66                found: metric.value.metric_type(),
67            }),
68        }
69    }
70
71    /// Set a gauge value
72    fn set_gauge(&self, id: &MetricId, value: f64) -> MetricsResult<()> {
73        let mut metric = self.get_metric(id)?;
74
75        match metric.value {
76            MetricValue::Gauge(ref mut gauge_value) => {
77                *gauge_value = value;
78                metric.metadata.update_timestamp();
79                self.put_metric(metric)?;
80                Ok(())
81            }
82            _ => Err(MetricsError::TypeMismatch {
83                expected: MetricType::Gauge,
84                found: metric.value.metric_type(),
85            }),
86        }
87    }
88
89    /// Add a sample to a histogram
90    fn record_histogram_sample(&self, id: &MetricId, sample: f64) -> MetricsResult<()> {
91        let mut metric = self.get_metric(id)?;
92
93        match metric.value {
94            MetricValue::Histogram {
95                ref mut samples,
96                ref bucket_bounds,
97                ref mut bucket_counts,
98            } => {
99                // Add sample to the list
100                samples.push(sample);
101
102                // Update bucket counts
103                for (i, &bound) in bucket_bounds.iter().enumerate() {
104                    if sample <= bound {
105                        bucket_counts[i] += 1;
106                    }
107                }
108
109                metric.metadata.update_timestamp();
110                self.put_metric(metric)?;
111                Ok(())
112            }
113            _ => Err(MetricsError::TypeMismatch {
114                expected: MetricType::Histogram,
115                found: metric.value.metric_type(),
116            }),
117        }
118    }
119}
120
121/// Statistics about the metrics backend
122#[derive(Debug, Clone)]
123pub struct BackendStats {
124    /// Total number of metrics stored
125    pub total_metrics: usize,
126    /// Number of counters
127    pub counter_count: usize,
128    /// Number of gauges
129    pub gauge_count: usize,
130    /// Number of histograms
131    pub histogram_count: usize,
132    /// Approximate memory usage in bytes
133    pub estimated_memory_bytes: usize,
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use khora_core::telemetry::metrics::{Metric, MetricId};
140
141    // Mock backend for testing
142    #[derive(Debug)]
143    struct MockBackend;
144
145    impl MetricsBackend for MockBackend {
146        fn as_any(&self) -> &dyn std::any::Any {
147            self
148        }
149
150        fn put_metric(&self, _metric: Metric) -> MetricsResult<()> {
151            Ok(())
152        }
153
154        fn get_metric(&self, id: &MetricId) -> MetricsResult<Metric> {
155            Err(MetricsError::MetricNotFound(id.clone()))
156        }
157
158        fn contains_metric(&self, _id: &MetricId) -> bool {
159            false
160        }
161
162        fn remove_metric(&self, id: &MetricId) -> MetricsResult<()> {
163            Err(MetricsError::MetricNotFound(id.clone()))
164        }
165
166        fn list_metric_ids(&self) -> Vec<MetricId> {
167            Vec::new()
168        }
169
170        fn list_all_metrics(&self) -> Vec<Metric> {
171            Vec::new()
172        }
173
174        fn clear_all(&self) -> MetricsResult<()> {
175            Ok(())
176        }
177
178        fn metric_count(&self) -> usize {
179            0
180        }
181    }
182
183    #[test]
184    fn test_backend_trait_compilation() {
185        let backend = MockBackend;
186        assert_eq!(backend.metric_count(), 0);
187        assert!(!backend.contains_metric(&MetricId::new("test", "metric")));
188    }
189}