Skip to main content

metrics_lib/
registry.rs

1//! Thread-safe registry for storing and retrieving metrics by name.
2//!
3//! The `Registry` uses one `RwLock<HashMap>` per enabled metric type.
4//! Read-heavy workloads (metric *lookups*) proceed with shared read locks and
5//! minimal contention. Write locks are only acquired when a new metric name is
6//! registered, which is expected to be an infrequent, one-time-per-name event.
7
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10
11#[cfg(feature = "count")]
12use crate::Counter;
13#[cfg(feature = "gauge")]
14use crate::Gauge;
15#[cfg(feature = "meter")]
16use crate::RateMeter;
17#[cfg(feature = "timer")]
18use crate::Timer;
19
20/// A thread-safe registry for storing metrics by name.
21///
22/// Stores one `Arc`-wrapped instance per unique metric name for each enabled
23/// metric type. Repeated lookups for the same name return the same `Arc`
24/// (pointer equality holds). Only metric types enabled via Cargo features are
25/// compiled in; attempting to call a method for a disabled type will result in
26/// a compile-time error.
27///
28/// Cache-line aligned to prevent false sharing in concurrent environments.
29#[repr(align(64))]
30pub struct Registry {
31    #[cfg(feature = "count")]
32    counters: RwLock<HashMap<String, Arc<Counter>>>,
33    #[cfg(feature = "gauge")]
34    gauges: RwLock<HashMap<String, Arc<Gauge>>>,
35    #[cfg(feature = "timer")]
36    timers: RwLock<HashMap<String, Arc<Timer>>>,
37    #[cfg(feature = "meter")]
38    rate_meters: RwLock<HashMap<String, Arc<RateMeter>>>,
39}
40
41impl Registry {
42    /// Create a new empty registry.
43    pub fn new() -> Self {
44        Self {
45            #[cfg(feature = "count")]
46            counters: RwLock::new(HashMap::new()),
47            #[cfg(feature = "gauge")]
48            gauges: RwLock::new(HashMap::new()),
49            #[cfg(feature = "timer")]
50            timers: RwLock::new(HashMap::new()),
51            #[cfg(feature = "meter")]
52            rate_meters: RwLock::new(HashMap::new()),
53        }
54    }
55
56    /// Get or create a counter by name.
57    ///
58    /// Requires the `count` feature.
59    #[cfg(feature = "count")]
60    pub fn get_or_create_counter(&self, name: &str) -> Arc<Counter> {
61        // Fast path: try read lock first
62        if let Ok(counters) = self.counters.read() {
63            if let Some(counter) = counters.get(name) {
64                return counter.clone();
65            }
66        }
67
68        // Slow path: write lock to create new counter
69        let mut counters = self.counters.write().unwrap_or_else(|e| e.into_inner());
70        counters
71            .entry(name.to_string())
72            .or_insert_with(|| Arc::new(Counter::new()))
73            .clone()
74    }
75
76    /// Get or create a gauge by name.
77    ///
78    /// Requires the `gauge` feature.
79    #[cfg(feature = "gauge")]
80    pub fn get_or_create_gauge(&self, name: &str) -> Arc<Gauge> {
81        // Fast path: try read lock first
82        if let Ok(gauges) = self.gauges.read() {
83            if let Some(gauge) = gauges.get(name) {
84                return gauge.clone();
85            }
86        }
87
88        // Slow path: write lock to create new gauge
89        let mut gauges = self.gauges.write().unwrap_or_else(|e| e.into_inner());
90        gauges
91            .entry(name.to_string())
92            .or_insert_with(|| Arc::new(Gauge::new()))
93            .clone()
94    }
95
96    /// Get or create a timer by name.
97    ///
98    /// Requires the `timer` feature.
99    #[cfg(feature = "timer")]
100    pub fn get_or_create_timer(&self, name: &str) -> Arc<Timer> {
101        // Fast path: try read lock first
102        if let Ok(timers) = self.timers.read() {
103            if let Some(timer) = timers.get(name) {
104                return timer.clone();
105            }
106        }
107
108        // Slow path: write lock to create new timer
109        let mut timers = self.timers.write().unwrap_or_else(|e| e.into_inner());
110        timers
111            .entry(name.to_string())
112            .or_insert_with(|| Arc::new(Timer::new()))
113            .clone()
114    }
115
116    /// Get or create a rate meter by name.
117    ///
118    /// Requires the `meter` feature.
119    #[cfg(feature = "meter")]
120    pub fn get_or_create_rate_meter(&self, name: &str) -> Arc<RateMeter> {
121        // Fast path: try read lock first
122        if let Ok(rate_meters) = self.rate_meters.read() {
123            if let Some(rate_meter) = rate_meters.get(name) {
124                return rate_meter.clone();
125            }
126        }
127
128        // Slow path: write lock to create new rate meter
129        let mut rate_meters = self.rate_meters.write().unwrap_or_else(|e| e.into_inner());
130        rate_meters
131            .entry(name.to_string())
132            .or_insert_with(|| Arc::new(RateMeter::new()))
133            .clone()
134    }
135
136    /// Get all registered counter names. Requires the `count` feature.
137    #[cfg(feature = "count")]
138    pub fn counter_names(&self) -> Vec<String> {
139        self.counters
140            .read()
141            .unwrap_or_else(|e| e.into_inner())
142            .keys()
143            .cloned()
144            .collect()
145    }
146
147    /// Get all registered gauge names. Requires the `gauge` feature.
148    #[cfg(feature = "gauge")]
149    pub fn gauge_names(&self) -> Vec<String> {
150        self.gauges
151            .read()
152            .unwrap_or_else(|e| e.into_inner())
153            .keys()
154            .cloned()
155            .collect()
156    }
157
158    /// Get all registered timer names. Requires the `timer` feature.
159    #[cfg(feature = "timer")]
160    pub fn timer_names(&self) -> Vec<String> {
161        self.timers
162            .read()
163            .unwrap_or_else(|e| e.into_inner())
164            .keys()
165            .cloned()
166            .collect()
167    }
168
169    /// Get all registered rate meter names. Requires the `meter` feature.
170    #[cfg(feature = "meter")]
171    pub fn rate_meter_names(&self) -> Vec<String> {
172        self.rate_meters
173            .read()
174            .unwrap_or_else(|e| e.into_inner())
175            .keys()
176            .cloned()
177            .collect()
178    }
179
180    /// Get total number of registered metrics across all enabled metric types.
181    pub fn metric_count(&self) -> usize {
182        #[allow(unused_mut)]
183        let mut total = 0;
184        #[cfg(feature = "count")]
185        {
186            total += self
187                .counters
188                .read()
189                .unwrap_or_else(|e| e.into_inner())
190                .len();
191        }
192        #[cfg(feature = "gauge")]
193        {
194            total += self.gauges.read().unwrap_or_else(|e| e.into_inner()).len();
195        }
196        #[cfg(feature = "timer")]
197        {
198            total += self.timers.read().unwrap_or_else(|e| e.into_inner()).len();
199        }
200        #[cfg(feature = "meter")]
201        {
202            total += self
203                .rate_meters
204                .read()
205                .unwrap_or_else(|e| e.into_inner())
206                .len();
207        }
208        total
209    }
210
211    /// Clear all metrics from the registry.
212    pub fn clear(&self) {
213        #[cfg(feature = "count")]
214        self.counters
215            .write()
216            .unwrap_or_else(|e| e.into_inner())
217            .clear();
218        #[cfg(feature = "gauge")]
219        self.gauges
220            .write()
221            .unwrap_or_else(|e| e.into_inner())
222            .clear();
223        #[cfg(feature = "timer")]
224        self.timers
225            .write()
226            .unwrap_or_else(|e| e.into_inner())
227            .clear();
228        #[cfg(feature = "meter")]
229        self.rate_meters
230            .write()
231            .unwrap_or_else(|e| e.into_inner())
232            .clear();
233    }
234}
235
236impl Default for Registry {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242// `Registry` is Send + Sync automatically because `RwLock<HashMap<…>>` is
243// Send + Sync when its contents are Send + Sync. No unsafe impls required.
244
245#[cfg(test)]
246// Registry integration tests require the default metric features.
247#[cfg(all(feature = "count", feature = "gauge", feature = "timer"))]
248mod tests {
249    use super::*;
250    use std::sync::Arc;
251    use std::thread;
252    #[test]
253    fn test_counter_registration() {
254        let registry = Registry::new();
255
256        let counter1 = registry.get_or_create_counter("requests");
257        let counter2 = registry.get_or_create_counter("requests");
258
259        // Should return the same instance
260        assert!(Arc::ptr_eq(&counter1, &counter2));
261        assert_eq!(registry.metric_count(), 1);
262    }
263
264    #[test]
265    fn test_gauge_registration() {
266        let registry = Registry::new();
267
268        let gauge1 = registry.get_or_create_gauge("cpu_usage");
269        let gauge2 = registry.get_or_create_gauge("cpu_usage");
270
271        // Should return the same instance
272        assert!(Arc::ptr_eq(&gauge1, &gauge2));
273        assert_eq!(registry.metric_count(), 1);
274    }
275
276    #[test]
277    fn test_timer_registration() {
278        let registry = Registry::new();
279
280        let timer1 = registry.get_or_create_timer("db_query");
281        let timer2 = registry.get_or_create_timer("db_query");
282
283        // Should return the same instance
284        assert!(Arc::ptr_eq(&timer1, &timer2));
285        assert_eq!(registry.metric_count(), 1);
286    }
287
288    #[test]
289    #[cfg(feature = "meter")]
290    fn test_rate_meter_registration() {
291        let registry = Registry::new();
292
293        let meter1 = registry.get_or_create_rate_meter("api_calls");
294        let meter2 = registry.get_or_create_rate_meter("api_calls");
295
296        // Should return the same instance
297        assert!(Arc::ptr_eq(&meter1, &meter2));
298        assert_eq!(registry.metric_count(), 1);
299    }
300
301    #[test]
302    #[cfg(feature = "meter")]
303    fn test_mixed_metrics() {
304        let registry = Registry::new();
305
306        let _counter = registry.get_or_create_counter("requests");
307        let _gauge = registry.get_or_create_gauge("cpu_usage");
308        let _timer = registry.get_or_create_timer("db_query");
309        let _meter = registry.get_or_create_rate_meter("api_calls");
310
311        assert_eq!(registry.metric_count(), 4);
312        assert_eq!(registry.counter_names().len(), 1);
313        assert_eq!(registry.gauge_names().len(), 1);
314        assert_eq!(registry.timer_names().len(), 1);
315        assert_eq!(registry.rate_meter_names().len(), 1);
316    }
317
318    #[test]
319    fn test_concurrent_access() {
320        let registry = Arc::new(Registry::new());
321        let mut handles = vec![];
322
323        // Spawn multiple threads accessing the same counter
324        for _i in 0..10 {
325            let registry = registry.clone();
326            let handle = thread::spawn(move || {
327                let counter = registry.get_or_create_counter("concurrent_test");
328                counter.inc();
329                counter.get()
330            });
331            handles.push(handle);
332        }
333
334        // Wait for all threads to complete
335        for handle in handles {
336            handle.join().unwrap();
337        }
338
339        // Should have exactly one counter registered
340        assert_eq!(registry.metric_count(), 1);
341        let counter = registry.get_or_create_counter("concurrent_test");
342        assert_eq!(counter.get(), 10);
343    }
344
345    #[test]
346    fn test_clear() {
347        let registry = Registry::new();
348
349        let _counter = registry.get_or_create_counter("requests");
350        let _gauge = registry.get_or_create_gauge("cpu_usage");
351
352        assert_eq!(registry.metric_count(), 2);
353
354        registry.clear();
355        assert_eq!(registry.metric_count(), 0);
356    }
357
358    #[test]
359    fn test_metric_names() {
360        let registry = Registry::new();
361
362        let _counter1 = registry.get_or_create_counter("requests");
363        let _counter2 = registry.get_or_create_counter("errors");
364        let _gauge1 = registry.get_or_create_gauge("cpu_usage");
365
366        let counter_names = registry.counter_names();
367        let gauge_names = registry.gauge_names();
368
369        assert_eq!(counter_names.len(), 2);
370        assert_eq!(gauge_names.len(), 1);
371        assert!(counter_names.contains(&"requests".to_string()));
372        assert!(counter_names.contains(&"errors".to_string()));
373        assert!(gauge_names.contains(&"cpu_usage".to_string()));
374    }
375
376    #[test]
377    #[cfg(feature = "meter")]
378    fn test_duplicate_names_across_types_are_independent() {
379        let registry = Registry::new();
380
381        let c = registry.get_or_create_counter("same_name");
382        let g = registry.get_or_create_gauge("same_name");
383        let t = registry.get_or_create_timer("same_name");
384        let r = registry.get_or_create_rate_meter("same_name");
385
386        // All should be registered independently (4 metrics total)
387        assert_eq!(registry.metric_count(), 4);
388
389        // And they should be distinct types; at least ensure their addresses differ pairwise
390        let c_addr = Arc::as_ptr(&c) as usize;
391        let g_addr = Arc::as_ptr(&g) as usize;
392        let t_addr = Arc::as_ptr(&t) as usize;
393        let r_addr = Arc::as_ptr(&r) as usize;
394
395        assert_ne!(c_addr, g_addr);
396        assert_ne!(c_addr, t_addr);
397        assert_ne!(c_addr, r_addr);
398        assert_ne!(g_addr, t_addr);
399        assert_ne!(g_addr, r_addr);
400        assert_ne!(t_addr, r_addr);
401    }
402
403    #[test]
404    fn test_clear_then_recreate_returns_new_instances() {
405        let registry = Registry::new();
406
407        let counter_before = registry.get_or_create_counter("requests");
408        let gauge_before = registry.get_or_create_gauge("cpu");
409        assert_eq!(registry.metric_count(), 2);
410
411        // Clear the registry; previously returned Arcs still exist but registry is empty
412        registry.clear();
413        assert_eq!(registry.metric_count(), 0);
414
415        let counter_after = registry.get_or_create_counter("requests");
416        let gauge_after = registry.get_or_create_gauge("cpu");
417
418        // New instances should NOT be ptr-equal to the old ones
419        assert!(!Arc::ptr_eq(&counter_before, &counter_after));
420        assert!(!Arc::ptr_eq(&gauge_before, &gauge_after));
421    }
422
423    #[test]
424    fn test_concurrent_duplicate_registration_singleton_per_name() {
425        let registry = Arc::new(Registry::new());
426        let mut handles = vec![];
427
428        for _ in 0..16 {
429            let r = registry.clone();
430            handles.push(thread::spawn(move || r.get_or_create_timer("dup")));
431        }
432
433        let first = registry.get_or_create_timer("dup");
434        for h in handles {
435            let timer = h.join().unwrap();
436            assert!(Arc::ptr_eq(&first, &timer));
437        }
438        assert_eq!(registry.metric_count(), 1);
439    }
440}