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
916impl Default for Registry {
917    fn default() -> Self {
918        Self::new()
919    }
920}
921
922// ---------------------------------------------------------------------
923// Per-type process-global overflow sinks.
924//
925// When cardinality is exceeded, the non-`try` `*_with` methods route to these
926// process-global metrics so the caller's hot path never panics. These sinks
927// are *not* registered in the Registry and are *not* exported. The only way
928// to observe cardinality pressure is via `Registry::cardinality_overflows()`.
929// ---------------------------------------------------------------------
930
931#[cfg(feature = "count")]
932fn counter_overflow_sink() -> &'static Arc<Counter> {
933    static SINK: OnceLock<Arc<Counter>> = OnceLock::new();
934    SINK.get_or_init(|| Arc::new(Counter::new()))
935}
936
937#[cfg(feature = "gauge")]
938fn gauge_overflow_sink() -> &'static Arc<Gauge> {
939    static SINK: OnceLock<Arc<Gauge>> = OnceLock::new();
940    SINK.get_or_init(|| Arc::new(Gauge::new()))
941}
942
943#[cfg(feature = "timer")]
944fn timer_overflow_sink() -> &'static Arc<Timer> {
945    static SINK: OnceLock<Arc<Timer>> = OnceLock::new();
946    SINK.get_or_init(|| Arc::new(Timer::new()))
947}
948
949#[cfg(feature = "meter")]
950fn rate_meter_overflow_sink() -> &'static Arc<RateMeter> {
951    static SINK: OnceLock<Arc<RateMeter>> = OnceLock::new();
952    SINK.get_or_init(|| Arc::new(RateMeter::new()))
953}
954
955#[cfg(feature = "histogram")]
956fn histogram_overflow_sink() -> &'static Arc<Histogram> {
957    static SINK: OnceLock<Arc<Histogram>> = OnceLock::new();
958    SINK.get_or_init(|| Arc::new(Histogram::default_seconds()))
959}
960
961// `Registry` is Send + Sync automatically because every field is Send + Sync.
962// No unsafe impls required.
963
964#[cfg(test)]
965#[cfg(all(feature = "count", feature = "gauge", feature = "timer"))]
966mod tests {
967    use super::*;
968    use std::sync::Arc;
969    use std::thread;
970
971    #[test]
972    fn test_counter_registration() {
973        let registry = Registry::new();
974        let c1 = registry.get_or_create_counter("requests");
975        let c2 = registry.get_or_create_counter("requests");
976        assert!(Arc::ptr_eq(&c1, &c2));
977    }
978
979    #[test]
980    fn test_gauge_registration() {
981        let registry = Registry::new();
982        let g1 = registry.get_or_create_gauge("cpu_usage");
983        let g2 = registry.get_or_create_gauge("cpu_usage");
984        assert!(Arc::ptr_eq(&g1, &g2));
985    }
986
987    #[test]
988    fn test_timer_registration() {
989        let registry = Registry::new();
990        let t1 = registry.get_or_create_timer("db_query");
991        let t2 = registry.get_or_create_timer("db_query");
992        assert!(Arc::ptr_eq(&t1, &t2));
993    }
994
995    #[test]
996    #[cfg(feature = "meter")]
997    fn test_rate_meter_registration() {
998        let registry = Registry::new();
999        let r1 = registry.get_or_create_rate_meter("api_calls");
1000        let r2 = registry.get_or_create_rate_meter("api_calls");
1001        assert!(Arc::ptr_eq(&r1, &r2));
1002    }
1003
1004    #[test]
1005    #[cfg(feature = "meter")]
1006    fn test_mixed_metrics() {
1007        let registry = Registry::new();
1008        let _ = registry.get_or_create_counter("a");
1009        let _ = registry.get_or_create_gauge("b");
1010        let _ = registry.get_or_create_timer("c");
1011        let _ = registry.get_or_create_rate_meter("d");
1012        assert_eq!(registry.metric_count(), 4);
1013    }
1014
1015    #[test]
1016    fn test_concurrent_access() {
1017        let registry = Arc::new(Registry::new());
1018        let mut handles = vec![];
1019        for _ in 0..10 {
1020            let r = registry.clone();
1021            handles.push(thread::spawn(move || {
1022                let c = r.get_or_create_counter("concurrent_test");
1023                c.inc();
1024            }));
1025        }
1026        for h in handles {
1027            h.join().unwrap();
1028        }
1029        assert_eq!(registry.get_or_create_counter("concurrent_test").get(), 10);
1030    }
1031
1032    #[test]
1033    fn test_clear() {
1034        let registry = Registry::new();
1035        let _ = registry.get_or_create_counter("a");
1036        let _ = registry.get_or_create_gauge("b");
1037        assert_eq!(registry.metric_count(), 2);
1038        registry.clear();
1039        assert_eq!(registry.metric_count(), 0);
1040    }
1041
1042    #[test]
1043    fn test_metric_names() {
1044        let registry = Registry::new();
1045        let _ = registry.get_or_create_counter("requests");
1046        let _ = registry.get_or_create_counter("errors");
1047        let _ = registry.get_or_create_gauge("cpu");
1048        assert_eq!(registry.counter_names().len(), 2);
1049        assert_eq!(registry.gauge_names().len(), 1);
1050    }
1051
1052    #[test]
1053    #[cfg(feature = "meter")]
1054    fn test_duplicate_names_across_types_are_independent() {
1055        let registry = Registry::new();
1056        let c = registry.get_or_create_counter("x");
1057        let g = registry.get_or_create_gauge("x");
1058        let t = registry.get_or_create_timer("x");
1059        let r = registry.get_or_create_rate_meter("x");
1060        let addrs = [
1061            Arc::as_ptr(&c) as usize,
1062            Arc::as_ptr(&g) as usize,
1063            Arc::as_ptr(&t) as usize,
1064            Arc::as_ptr(&r) as usize,
1065        ];
1066        for i in 0..addrs.len() {
1067            for j in (i + 1)..addrs.len() {
1068                assert_ne!(addrs[i], addrs[j]);
1069            }
1070        }
1071    }
1072
1073    #[test]
1074    fn test_clear_then_recreate_returns_new_instances() {
1075        let registry = Registry::new();
1076        let c_before = registry.get_or_create_counter("requests");
1077        registry.clear();
1078        let c_after = registry.get_or_create_counter("requests");
1079        assert!(!Arc::ptr_eq(&c_before, &c_after));
1080    }
1081
1082    #[test]
1083    fn test_concurrent_duplicate_registration_singleton_per_name() {
1084        let registry = Arc::new(Registry::new());
1085        let mut handles = vec![];
1086        for _ in 0..16 {
1087            let r = registry.clone();
1088            handles.push(thread::spawn(move || r.get_or_create_timer("dup")));
1089        }
1090        let first = registry.get_or_create_timer("dup");
1091        for h in handles {
1092            let t = h.join().unwrap();
1093            assert!(Arc::ptr_eq(&first, &t));
1094        }
1095    }
1096
1097    // ---------- v0.9.3 additions ----------
1098
1099    #[test]
1100    fn labeled_counter_distinct_from_unlabeled() {
1101        let r = Registry::new();
1102        let plain = r.get_or_create_counter("hits");
1103        let labels = LabelSet::from([("region", "us")]);
1104        let labeled = r.get_or_create_counter_with("hits", &labels);
1105        assert!(!Arc::ptr_eq(&plain, &labeled));
1106        plain.inc();
1107        labeled.add(5);
1108        assert_eq!(plain.get(), 1);
1109        assert_eq!(labeled.get(), 5);
1110        assert_eq!(r.cardinality_count(), 1);
1111    }
1112
1113    #[test]
1114    fn empty_labelset_routes_to_unlabeled_fast_path() {
1115        let r = Registry::new();
1116        let plain = r.get_or_create_counter("x");
1117        let same = r.get_or_create_counter_with("x", &LabelSet::EMPTY);
1118        assert!(Arc::ptr_eq(&plain, &same));
1119        assert_eq!(r.cardinality_count(), 0);
1120    }
1121
1122    #[test]
1123    fn cardinality_cap_routes_overflows_to_sink() {
1124        let r = Registry::new();
1125        r.set_cardinality_cap(2);
1126        let l1 = LabelSet::from([("k", "1")]);
1127        let l2 = LabelSet::from([("k", "2")]);
1128        let l3 = LabelSet::from([("k", "3")]);
1129        let _ = r.get_or_create_counter_with("c", &l1);
1130        let _ = r.get_or_create_counter_with("c", &l2);
1131        // Third registration overflows.
1132        let over = r.get_or_create_counter_with("c", &l3);
1133        let sink = counter_overflow_sink();
1134        assert!(Arc::ptr_eq(&over, sink));
1135        assert_eq!(r.cardinality_count(), 2);
1136        assert!(r.cardinality_overflows() >= 1);
1137    }
1138
1139    #[test]
1140    fn try_cardinality_cap_returns_error() {
1141        let r = Registry::new();
1142        r.set_cardinality_cap(1);
1143        let _ = r
1144            .try_get_or_create_counter_with("c", &LabelSet::from([("k", "1")]))
1145            .unwrap();
1146        let err = r
1147            .try_get_or_create_counter_with("c", &LabelSet::from([("k", "2")]))
1148            .unwrap_err();
1149        assert_eq!(err, MetricsError::CardinalityExceeded);
1150    }
1151
1152    #[test]
1153    fn metadata_roundtrip() {
1154        let r = Registry::new();
1155        r.describe_counter("requests", "Total HTTP requests", Unit::Custom("requests"));
1156        let meta = r.metadata("requests").unwrap();
1157        assert_eq!(meta.kind, MetricKind::Counter);
1158        assert_eq!(meta.help.as_ref(), "Total HTTP requests");
1159        assert_eq!(meta.unit, Unit::Custom("requests"));
1160    }
1161
1162    #[test]
1163    #[cfg(feature = "histogram")]
1164    fn histogram_uses_configured_buckets() {
1165        let r = Registry::new();
1166        r.configure_histogram("latency", [0.1, 0.5, 1.0]);
1167        let h = r.get_or_create_histogram("latency");
1168        // 3 explicit + implicit +Inf = 4.
1169        let snap = h.snapshot();
1170        assert_eq!(snap.buckets.len(), 4);
1171    }
1172
1173    // ---------- Coverage-completing tests for every labeled metric type ----------
1174
1175    #[test]
1176    fn labeled_gauge_distinct_from_unlabeled_and_caps() {
1177        let r = Registry::new();
1178        let plain = r.get_or_create_gauge("temp");
1179        let labels = LabelSet::from([("zone", "a")]);
1180        let labeled = r.get_or_create_gauge_with("temp", &labels);
1181        assert!(!Arc::ptr_eq(&plain, &labeled));
1182        plain.set(1.0);
1183        labeled.set(2.0);
1184        assert_eq!(plain.get(), 1.0);
1185        assert_eq!(labeled.get(), 2.0);
1186
1187        // Empty label set hits the fast path.
1188        let same = r.get_or_create_gauge_with("temp", &LabelSet::EMPTY);
1189        assert!(Arc::ptr_eq(&plain, &same));
1190
1191        // Try path returns CardinalityExceeded on full cap.
1192        r.set_cardinality_cap(1);
1193        assert!(r
1194            .try_get_or_create_gauge_with("temp", &LabelSet::from([("zone", "b")]))
1195            .is_err());
1196        // The non-try path routes to the per-type sink without panicking.
1197        let _ = r.get_or_create_gauge_with("temp", &LabelSet::from([("zone", "c")]));
1198        assert!(r.cardinality_overflows() >= 1);
1199    }
1200
1201    #[test]
1202    fn labeled_timer_distinct_from_unlabeled_and_caps() {
1203        let r = Registry::new();
1204        let plain = r.get_or_create_timer("rpc");
1205        let labeled = r.get_or_create_timer_with("rpc", &LabelSet::from([("op", "send")]));
1206        assert!(!Arc::ptr_eq(&plain, &labeled));
1207        plain.record(std::time::Duration::from_micros(50));
1208        labeled.record(std::time::Duration::from_micros(100));
1209        assert_eq!(plain.count(), 1);
1210        assert_eq!(labeled.count(), 1);
1211        let same = r.get_or_create_timer_with("rpc", &LabelSet::EMPTY);
1212        assert!(Arc::ptr_eq(&plain, &same));
1213
1214        r.set_cardinality_cap(1);
1215        assert!(r
1216            .try_get_or_create_timer_with("rpc", &LabelSet::from([("op", "recv")]))
1217            .is_err());
1218        let _ = r.get_or_create_timer_with("rpc", &LabelSet::from([("op", "ack")]));
1219        assert!(r.cardinality_overflows() >= 1);
1220    }
1221
1222    #[test]
1223    #[cfg(feature = "meter")]
1224    fn labeled_rate_meter_distinct_from_unlabeled_and_caps() {
1225        let r = Registry::new();
1226        let plain = r.get_or_create_rate_meter("qps");
1227        let labeled = r.get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "1")]));
1228        assert!(!Arc::ptr_eq(&plain, &labeled));
1229        plain.tick_n(3);
1230        labeled.tick_n(7);
1231        assert_eq!(plain.total(), 3);
1232        assert_eq!(labeled.total(), 7);
1233        let same = r.get_or_create_rate_meter_with("qps", &LabelSet::EMPTY);
1234        assert!(Arc::ptr_eq(&plain, &same));
1235
1236        r.set_cardinality_cap(1);
1237        assert!(r
1238            .try_get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "2")]))
1239            .is_err());
1240        let _ = r.get_or_create_rate_meter_with("qps", &LabelSet::from([("tier", "3")]));
1241        assert!(r.cardinality_overflows() >= 1);
1242    }
1243
1244    #[test]
1245    #[cfg(feature = "histogram")]
1246    fn labeled_histogram_caps_and_observes() {
1247        let r = Registry::new();
1248        r.configure_histogram("latency", [0.01, 0.1, 1.0]);
1249        let h = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "a")]));
1250        h.observe(0.005);
1251        assert_eq!(h.count(), 1);
1252
1253        r.set_cardinality_cap(1);
1254        // Already-registered labels should still resolve to the existing Arc.
1255        let h2 = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "a")]));
1256        assert!(Arc::ptr_eq(&h, &h2));
1257
1258        // New labeled key hits the cap.
1259        let err = r
1260            .try_get_or_create_histogram_with("latency", &LabelSet::from([("op", "b")]))
1261            .unwrap_err();
1262        assert_eq!(err, MetricsError::CardinalityExceeded);
1263        let _sink = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "c")]));
1264        assert!(r.cardinality_overflows() >= 1);
1265    }
1266
1267    #[test]
1268    fn describe_shorthands_cover_every_kind() {
1269        let r = Registry::new();
1270        r.describe_counter("c", "counter help", Unit::Custom("1"));
1271        r.describe_gauge("g", "gauge help", Unit::Bytes);
1272        r.describe_timer("t", "timer help", Unit::Seconds);
1273        #[cfg(feature = "meter")]
1274        r.describe_rate("rt", "rate help", Unit::Custom("ops"));
1275        #[cfg(feature = "histogram")]
1276        r.describe_histogram("h", "histogram help", Unit::Milliseconds);
1277
1278        assert_eq!(r.metadata("c").unwrap().kind, MetricKind::Counter);
1279        assert_eq!(r.metadata("g").unwrap().kind, MetricKind::Gauge);
1280        assert_eq!(r.metadata("t").unwrap().kind, MetricKind::Timer);
1281        #[cfg(feature = "meter")]
1282        assert_eq!(r.metadata("rt").unwrap().kind, MetricKind::Rate);
1283        #[cfg(feature = "histogram")]
1284        assert_eq!(r.metadata("h").unwrap().kind, MetricKind::Histogram);
1285
1286        // Re-describe replaces.
1287        r.describe_counter("c", "new help", Unit::None);
1288        assert_eq!(r.metadata("c").unwrap().help.as_ref(), "new help");
1289    }
1290
1291    #[test]
1292    fn snapshot_accessors_include_labeled_entries() {
1293        let r = Registry::new();
1294        r.get_or_create_counter("c1").inc();
1295        r.get_or_create_counter_with("c2", &LabelSet::from([("k", "v")]))
1296            .inc();
1297        r.get_or_create_gauge("g1").set(1.0);
1298        r.get_or_create_gauge_with("g2", &LabelSet::from([("k", "v")]))
1299            .set(2.0);
1300        r.get_or_create_timer("t1")
1301            .record(std::time::Duration::from_micros(1));
1302        r.get_or_create_timer_with("t2", &LabelSet::from([("k", "v")]))
1303            .record(std::time::Duration::from_micros(2));
1304
1305        assert_eq!(r.counter_entries().len(), 2);
1306        assert_eq!(r.gauge_entries().len(), 2);
1307        assert_eq!(r.timer_entries().len(), 2);
1308
1309        #[cfg(feature = "meter")]
1310        {
1311            r.get_or_create_rate_meter("r1").tick();
1312            r.get_or_create_rate_meter_with("r2", &LabelSet::from([("k", "v")]))
1313                .tick();
1314            assert_eq!(r.rate_meter_entries().len(), 2);
1315        }
1316
1317        #[cfg(feature = "histogram")]
1318        {
1319            r.get_or_create_histogram("h1").observe(0.1);
1320            r.get_or_create_histogram_with("h2", &LabelSet::from([("k", "v")]))
1321                .observe(0.1);
1322            assert_eq!(r.histogram_entries().len(), 2);
1323            let names = r.histogram_names();
1324            assert!(names.contains(&"h1".to_string()));
1325            assert!(names.contains(&"h2".to_string()));
1326        }
1327    }
1328
1329    #[test]
1330    fn cardinality_count_is_reset_by_clear_but_overflows_are_monotonic() {
1331        let r = Registry::new();
1332        r.set_cardinality_cap(2);
1333        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "1")]));
1334        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "2")]));
1335        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "3")])); // overflow
1336        assert_eq!(r.cardinality_count(), 2);
1337        assert!(r.cardinality_overflows() >= 1);
1338
1339        let prior_overflows = r.cardinality_overflows();
1340        r.clear();
1341        assert_eq!(r.cardinality_count(), 0);
1342        // overflows is monotonic across clears.
1343        assert_eq!(r.cardinality_overflows(), prior_overflows);
1344    }
1345
1346    #[test]
1347    fn cap_settings_round_trip() {
1348        let r = Registry::new();
1349        let default_cap = r.cardinality_cap();
1350        assert_eq!(default_cap, DEFAULT_CARDINALITY_CAP);
1351        r.set_cardinality_cap(42);
1352        assert_eq!(r.cardinality_cap(), 42);
1353        // Setting a cap below current count doesn't reset the count.
1354        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "v")]));
1355        r.set_cardinality_cap(0);
1356        assert_eq!(r.cardinality_cap(), 0);
1357        assert_eq!(r.cardinality_count(), 1);
1358        // But further labeled registrations now overflow.
1359        let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "v2")]));
1360        assert!(r.cardinality_overflows() >= 1);
1361    }
1362}