1use std::collections::HashMap;
41use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
42use std::sync::RwLock;
43
44#[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
77pub const DEFAULT_CARDINALITY_CAP: usize = 10_000;
79
80#[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 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 #[inline]
162 pub fn set_cardinality_cap(&self, cap: usize) {
163 self.cardinality_cap.store(cap, Ordering::Relaxed);
164 }
165
166 #[must_use]
168 #[inline]
169 pub fn cardinality_cap(&self) -> usize {
170 self.cardinality_cap.load(Ordering::Relaxed)
171 }
172
173 #[must_use]
176 #[inline]
177 pub fn cardinality_count(&self) -> usize {
178 self.cardinality_count.load(Ordering::Relaxed)
179 }
180
181 #[must_use]
184 #[inline]
185 pub fn cardinality_overflows(&self) -> u64 {
186 self.cardinality_overflows.load(Ordering::Relaxed)
187 }
188
189 #[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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[cfg(feature = "histogram")]
567 pub fn get_or_create_histogram(&self, name: &str) -> Arc<Histogram> {
568 self.get_or_create_histogram_with(name, &LabelSet::EMPTY)
571 }
572
573 #[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 #[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 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 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 #[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 #[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 #[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 #[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 #[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 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 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 }
812
813 #[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 #[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 #[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 #[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 #[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 pub fn scoped(&self, prefix: impl Into<String>) -> ScopedRegistry<'_> {
939 ScopedRegistry {
940 registry: self,
941 prefix: prefix.into(),
942 }
943 }
944}
945
946pub struct ScopedRegistry<'a> {
951 registry: &'a Registry,
952 prefix: String,
953}
954
955impl<'a> ScopedRegistry<'a> {
956 #[must_use]
958 pub fn prefix(&self) -> &str {
959 &self.prefix
960 }
961
962 #[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 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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#[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 #[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 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 let snap = h.snapshot();
1370 assert_eq!(snap.buckets.len(), 4);
1371 }
1372
1373 #[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 let same = r.get_or_create_gauge_with("temp", &LabelSet::EMPTY);
1389 assert!(Arc::ptr_eq(&plain, &same));
1390
1391 r.set_cardinality_cap(1);
1393 assert!(r
1394 .try_get_or_create_gauge_with("temp", &LabelSet::from([("zone", "b")]))
1395 .is_err());
1396 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 let h2 = r.get_or_create_histogram_with("latency", &LabelSet::from([("op", "a")]));
1456 assert!(Arc::ptr_eq(&h, &h2));
1457
1458 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 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")])); 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 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 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 let _ = r.get_or_create_counter_with("c", &LabelSet::from([("k", "v2")]));
1560 assert!(r.cardinality_overflows() >= 1);
1561 }
1562
1563 #[test]
1566 fn scoped_registry_prepends_prefix() {
1567 let r = Registry::new();
1568 let http = r.scoped("http.");
1569 http.counter("requests").inc();
1570 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 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 assert_eq!(h.snapshot().buckets.len(), 4);
1632 }
1633}