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