effect_rs/
metrics.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4#[derive(Debug, Clone, Hash, Eq, PartialEq)]
5pub struct MetricLabel {
6    pub key: String,
7    pub value: String,
8}
9
10impl MetricLabel {
11    pub fn new(key: &str, value: &str) -> Self {
12        Self {
13            key: key.to_string(),
14            value: value.to_string(),
15        }
16    }
17}
18
19#[derive(Debug, Clone, Hash, Eq, PartialEq)]
20pub struct MetricKey {
21    pub name: String,
22    pub labels: Vec<MetricLabel>,
23}
24
25impl MetricKey {
26    pub fn new(name: &str, labels: Vec<MetricLabel>) -> Self {
27        Self {
28            name: name.to_string(),
29            labels,
30        }
31    }
32}
33
34// Instruments
35
36pub trait Metric: Send + Sync {
37    fn as_any(&self) -> &dyn std::any::Any;
38}
39
40pub struct Counter {
41    value: std::sync::atomic::AtomicU64,
42}
43
44impl Default for Counter {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl Counter {
51    pub fn new() -> Self {
52        Self {
53            value: std::sync::atomic::AtomicU64::new(0),
54        }
55    }
56
57    pub fn increment(&self, amount: u64) {
58        self.value
59            .fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
60    }
61
62    pub fn get(&self) -> u64 {
63        self.value.load(std::sync::atomic::Ordering::Relaxed)
64    }
65}
66
67impl Metric for Counter {
68    fn as_any(&self) -> &dyn std::any::Any {
69        self
70    }
71}
72
73pub struct Gauge {
74    value: std::sync::atomic::AtomicI64,
75}
76
77impl Default for Gauge {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl Gauge {
84    pub fn new() -> Self {
85        Self {
86            value: std::sync::atomic::AtomicI64::new(0),
87        }
88    }
89
90    pub fn set(&self, val: i64) {
91        self.value.store(val, std::sync::atomic::Ordering::Relaxed);
92    }
93
94    pub fn get(&self) -> i64 {
95        self.value.load(std::sync::atomic::Ordering::Relaxed)
96    }
97}
98
99impl Metric for Gauge {
100    fn as_any(&self) -> &dyn std::any::Any {
101        self
102    }
103}
104
105// Simple Histogram with fixed buckets for now
106use std::sync::atomic::{AtomicU64, Ordering};
107
108pub struct Histogram {
109    buckets: Vec<f64>,
110    counts: Vec<AtomicU64>,
111    sum: Mutex<f64>,
112    count: AtomicU64,
113}
114
115impl Histogram {
116    pub fn new(buckets: Vec<f64>) -> Self {
117        let len = buckets.len() + 1;
118        let mut counts = Vec::with_capacity(len);
119        for _ in 0..len {
120            counts.push(AtomicU64::new(0));
121        }
122
123        Self {
124            buckets,
125            counts,
126            sum: Mutex::new(0.0),
127            count: AtomicU64::new(0),
128        }
129    }
130
131    pub fn record(&self, value: f64) {
132        self.count.fetch_add(1, Ordering::Relaxed);
133
134        // Sum still needs lock for float
135        if let Ok(mut g) = self.sum.lock() {
136            *g += value;
137        }
138
139        // Find bucket
140        let mut idx = self.buckets.len();
141        for (i, boundary) in self.buckets.iter().enumerate() {
142            if value <= *boundary {
143                idx = i;
144                break;
145            }
146        }
147
148        // Lock-free increment
149        self.counts[idx].fetch_add(1, Ordering::Relaxed);
150    }
151}
152
153impl Metric for Histogram {
154    fn as_any(&self) -> &dyn std::any::Any {
155        self
156    }
157}
158
159// Registry
160pub struct MetricRegistry {
161    metrics: Mutex<HashMap<MetricKey, Arc<dyn Metric>>>,
162}
163
164impl Default for MetricRegistry {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170impl MetricRegistry {
171    pub fn new() -> Self {
172        Self {
173            metrics: Mutex::new(HashMap::new()),
174        }
175    }
176
177    pub fn counter(&self, name: &str, labels: Vec<MetricLabel>) -> Arc<Counter> {
178        let _key = MetricKey::new(name, labels);
179        let _map = self.metrics.lock().unwrap();
180
181        // Remove the generic registry for now to avoid complexity.
182        todo!()
183    }
184
185    // Better inner impl: use entry API fully
186}
187
188// Simplified Global Registry
189// To avoid strict typing issues in the mock, usage:
190// `get_counter` returns new or existing.
191// We need to cast `Arc<dyn Metric>` to `Arc<Counter>`.
192// Rust `downcast-rs` crate helps, or `std::any::Any` on Arc.
193// `Arc<dyn Any + Send + Sync>` works.
194
195pub struct SimpleRegistry {
196    counters: Mutex<HashMap<MetricKey, Arc<Counter>>>,
197    gauges: Mutex<HashMap<MetricKey, Arc<Gauge>>>,
198    histograms: Mutex<HashMap<MetricKey, Arc<Histogram>>>,
199}
200
201impl Default for SimpleRegistry {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207impl SimpleRegistry {
208    pub fn new() -> Self {
209        Self {
210            counters: Mutex::new(HashMap::new()),
211            gauges: Mutex::new(HashMap::new()),
212            histograms: Mutex::new(HashMap::new()),
213        }
214    }
215
216    pub fn get_counter(&self, name: &str, labels: Vec<MetricLabel>) -> Arc<Counter> {
217        let key = MetricKey::new(name, labels);
218        let mut map = self.counters.lock().unwrap();
219        map.entry(key)
220            .or_insert_with(|| Arc::new(Counter::new()))
221            .clone()
222    }
223
224    pub fn get_gauge(&self, name: &str, labels: Vec<MetricLabel>) -> Arc<Gauge> {
225        let key = MetricKey::new(name, labels);
226        let mut map = self.gauges.lock().unwrap();
227        map.entry(key)
228            .or_insert_with(|| Arc::new(Gauge::new()))
229            .clone()
230    }
231
232    pub fn get_histogram(
233        &self,
234        name: &str,
235        labels: Vec<MetricLabel>,
236        buckets: Vec<f64>,
237    ) -> Arc<Histogram> {
238        let key = MetricKey::new(name, labels);
239        let mut map = self.histograms.lock().unwrap();
240        // If exists, ignore buckets arg? Yes.
241        map.entry(key)
242            .or_insert_with(|| Arc::new(Histogram::new(buckets)))
243            .clone()
244    }
245}
246
247lazy_static::lazy_static! {
248    pub static ref REGISTRY: SimpleRegistry = SimpleRegistry::new();
249}