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