Skip to main content

metrics_lib/
registry.rs

1//! Thread-safe registry for storing and retrieving metrics by name and labels.
2//!
3//! # Architecture
4//!
5//! The registry maintains two parallel storage tracks for each metric type:
6//!
7//! 1. **Unlabeled fast path** — `HashMap<String, Arc<T>>` keyed by name only.
8//!    This is the original (pre-0.9.3) storage and remains the cheapest
9//!    lookup: read-locked `HashMap::get(&str)` returns a cloned `Arc` with
10//!    zero allocations on hit.
11//! 2. **Labeled path** — `HashMap<(String, LabelSet), Arc<T>>` keyed by
12//!    `(name, labels)`. Each unique `(name, labels)` tuple maps to a distinct
13//!    `Arc`. Lookups on this path allocate the composite key on every hit
14//!    (`String` clone + `LabelSet` clone) — callers should cache the returned
15//!    `Arc` in long-lived references for hot paths.
16//!
17//! # Cardinality
18//!
19//! The labeled path is subject to a hard cap on the total number of unique
20//! `(name, labels)` tuples across **all** metric types. The default is
21//! 10 000; configure via [`Registry::set_cardinality_cap`]. When a fresh
22//! `(name, labels)` registration would exceed the cap:
23//!
24//! - The `try_*_with` lookup variants return [`MetricsError::CardinalityExceeded`].
25//! - The non-`try` `*_with` variants route to a process-global per-type
26//!   "overflow sink" `Arc<T>` (initialised on first use, never registered in
27//!   the maps, never exported). Updates land on the sink and are observable
28//!   only via [`Registry::cardinality_overflows`].
29//!
30//! This preserves a panic-free hot path for misbehaving label producers while
31//! still surfacing the problem through the overflow counter.
32//!
33//! # Metadata
34//!
35//! Optional per-name [`MetricMetadata`] (help text, unit, kind) is stored in
36//! a separate map and consumed by exporters. Register via
37//! [`Registry::describe`] or the kind-specific shorthands
38//! (`describe_counter` / `describe_gauge` / …).
39
40use std::collections::HashMap;
41use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
42use std::sync::RwLock;
43
44/// Convenience alias used throughout this file to gate code that only
45/// matters when at least one metric type is compiled in.
46#[cfg(any(
47    feature = "count",
48    feature = "gauge",
49    feature = "timer",
50    feature = "meter",
51    feature = "histogram"
52))]
53use std::sync::{Arc, OnceLock};
54
55#[cfg(feature = "count")]
56use crate::Counter;
57#[cfg(feature = "gauge")]
58use crate::Gauge;
59#[cfg(feature = "histogram")]
60use crate::Histogram;
61#[cfg(feature = "meter")]
62use crate::RateMeter;
63#[cfg(feature = "timer")]
64use crate::Timer;
65
66#[cfg(any(
67    feature = "count",
68    feature = "gauge",
69    feature = "timer",
70    feature = "meter",
71    feature = "histogram"
72))]
73use crate::{LabelSet, MetricsError, Result};
74
75use crate::{MetricKind, MetricMetadata, Unit};
76
77/// Default per-registry cardinality cap on unique `(name, labels)` tuples.
78pub const DEFAULT_CARDINALITY_CAP: usize = 10_000;
79
80/// A thread-safe registry for storing metrics by name and labels.
81///
82/// The registry maintains two parallel storage tracks per metric type — an
83/// unlabeled fast path (`HashMap<String, Arc<T>>`) and a labeled path keyed
84/// by `(name, LabelSet)`. The labeled path is subject to a hard cardinality
85/// cap (default 10 000; see [`Registry::set_cardinality_cap`]). Per-metric
86/// description, unit, and kind metadata are stored separately and consumed
87/// by exporters in [`crate::exporters`].
88#[repr(align(64))]
89pub struct Registry {
90    #[cfg(feature = "count")]
91    counters: RwLock<HashMap<String, Arc<Counter>>>,
92    #[cfg(feature = "gauge")]
93    gauges: RwLock<HashMap<String, Arc<Gauge>>>,
94    #[cfg(feature = "timer")]
95    timers: RwLock<HashMap<String, Arc<Timer>>>,
96    #[cfg(feature = "meter")]
97    rate_meters: RwLock<HashMap<String, Arc<RateMeter>>>,
98
99    #[cfg(feature = "count")]
100    labeled_counters: RwLock<HashMap<(String, LabelSet), Arc<Counter>>>,
101    #[cfg(feature = "gauge")]
102    labeled_gauges: RwLock<HashMap<(String, LabelSet), Arc<Gauge>>>,
103    #[cfg(feature = "timer")]
104    labeled_timers: RwLock<HashMap<(String, LabelSet), Arc<Timer>>>,
105    #[cfg(feature = "meter")]
106    labeled_rate_meters: RwLock<HashMap<(String, LabelSet), Arc<RateMeter>>>,
107    #[cfg(feature = "histogram")]
108    histograms: RwLock<HashMap<(String, LabelSet), Arc<Histogram>>>,
109    #[cfg(feature = "histogram")]
110    histogram_buckets: RwLock<HashMap<String, Vec<f64>>>,
111
112    metadata: RwLock<HashMap<String, MetricMetadata>>,
113
114    cardinality_cap: AtomicUsize,
115    cardinality_count: AtomicUsize,
116    cardinality_overflows: AtomicU64,
117}
118
119impl Registry {
120    /// Create a new empty registry with the default cardinality cap
121    /// ([`DEFAULT_CARDINALITY_CAP`]).
122    pub fn new() -> Self {
123        Self {
124            #[cfg(feature = "count")]
125            counters: RwLock::new(HashMap::new()),
126            #[cfg(feature = "gauge")]
127            gauges: RwLock::new(HashMap::new()),
128            #[cfg(feature = "timer")]
129            timers: RwLock::new(HashMap::new()),
130            #[cfg(feature = "meter")]
131            rate_meters: RwLock::new(HashMap::new()),
132
133            #[cfg(feature = "count")]
134            labeled_counters: RwLock::new(HashMap::new()),
135            #[cfg(feature = "gauge")]
136            labeled_gauges: RwLock::new(HashMap::new()),
137            #[cfg(feature = "timer")]
138            labeled_timers: RwLock::new(HashMap::new()),
139            #[cfg(feature = "meter")]
140            labeled_rate_meters: RwLock::new(HashMap::new()),
141            #[cfg(feature = "histogram")]
142            histograms: RwLock::new(HashMap::new()),
143            #[cfg(feature = "histogram")]
144            histogram_buckets: RwLock::new(HashMap::new()),
145
146            metadata: RwLock::new(HashMap::new()),
147
148            cardinality_cap: AtomicUsize::new(DEFAULT_CARDINALITY_CAP),
149            cardinality_count: AtomicUsize::new(0),
150            cardinality_overflows: AtomicU64::new(0),
151        }
152    }
153
154    // ---------------------------------------------------------------------
155    // Cardinality control
156    // ---------------------------------------------------------------------
157
158    /// Set the cardinality cap. New labeled registrations beyond this cap
159    /// return overflow sinks (or `Err(CardinalityExceeded)` via the
160    /// `try_*_with` paths).
161    #[inline]
162    pub fn set_cardinality_cap(&self, cap: usize) {
163        self.cardinality_cap.store(cap, Ordering::Relaxed);
164    }
165
166    /// Current cardinality cap.
167    #[must_use]
168    #[inline]
169    pub fn cardinality_cap(&self) -> usize {
170        self.cardinality_cap.load(Ordering::Relaxed)
171    }
172
173    /// Count of unique `(name, labels)` tuples currently registered across
174    /// all labeled metric types.
175    #[must_use]
176    #[inline]
177    pub fn cardinality_count(&self) -> usize {
178        self.cardinality_count.load(Ordering::Relaxed)
179    }
180
181    /// Total number of overflow events (labeled registrations that hit the
182    /// cap and were routed to the sink).
183    #[must_use]
184    #[inline]
185    pub fn cardinality_overflows(&self) -> u64 {
186        self.cardinality_overflows.load(Ordering::Relaxed)
187    }
188
189    /// Reserve one cardinality slot. Returns `true` if a slot was acquired;
190    /// `false` if the cap is full (caller routes to overflow sink).
191    #[cfg(any(
192        feature = "count",
193        feature = "gauge",
194        feature = "timer",
195        feature = "meter",
196        feature = "histogram"
197    ))]
198    fn try_acquire_slot(&self) -> bool {
199        let cap = self.cardinality_cap();
200        loop {
201            let current = self.cardinality_count.load(Ordering::Relaxed);
202            if current >= cap {
203                self.cardinality_overflows.fetch_add(1, Ordering::Relaxed);
204                return false;
205            }
206            if self
207                .cardinality_count
208                .compare_exchange_weak(current, current + 1, Ordering::Relaxed, Ordering::Relaxed)
209                .is_ok()
210            {
211                return true;
212            }
213        }
214    }
215
216    // ---------------------------------------------------------------------
217    // Metadata
218    // ---------------------------------------------------------------------
219
220    /// Register metadata (help text + unit + kind) for a metric name.
221    ///
222    /// Calling `describe` again with the same name replaces the prior entry.
223    pub fn describe(&self, name: &str, metadata: MetricMetadata) {
224        self.metadata
225            .write()
226            .unwrap_or_else(|e| e.into_inner())
227            .insert(name.to_string(), metadata);
228    }
229
230    /// Convenience: describe a counter.
231    pub fn describe_counter(
232        &self,
233        name: &str,
234        help: impl Into<std::borrow::Cow<'static, str>>,
235        unit: Unit,
236    ) {
237        self.describe(name, MetricMetadata::new(MetricKind::Counter, help, unit));
238    }
239
240    /// Convenience: describe a gauge.
241    pub fn describe_gauge(
242        &self,
243        name: &str,
244        help: impl Into<std::borrow::Cow<'static, str>>,
245        unit: Unit,
246    ) {
247        self.describe(name, MetricMetadata::new(MetricKind::Gauge, help, unit));
248    }
249
250    /// Convenience: describe a timer.
251    pub fn describe_timer(
252        &self,
253        name: &str,
254        help: impl Into<std::borrow::Cow<'static, str>>,
255        unit: Unit,
256    ) {
257        self.describe(name, MetricMetadata::new(MetricKind::Timer, help, unit));
258    }
259
260    /// Convenience: describe a rate meter.
261    pub fn describe_rate(
262        &self,
263        name: &str,
264        help: impl Into<std::borrow::Cow<'static, str>>,
265        unit: Unit,
266    ) {
267        self.describe(name, MetricMetadata::new(MetricKind::Rate, help, unit));
268    }
269
270    /// Convenience: describe a histogram.
271    pub fn describe_histogram(
272        &self,
273        name: &str,
274        help: impl Into<std::borrow::Cow<'static, str>>,
275        unit: Unit,
276    ) {
277        self.describe(name, MetricMetadata::new(MetricKind::Histogram, help, unit));
278    }
279
280    /// Look up metadata for a metric by name.
281    #[must_use]
282    pub fn metadata(&self, name: &str) -> Option<MetricMetadata> {
283        self.metadata
284            .read()
285            .unwrap_or_else(|e| e.into_inner())
286            .get(name)
287            .cloned()
288    }
289
290    // ---------------------------------------------------------------------
291    // Counter
292    // ---------------------------------------------------------------------
293
294    /// Get or create an unlabeled counter.
295    ///
296    /// Requires the `count` feature.
297    #[cfg(feature = "count")]
298    pub fn get_or_create_counter(&self, name: &str) -> Arc<Counter> {
299        if let Ok(map) = self.counters.read() {
300            if let Some(c) = map.get(name) {
301                return c.clone();
302            }
303        }
304        let mut map = self.counters.write().unwrap_or_else(|e| e.into_inner());
305        map.entry(name.to_string())
306            .or_insert_with(|| Arc::new(Counter::new()))
307            .clone()
308    }
309
310    /// Get or create a counter for the supplied `(name, labels)` tuple.
311    /// Routes to the per-type cardinality-overflow sink when the cap is full.
312    #[cfg(feature = "count")]
313    pub fn get_or_create_counter_with(&self, name: &str, labels: &LabelSet) -> Arc<Counter> {
314        if labels.is_empty() {
315            return self.get_or_create_counter(name);
316        }
317        match self.try_get_or_create_counter_with(name, labels) {
318            Ok(c) => c,
319            Err(_) => counter_overflow_sink().clone(),
320        }
321    }
322
323    /// Try to get or create a labeled counter. Returns
324    /// `Err(CardinalityExceeded)` when the cap is full.
325    #[cfg(feature = "count")]
326    pub fn try_get_or_create_counter_with(
327        &self,
328        name: &str,
329        labels: &LabelSet,
330    ) -> Result<Arc<Counter>> {
331        if labels.is_empty() {
332            return Ok(self.get_or_create_counter(name));
333        }
334        if let Ok(map) = self.labeled_counters.read() {
335            if let Some(c) = map.get(&(name.to_string(), labels.clone())) {
336                return Ok(c.clone());
337            }
338        }
339        let mut map = self
340            .labeled_counters
341            .write()
342            .unwrap_or_else(|e| e.into_inner());
343        let key = (name.to_string(), labels.clone());
344        if let Some(c) = map.get(&key) {
345            return Ok(c.clone());
346        }
347        if !self.try_acquire_slot() {
348            return Err(MetricsError::CardinalityExceeded);
349        }
350        let c = Arc::new(Counter::new());
351        map.insert(key, c.clone());
352        Ok(c)
353    }
354
355    // ---------------------------------------------------------------------
356    // Gauge
357    // ---------------------------------------------------------------------
358
359    /// Get or create an unlabeled gauge. Requires the `gauge` feature.
360    #[cfg(feature = "gauge")]
361    pub fn get_or_create_gauge(&self, name: &str) -> Arc<Gauge> {
362        if let Ok(map) = self.gauges.read() {
363            if let Some(g) = map.get(name) {
364                return g.clone();
365            }
366        }
367        let mut map = self.gauges.write().unwrap_or_else(|e| e.into_inner());
368        map.entry(name.to_string())
369            .or_insert_with(|| Arc::new(Gauge::new()))
370            .clone()
371    }
372
373    /// Labeled gauge with overflow-sink fallback. Requires the `gauge` feature.
374    #[cfg(feature = "gauge")]
375    pub fn get_or_create_gauge_with(&self, name: &str, labels: &LabelSet) -> Arc<Gauge> {
376        if labels.is_empty() {
377            return self.get_or_create_gauge(name);
378        }
379        match self.try_get_or_create_gauge_with(name, labels) {
380            Ok(g) => g,
381            Err(_) => gauge_overflow_sink().clone(),
382        }
383    }
384
385    /// Labeled gauge returning `Err(CardinalityExceeded)` on overflow.
386    /// Requires the `gauge` feature.
387    #[cfg(feature = "gauge")]
388    pub fn try_get_or_create_gauge_with(
389        &self,
390        name: &str,
391        labels: &LabelSet,
392    ) -> Result<Arc<Gauge>> {
393        if labels.is_empty() {
394            return Ok(self.get_or_create_gauge(name));
395        }
396        if let Ok(map) = self.labeled_gauges.read() {
397            if let Some(g) = map.get(&(name.to_string(), labels.clone())) {
398                return Ok(g.clone());
399            }
400        }
401        let mut map = self
402            .labeled_gauges
403            .write()
404            .unwrap_or_else(|e| e.into_inner());
405        let key = (name.to_string(), labels.clone());
406        if let Some(g) = map.get(&key) {
407            return Ok(g.clone());
408        }
409        if !self.try_acquire_slot() {
410            return Err(MetricsError::CardinalityExceeded);
411        }
412        let g = Arc::new(Gauge::new());
413        map.insert(key, g.clone());
414        Ok(g)
415    }
416
417    // ---------------------------------------------------------------------
418    // Timer
419    // ---------------------------------------------------------------------
420
421    /// Get or create an unlabeled timer. Requires the `timer` feature.
422    #[cfg(feature = "timer")]
423    pub fn get_or_create_timer(&self, name: &str) -> Arc<Timer> {
424        if let Ok(map) = self.timers.read() {
425            if let Some(t) = map.get(name) {
426                return t.clone();
427            }
428        }
429        let mut map = self.timers.write().unwrap_or_else(|e| e.into_inner());
430        map.entry(name.to_string())
431            .or_insert_with(|| Arc::new(Timer::new()))
432            .clone()
433    }
434
435    /// Labeled timer with overflow-sink fallback. Requires the `timer` feature.
436    #[cfg(feature = "timer")]
437    pub fn get_or_create_timer_with(&self, name: &str, labels: &LabelSet) -> Arc<Timer> {
438        if labels.is_empty() {
439            return self.get_or_create_timer(name);
440        }
441        match self.try_get_or_create_timer_with(name, labels) {
442            Ok(t) => t,
443            Err(_) => timer_overflow_sink().clone(),
444        }
445    }
446
447    /// Labeled timer returning `Err(CardinalityExceeded)` on overflow.
448    /// Requires the `timer` feature.
449    #[cfg(feature = "timer")]
450    pub fn try_get_or_create_timer_with(
451        &self,
452        name: &str,
453        labels: &LabelSet,
454    ) -> Result<Arc<Timer>> {
455        if labels.is_empty() {
456            return Ok(self.get_or_create_timer(name));
457        }
458        if let Ok(map) = self.labeled_timers.read() {
459            if let Some(t) = map.get(&(name.to_string(), labels.clone())) {
460                return Ok(t.clone());
461            }
462        }
463        let mut map = self
464            .labeled_timers
465            .write()
466            .unwrap_or_else(|e| e.into_inner());
467        let key = (name.to_string(), labels.clone());
468        if let Some(t) = map.get(&key) {
469            return Ok(t.clone());
470        }
471        if !self.try_acquire_slot() {
472            return Err(MetricsError::CardinalityExceeded);
473        }
474        let t = Arc::new(Timer::new());
475        map.insert(key, t.clone());
476        Ok(t)
477    }
478
479    // ---------------------------------------------------------------------
480    // Rate meter
481    // ---------------------------------------------------------------------
482
483    /// Get or create an unlabeled rate meter. Requires the `meter` feature.
484    #[cfg(feature = "meter")]
485    pub fn get_or_create_rate_meter(&self, name: &str) -> Arc<RateMeter> {
486        if let Ok(map) = self.rate_meters.read() {
487            if let Some(r) = map.get(name) {
488                return r.clone();
489            }
490        }
491        let mut map = self.rate_meters.write().unwrap_or_else(|e| e.into_inner());
492        map.entry(name.to_string())
493            .or_insert_with(|| Arc::new(RateMeter::new()))
494            .clone()
495    }
496
497    /// Labeled rate meter with overflow-sink fallback. Requires the `meter`
498    /// feature.
499    #[cfg(feature = "meter")]
500    pub fn get_or_create_rate_meter_with(&self, name: &str, labels: &LabelSet) -> Arc<RateMeter> {
501        if labels.is_empty() {
502            return self.get_or_create_rate_meter(name);
503        }
504        match self.try_get_or_create_rate_meter_with(name, labels) {
505            Ok(r) => r,
506            Err(_) => rate_meter_overflow_sink().clone(),
507        }
508    }
509
510    /// Labeled rate meter returning `Err(CardinalityExceeded)` on overflow.
511    /// Requires the `meter` feature.
512    #[cfg(feature = "meter")]
513    pub fn try_get_or_create_rate_meter_with(
514        &self,
515        name: &str,
516        labels: &LabelSet,
517    ) -> Result<Arc<RateMeter>> {
518        if labels.is_empty() {
519            return Ok(self.get_or_create_rate_meter(name));
520        }
521        if let Ok(map) = self.labeled_rate_meters.read() {
522            if let Some(r) = map.get(&(name.to_string(), labels.clone())) {
523                return Ok(r.clone());
524            }
525        }
526        let mut map = self
527            .labeled_rate_meters
528            .write()
529            .unwrap_or_else(|e| e.into_inner());
530        let key = (name.to_string(), labels.clone());
531        if let Some(r) = map.get(&key) {
532            return Ok(r.clone());
533        }
534        if !self.try_acquire_slot() {
535            return Err(MetricsError::CardinalityExceeded);
536        }
537        let r = Arc::new(RateMeter::new());
538        map.insert(key, r.clone());
539        Ok(r)
540    }
541
542    // ---------------------------------------------------------------------
543    // Histogram
544    // ---------------------------------------------------------------------
545
546    /// Pre-configure histogram bucket boundaries for the supplied metric
547    /// name. Subsequent `histogram` / `histogram_with` registrations for the
548    /// same name will use these bounds. Already-registered histograms are
549    /// **not** retroactively rebucketed; configure before first use.
550    ///
551    /// Requires the `histogram` feature.
552    #[cfg(feature = "histogram")]
553    pub fn configure_histogram(&self, name: &str, buckets: impl IntoIterator<Item = f64>) {
554        let buckets: Vec<f64> = buckets.into_iter().collect();
555        self.histogram_buckets
556            .write()
557            .unwrap_or_else(|e| e.into_inner())
558            .insert(name.to_string(), buckets);
559    }
560
561    /// Get or create an unlabeled histogram.
562    ///
563    /// Uses buckets configured via [`Self::configure_histogram`] or the
564    /// Prometheus default seconds buckets if none configured.
565    /// Requires the `histogram` feature.
566    #[cfg(feature = "histogram")]
567    pub fn get_or_create_histogram(&self, name: &str) -> Arc<Histogram> {
568        // Histograms always live in the labeled map keyed by `LabelSet::EMPTY`
569        // so a single iteration point covers both labeled and unlabeled.
570        self.get_or_create_histogram_with(name, &LabelSet::EMPTY)
571    }
572
573    /// Labeled histogram with overflow-sink fallback. Requires the
574    /// `histogram` feature.
575    #[cfg(feature = "histogram")]
576    pub fn get_or_create_histogram_with(&self, name: &str, labels: &LabelSet) -> Arc<Histogram> {
577        match self.try_get_or_create_histogram_with(name, labels) {
578            Ok(h) => h,
579            Err(_) => histogram_overflow_sink().clone(),
580        }
581    }
582
583    /// Labeled histogram returning `Err(CardinalityExceeded)` on overflow.
584    /// Requires the `histogram` feature.
585    #[cfg(feature = "histogram")]
586    pub fn try_get_or_create_histogram_with(
587        &self,
588        name: &str,
589        labels: &LabelSet,
590    ) -> Result<Arc<Histogram>> {
591        if let Ok(map) = self.histograms.read() {
592            if let Some(h) = map.get(&(name.to_string(), labels.clone())) {
593                return Ok(h.clone());
594            }
595        }
596        // Only labeled-empty histograms skip the cardinality cap (they are
597        // the unlabeled "default" series). Labeled variants consume slots.
598        if !labels.is_empty() && !self.try_acquire_slot() {
599            return Err(MetricsError::CardinalityExceeded);
600        }
601        let mut map = self.histograms.write().unwrap_or_else(|e| e.into_inner());
602        let key = (name.to_string(), labels.clone());
603        if let Some(h) = map.get(&key) {
604            return Ok(h.clone());
605        }
606        // Materialise a histogram with the configured buckets (or the
607        // Prometheus default if none configured).
608        let buckets = self
609            .histogram_buckets
610            .read()
611            .unwrap_or_else(|e| e.into_inner())
612            .get(name)
613            .cloned();
614        let h = Arc::new(match buckets {
615            Some(b) => Histogram::with_buckets(b),
616            None => Histogram::default_seconds(),
617        });
618        map.insert(key, h.clone());
619        Ok(h)
620    }
621
622    // ---------------------------------------------------------------------
623    // Listing accessors (existing API + new labeled accessors)
624    // ---------------------------------------------------------------------
625
626    /// All unlabeled counter names. Requires the `count` feature.
627    #[cfg(feature = "count")]
628    pub fn counter_names(&self) -> Vec<String> {
629        self.counters
630            .read()
631            .unwrap_or_else(|e| e.into_inner())
632            .keys()
633            .cloned()
634            .collect()
635    }
636
637    /// All unlabeled gauge names. Requires the `gauge` feature.
638    #[cfg(feature = "gauge")]
639    pub fn gauge_names(&self) -> Vec<String> {
640        self.gauges
641            .read()
642            .unwrap_or_else(|e| e.into_inner())
643            .keys()
644            .cloned()
645            .collect()
646    }
647
648    /// All unlabeled timer names. Requires the `timer` feature.
649    #[cfg(feature = "timer")]
650    pub fn timer_names(&self) -> Vec<String> {
651        self.timers
652            .read()
653            .unwrap_or_else(|e| e.into_inner())
654            .keys()
655            .cloned()
656            .collect()
657    }
658
659    /// All unlabeled rate meter names. Requires the `meter` feature.
660    #[cfg(feature = "meter")]
661    pub fn rate_meter_names(&self) -> Vec<String> {
662        self.rate_meters
663            .read()
664            .unwrap_or_else(|e| e.into_inner())
665            .keys()
666            .cloned()
667            .collect()
668    }
669
670    /// All registered histogram names (labeled + unlabeled). Requires the
671    /// `histogram` feature.
672    #[cfg(feature = "histogram")]
673    pub fn histogram_names(&self) -> Vec<String> {
674        let mut names: Vec<String> = self
675            .histograms
676            .read()
677            .unwrap_or_else(|e| e.into_inner())
678            .keys()
679            .map(|(n, _)| n.clone())
680            .collect();
681        names.sort();
682        names.dedup();
683        names
684    }
685
686    /// Total number of registered metrics across all enabled metric types
687    /// and label combinations.
688    pub fn metric_count(&self) -> usize {
689        #[allow(unused_mut)]
690        let mut total = 0;
691        #[cfg(feature = "count")]
692        {
693            total += self
694                .counters
695                .read()
696                .unwrap_or_else(|e| e.into_inner())
697                .len();
698            total += self
699                .labeled_counters
700                .read()
701                .unwrap_or_else(|e| e.into_inner())
702                .len();
703        }
704        #[cfg(feature = "gauge")]
705        {
706            total += self.gauges.read().unwrap_or_else(|e| e.into_inner()).len();
707            total += self
708                .labeled_gauges
709                .read()
710                .unwrap_or_else(|e| e.into_inner())
711                .len();
712        }
713        #[cfg(feature = "timer")]
714        {
715            total += self.timers.read().unwrap_or_else(|e| e.into_inner()).len();
716            total += self
717                .labeled_timers
718                .read()
719                .unwrap_or_else(|e| e.into_inner())
720                .len();
721        }
722        #[cfg(feature = "meter")]
723        {
724            total += self
725                .rate_meters
726                .read()
727                .unwrap_or_else(|e| e.into_inner())
728                .len();
729            total += self
730                .labeled_rate_meters
731                .read()
732                .unwrap_or_else(|e| e.into_inner())
733                .len();
734        }
735        #[cfg(feature = "histogram")]
736        {
737            total += self
738                .histograms
739                .read()
740                .unwrap_or_else(|e| e.into_inner())
741                .len();
742        }
743        total
744    }
745
746    /// Clear every registered metric, all metadata, and reset cardinality
747    /// counters. Previously-returned `Arc`s remain valid but become detached
748    /// from the registry.
749    pub fn clear(&self) {
750        #[cfg(feature = "count")]
751        {
752            self.counters
753                .write()
754                .unwrap_or_else(|e| e.into_inner())
755                .clear();
756            self.labeled_counters
757                .write()
758                .unwrap_or_else(|e| e.into_inner())
759                .clear();
760        }
761        #[cfg(feature = "gauge")]
762        {
763            self.gauges
764                .write()
765                .unwrap_or_else(|e| e.into_inner())
766                .clear();
767            self.labeled_gauges
768                .write()
769                .unwrap_or_else(|e| e.into_inner())
770                .clear();
771        }
772        #[cfg(feature = "timer")]
773        {
774            self.timers
775                .write()
776                .unwrap_or_else(|e| e.into_inner())
777                .clear();
778            self.labeled_timers
779                .write()
780                .unwrap_or_else(|e| e.into_inner())
781                .clear();
782        }
783        #[cfg(feature = "meter")]
784        {
785            self.rate_meters
786                .write()
787                .unwrap_or_else(|e| e.into_inner())
788                .clear();
789            self.labeled_rate_meters
790                .write()
791                .unwrap_or_else(|e| e.into_inner())
792                .clear();
793        }
794        #[cfg(feature = "histogram")]
795        {
796            self.histograms
797                .write()
798                .unwrap_or_else(|e| e.into_inner())
799                .clear();
800            self.histogram_buckets
801                .write()
802                .unwrap_or_else(|e| e.into_inner())
803                .clear();
804        }
805        self.metadata
806            .write()
807            .unwrap_or_else(|e| e.into_inner())
808            .clear();
809        self.cardinality_count.store(0, Ordering::Relaxed);
810        // Note: `cardinality_overflows` is monotonic and intentionally not reset.
811    }
812
813    // ---------------------------------------------------------------------
814    // Snapshot accessors (exporter hooks)
815    // ---------------------------------------------------------------------
816
817    /// Capture every counter as `(name, labels, Arc<Counter>)`. Requires the
818    /// `count` feature.
819    #[cfg(feature = "count")]
820    pub fn counter_entries(&self) -> Vec<(String, LabelSet, Arc<Counter>)> {
821        let mut out = Vec::new();
822        for (name, c) in self
823            .counters
824            .read()
825            .unwrap_or_else(|e| e.into_inner())
826            .iter()
827        {
828            out.push((name.clone(), LabelSet::EMPTY, c.clone()));
829        }
830        for ((name, labels), c) in self
831            .labeled_counters
832            .read()
833            .unwrap_or_else(|e| e.into_inner())
834            .iter()
835        {
836            out.push((name.clone(), labels.clone(), c.clone()));
837        }
838        out
839    }
840
841    /// Capture every gauge as `(name, labels, Arc<Gauge>)`. Requires the
842    /// `gauge` feature.
843    #[cfg(feature = "gauge")]
844    pub fn gauge_entries(&self) -> Vec<(String, LabelSet, Arc<Gauge>)> {
845        let mut out = Vec::new();
846        for (name, g) in self.gauges.read().unwrap_or_else(|e| e.into_inner()).iter() {
847            out.push((name.clone(), LabelSet::EMPTY, g.clone()));
848        }
849        for ((name, labels), g) in self
850            .labeled_gauges
851            .read()
852            .unwrap_or_else(|e| e.into_inner())
853            .iter()
854        {
855            out.push((name.clone(), labels.clone(), g.clone()));
856        }
857        out
858    }
859
860    /// Capture every timer as `(name, labels, Arc<Timer>)`. Requires the
861    /// `timer` feature.
862    #[cfg(feature = "timer")]
863    pub fn timer_entries(&self) -> Vec<(String, LabelSet, Arc<Timer>)> {
864        let mut out = Vec::new();
865        for (name, t) in self.timers.read().unwrap_or_else(|e| e.into_inner()).iter() {
866            out.push((name.clone(), LabelSet::EMPTY, t.clone()));
867        }
868        for ((name, labels), t) in self
869            .labeled_timers
870            .read()
871            .unwrap_or_else(|e| e.into_inner())
872            .iter()
873        {
874            out.push((name.clone(), labels.clone(), t.clone()));
875        }
876        out
877    }
878
879    /// Capture every rate meter as `(name, labels, Arc<RateMeter>)`.
880    /// Requires the `meter` feature.
881    #[cfg(feature = "meter")]
882    pub fn rate_meter_entries(&self) -> Vec<(String, LabelSet, Arc<RateMeter>)> {
883        let mut out = Vec::new();
884        for (name, r) in self
885            .rate_meters
886            .read()
887            .unwrap_or_else(|e| e.into_inner())
888            .iter()
889        {
890            out.push((name.clone(), LabelSet::EMPTY, r.clone()));
891        }
892        for ((name, labels), r) in self
893            .labeled_rate_meters
894            .read()
895            .unwrap_or_else(|e| e.into_inner())
896            .iter()
897        {
898            out.push((name.clone(), labels.clone(), r.clone()));
899        }
900        out
901    }
902
903    /// Capture every histogram as `(name, labels, Arc<Histogram>)`. Requires
904    /// the `histogram` feature.
905    #[cfg(feature = "histogram")]
906    pub fn histogram_entries(&self) -> Vec<(String, LabelSet, Arc<Histogram>)> {
907        self.histograms
908            .read()
909            .unwrap_or_else(|e| e.into_inner())
910            .iter()
911            .map(|((n, l), h)| (n.clone(), l.clone(), h.clone()))
912            .collect()
913    }
914
915    /// Build a [`ScopedRegistry`] that prepends `prefix` to every metric
916    /// name on registration and lookup.
917    ///
918    /// All scoped lookups land in the same underlying `Registry`, so a scope
919    /// is a pure naming convention — there's no separate `Arc` storage. Two
920    /// scopes that produce the same effective name (e.g.
921    /// `scoped("http_").counter("requests")` and `counter("http_requests")`)
922    /// return the **same** `Arc<Counter>`.
923    ///
924    /// Available since v0.9.5.
925    ///
926    /// # Example
927    ///
928    /// ```
929    /// # #[cfg(all(feature = "count", feature = "gauge"))]
930    /// # {
931    /// use metrics_lib::{init, metrics};
932    /// init();
933    /// let http = metrics().registry().scoped("http.");
934    /// http.counter("requests").inc();   // registers "http.requests"
935    /// http.gauge("inflight").set(1.0);  // registers "http.inflight"
936    /// # }
937    /// ```
938    pub fn scoped(&self, prefix: impl Into<String>) -> ScopedRegistry<'_> {
939        ScopedRegistry {
940            registry: self,
941            prefix: prefix.into(),
942        }
943    }
944}
945
946/// View into a [`Registry`] that prepends a fixed `prefix` to every metric
947/// name. Created via [`Registry::scoped`].
948///
949/// Available since v0.9.5.
950pub struct ScopedRegistry<'a> {
951    registry: &'a Registry,
952    prefix: String,
953}
954
955impl<'a> ScopedRegistry<'a> {
956    /// Borrow the prefix this scope prepends.
957    #[must_use]
958    pub fn prefix(&self) -> &str {
959        &self.prefix
960    }
961
962    /// Borrow the underlying [`Registry`].
963    #[must_use]
964    pub fn registry(&self) -> &'a Registry {
965        self.registry
966    }
967
968    #[inline]
969    fn join(&self, name: &str) -> String {
970        let mut s = String::with_capacity(self.prefix.len() + name.len());
971        s.push_str(&self.prefix);
972        s.push_str(name);
973        s
974    }
975
976    // ----- describe shorthands -----
977
978    /// Describe a counter under the scoped name.
979    pub fn describe_counter(
980        &self,
981        name: &str,
982        help: impl Into<std::borrow::Cow<'static, str>>,
983        unit: Unit,
984    ) {
985        self.registry.describe_counter(&self.join(name), help, unit);
986    }
987
988    /// Describe a gauge under the scoped name.
989    pub fn describe_gauge(
990        &self,
991        name: &str,
992        help: impl Into<std::borrow::Cow<'static, str>>,
993        unit: Unit,
994    ) {
995        self.registry.describe_gauge(&self.join(name), help, unit);
996    }
997
998    /// Describe a timer under the scoped name.
999    pub fn describe_timer(
1000        &self,
1001        name: &str,
1002        help: impl Into<std::borrow::Cow<'static, str>>,
1003        unit: Unit,
1004    ) {
1005        self.registry.describe_timer(&self.join(name), help, unit);
1006    }
1007
1008    /// Describe a rate meter under the scoped name.
1009    pub fn describe_rate(
1010        &self,
1011        name: &str,
1012        help: impl Into<std::borrow::Cow<'static, str>>,
1013        unit: Unit,
1014    ) {
1015        self.registry.describe_rate(&self.join(name), help, unit);
1016    }
1017
1018    /// Describe a histogram under the scoped name.
1019    pub fn describe_histogram(
1020        &self,
1021        name: &str,
1022        help: impl Into<std::borrow::Cow<'static, str>>,
1023        unit: Unit,
1024    ) {
1025        self.registry
1026            .describe_histogram(&self.join(name), help, unit);
1027    }
1028
1029    /// Pre-configure histogram bucket boundaries under the scoped name.
1030    /// Requires the `histogram` feature.
1031    #[cfg(feature = "histogram")]
1032    pub fn configure_histogram(&self, name: &str, buckets: impl IntoIterator<Item = f64>) {
1033        self.registry.configure_histogram(&self.join(name), buckets);
1034    }
1035
1036    // ----- metric lookups (unlabeled) -----
1037
1038    /// Counter under the scoped name. Requires `count`.
1039    #[cfg(feature = "count")]
1040    pub fn counter(&self, name: &str) -> Arc<Counter> {
1041        self.registry.get_or_create_counter(&self.join(name))
1042    }
1043
1044    /// Gauge under the scoped name. Requires `gauge`.
1045    #[cfg(feature = "gauge")]
1046    pub fn gauge(&self, name: &str) -> Arc<Gauge> {
1047        self.registry.get_or_create_gauge(&self.join(name))
1048    }
1049
1050    /// Timer under the scoped name. Requires `timer`.
1051    #[cfg(feature = "timer")]
1052    pub fn timer(&self, name: &str) -> Arc<Timer> {
1053        self.registry.get_or_create_timer(&self.join(name))
1054    }
1055
1056    /// Rate meter under the scoped name. Requires `meter`.
1057    #[cfg(feature = "meter")]
1058    pub fn rate(&self, name: &str) -> Arc<RateMeter> {
1059        self.registry.get_or_create_rate_meter(&self.join(name))
1060    }
1061
1062    /// Histogram under the scoped name. Requires `histogram`.
1063    #[cfg(feature = "histogram")]
1064    pub fn histogram(&self, name: &str) -> Arc<Histogram> {
1065        self.registry.get_or_create_histogram(&self.join(name))
1066    }
1067
1068    // ----- labeled lookups -----
1069
1070    /// Labeled counter under the scoped name. Requires `count`.
1071    #[cfg(feature = "count")]
1072    pub fn counter_with(&self, name: &str, labels: &LabelSet) -> Arc<Counter> {
1073        self.registry
1074            .get_or_create_counter_with(&self.join(name), labels)
1075    }
1076
1077    /// Labeled gauge under the scoped name. Requires `gauge`.
1078    #[cfg(feature = "gauge")]
1079    pub fn gauge_with(&self, name: &str, labels: &LabelSet) -> Arc<Gauge> {
1080        self.registry
1081            .get_or_create_gauge_with(&self.join(name), labels)
1082    }
1083
1084    /// Labeled timer under the scoped name. Requires `timer`.
1085    #[cfg(feature = "timer")]
1086    pub fn timer_with(&self, name: &str, labels: &LabelSet) -> Arc<Timer> {
1087        self.registry
1088            .get_or_create_timer_with(&self.join(name), labels)
1089    }
1090
1091    /// Labeled rate meter under the scoped name. Requires `meter`.
1092    #[cfg(feature = "meter")]
1093    pub fn rate_with(&self, name: &str, labels: &LabelSet) -> Arc<RateMeter> {
1094        self.registry
1095            .get_or_create_rate_meter_with(&self.join(name), labels)
1096    }
1097
1098    /// Labeled histogram under the scoped name. Requires `histogram`.
1099    #[cfg(feature = "histogram")]
1100    pub fn histogram_with(&self, name: &str, labels: &LabelSet) -> Arc<Histogram> {
1101        self.registry
1102            .get_or_create_histogram_with(&self.join(name), labels)
1103    }
1104
1105    /// Nested scope: `scoped("a.").scoped("b.")` is equivalent to
1106    /// `scoped("a.b.")`. Allocates a single fresh `String`.
1107    #[must_use]
1108    pub fn scoped(&self, sub_prefix: impl Into<String>) -> ScopedRegistry<'a> {
1109        ScopedRegistry {
1110            registry: self.registry,
1111            prefix: self.join(&sub_prefix.into()),
1112        }
1113    }
1114}
1115
1116impl Default for Registry {
1117    fn default() -> Self {
1118        Self::new()
1119    }
1120}
1121
1122// ---------------------------------------------------------------------
1123// Per-type process-global overflow sinks.
1124//
1125// When cardinality is exceeded, the non-`try` `*_with` methods route to these
1126// process-global metrics so the caller's hot path never panics. These sinks
1127// are *not* registered in the Registry and are *not* exported. The only way
1128// to observe cardinality pressure is via `Registry::cardinality_overflows()`.
1129// ---------------------------------------------------------------------
1130
1131#[cfg(feature = "count")]
1132fn counter_overflow_sink() -> &'static Arc<Counter> {
1133    static SINK: OnceLock<Arc<Counter>> = OnceLock::new();
1134    SINK.get_or_init(|| Arc::new(Counter::new()))
1135}
1136
1137#[cfg(feature = "gauge")]
1138fn gauge_overflow_sink() -> &'static Arc<Gauge> {
1139    static SINK: OnceLock<Arc<Gauge>> = OnceLock::new();
1140    SINK.get_or_init(|| Arc::new(Gauge::new()))
1141}
1142
1143#[cfg(feature = "timer")]
1144fn timer_overflow_sink() -> &'static Arc<Timer> {
1145    static SINK: OnceLock<Arc<Timer>> = OnceLock::new();
1146    SINK.get_or_init(|| Arc::new(Timer::new()))
1147}
1148
1149#[cfg(feature = "meter")]
1150fn rate_meter_overflow_sink() -> &'static Arc<RateMeter> {
1151    static SINK: OnceLock<Arc<RateMeter>> = OnceLock::new();
1152    SINK.get_or_init(|| Arc::new(RateMeter::new()))
1153}
1154
1155#[cfg(feature = "histogram")]
1156fn histogram_overflow_sink() -> &'static Arc<Histogram> {
1157    static SINK: OnceLock<Arc<Histogram>> = OnceLock::new();
1158    SINK.get_or_init(|| Arc::new(Histogram::default_seconds()))
1159}
1160
1161// `Registry` is Send + Sync automatically because every field is Send + Sync.
1162// No unsafe impls required.
1163
1164#[cfg(test)]
1165#[cfg(all(feature = "count", feature = "gauge", feature = "timer"))]
1166mod tests {
1167    use super::*;
1168    use std::sync::Arc;
1169    use std::thread;
1170
1171    #[test]
1172    fn test_counter_registration() {
1173        let registry = Registry::new();
1174        let c1 = registry.get_or_create_counter("requests");
1175        let c2 = registry.get_or_create_counter("requests");
1176        assert!(Arc::ptr_eq(&c1, &c2));
1177    }
1178
1179    #[test]
1180    fn test_gauge_registration() {
1181        let registry = Registry::new();
1182        let g1 = registry.get_or_create_gauge("cpu_usage");
1183        let g2 = registry.get_or_create_gauge("cpu_usage");
1184        assert!(Arc::ptr_eq(&g1, &g2));
1185    }
1186
1187    #[test]
1188    fn test_timer_registration() {
1189        let registry = Registry::new();
1190        let t1 = registry.get_or_create_timer("db_query");
1191        let t2 = registry.get_or_create_timer("db_query");
1192        assert!(Arc::ptr_eq(&t1, &t2));
1193    }
1194
1195    #[test]
1196    #[cfg(feature = "meter")]
1197    fn test_rate_meter_registration() {
1198        let registry = Registry::new();
1199        let r1 = registry.get_or_create_rate_meter("api_calls");
1200        let r2 = registry.get_or_create_rate_meter("api_calls");
1201        assert!(Arc::ptr_eq(&r1, &r2));
1202    }
1203
1204    #[test]
1205    #[cfg(feature = "meter")]
1206    fn test_mixed_metrics() {
1207        let registry = Registry::new();
1208        let _ = registry.get_or_create_counter("a");
1209        let _ = registry.get_or_create_gauge("b");
1210        let _ = registry.get_or_create_timer("c");
1211        let _ = registry.get_or_create_rate_meter("d");
1212        assert_eq!(registry.metric_count(), 4);
1213    }
1214
1215    #[test]
1216    fn test_concurrent_access() {
1217        let registry = Arc::new(Registry::new());
1218        let mut handles = vec![];
1219        for _ in 0..10 {
1220            let r = registry.clone();
1221            handles.push(thread::spawn(move || {
1222                let c = r.get_or_create_counter("concurrent_test");
1223                c.inc();
1224            }));
1225        }
1226        for h in handles {
1227            h.join().unwrap();
1228        }
1229        assert_eq!(registry.get_or_create_counter("concurrent_test").get(), 10);
1230    }
1231
1232    #[test]
1233    fn test_clear() {
1234        let registry = Registry::new();
1235        let _ = registry.get_or_create_counter("a");
1236        let _ = registry.get_or_create_gauge("b");
1237        assert_eq!(registry.metric_count(), 2);
1238        registry.clear();
1239        assert_eq!(registry.metric_count(), 0);
1240    }
1241
1242    #[test]
1243    fn test_metric_names() {
1244        let registry = Registry::new();
1245        let _ = registry.get_or_create_counter("requests");
1246        let _ = registry.get_or_create_counter("errors");
1247        let _ = registry.get_or_create_gauge("cpu");
1248        assert_eq!(registry.counter_names().len(), 2);
1249        assert_eq!(registry.gauge_names().len(), 1);
1250    }
1251
1252    #[test]
1253    #[cfg(feature = "meter")]
1254    fn test_duplicate_names_across_types_are_independent() {
1255        let registry = Registry::new();
1256        let c = registry.get_or_create_counter("x");
1257        let g = registry.get_or_create_gauge("x");
1258        let t = registry.get_or_create_timer("x");
1259        let r = registry.get_or_create_rate_meter("x");
1260        let addrs = [
1261            Arc::as_ptr(&c) as usize,
1262            Arc::as_ptr(&g) as usize,
1263            Arc::as_ptr(&t) as usize,
1264            Arc::as_ptr(&r) as usize,
1265        ];
1266        for i in 0..addrs.len() {
1267            for j in (i + 1)..addrs.len() {
1268                assert_ne!(addrs[i], addrs[j]);
1269            }
1270        }
1271    }
1272
1273    #[test]
1274    fn test_clear_then_recreate_returns_new_instances() {
1275        let registry = Registry::new();
1276        let c_before = registry.get_or_create_counter("requests");
1277        registry.clear();
1278        let c_after = registry.get_or_create_counter("requests");
1279        assert!(!Arc::ptr_eq(&c_before, &c_after));
1280    }
1281
1282    #[test]
1283    fn test_concurrent_duplicate_registration_singleton_per_name() {
1284        let registry = Arc::new(Registry::new());
1285        let mut handles = vec![];
1286        for _ in 0..16 {
1287            let r = registry.clone();
1288            handles.push(thread::spawn(move || r.get_or_create_timer("dup")));
1289        }
1290        let first = registry.get_or_create_timer("dup");
1291        for h in handles {
1292            let t = h.join().unwrap();
1293            assert!(Arc::ptr_eq(&first, &t));
1294        }
1295    }
1296
1297    // ---------- v0.9.3 additions ----------
1298
1299    #[test]
1300    fn labeled_counter_distinct_from_unlabeled() {
1301        let r = Registry::new();
1302        let plain = r.get_or_create_counter("hits");
1303        let labels = LabelSet::from([("region", "us")]);
1304        let labeled = r.get_or_create_counter_with("hits", &labels);
1305        assert!(!Arc::ptr_eq(&plain, &labeled));
1306        plain.inc();
1307        labeled.add(5);
1308        assert_eq!(plain.get(), 1);
1309        assert_eq!(labeled.get(), 5);
1310        assert_eq!(r.cardinality_count(), 1);
1311    }
1312
1313    #[test]
1314    fn empty_labelset_routes_to_unlabeled_fast_path() {
1315        let r = Registry::new();
1316        let plain = r.get_or_create_counter("x");
1317        let same = r.get_or_create_counter_with("x", &LabelSet::EMPTY);
1318        assert!(Arc::ptr_eq(&plain, &same));
1319        assert_eq!(r.cardinality_count(), 0);
1320    }
1321
1322    #[test]
1323    fn cardinality_cap_routes_overflows_to_sink() {
1324        let r = Registry::new();
1325        r.set_cardinality_cap(2);
1326        let l1 = LabelSet::from([("k", "1")]);
1327        let l2 = LabelSet::from([("k", "2")]);
1328        let l3 = LabelSet::from([("k", "3")]);
1329        let _ = r.get_or_create_counter_with("c", &l1);
1330        let _ = r.get_or_create_counter_with("c", &l2);
1331        // Third registration overflows.
1332        let over = r.get_or_create_counter_with("c", &l3);
1333        let sink = counter_overflow_sink();
1334        assert!(Arc::ptr_eq(&over, sink));
1335        assert_eq!(r.cardinality_count(), 2);
1336        assert!(r.cardinality_overflows() >= 1);
1337    }
1338
1339    #[test]
1340    fn try_cardinality_cap_returns_error() {
1341        let r = Registry::new();
1342        r.set_cardinality_cap(1);
1343        let _ = r
1344            .try_get_or_create_counter_with("c", &LabelSet::from([("k", "1")]))
1345            .unwrap();
1346        let err = r
1347            .try_get_or_create_counter_with("c", &LabelSet::from([("k", "2")]))
1348            .unwrap_err();
1349        assert_eq!(err, MetricsError::CardinalityExceeded);
1350    }
1351
1352    #[test]
1353    fn metadata_roundtrip() {
1354        let r = Registry::new();
1355        r.describe_counter("requests", "Total HTTP requests", Unit::Custom("requests"));
1356        let meta = r.metadata("requests").unwrap();
1357        assert_eq!(meta.kind, MetricKind::Counter);
1358        assert_eq!(meta.help.as_ref(), "Total HTTP requests");
1359        assert_eq!(meta.unit, Unit::Custom("requests"));
1360    }
1361
1362    #[test]
1363    #[cfg(feature = "histogram")]
1364    fn histogram_uses_configured_buckets() {
1365        let r = Registry::new();
1366        r.configure_histogram("latency", [0.1, 0.5, 1.0]);
1367        let h = r.get_or_create_histogram("latency");
1368        // 3 explicit + implicit +Inf = 4.
1369        let snap = h.snapshot();
1370        assert_eq!(snap.buckets.len(), 4);
1371    }
1372
1373    // ---------- Coverage-completing tests for every labeled metric type ----------
1374
1375    #[test]
1376    fn labeled_gauge_distinct_from_unlabeled_and_caps() {
1377        let r = Registry::new();
1378        let plain = r.get_or_create_gauge("temp");
1379        let labels = LabelSet::from([("zone", "a")]);
1380        let labeled = r.get_or_create_gauge_with("temp", &labels);
1381        assert!(!Arc::ptr_eq(&plain, &labeled));
1382        plain.set(1.0);
1383        labeled.set(2.0);
1384        assert_eq!(plain.get(), 1.0);
1385        assert_eq!(labeled.get(), 2.0);
1386
1387        // Empty label set hits the fast path.
1388        let same = r.get_or_create_gauge_with("temp", &LabelSet::EMPTY);
1389        assert!(Arc::ptr_eq(&plain, &same));
1390
1391        // Try path returns CardinalityExceeded on full cap.
1392        r.set_cardinality_cap(1);
1393        assert!(r
1394            .try_get_or_create_gauge_with("temp", &LabelSet::from([("zone", "b")]))
1395            .is_err());
1396        // The non-try path routes to the per-type sink without panicking.
1397        let _ = r.get_or_create_gauge_with("temp", &LabelSet::from([("zone", "c")]));
1398        assert!(r.cardinality_overflows() >= 1);
1399    }
1400
1401    #[test]
1402    fn labeled_timer_distinct_from_unlabeled_and_caps() {
1403        let r = Registry::new();
1404        let plain = r.get_or_create_timer("rpc");
1405        let labeled = r.get_or_create_timer_with("rpc", &LabelSet::from([("op", "send")]));
1406        assert!(!Arc::ptr_eq(&plain, &labeled));
1407        plain.record(std::time::Duration::from_micros(50));
1408        labeled.record(std::time::Duration::from_micros(100));
1409        assert_eq!(plain.count(), 1);
1410        assert_eq!(labeled.count(), 1);
1411        let same = r.get_or_create_timer_with("rpc", &LabelSet::EMPTY);
1412        assert!(Arc::ptr_eq(&plain, &same));
1413
1414        r.set_cardinality_cap(1);
1415        assert!(r
1416            .try_get_or_create_timer_with("rpc", &LabelSet::from([("op", "recv")]))
1417            .is_err());
1418        let _ = r.get_or_create_timer_with("rpc", &LabelSet::from([("op", "ack")]));
1419        assert!(r.cardinality_overflows() >= 1);
1420    }
1421
1422    #[test]
1423    #[cfg(feature = "meter")]
1424    fn labeled_rate_meter_distinct_from_unlabeled_and_caps() {
1425        let r = Registry::new();
1426        let plain = r.get_or_create_rate_meter("qps");
1427        let labeled = r.get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "1")]));
1428        assert!(!Arc::ptr_eq(&plain, &labeled));
1429        plain.tick_n(3);
1430        labeled.tick_n(7);
1431        assert_eq!(plain.total(), 3);
1432        assert_eq!(labeled.total(), 7);
1433        let same = r.get_or_create_rate_meter_with("qps", &LabelSet::EMPTY);
1434        assert!(Arc::ptr_eq(&plain, &same));
1435
1436        r.set_cardinality_cap(1);
1437        assert!(r
1438            .try_get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "2")]))
1439            .is_err());
1440        let _ = r.get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "3")]));
1441        assert!(r.cardinality_overflows() >= 1);
1442    }
1443
1444    #[test]
1445    #[cfg(feature = "histogram")]
1446    fn labeled_histogram_caps_and_observes() {
1447        let r = Registry::new();
1448        r.configure_histogram("latency", [0.01, 0.1, 1.0]);
1449        let h = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "a")]));
1450        h.observe(0.005);
1451        assert_eq!(h.count(), 1);
1452
1453        r.set_cardinality_cap(1);
1454        // Already-registered labels should still resolve to the existing Arc.
1455        let h2 = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "a")]));
1456        assert!(Arc::ptr_eq(&h, &h2));
1457
1458        // New labeled key hits the cap.
1459        let err = r
1460            .try_get_or_create_histogram_with("latency", &LabelSet::from([("op", "b")]))
1461            .unwrap_err();
1462        assert_eq!(err, MetricsError::CardinalityExceeded);
1463        let _sink = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "c")]));
1464        assert!(r.cardinality_overflows() >= 1);
1465    }
1466
1467    #[test]
1468    fn describe_shorthands_cover_every_kind() {
1469        let r = Registry::new();
1470        r.describe_counter("c", "counter help", Unit::Custom("1"));
1471        r.describe_gauge("g", "gauge help", Unit::Bytes);
1472        r.describe_timer("t", "timer help", Unit::Seconds);
1473        #[cfg(feature = "meter")]
1474        r.describe_rate("rt", "rate help", Unit::Custom("ops"));
1475        #[cfg(feature = "histogram")]
1476        r.describe_histogram("h", "histogram help", Unit::Milliseconds);
1477
1478        assert_eq!(r.metadata("c").unwrap().kind, MetricKind::Counter);
1479        assert_eq!(r.metadata("g").unwrap().kind, MetricKind::Gauge);
1480        assert_eq!(r.metadata("t").unwrap().kind, MetricKind::Timer);
1481        #[cfg(feature = "meter")]
1482        assert_eq!(r.metadata("rt").unwrap().kind, MetricKind::Rate);
1483        #[cfg(feature = "histogram")]
1484        assert_eq!(r.metadata("h").unwrap().kind, MetricKind::Histogram);
1485
1486        // Re-describe replaces.
1487        r.describe_counter("c", "new help", Unit::None);
1488        assert_eq!(r.metadata("c").unwrap().help.as_ref(), "new help");
1489    }
1490
1491    #[test]
1492    fn snapshot_accessors_include_labeled_entries() {
1493        let r = Registry::new();
1494        r.get_or_create_counter("c1").inc();
1495        r.get_or_create_counter_with("c2", &LabelSet::from([("k", "v")]))
1496            .inc();
1497        r.get_or_create_gauge("g1").set(1.0);
1498        r.get_or_create_gauge_with("g2", &LabelSet::from([("k", "v")]))
1499            .set(2.0);
1500        r.get_or_create_timer("t1")
1501            .record(std::time::Duration::from_micros(1));
1502        r.get_or_create_timer_with("t2", &LabelSet::from([("k", "v")]))
1503            .record(std::time::Duration::from_micros(2));
1504
1505        assert_eq!(r.counter_entries().len(), 2);
1506        assert_eq!(r.gauge_entries().len(), 2);
1507        assert_eq!(r.timer_entries().len(), 2);
1508
1509        #[cfg(feature = "meter")]
1510        {
1511            r.get_or_create_rate_meter("r1").tick();
1512            r.get_or_create_rate_meter_with("r2", &LabelSet::from([("k", "v")]))
1513                .tick();
1514            assert_eq!(r.rate_meter_entries().len(), 2);
1515        }
1516
1517        #[cfg(feature = "histogram")]
1518        {
1519            r.get_or_create_histogram("h1").observe(0.1);
1520            r.get_or_create_histogram_with("h2", &LabelSet::from([("k", "v")]))
1521                .observe(0.1);
1522            assert_eq!(r.histogram_entries().len(), 2);
1523            let names = r.histogram_names();
1524            assert!(names.contains(&"h1".to_string()));
1525            assert!(names.contains(&"h2".to_string()));
1526        }
1527    }
1528
1529    #[test]
1530    fn cardinality_count_is_reset_by_clear_but_overflows_are_monotonic() {
1531        let r = Registry::new();
1532        r.set_cardinality_cap(2);
1533        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "1")]));
1534        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "2")]));
1535        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "3")])); // overflow
1536        assert_eq!(r.cardinality_count(), 2);
1537        assert!(r.cardinality_overflows() >= 1);
1538
1539        let prior_overflows = r.cardinality_overflows();
1540        r.clear();
1541        assert_eq!(r.cardinality_count(), 0);
1542        // overflows is monotonic across clears.
1543        assert_eq!(r.cardinality_overflows(), prior_overflows);
1544    }
1545
1546    #[test]
1547    fn cap_settings_round_trip() {
1548        let r = Registry::new();
1549        let default_cap = r.cardinality_cap();
1550        assert_eq!(default_cap, DEFAULT_CARDINALITY_CAP);
1551        r.set_cardinality_cap(42);
1552        assert_eq!(r.cardinality_cap(), 42);
1553        // Setting a cap below current count doesn't reset the count.
1554        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "v")]));
1555        r.set_cardinality_cap(0);
1556        assert_eq!(r.cardinality_cap(), 0);
1557        assert_eq!(r.cardinality_count(), 1);
1558        // But further labeled registrations now overflow.
1559        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "v2")]));
1560        assert!(r.cardinality_overflows() >= 1);
1561    }
1562
1563    // ---------- v0.9.5 additions: ScopedRegistry ----------
1564
1565    #[test]
1566    fn scoped_registry_prepends_prefix() {
1567        let r = Registry::new();
1568        let http = r.scoped("http.");
1569        http.counter("requests").inc();
1570        // Same Arc resolves both via the scope and via the underlying name.
1571        let c_scope = http.counter("requests");
1572        let c_full = r.get_or_create_counter("http.requests");
1573        assert!(Arc::ptr_eq(&c_scope, &c_full));
1574        assert_eq!(c_full.get(), 1);
1575    }
1576
1577    #[test]
1578    fn scoped_registry_describe_lands_under_prefixed_name() {
1579        let r = Registry::new();
1580        let s = r.scoped("svc.");
1581        s.describe_counter("hits", "Total hits", Unit::Custom("1"));
1582        assert!(r.metadata("svc.hits").is_some());
1583        assert!(r.metadata("hits").is_none());
1584    }
1585
1586    #[test]
1587    #[cfg(feature = "meter")]
1588    fn scoped_registry_supports_every_metric_type() {
1589        let r = Registry::new();
1590        let s = r.scoped("zone_a.");
1591        s.counter("c").inc();
1592        s.gauge("g").set(1.0);
1593        s.timer("t").record(std::time::Duration::from_micros(1));
1594        s.rate("r").tick();
1595        assert_eq!(r.get_or_create_counter("zone_a.c").get(), 1);
1596        assert_eq!(r.get_or_create_gauge("zone_a.g").get(), 1.0);
1597        assert_eq!(r.get_or_create_timer("zone_a.t").count(), 1);
1598        assert_eq!(r.get_or_create_rate_meter("zone_a.r").total(), 1);
1599    }
1600
1601    #[test]
1602    fn scoped_registry_labeled_routes_through_cardinality_cap() {
1603        let r = Registry::new();
1604        r.set_cardinality_cap(2);
1605        let s = r.scoped("api.");
1606        let _ = s.counter_with("hits", &LabelSet::from([("k", "1")]));
1607        let _ = s.counter_with("hits", &LabelSet::from([("k", "2")]));
1608        // Third labeled key overflows.
1609        let _ = s.counter_with("hits", &LabelSet::from([("k", "3")]));
1610        assert!(r.cardinality_overflows() >= 1);
1611    }
1612
1613    #[test]
1614    fn nested_scopes_compose_prefixes() {
1615        let r = Registry::new();
1616        let a = r.scoped("a.");
1617        let ab = a.scoped("b.");
1618        ab.counter("x").inc();
1619        assert_eq!(r.get_or_create_counter("a.b.x").get(), 1);
1620        assert_eq!(ab.prefix(), "a.b.");
1621    }
1622
1623    #[test]
1624    #[cfg(feature = "histogram")]
1625    fn scoped_registry_histogram_uses_configured_buckets() {
1626        let r = Registry::new();
1627        let s = r.scoped("rtt.");
1628        s.configure_histogram("seconds", [0.01, 0.1, 1.0]);
1629        let h = s.histogram("seconds");
1630        // 3 explicit + +Inf = 4
1631        assert_eq!(h.snapshot().buckets.len(), 4);
1632    }
1633}