1use std::{any::Any, sync::Arc};
2
3use crate::{
4 Metric, MetricType, MetricValue,
5 encoding::EncodableMetric,
6 iterable::{FieldIter, IntoIterable, Iterable},
7};
8
9pub trait MetricsGroup:
11 Any + Iterable + IntoIterable + std::fmt::Debug + 'static + Send + Sync
12{
13 fn name(&self) -> &'static str;
15
16 fn iter(&self) -> FieldIter<'_> {
18 self.field_iter()
19 }
20}
21
22#[derive(Debug, Clone, Copy)]
24pub struct MetricItem<'a> {
25 pub(crate) name: &'static str,
26 pub(crate) help: &'static str,
27 pub(crate) metric: &'a dyn Metric,
28}
29
30impl EncodableMetric for MetricItem<'_> {
31 fn name(&self) -> &str {
32 self.name
33 }
34
35 fn help(&self) -> &str {
36 self.help
37 }
38
39 fn r#type(&self) -> MetricType {
40 self.metric.r#type()
41 }
42
43 fn value(&self) -> MetricValue {
44 self.metric.value()
45 }
46}
47
48impl<'a> MetricItem<'a> {
49 pub fn new(name: &'static str, help: &'static str, metric: &'a dyn Metric) -> Self {
51 Self { name, help, metric }
52 }
53
54 pub fn as_any(&self) -> &dyn Any {
56 self.metric.as_any()
57 }
58
59 pub fn name(&self) -> &'static str {
61 self.name
62 }
63
64 pub fn help(&self) -> &'static str {
66 self.help
67 }
68
69 pub fn r#type(&self) -> MetricType {
71 self.metric.r#type()
72 }
73
74 pub fn value(&self) -> MetricValue {
76 self.metric.value()
77 }
78}
79
80pub trait MetricsGroupSet {
82 fn name(&self) -> &'static str;
84
85 fn groups_cloned(&self) -> impl Iterator<Item = Arc<dyn MetricsGroup>>;
87
88 fn groups(&self) -> impl Iterator<Item = &dyn MetricsGroup>;
90
91 fn iter(&self) -> impl Iterator<Item = (&'static str, MetricItem<'_>)> + '_ {
95 self.groups()
96 .flat_map(|group| group.iter().map(|item| (group.name(), item)))
97 }
98}
99
100#[cfg(all(test, not(feature = "metrics")))]
103mod tests {
104 use crate::Counter;
105
106 #[test]
107 fn test() {
108 let counter = Counter::new();
109 counter.inc();
110 assert_eq!(counter.get(), 0);
111 }
112}
113
114#[cfg(all(test, feature = "metrics"))]
116mod tests {
117 #[cfg(feature = "postcard")]
118 use std::sync::RwLock;
119
120 use serde::{Deserialize, Serialize};
121
122 use super::*;
123 #[cfg(feature = "postcard")]
124 use crate::encoding::{Decoder, Encoder};
125 use crate::{
126 Counter, Gauge, Histogram, MetricType, MetricsGroupSet, MetricsSource, Registry,
127 iterable::Iterable,
128 };
129
130 #[derive(Debug, Iterable, Serialize, Deserialize)]
131 pub struct FooMetrics {
132 pub metric_a: Counter,
133 pub metric_b: Gauge,
134 }
135
136 impl Default for FooMetrics {
137 fn default() -> Self {
138 Self {
139 metric_a: Counter::new(),
140 metric_b: Gauge::new(),
141 }
142 }
143 }
144
145 impl MetricsGroup for FooMetrics {
146 fn name(&self) -> &'static str {
147 "foo"
148 }
149 }
150
151 #[derive(Debug, Default, Iterable, Serialize, Deserialize)]
152 pub struct BarMetrics {
153 pub count: Counter,
155 }
156
157 impl MetricsGroup for BarMetrics {
158 fn name(&self) -> &'static str {
159 "bar"
160 }
161 }
162
163 #[derive(Debug, Default, Serialize, Deserialize, MetricsGroupSet)]
164 #[metrics(name = "combined")]
165 struct CombinedMetrics {
166 foo: Arc<FooMetrics>,
167 bar: Arc<BarMetrics>,
168 }
169
170 #[allow(unused)]
172 #[derive(Debug, Default)]
173 struct CombinedMetricsManual {
174 foo: Arc<FooMetrics>,
175 bar: Arc<BarMetrics>,
176 }
177
178 impl MetricsGroupSet for CombinedMetricsManual {
179 fn name(&self) -> &'static str {
180 "combined"
181 }
182
183 fn groups_cloned(&self) -> impl Iterator<Item = Arc<dyn MetricsGroup>> {
184 [
185 self.foo.clone() as Arc<dyn MetricsGroup>,
186 self.bar.clone() as Arc<dyn MetricsGroup>,
187 ]
188 .into_iter()
189 }
190
191 fn groups(&self) -> impl Iterator<Item = &dyn MetricsGroup> {
192 [
193 &*self.foo as &dyn MetricsGroup,
194 &*self.bar as &dyn MetricsGroup,
195 ]
196 .into_iter()
197 }
198 }
199
200 #[test]
201 fn test_metric_help() -> Result<(), Box<dyn std::error::Error>> {
202 let metrics = FooMetrics::default();
203 let items: Vec<_> = metrics.iter().collect();
204 assert_eq!(items.len(), 2);
205 assert_eq!(items[0].name(), "metric_a");
206 assert_eq!(items[0].help(), "metric_a");
207 assert_eq!(items[0].r#type(), MetricType::Counter);
208 assert_eq!(items[1].name(), "metric_b");
209 assert_eq!(items[1].help(), "metric_b");
210 assert_eq!(items[1].r#type(), MetricType::Gauge);
211
212 Ok(())
213 }
214
215 #[test]
216 fn test_solo_registry() -> Result<(), Box<dyn std::error::Error>> {
217 let mut registry = Registry::default();
218 let metrics = Arc::new(FooMetrics::default());
219 registry.register(metrics.clone());
220
221 metrics.metric_a.inc();
222 metrics.metric_b.inc_by(2);
223 metrics.metric_b.inc_by(3);
224 assert_eq!(metrics.metric_a.get(), 1);
225 assert_eq!(metrics.metric_b.get(), 5);
226 metrics.metric_a.set(0);
227 metrics.metric_b.set(0);
228 assert_eq!(metrics.metric_a.get(), 0);
229 assert_eq!(metrics.metric_b.get(), 0);
230 metrics.metric_a.inc_by(5);
231 metrics.metric_b.inc_by(2);
232 assert_eq!(metrics.metric_a.get(), 5);
233 assert_eq!(metrics.metric_b.get(), 2);
234
235 let exp = "# HELP foo_metric_a metric_a.
236# TYPE foo_metric_a counter
237foo_metric_a_total 5
238# HELP foo_metric_b metric_b.
239# TYPE foo_metric_b gauge
240foo_metric_b 2
241# EOF
242";
243 let enc = registry.encode_openmetrics_to_string()?;
244 assert_eq!(enc, exp);
245 Ok(())
246 }
247
248 #[test]
249 fn test_metric_sets() {
250 let metrics = CombinedMetrics::default();
251 metrics.foo.metric_a.inc();
252 metrics.foo.metric_b.set(-42);
253 metrics.bar.count.inc_by(10);
254
255 let collected = metrics
257 .iter()
258 .map(|(group, metric)| (group, metric.name(), metric.help(), metric.value().to_f32()));
259 assert_eq!(
260 collected.collect::<Vec<_>>(),
261 vec![
262 ("foo", "metric_a", "metric_a", 1.0),
263 ("foo", "metric_b", "metric_b", -42.0),
264 ("bar", "count", "Bar Count", 10.0),
265 ]
266 );
267
268 let mut collected = vec![];
270 for group in metrics.groups() {
271 for metric in group.iter() {
272 if let Some(counter) = metric.as_any().downcast_ref::<Counter>() {
273 collected.push((group.name(), metric.name(), counter.value()));
274 }
275 if let Some(gauge) = metric.as_any().downcast_ref::<Gauge>() {
276 collected.push((group.name(), metric.name(), gauge.value()));
277 }
278 }
279 }
280 assert_eq!(
281 collected,
282 vec![
283 ("foo", "metric_a", MetricValue::Counter(1)),
284 ("foo", "metric_b", MetricValue::Gauge(-42)),
285 ("bar", "count", MetricValue::Counter(10)),
286 ]
287 );
288
289 let mut registry = Registry::default();
291 let sub = registry.sub_registry_with_prefix("boo");
292 sub.register_all(&metrics);
293 let exp = "# HELP boo_foo_metric_a metric_a.
294# TYPE boo_foo_metric_a counter
295boo_foo_metric_a_total 1
296# HELP boo_foo_metric_b metric_b.
297# TYPE boo_foo_metric_b gauge
298boo_foo_metric_b -42
299# HELP boo_bar_count Bar Count.
300# TYPE boo_bar_count counter
301boo_bar_count_total 10
302# EOF
303";
304 assert_eq!(registry.encode_openmetrics_to_string().unwrap(), exp);
305
306 let sub = registry.sub_registry_with_labels([("x", "y")]);
307 sub.register_all_prefixed(&metrics);
308 let exp = r#"# HELP boo_foo_metric_a metric_a.
309# TYPE boo_foo_metric_a counter
310boo_foo_metric_a_total 1
311# HELP boo_foo_metric_b metric_b.
312# TYPE boo_foo_metric_b gauge
313boo_foo_metric_b -42
314# HELP boo_bar_count Bar Count.
315# TYPE boo_bar_count counter
316boo_bar_count_total 10
317# HELP combined_foo_metric_a metric_a.
318# TYPE combined_foo_metric_a counter
319combined_foo_metric_a_total{x="y"} 1
320# HELP combined_foo_metric_b metric_b.
321# TYPE combined_foo_metric_b gauge
322combined_foo_metric_b{x="y"} -42
323# HELP combined_bar_count Bar Count.
324# TYPE combined_bar_count counter
325combined_bar_count_total{x="y"} 10
326# EOF
327"#;
328
329 assert_eq!(registry.encode_openmetrics_to_string().unwrap(), exp);
330 }
331
332 #[test]
333 fn test_derive() {
334 use crate::{MetricValue, MetricsGroup};
335
336 #[derive(Debug, MetricsGroup)]
337 #[metrics(default, name = "my-metrics")]
338 struct Metrics {
339 foo: Counter,
343 bar: Counter,
345 #[metrics(help = "Measures baz")]
347 baz: Gauge,
348 #[metrics(help = "foo")]
349 #[default(Histogram::new(vec![0.0, 0.01, 0.05, 0.1, 0.2, 0.5, 1.0]))]
350 histo: Histogram,
351 }
352
353 let metrics = Metrics::default();
354 assert_eq!(metrics.name(), "my-metrics");
355
356 metrics.foo.inc();
357 metrics.bar.inc_by(2);
358 metrics.baz.set(3);
359
360 let mut values = metrics.iter();
361 let foo = values.next().unwrap();
362 let bar = values.next().unwrap();
363 let baz = values.next().unwrap();
364 assert_eq!(foo.value(), MetricValue::Counter(1));
365 assert_eq!(foo.name(), "foo");
366 assert_eq!(foo.help(), "Counts foos");
367 assert_eq!(bar.value(), MetricValue::Counter(2));
368 assert_eq!(bar.name(), "bar");
369 assert_eq!(bar.help(), "bar");
370 assert_eq!(baz.value(), MetricValue::Gauge(3));
371 assert_eq!(baz.name(), "baz");
372 assert_eq!(baz.help(), "Measures baz");
373
374 #[derive(Debug, Default, MetricsGroup)]
375 struct FooMetrics {}
376 let metrics = FooMetrics::default();
377 assert_eq!(metrics.name(), "foo_metrics");
378 let mut values = metrics.iter();
379 assert!(values.next().is_none());
380 }
381
382 #[test]
383 fn test_serde() {
384 let metrics = CombinedMetrics::default();
385 metrics.foo.metric_a.inc();
386 metrics.foo.metric_b.set(-42);
387 metrics.bar.count.inc_by(10);
388 let encoded = postcard::to_stdvec(&metrics).unwrap();
389 let decoded: CombinedMetrics = postcard::from_bytes(&encoded).unwrap();
390 assert_eq!(decoded.foo.metric_a.get(), 1);
391 assert_eq!(decoded.foo.metric_b.get(), -42);
392 assert_eq!(decoded.bar.count.get(), 10);
393 }
394
395 #[test]
396 #[cfg(feature = "postcard")]
397 fn test_encode_decode() {
398 let mut registry = Registry::default();
399 let metrics = Arc::new(FooMetrics::default());
400 registry.register(metrics.clone());
401
402 metrics.metric_a.inc();
403 metrics.metric_b.set(-42);
404
405 let om_from_registry = registry.encode_openmetrics_to_string().unwrap();
406 println!("openmetrics len {}", om_from_registry.len());
407
408 let registry = Arc::new(RwLock::new(registry));
409
410 let mut encoder = Encoder::new(registry.clone());
411 let update = encoder.export_bytes().unwrap();
412 println!("first update len {}", update.len());
413
414 let mut decoder = Decoder::default();
415 decoder.import_bytes(&update).unwrap();
416
417 let om_from_decoder = decoder.encode_openmetrics_to_string().unwrap();
418 assert_eq!(om_from_decoder, om_from_registry);
419
420 metrics.metric_a.inc();
421 metrics.metric_b.set(99);
422
423 let update = encoder.export_bytes().unwrap();
424 println!("second update len {}", update.len());
425 decoder.import_bytes(&update).unwrap();
426
427 let om_from_registry = registry.encode_openmetrics_to_string().unwrap();
428 let om_from_decoder = decoder.encode_openmetrics_to_string().unwrap();
429 assert_eq!(om_from_decoder, om_from_registry);
430
431 for item in decoder.iter() {
432 assert!(item.help.is_some());
433 }
434
435 let mut encoder = Encoder::new_with_opts(
436 registry.clone(),
437 crate::encoding::EncoderOpts {
438 include_help: false,
439 },
440 );
441 let mut decoder = Decoder::default();
442 decoder.import_bytes(&update).unwrap();
443 decoder.import(encoder.export());
444 for item in decoder.iter() {
445 assert_eq!(item.help, None);
446 }
447 }
448
449 #[test]
450 fn test_histogram() {
451 use crate::Histogram;
452
453 let histogram = Histogram::new(vec![1.0, 5.0, 10.0, 50.0, 100.0, f64::INFINITY]);
454
455 histogram.observe(0.5);
456 histogram.observe(2.5);
457 histogram.observe(7.5);
458 histogram.observe(25.0);
459 histogram.observe(75.0);
460 histogram.observe(150.0);
461
462 assert_eq!(histogram.count(), 6);
463 assert_eq!(histogram.sum(), 260.5);
464
465 let buckets = histogram.buckets();
466 assert_eq!(buckets.len(), 6);
467 assert_eq!(buckets[0], (1.0, 1));
468 assert_eq!(buckets[1], (5.0, 2));
469 assert_eq!(buckets[2], (10.0, 3));
470 assert_eq!(buckets[3], (50.0, 4));
471 assert_eq!(buckets[4], (100.0, 5));
472 assert_eq!(buckets[5], (f64::INFINITY, 6));
473
474 let p50 = histogram.percentile(0.5);
475 assert_eq!(p50, 10.0);
476
477 let p99 = histogram.percentile(0.99);
478 assert_eq!(p99, 100.0);
479
480 let p100 = histogram.percentile(1.0);
481 assert_eq!(p100, f64::INFINITY);
482 }
483
484 #[test]
485 fn test_histogram_prometheus_format() {
486 use crate::Histogram;
487
488 #[derive(Debug, Iterable)]
489 pub struct HistogramMetrics {
490 pub response_time: Histogram,
491 }
492
493 impl MetricsGroup for HistogramMetrics {
494 fn name(&self) -> &'static str {
495 "http"
496 }
497 }
498
499 let metrics = HistogramMetrics {
500 response_time: Histogram::new(vec![0.1, 0.5, 1.0, 5.0, f64::INFINITY]),
501 };
502
503 metrics.response_time.observe(0.05);
504 metrics.response_time.observe(0.3);
505 metrics.response_time.observe(0.8);
506 metrics.response_time.observe(2.5);
507
508 let mut registry = Registry::default();
509 registry.register(Arc::new(metrics));
510
511 let output = registry.encode_openmetrics_to_string().unwrap();
512
513 let parsed = prometheus_parse::Scrape::parse(output.lines().map(|s| Ok(s.to_owned())))
514 .expect("Failed to parse Prometheus output");
515
516 assert_eq!(parsed.samples.len(), 3);
517
518 let histogram_sample = parsed
519 .samples
520 .iter()
521 .find(|s| s.metric == "http_response_time")
522 .expect("Expected to find http_response_time histogram");
523
524 let sum_sample = parsed
525 .samples
526 .iter()
527 .find(|s| s.metric == "http_response_time_sum")
528 .expect("Expected to find http_response_time_sum");
529
530 let count_sample = parsed
531 .samples
532 .iter()
533 .find(|s| s.metric == "http_response_time_count")
534 .expect("Expected to find http_response_time_count");
535
536 if let prometheus_parse::Value::Untyped(sum) = sum_sample.value {
537 assert_eq!(sum, 3.65);
538 } else {
539 panic!("Expected sum value");
540 }
541
542 if let prometheus_parse::Value::Untyped(count) = count_sample.value {
543 assert_eq!(count, 4.0);
544 } else {
545 panic!("Expected count value");
546 }
547
548 if let prometheus_parse::Value::Histogram(buckets) = &histogram_sample.value {
549 assert_eq!(buckets.len(), 5);
550
551 assert_eq!(buckets[0].less_than, 0.1);
552 assert_eq!(buckets[0].count, 1.0);
553
554 assert_eq!(buckets[1].less_than, 0.5);
555 assert_eq!(buckets[1].count, 2.0);
556
557 assert_eq!(buckets[2].less_than, 1.0);
558 assert_eq!(buckets[2].count, 3.0);
559
560 assert_eq!(buckets[3].less_than, 5.0);
561 assert_eq!(buckets[3].count, 4.0);
562
563 assert_eq!(buckets[4].less_than, f64::INFINITY);
564 assert_eq!(buckets[4].count, 4.0);
565 } else {
566 panic!("Expected histogram value, got {:?}", histogram_sample.value);
567 }
568 }
569
570 #[test]
571 #[cfg(feature = "postcard")]
572 fn test_histogram_encode_decode() {
573 use std::sync::{Arc, RwLock};
574
575 use crate::Histogram;
576
577 #[derive(Debug, Iterable)]
578 pub struct HistogramMetrics {
579 pub response_time: Histogram,
580 }
581
582 impl MetricsGroup for HistogramMetrics {
583 fn name(&self) -> &'static str {
584 "http"
585 }
586 }
587
588 let mut registry = Registry::default();
589 let metrics = Arc::new(HistogramMetrics {
590 response_time: Histogram::new(vec![0.1, 0.5, 1.0, 5.0, f64::INFINITY]),
591 });
592 registry.register(metrics.clone());
593
594 metrics.response_time.observe(0.05);
595 metrics.response_time.observe(0.3);
596 metrics.response_time.observe(0.8);
597 metrics.response_time.observe(2.5);
598
599 let registry = Arc::new(RwLock::new(registry));
600
601 let mut encoder = Encoder::new(registry.clone());
602 let update = encoder.export_bytes().unwrap();
603
604 let mut decoder = Decoder::default();
605 decoder.import_bytes(&update).unwrap();
606
607 let mut items = decoder.iter();
608 let item = items.next().expect("Expected one metric");
609
610 if let MetricValue::Histogram {
611 buckets,
612 sum,
613 count,
614 } = item.value
615 {
616 assert_eq!(*count, 4);
617 assert_eq!(*sum, 3.65);
618 assert_eq!(buckets.len(), 5);
619 assert_eq!(buckets[0], (0.1, 1));
620 assert_eq!(buckets[1], (0.5, 2));
621 assert_eq!(buckets[2], (1.0, 3));
622 assert_eq!(buckets[3], (5.0, 4));
623 assert_eq!(buckets[4], (f64::INFINITY, 4));
624 } else {
625 panic!("Expected histogram value");
626 }
627
628 metrics.response_time.observe(0.02);
629 metrics.response_time.observe(1.5);
630
631 let update = encoder.export_bytes().unwrap();
632 decoder.import_bytes(&update).unwrap();
633
634 let mut items = decoder.iter();
635 let item = items.next().expect("Expected one metric");
636
637 if let MetricValue::Histogram {
638 buckets,
639 sum,
640 count,
641 } = item.value
642 {
643 assert_eq!(*count, 6);
644 assert_eq!(*sum, 5.17);
645 assert_eq!(buckets[0], (0.1, 2)); assert_eq!(buckets[1], (0.5, 3)); assert_eq!(buckets[2], (1.0, 4)); assert_eq!(buckets[3], (5.0, 6)); assert_eq!(buckets[4], (f64::INFINITY, 6));
650 } else {
651 panic!("Expected histogram value");
652 }
653 }
654
655 #[test]
656 #[cfg(feature = "postcard")]
657 fn test_histogram_openmetrics_from_decoder() {
658 use std::sync::{Arc, RwLock};
659
660 use crate::Histogram;
661
662 #[derive(Debug, Iterable)]
663 pub struct HistogramMetrics {
664 pub response_time: Histogram,
665 }
666
667 impl MetricsGroup for HistogramMetrics {
668 fn name(&self) -> &'static str {
669 "http"
670 }
671 }
672
673 let mut registry = Registry::default();
674 let metrics = Arc::new(HistogramMetrics {
675 response_time: Histogram::new(vec![0.1, 0.5, 1.0, 5.0, f64::INFINITY]),
676 });
677 registry.register(metrics.clone());
678
679 metrics.response_time.observe(0.05);
680 metrics.response_time.observe(0.3);
681 metrics.response_time.observe(0.8);
682 metrics.response_time.observe(2.5);
683
684 let om_from_registry = registry.encode_openmetrics_to_string().unwrap();
685
686 let registry = Arc::new(RwLock::new(registry));
687 let mut encoder = Encoder::new(registry.clone());
688 let update = encoder.export_bytes().unwrap();
689
690 let mut decoder = Decoder::default();
691 decoder.import_bytes(&update).unwrap();
692
693 let om_from_decoder = decoder.encode_openmetrics_to_string().unwrap();
694
695 assert_eq!(
696 om_from_decoder, om_from_registry,
697 "Decoder should produce identical OpenMetrics output to registry for histograms"
698 );
699 }
700
701 #[test]
702 fn test_family_in_metrics_group() {
703 use std::borrow::Cow;
704
705 use iroh_metrics_derive::MetricsGroup;
706
707 use crate::{Family, LabelPair, LabelValue, NoLabels};
708
709 #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
710 struct TransportLabels {
711 transport: String,
712 }
713
714 impl crate::EncodeLabelSet for TransportLabels {
715 fn encode_label_pairs(&self) -> Vec<LabelPair<'_>> {
716 vec![("transport", LabelValue::Str(Cow::Borrowed(&self.transport)))]
717 }
718 }
719
720 #[derive(Debug, MetricsGroup)]
721 #[metrics(default, name = "magicsock")]
722 struct Metrics {
723 total_bytes: Counter,
725 bytes_by_transport: Family<TransportLabels, Counter>,
727 #[default(Family::with_constructor(|| crate::Histogram::new(vec![0.1, 1.0, 10.0])))]
729 latency: Family<NoLabels, crate::Histogram>,
730 }
731
732 let metrics = Metrics::default();
733
734 metrics.total_bytes.inc_by(100);
736
737 metrics
739 .bytes_by_transport
740 .get_or_create(&TransportLabels {
741 transport: "ipv4".into(),
742 })
743 .inc_by(50);
744 metrics
745 .bytes_by_transport
746 .get_or_create(&TransportLabels {
747 transport: "relay".into(),
748 })
749 .inc_by(30);
750 metrics.latency.get_or_create(&NoLabels).observe(0.5);
751
752 let regular_count = metrics.iter().count();
754 assert_eq!(regular_count, 1, "Should have 1 regular metric");
755
756 let family_count = IntoIterable::family_iter(&metrics).count();
758 assert_eq!(family_count, 2, "Should have 2 family metrics");
759
760 let mut registry = Registry::default();
762 registry.register(Arc::new(metrics));
763
764 let output = registry.encode_openmetrics_to_string().unwrap();
765 assert!(output.contains("magicsock_total_bytes_total 100"));
766 assert!(output.contains("magicsock_bytes_by_transport"));
767 assert!(output.contains(r#"transport="ipv4""#));
768 assert!(output.contains(r#"transport="relay""#));
769 assert!(output.contains("magicsock_latency"));
770 }
771
772 #[cfg(test)]
774 mod family_tests {
775 use std::sync::{Arc, RwLock};
776
777 use iroh_metrics_derive::MetricsGroup;
778
779 use crate::{
780 Counter, Family, MetricsSource, Registry,
781 encoding::{Decoder, Encoder, Update},
782 iterable::IntoIterable,
783 };
784
785 #[derive(
786 Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, iroh_metrics::EncodeLabelSet,
787 )]
788 struct Proto {
789 proto: String,
790 }
791
792 fn proto(s: &str) -> Proto {
793 Proto { proto: s.into() }
794 }
795
796 #[derive(Debug, Default, MetricsGroup)]
797 #[metrics(name = "net")]
798 struct Net {
799 bytes: Counter,
801 bytes_by_proto: Family<Proto, Counter>,
803 }
804
805 fn registered() -> (Arc<Net>, Arc<RwLock<Registry>>) {
806 let metrics = Arc::new(Net::default());
807 let mut registry = Registry::default();
808 registry.register(metrics.clone());
809 (metrics, Arc::new(RwLock::new(registry)))
810 }
811
812 #[test]
813 #[cfg(feature = "postcard")]
814 fn encode_decode_roundtrip_matches_registry() {
815 let (metrics, registry) = registered();
816 metrics.bytes.inc_by(100);
817 metrics
818 .bytes_by_proto
819 .get_or_create(&proto("tcp"))
820 .inc_by(40);
821 metrics
822 .bytes_by_proto
823 .get_or_create(&proto("udp"))
824 .inc_by(60);
825
826 let mut encoder = Encoder::new(registry.clone());
827 let mut decoder = Decoder::default();
828 decoder
829 .import_bytes(&encoder.export_bytes().unwrap())
830 .unwrap();
831
832 let from_decoder = decoder.encode_openmetrics_to_string().unwrap();
833 assert_eq!(
834 from_decoder,
835 registry.encode_openmetrics_to_string().unwrap()
836 );
837 assert!(from_decoder.contains(r#"net_bytes_by_proto_total{proto="tcp"} 40"#));
838 assert!(from_decoder.contains(r#"net_bytes_by_proto_total{proto="udp"} 60"#));
839
840 metrics
842 .bytes_by_proto
843 .get_or_create(&proto("tcp"))
844 .inc_by(5);
845 decoder
846 .import_bytes(&encoder.export_bytes().unwrap())
847 .unwrap();
848 assert_eq!(
849 decoder.encode_openmetrics_to_string().unwrap(),
850 registry.encode_openmetrics_to_string().unwrap(),
851 );
852 }
853
854 #[test]
855 fn new_label_combo_bumps_schema_version() {
856 let (metrics, registry) = registered();
857 metrics.bytes_by_proto.get_or_create(&proto("tcp")).inc();
858
859 let mut encoder = Encoder::new(registry.clone());
860 let first = encoder.export();
862 assert_eq!(first.schema.expect("initial schema").items.len(), 2);
863 assert!(encoder.export().schema.is_none(), "schema must be cached");
864
865 metrics.bytes_by_proto.get_or_create(&proto("udp")).inc();
867 let third = encoder.export();
868 let schema = third.schema.expect("schema re-published after new combo");
869 assert_eq!(schema.items.len(), 3);
870
871 let mut decoder = Decoder::default();
872 decoder.import(Update {
873 schema: Some(schema),
874 values: third.values,
875 });
876 assert_eq!(
877 decoder.encode_openmetrics_to_string().unwrap(),
878 registry.encode_openmetrics_to_string().unwrap(),
879 );
880 }
881
882 #[test]
883 fn metrics_family_attr_overrides_alias_detection() {
884 type Aliased = Family<Proto, Counter>;
885
886 #[derive(Debug, Default, MetricsGroup)]
887 #[metrics(name = "alias")]
888 struct AliasMetrics {
889 #[metrics(family)]
890 via_alias: Aliased,
891 }
892
893 let m = AliasMetrics::default();
894 assert_eq!(IntoIterable::family_iter(&m).count(), 1);
895 assert_eq!(crate::MetricsGroup::iter(&m).count(), 0);
896 }
897 }
898}