1pub mod histogram;
4mod registration;
5pub mod status;
6pub(crate) mod task;
7
8pub(crate) const METRICS_PREFIX: &str = "runtime";
10
11pub use commonware_runtime_macros::{EncodeLabelSet, EncodeLabelValue, EncodeStruct};
12pub use prometheus_client::{
13 collector, encoding,
14 encoding::{
15 CounterValueEncoder, DescriptorEncoder, EncodeCounterValue, EncodeExemplarTime,
16 EncodeExemplarValue, EncodeGaugeValue, EncodeLabel, EncodeLabelKey,
17 EncodeLabelSet as EncodeLabelSetTrait, EncodeLabelValue as EncodeLabelValueTrait,
18 EncodeMetric, ExemplarValueEncoder, GaugeValueEncoder, LabelEncoder, LabelKeyEncoder,
19 LabelSetEncoder, LabelValueEncoder, MetricEncoder, NoLabelSet,
20 },
21 metrics::{MetricType, TypedMetric},
22 registry,
23 registry::Metric,
24};
25
26pub mod raw {
29 pub use prometheus_client::metrics::{
30 counter::Counter,
31 family::{self, Family},
32 gauge::Gauge,
33 histogram::Histogram,
34 };
35}
36
37use commonware_utils::sync::Mutex;
38use prometheus_client::encoding::{
39 MetricEncoder as PromMetricEncoder,
40 text::{encode, encode_eof},
41};
42pub use registration::Registration;
43use std::{
44 any::Any,
45 borrow::Cow,
46 collections::{BTreeMap, HashMap},
47 ops::Deref,
48 sync::{Arc, Weak, atomic::Ordering},
49};
50
51#[cfg(target_has_atomic = "64")]
56pub type GaugeValue = i64;
57#[cfg(not(target_has_atomic = "64"))]
58pub type GaugeValue = i32;
59
60pub type Counter = Registered<raw::Counter>;
62pub type Gauge = Registered<raw::Gauge>;
64pub type Histogram = Registered<raw::Histogram>;
66pub type CounterFamily<L> = Registered<raw::Family<L, raw::Counter>>;
68pub type GaugeFamily<L> = Registered<raw::Family<L, raw::Gauge>>;
70
71pub trait GaugeExt {
73 fn try_set<T: TryInto<GaugeValue>>(&self, value: T) -> Result<GaugeValue, T::Error>;
75
76 fn try_set_max<T: TryInto<GaugeValue> + Copy>(&self, value: T) -> Result<GaugeValue, T::Error>;
78}
79
80impl GaugeExt for raw::Gauge {
81 fn try_set<T: TryInto<GaugeValue>>(&self, value: T) -> Result<GaugeValue, T::Error> {
82 let value = value.try_into()?;
83 Ok(self.set(value))
84 }
85
86 fn try_set_max<T: TryInto<GaugeValue> + Copy>(&self, value: T) -> Result<GaugeValue, T::Error> {
87 let value = value.try_into()?;
88 Ok(self.inner().fetch_max(value, Ordering::Relaxed))
89 }
90}
91
92pub use histogram::HistogramExt;
93
94pub trait MetricsExt: crate::Metrics {
96 fn counter<N: Into<String>, H: Into<String>>(&self, name: N, help: H) -> Counter {
98 self.register(name, help, raw::Counter::default())
99 }
100
101 fn gauge<N: Into<String>, H: Into<String>>(&self, name: N, help: H) -> Gauge {
103 self.register(name, help, raw::Gauge::default())
104 }
105
106 fn histogram<N: Into<String>, H: Into<String>, I>(
108 &self,
109 name: N,
110 help: H,
111 buckets: I,
112 ) -> Histogram
113 where
114 I: IntoIterator<Item = f64>,
115 {
116 self.register(name, help, raw::Histogram::new(buckets))
117 }
118
119 fn family<N, H, S, M>(&self, name: N, help: H) -> Registered<raw::Family<S, M>>
121 where
122 N: Into<String>,
123 H: Into<String>,
124 S: Clone + std::hash::Hash + Eq,
125 M: Default,
126 raw::Family<S, M>: Metric,
127 {
128 self.register(name, help, raw::Family::<S, M>::default())
129 }
130}
131
132impl<T: crate::Metrics> MetricsExt for T {}
133
134pub fn validate_label(label: &str) {
141 let mut chars = label.chars();
142 assert!(
143 chars.next().is_some_and(|c| c.is_ascii_alphabetic()),
144 "label must start with [a-zA-Z]: {label}"
145 );
146 assert!(
147 chars.all(|c| c.is_ascii_alphanumeric() || c == '_'),
148 "label must only contain [a-zA-Z0-9_]: {label}"
149 );
150}
151
152pub fn add_attribute(
156 attributes: &mut Vec<(String, String)>,
157 key: &str,
158 value: impl std::fmt::Display,
159) -> bool {
160 let key_string = key.to_string();
161 let value_string = value.to_string();
162
163 match attributes.binary_search_by(|(k, _)| k.cmp(&key_string)) {
164 Ok(pos) => {
165 attributes[pos].1 = value_string;
166 false
167 }
168 Err(pos) => {
169 attributes.insert(pos, (key_string, value_string));
170 true
171 }
172 }
173}
174
175#[cfg(any(test, feature = "test-utils"))]
176fn matches_metric_name(full: &str, name: &str) -> bool {
177 full == name
178 || full
179 .strip_suffix(name)
180 .is_some_and(|prefix| prefix.ends_with('_'))
181}
182
183#[cfg(any(test, feature = "test-utils"))]
188#[must_use]
189pub fn has_metric_value(metrics: &str, name: &str, value: impl std::fmt::Display) -> bool {
190 let value = value.to_string();
191 metrics.lines().any(|line| {
192 let line = line.trim();
193 if line.starts_with('#') {
194 return false;
195 }
196
197 let Some(sample_end) = line.find(|c: char| c == '{' || c.is_whitespace()) else {
198 return false;
199 };
200 let sample_name = &line[..sample_end];
201 if !matches_metric_name(sample_name, name) {
202 return false;
203 }
204
205 let mut rest = &line[sample_end..];
206 if let Some(labeled) = rest.strip_prefix('{') {
207 let Some(labels_end) = labeled.find('}') else {
208 return false;
209 };
210 rest = &labeled[labels_end + 1..];
211 }
212 if !rest.chars().next().is_some_and(char::is_whitespace) {
213 return false;
214 }
215
216 rest.split_whitespace().next() == Some(value.as_str())
217 })
218}
219
220#[cfg(any(test, feature = "test-utils"))]
262pub fn count_running_tasks(metrics: &impl crate::Metrics, prefix: &str) -> usize {
263 let encoded = metrics.encode();
264 encoded
265 .lines()
266 .filter_map(|line| {
267 if !line.starts_with("runtime_tasks_running{") || !line.contains("kind=\"Task\"") {
268 return None;
269 }
270 let name = line.split("name=\"").nth(1)?.split('"').next()?;
271 if !name.starts_with(prefix) {
272 return None;
273 }
274 line.trim_end().rsplit(' ').next()?.parse::<usize>().ok()
275 })
276 .sum()
277}
278
279fn encode_descriptor<W>(
284 writer: &mut W,
285 name: &str,
286 help: &str,
287 metric_type: MetricType,
288) -> Result<(), std::fmt::Error>
289where
290 W: std::fmt::Write,
291{
292 writer.write_str("# HELP ")?;
293 writer.write_str(name)?;
294 writer.write_str(" ")?;
295 writer.write_str(help)?;
296 writer.write_str("\n# TYPE ")?;
297 writer.write_str(name)?;
298 writer.write_str(" ")?;
299 writer.write_str(metric_type.as_str())?;
300 writer.write_str("\n")?;
301 Ok(())
302}
303
304pub(crate) fn prefixed_name(prefix: &str, name: &str) -> String {
306 if prefix.is_empty() {
307 name.to_string()
308 } else {
309 format!("{prefix}_{name}")
310 }
311}
312
313pub(crate) fn child_label(prefix: &str, label: &str) -> String {
316 validate_label(label);
317 let name = prefixed_name(prefix, label);
318 assert!(
319 !name.starts_with(METRICS_PREFIX),
320 "using runtime label is not allowed"
321 );
322 name
323}
324
325struct RegistryGuard {
326 id: usize,
327 registry: Weak<Mutex<RegistryInner>>,
328}
329
330impl Drop for RegistryGuard {
331 fn drop(&mut self) {
332 let Some(registry) = self.registry.upgrade() else {
333 return;
334 };
335 registry.lock().release_registration(self.id);
336 }
337}
338
339#[must_use = "registered metrics are removed when the returned handle is dropped"]
341pub struct Registered<M> {
342 metric: Arc<M>,
343 registration: Registration,
344}
345
346impl<M> Clone for Registered<M> {
347 fn clone(&self) -> Self {
348 Self {
349 metric: self.metric.clone(),
350 registration: self.registration.clone(),
351 }
352 }
353}
354
355impl<M> Registered<M> {
356 pub fn with_registration(metric: M, registration: Registration) -> Self {
362 Self {
363 metric: Arc::new(metric),
364 registration,
365 }
366 }
367
368 pub fn metric(&self) -> &M {
369 self.metric.as_ref()
370 }
371}
372
373impl<S, M, C> Registered<raw::Family<S, M, C>>
374where
375 S: Clone + std::hash::Hash + Eq,
376 C: raw::family::MetricConstructor<M>,
377{
378 pub fn get_by<Q>(
379 &self,
380 label_set: &Q,
381 ) -> Option<impl Deref<Target = M> + '_ + use<'_, Q, S, M, C>>
382 where
383 for<'a> S: From<&'a Q>,
384 {
385 let label_set = S::from(label_set);
386 self.get(&label_set)
387 }
388
389 pub fn get_or_create_by<Q>(
390 &self,
391 label_set: &Q,
392 ) -> impl Deref<Target = M> + '_ + use<'_, Q, S, M, C>
393 where
394 for<'a> S: From<&'a Q>,
395 {
396 let label_set = S::from(label_set);
397 self.get_or_create(&label_set)
398 }
399
400 pub fn remove_by<Q>(&self, label_set: &Q) -> bool
401 where
402 for<'a> S: From<&'a Q>,
403 {
404 let label_set = S::from(label_set);
405 self.remove(&label_set)
406 }
407}
408
409impl<M> Deref for Registered<M> {
410 type Target = M;
411
412 fn deref(&self) -> &Self::Target {
413 self.metric()
414 }
415}
416
417impl<M: std::fmt::Debug> std::fmt::Debug for Registered<M> {
418 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419 f.debug_struct("Registered")
420 .field("metric", self.metric())
421 .finish_non_exhaustive()
422 }
423}
424
425type MetricAttributes = Vec<(Cow<'static, str>, Cow<'static, str>)>;
426type MetricKey = (String, MetricAttributes);
427type SampleEncoder = dyn Fn(&mut String) -> Result<(), std::fmt::Error> + Send + Sync;
428
429struct PendingMetricEntry {
430 family_name: String,
431 attributes: MetricAttributes,
432 encode_samples: Box<SampleEncoder>,
433 metric_any: Arc<dyn Any + Send + Sync>,
434}
435
436pub(crate) struct SharedMetric<M>(pub(crate) Arc<M>);
437
438impl<M: std::fmt::Debug> std::fmt::Debug for SharedMetric<M> {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 self.0.fmt(f)
441 }
442}
443
444impl<M: EncodeMetric> EncodeMetric for SharedMetric<M> {
445 fn encode(&self, encoder: PromMetricEncoder<'_>) -> Result<(), std::fmt::Error> {
446 self.0.encode(encoder)
447 }
448
449 fn metric_type(&self) -> MetricType {
450 self.0.metric_type()
451 }
452}
453
454fn create_sample_encoder<M>(
455 name: String,
456 labels: MetricAttributes,
457 metric: Arc<M>,
458) -> Box<SampleEncoder>
459where
460 M: Metric,
461{
462 let mut registry = registry::Registry::with_labels(labels.into_iter());
466 registry.register(name, "", SharedMetric(metric));
467
468 Box::new(move |samples| {
469 let mut encoded = String::new();
470 encode(&mut encoded, ®istry).expect("encoding temporary metric registry failed");
471 for line in encoded.lines() {
472 if line.starts_with('#') {
473 continue;
474 }
475 samples.push_str(line);
476 samples.push('\n');
477 }
478 Ok(())
479 })
480}
481
482fn owned_attributes(attributes: Vec<(String, String)>) -> MetricAttributes {
483 attributes
484 .into_iter()
485 .map(|(k, v)| (Cow::Owned(k), Cow::Owned(v)))
486 .collect()
487}
488
489fn normalize_help(help: String) -> String {
494 help + "."
495}
496
497struct MetricEntry {
498 family_name: String,
499 attributes: MetricAttributes,
500 encode_samples: Box<SampleEncoder>,
501 metric_any: Arc<dyn Any + Send + Sync>,
502 claims: usize,
503 family_index: usize,
504}
505
506#[derive(Debug)]
507struct MetricFamily {
508 help: String,
509 metric_type: MetricType,
510 descriptor: String,
511 metric_ids: Vec<usize>,
512}
513
514#[derive(Clone)]
516pub struct Registry {
517 inner: Arc<Mutex<RegistryInner>>,
518}
519
520struct RegistryInner {
521 metrics: Vec<Option<MetricEntry>>,
523 free_metric_ids: Vec<usize>,
525 families: BTreeMap<String, MetricFamily>,
527 keys: HashMap<MetricKey, usize>,
529 next_metric_id: usize,
531}
532
533impl Default for Registry {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539impl Registry {
540 pub fn new() -> Self {
541 Self {
542 inner: Arc::new(Mutex::new(RegistryInner::new())),
543 }
544 }
545
546 pub(crate) fn register<M>(
547 &self,
548 name: String,
549 help: String,
550 attributes: Vec<(String, String)>,
551 metric: Arc<M>,
552 ) -> Registered<M>
553 where
554 M: Metric,
555 {
556 let mut inner = self.inner.lock();
557 inner.register(Arc::downgrade(&self.inner), name, help, attributes, metric)
558 }
559
560 pub fn encode(&self) -> String {
561 self.inner.lock().encode()
562 }
563}
564
565impl RegistryInner {
566 fn new() -> Self {
567 Self {
568 metrics: Vec::new(),
569 free_metric_ids: Vec::new(),
570 families: BTreeMap::new(),
571 keys: HashMap::new(),
572 next_metric_id: 0,
573 }
574 }
575
576 fn register<M>(
577 &mut self,
578 registry: Weak<Mutex<Self>>,
579 name: String,
580 help: String,
581 attributes: Vec<(String, String)>,
582 metric: Arc<M>,
583 ) -> Registered<M>
584 where
585 M: Metric,
586 {
587 let attributes = owned_attributes(attributes);
588 let help = normalize_help(help);
589 let metric_type = metric.metric_type();
590 let encode_samples =
591 create_sample_encoder(name.clone(), attributes.clone(), metric.clone());
592 let key = (name.clone(), attributes.clone());
593 if let Some(existing_id) = self.keys.get(&key).copied() {
594 let entry = self.metric_ref(existing_id);
595 if let Some(family) = self.families.get(&name) {
596 assert_eq!(
597 family.help, help,
598 "metric family `{}` registered with inconsistent help text",
599 name
600 );
601 }
602 let existing_metric = Arc::clone(&entry.metric_any)
603 .downcast::<M>()
604 .unwrap_or_else(|_| {
605 panic!(
606 "duplicate metric `{}` with attributes {:?} registered with different type",
607 key.0, key.1
608 )
609 });
610 self.claim_registration(existing_id);
611 return Registered {
612 metric: existing_metric,
613 registration: Registration::from(RegistryGuard {
614 id: existing_id,
615 registry,
616 }),
617 };
618 }
619 self.assert_family_matches(&name, &help, metric_type);
620
621 let id = self.allocate_metric_id();
622 let registration = Registration::from(RegistryGuard { id, registry });
623 let metric_any: Arc<dyn Any + Send + Sync> = metric.clone();
624 self.insert_metric_entry(
625 id,
626 help,
627 metric_type,
628 PendingMetricEntry {
629 family_name: name,
630 attributes,
631 encode_samples,
632 metric_any,
633 },
634 );
635 Registered {
636 metric,
637 registration,
638 }
639 }
640
641 fn metric_slot_mut(&mut self, id: usize) -> &mut Option<MetricEntry> {
642 if id == self.metrics.len() {
643 self.metrics.push(None);
644 }
645 &mut self.metrics[id]
646 }
647
648 fn metric_ref(&self, id: usize) -> &MetricEntry {
649 self.metrics
650 .get(id)
651 .and_then(Option::as_ref)
652 .expect("metric id missing from registry")
653 }
654
655 fn metric_mut(&mut self, id: usize) -> &mut MetricEntry {
656 self.metrics
657 .get_mut(id)
658 .and_then(Option::as_mut)
659 .expect("metric id missing from registry")
660 }
661
662 fn allocate_metric_id(&mut self) -> usize {
663 if let Some(id) = self.free_metric_ids.pop() {
664 return id;
665 }
666 let id = self.next_metric_id;
667 self.next_metric_id = self
668 .next_metric_id
669 .checked_add(1)
670 .expect("metric id overflow");
671 id
672 }
673
674 fn assert_family_matches(&self, name: &str, help: &str, metric_type: MetricType) {
675 if let Some(family) = self.families.get(name) {
676 assert_eq!(
677 family.help, help,
678 "metric family `{}` registered with inconsistent help text",
679 name
680 );
681 assert_eq!(
682 family.metric_type.as_str(),
683 metric_type.as_str(),
684 "metric family `{}` registered with inconsistent metric type",
685 name
686 );
687 }
688 }
689
690 fn insert_metric_entry(
691 &mut self,
692 id: usize,
693 help: String,
694 metric_type: MetricType,
695 entry: PendingMetricEntry,
696 ) {
697 let PendingMetricEntry {
698 family_name,
699 attributes,
700 encode_samples,
701 metric_any,
702 } = entry;
703 self.keys
704 .insert((family_name.clone(), attributes.clone()), id);
705 let family = match self.families.entry(family_name.clone()) {
706 std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(),
707 std::collections::btree_map::Entry::Vacant(entry) => {
708 let mut descriptor = String::new();
709 encode_descriptor(&mut descriptor, &family_name, &help, metric_type)
710 .expect("encoding cached descriptor failed");
711 entry.insert(MetricFamily {
712 help,
713 metric_type,
714 descriptor,
715 metric_ids: Vec::new(),
716 })
717 }
718 };
719 let family_index = family.metric_ids.len();
720 family.metric_ids.push(id);
721 self.metric_slot_mut(id).replace(MetricEntry {
722 family_name,
723 attributes,
724 encode_samples,
725 metric_any,
726 claims: 1,
727 family_index,
728 });
729 }
730
731 fn claim_registration(&mut self, id: usize) {
732 let entry = self.metric_mut(id);
733 entry.claims = entry
734 .claims
735 .checked_add(1)
736 .expect("registration claims overflow");
737 }
738
739 fn release_registration(&mut self, id: usize) {
740 let entry = self.metric_mut(id);
741 entry.claims = entry
742 .claims
743 .checked_sub(1)
744 .expect("registration claim count underflow");
745 if entry.claims > 0 {
746 return;
747 }
748 self.drop_metric_entry(id);
749 }
750
751 fn drop_metric_entry(&mut self, id: usize) {
752 let metric = self
753 .metrics
754 .get_mut(id)
755 .and_then(Option::take)
756 .expect("metric id missing from registry");
757 let MetricEntry {
758 family_name,
759 attributes,
760 family_index,
761 ..
762 } = metric;
763 let key = (family_name, attributes);
764 if self.keys.get(&key).copied() == Some(id) {
765 self.keys.remove(&key);
766 }
767 let (family_name, _) = key;
768 let (swapped_metric_id, remove_family) = {
769 let family = self
770 .families
771 .get_mut(&family_name)
772 .expect("family missing during unregister");
773 let removed = family.metric_ids.swap_remove(family_index);
774 assert_eq!(removed, id, "family index mismatch during unregister");
775 let swapped = family.metric_ids.get(family_index).copied();
776 (swapped, family.metric_ids.is_empty())
777 };
778 if let Some(swapped_metric_id) = swapped_metric_id {
779 self.metric_mut(swapped_metric_id).family_index = family_index;
780 }
781 if remove_family {
782 self.families.remove(&family_name);
783 }
784 self.free_metric_ids.push(id);
785 }
786
787 pub fn encode(&self) -> String {
788 let mut output = String::new();
789 let mut samples = String::new();
790 for family in self.families.values() {
791 samples.clear();
792 for metric_id in &family.metric_ids {
793 let metric = self.metric_ref(*metric_id);
794 (metric.encode_samples)(&mut samples).expect("encoding live metric samples failed");
795 }
796 if samples.is_empty() {
800 continue;
801 }
802 output.push_str(&family.descriptor);
803 output.push_str(&samples);
804 }
805
806 encode_eof(&mut output).expect("encoding EOF failed");
807 output
808 }
809}
810
811pub(crate) struct Scope {
812 registry: Registry,
813 prefix: String,
814}
815
816pub(crate) trait Register {
817 fn register<M: Metric>(&mut self, name: &str, help: &str, metric: M) -> Registered<M>;
819
820 fn sub_registry(&mut self, prefix: &str) -> Scope;
822}
823
824impl Register for Registry {
825 fn register<M: Metric>(&mut self, name: &str, help: &str, metric: M) -> Registered<M> {
826 validate_label(name);
827 Self::register(
828 self,
829 name.to_string(),
830 help.to_string(),
831 Vec::new(),
832 Arc::new(metric),
833 )
834 }
835
836 fn sub_registry(&mut self, prefix: &str) -> Scope {
837 validate_label(prefix);
838 Scope {
839 registry: self.clone(),
840 prefix: prefix.to_string(),
841 }
842 }
843}
844
845impl Register for Scope {
846 fn register<M: Metric>(&mut self, name: &str, help: &str, metric: M) -> Registered<M> {
847 validate_label(name);
848 let name = prefixed_name(&self.prefix, name);
849 let help = help.to_string();
850 let metric = Arc::new(metric);
851 Registry::register(&self.registry, name, help, Vec::new(), metric)
852 }
853
854 fn sub_registry(&mut self, prefix: &str) -> Scope {
855 validate_label(prefix);
856 Self {
857 registry: self.registry.clone(),
858 prefix: prefixed_name(&self.prefix, prefix),
859 }
860 }
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866 use crate::{Metrics as _, Runner, Spawner, Supervisor as _, deterministic};
867 use commonware_macros::test_traced;
868 use futures::future;
869 use std::sync::mpsc::{self, TryRecvError};
870
871 #[test]
872 fn test_has_metric_value_unlabeled() {
873 let metrics = "# HELP storage_items_tracked items\nstorage_items_tracked 2\n";
874 assert!(has_metric_value(metrics, "items_tracked", 2));
875 assert!(has_metric_value(metrics, "storage_items_tracked", 2));
876 assert!(!has_metric_value(metrics, "items_tracked_extra", 2));
877 assert!(!has_metric_value(metrics, "items_tracked", 3));
878 }
879
880 #[test]
881 fn test_has_metric_value_labeled() {
882 let metrics = r#"storage_init_items_tracked{index="2"} 2"#;
883 assert!(has_metric_value(metrics, "items_tracked", 2));
884 assert!(has_metric_value(metrics, "storage_init_items_tracked", 2));
885 }
886
887 #[test_traced]
888 fn test_count_running_tasks() {
889 let executor = deterministic::Runner::default();
890 executor.start(|context| async move {
891 assert_eq!(
893 count_running_tasks(&context, "worker"),
894 0,
895 "no worker tasks initially"
896 );
897
898 let handle1 = context.child("worker").spawn(|_| async move {
900 future::pending::<()>().await;
901 });
902
903 let count = count_running_tasks(&context, "worker");
905 assert_eq!(count, 1, "worker task should be running");
906
907 assert_eq!(
909 count_running_tasks(&context, "other"),
910 0,
911 "no tasks with 'other' prefix"
912 );
913
914 let handle2 = context
916 .child("worker")
917 .child("child")
918 .spawn(|_| async move {
919 future::pending::<()>().await;
920 });
921
922 let count = count_running_tasks(&context, "worker");
924 assert_eq!(count, 2, "both worker and worker_child should be counted");
925
926 handle1.abort();
928 let _ = handle1.await;
929
930 let count = count_running_tasks(&context, "worker");
932 assert_eq!(count, 1, "only worker_child should remain");
933
934 handle2.abort();
936 let _ = handle2.await;
937
938 assert_eq!(
940 count_running_tasks(&context, "worker"),
941 0,
942 "all worker tasks should be stopped"
943 );
944 });
945 }
946
947 #[test_traced]
948 fn test_no_duplicate_metrics() {
949 let executor = deterministic::Runner::default();
950 executor.start(|context| async move {
951 let c1 = raw::Counter::<u64>::default();
953 let _metric_a = context.child("a").register("test", "help", c1);
954 let c2 = raw::Counter::<u64>::default();
955 let _metric_b = context.child("b").register("test", "help", c2);
956 });
957 }
959
960 #[test_traced]
961 fn test_duplicate_metrics_reuse_existing_handle() {
962 let executor = deterministic::Runner::default();
963 executor.start(|context| async move {
964 let c1 = raw::Counter::<u64>::default();
965 let metric_a = context.child("a").register("test", "help", c1);
966 let c2 = raw::Counter::<u64>::default();
967 let metric_b = context.child("a").register("test", "help", c2);
968
969 assert!(std::ptr::eq(metric_a.metric(), metric_b.metric()));
970
971 metric_a.inc();
972 metric_b.inc_by(2);
973 let encoded = context.encode();
974 assert!(encoded.contains("a_test_total 3"));
975 });
976 }
977
978 #[test]
979 fn test_claims_track_register_calls_not_handle_clones() {
980 let registry = Registry::new();
981 let key: MetricKey = ("votes".to_string(), Vec::new());
982
983 let first = registry.register(
984 key.0.clone(),
985 "vote count".to_string(),
986 Vec::new(),
987 Arc::new(raw::Counter::<u64>::default()),
988 );
989 let first_clone = first.clone();
990 let id = {
991 let registry = registry.inner.lock();
992 let id = *registry.keys.get(&key).expect("metric key missing");
993 assert_eq!(registry.metric_ref(id).claims, 1);
994 id
995 };
996
997 let second = registry.register(
998 key.0,
999 "vote count".to_string(),
1000 Vec::new(),
1001 Arc::new(raw::Counter::<u64>::default()),
1002 );
1003 let second_clone = second.clone();
1004 {
1005 let registry = registry.inner.lock();
1006 assert_eq!(registry.metric_ref(id).claims, 2);
1007 }
1008
1009 drop(first);
1010 drop(second);
1011 {
1012 let registry = registry.inner.lock();
1013 assert_eq!(registry.metric_ref(id).claims, 2);
1014 }
1015
1016 drop(second_clone);
1017 {
1018 let registry = registry.inner.lock();
1019 assert_eq!(registry.metric_ref(id).claims, 1);
1020 }
1021
1022 drop(first_clone);
1023 let registry = registry.inner.lock();
1024 assert!(
1025 registry.keys.is_empty(),
1026 "keys left behind: {:?}",
1027 registry.keys
1028 );
1029 assert!(
1030 registry.families.is_empty(),
1031 "families left behind: {:?}",
1032 registry.families
1033 );
1034 }
1035
1036 #[test]
1037 #[should_panic(expected = "registered with different type")]
1038 fn test_duplicate_metrics_different_type_panics() {
1039 let executor = deterministic::Runner::default();
1040 executor.start(|context| async move {
1041 let counter = raw::Counter::<u64>::default();
1042 let _metric_a = context.child("a").register("test", "help", counter);
1043 let gauge = raw::Gauge::<i64>::default();
1044 let _metric_b = context.child("a").register("test", "help", gauge);
1045 });
1046 }
1047
1048 #[test]
1049 fn test_duplicate_register_acquires_during_last_drop_window() {
1050 let registry = Registry::new();
1051 let key: MetricKey = ("votes".to_string(), Vec::new());
1052
1053 let original = registry.register(
1054 key.0.clone(),
1055 "vote count".to_string(),
1056 Vec::new(),
1057 Arc::new(raw::Counter::<u64>::default()),
1058 );
1059 let original_metric = Arc::clone(&original.metric);
1060 let _original = std::mem::ManuallyDrop::new(original);
1061 let original_id = {
1062 let registry = registry.inner.lock();
1063 *registry.keys.get(&key).expect("metric key missing")
1064 };
1065 let duplicate = registry.register(
1069 key.0,
1070 "vote count".to_string(),
1071 Vec::new(),
1072 Arc::new(raw::Counter::<u64>::default()),
1073 );
1074 assert!(Arc::ptr_eq(&original_metric, &duplicate.metric));
1075
1076 registry.inner.lock().release_registration(original_id);
1077
1078 duplicate.inc_by(7);
1079 let encoded = registry.encode();
1080 assert!(
1081 encoded.contains("votes_total 7"),
1082 "last drop removed duplicate registration: {encoded}"
1083 );
1084
1085 drop(duplicate);
1086 let registry = registry.inner.lock();
1087 assert!(
1088 registry.keys.is_empty(),
1089 "keys left behind: {:?}",
1090 registry.keys
1091 );
1092 assert!(
1093 registry.families.is_empty(),
1094 "families left behind: {:?}",
1095 registry.families
1096 );
1097 }
1098
1099 #[test]
1100 fn test_registered_with_registration_notifies_on_last_drop() {
1101 struct NotifyOnDrop(mpsc::Sender<&'static str>);
1102
1103 impl Drop for NotifyOnDrop {
1104 fn drop(&mut self) {
1105 let _ = self.0.send("dropped");
1106 }
1107 }
1108
1109 let (tx, rx) = mpsc::channel();
1110 let registered = Registered::with_registration(
1111 raw::Counter::<u64>::default(),
1112 Registration::from(NotifyOnDrop(tx)),
1113 );
1114 let clone = registered.clone();
1115
1116 drop(registered);
1117 assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
1118
1119 drop(clone);
1120 assert_eq!(rx.recv().unwrap(), "dropped");
1121 assert!(matches!(rx.try_recv(), Err(TryRecvError::Disconnected)));
1122 }
1123
1124 fn register_counter(registry: &Registry, name: &str, help: &str, value: u64) -> Counter {
1125 let counter = raw::Counter::<u64>::default();
1126 counter.inc_by(value);
1127 registry.register(
1128 name.to_string(),
1129 help.to_string(),
1130 Vec::new(),
1131 Arc::new(counter),
1132 )
1133 }
1134
1135 #[test]
1136 fn test_encode_is_deterministic() {
1137 let registry = Registry::default();
1138 let _beta = register_counter(®istry, "beta", "beta counter", 2);
1139 let _alpha = register_counter(®istry, "alpha", "alpha counter", 1);
1140 let first = registry.encode();
1141 let second = registry.encode();
1142 assert_eq!(first, second);
1143 let alpha = first
1144 .find("# TYPE alpha")
1145 .expect("alpha family header present");
1146 let beta = first
1147 .find("# TYPE beta")
1148 .expect("beta family header present");
1149 assert!(alpha < beta, "families emitted in sorted order: {first}");
1150 }
1151
1152 #[test]
1153 fn test_encode_emits_single_eof() {
1154 let registry = Registry::default();
1155 let _a = register_counter(®istry, "a", "help", 1);
1156 let _b = register_counter(®istry, "b", "help", 2);
1157 let encoded = registry.encode();
1158 assert_eq!(encoded.matches("# EOF").count(), 1);
1159 assert!(
1160 encoded.ends_with("# EOF\n"),
1161 "must terminate with EOF: {encoded}"
1162 );
1163 }
1164
1165 #[test]
1166 fn test_encode_type_aware_suffixes() {
1167 let registry = Registry::default();
1168 let _requests = register_counter(®istry, "requests", "request count", 3);
1169 let histogram = raw::Histogram::new([0.1, 1.0, 10.0]);
1170 histogram.observe(0.5);
1171 let _histogram = registry.register(
1172 "latency".to_string(),
1173 "latency seconds".to_string(),
1174 Vec::new(),
1175 Arc::new(histogram),
1176 );
1177 let encoded = registry.encode();
1178 assert!(
1179 encoded.contains("requests_total 3"),
1180 "counter _total suffix: {encoded}"
1181 );
1182 assert!(
1183 encoded.contains("latency_bucket"),
1184 "histogram _bucket suffix: {encoded}"
1185 );
1186 assert!(
1187 encoded.contains("latency_sum"),
1188 "histogram _sum suffix: {encoded}"
1189 );
1190 assert!(
1191 encoded.contains("latency_count"),
1192 "histogram _count suffix: {encoded}"
1193 );
1194 }
1195
1196 #[test]
1197 fn test_encode_shares_family_header_across_attributes() {
1198 let registry = Registry::default();
1199 let c1 = raw::Counter::<u64>::default();
1200 c1.inc();
1201 let _c1 = registry.register(
1202 "votes".to_string(),
1203 "vote count".to_string(),
1204 vec![("epoch".to_string(), "1".to_string())],
1205 Arc::new(c1),
1206 );
1207 let c2 = raw::Counter::<u64>::default();
1208 c2.inc_by(2);
1209 let _c2 = registry.register(
1210 "votes".to_string(),
1211 "vote count".to_string(),
1212 vec![("epoch".to_string(), "2".to_string())],
1213 Arc::new(c2),
1214 );
1215 let encoded = registry.encode();
1216 assert_eq!(
1217 encoded.matches("# HELP votes").count(),
1218 1,
1219 "single HELP: {encoded}"
1220 );
1221 assert_eq!(
1222 encoded.matches("# TYPE votes").count(),
1223 1,
1224 "single TYPE: {encoded}"
1225 );
1226 assert!(encoded.contains("votes_total{epoch=\"1\"} 1"));
1227 assert!(encoded.contains("votes_total{epoch=\"2\"} 2"));
1228 }
1229
1230 #[test]
1231 fn test_encode_registers_without_prefix() {
1232 let registry = Registry::default();
1233 let _registered = register_counter(®istry, "votes", "vote count", 1);
1234 let encoded = registry.encode();
1235 assert!(
1236 encoded.contains("votes_total 1"),
1237 "no prefix applied: {encoded}"
1238 );
1239 assert!(
1240 encoded.starts_with("# HELP votes"),
1241 "family header at start: {encoded}"
1242 );
1243 }
1244
1245 #[test]
1246 fn test_encode_suppresses_empty_family() {
1247 let registry = Registry::default();
1251 let empty_family = raw::Family::<Vec<(String, String)>, raw::Counter>::default();
1252 let _empty_family = registry.register(
1253 "votes".to_string(),
1254 "vote count".to_string(),
1255 Vec::new(),
1256 Arc::new(empty_family),
1257 );
1258 let _ticks = register_counter(®istry, "ticks", "tick count", 1);
1259 let encoded = registry.encode();
1260 assert!(!encoded.contains("votes"), "empty family leaked: {encoded}");
1261 assert!(
1262 encoded.contains("ticks_total 1"),
1263 "populated metric missing: {encoded}"
1264 );
1265 assert_eq!(encoded.matches("# EOF").count(), 1);
1266 }
1267
1268 #[test]
1269 fn test_encode_matches_upstream_registry() {
1270 let counter = raw::Counter::<u64>::default();
1279 counter.inc_by(7);
1280 let gauge = raw::Gauge::<i64>::default();
1281 gauge.set(-3);
1282 let histogram = raw::Histogram::new([0.1, 1.0]);
1283 histogram.observe(0.5);
1284
1285 let ours = Registry::default();
1286 let _latency = ours.register(
1287 "latency".to_string(),
1288 "request latency seconds".to_string(),
1289 Vec::new(),
1290 Arc::new(histogram.clone()),
1291 );
1292 let _level = ours.register(
1293 "level".to_string(),
1294 "current level".to_string(),
1295 Vec::new(),
1296 Arc::new(gauge.clone()),
1297 );
1298 let _votes = ours.register(
1299 "votes".to_string(),
1300 "number of votes".to_string(),
1301 Vec::new(),
1302 Arc::new(counter.clone()),
1303 );
1304 let ours_encoded = ours.encode();
1305
1306 let mut theirs = registry::Registry::default();
1307 theirs.register("latency", "request latency seconds", histogram);
1308 theirs.register("level", "current level", gauge);
1309 theirs.register("votes", "number of votes", counter);
1310 let mut theirs_encoded = String::new();
1311 encode(&mut theirs_encoded, &theirs).expect("upstream encode failed");
1312
1313 assert_eq!(
1314 ours_encoded, theirs_encoded,
1315 "output diverged from upstream prometheus-client registry"
1316 );
1317 }
1318
1319 #[test]
1320 fn test_shuffled_duplicate_drops_do_not_leave_registry_entries() {
1321 let registry = Registry::new();
1322 let mut handles = Vec::new();
1323
1324 for _ in 0..8 {
1325 handles.push(registry.register(
1326 "votes".to_string(),
1327 "vote count".to_string(),
1328 Vec::new(),
1329 Arc::new(raw::Counter::<u64>::default()),
1330 ));
1331 }
1332
1333 for index in [3, 0, 6, 1] {
1334 let _ = handles.swap_remove(index);
1335 handles.push(registry.register(
1336 "votes".to_string(),
1337 "vote count".to_string(),
1338 Vec::new(),
1339 Arc::new(raw::Counter::<u64>::default()),
1340 ));
1341 }
1342
1343 drop(handles);
1344 let registry = registry.inner.lock();
1345 assert!(
1346 registry.keys.is_empty(),
1347 "keys left behind: {:?}",
1348 registry.keys
1349 );
1350 assert!(
1351 registry.families.is_empty(),
1352 "families left behind: {:?}",
1353 registry.families
1354 );
1355 }
1356}