1use crate::client::{MetricBackend, StatsdClient};
13use crate::types::{Metric, MetricError, MetricResult};
14use std::fmt::{self, Write};
15use std::marker::PhantomData;
16
17#[derive(Debug, Clone, Copy)]
19enum MetricType {
20 Counter,
21 Timer,
22 Gauge,
23 Meter,
24 Histogram,
25 Set,
26 Distribution,
27}
28
29impl fmt::Display for MetricType {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match *self {
32 MetricType::Counter => "c".fmt(f),
33 MetricType::Timer => "ms".fmt(f),
34 MetricType::Gauge => "g".fmt(f),
35 MetricType::Meter => "m".fmt(f),
36 MetricType::Histogram => "h".fmt(f),
37 MetricType::Set => "s".fmt(f),
38 MetricType::Distribution => "d".fmt(f),
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
51pub enum MetricValue {
52 Signed(i64),
53 PackedSigned(Vec<i64>),
54 Unsigned(u64),
55 PackedUnsigned(Vec<u64>),
56 Float(f64),
57 PackedFloat(Vec<f64>),
58}
59
60impl MetricValue {
61 fn count(&self) -> usize {
62 match self {
63 Self::PackedSigned(x) => x.len(),
64 Self::PackedUnsigned(x) => x.len(),
65 Self::PackedFloat(x) => x.len(),
66 _ => 1,
67 }
68 }
69}
70
71fn write_value<T>(f: &mut fmt::Formatter<'_>, vals: &[T]) -> fmt::Result
72where
73 T: fmt::Display,
74{
75 for (i, value) in vals.iter().enumerate() {
76 if i > 0 {
77 f.write_char(':')?;
78 }
79 value.fmt(f)?;
80 }
81
82 fmt::Result::Ok(())
83}
84
85impl fmt::Display for MetricValue {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match &*self {
88 MetricValue::Signed(v) => v.fmt(f),
89 MetricValue::PackedSigned(v) => write_value(f, v),
90 MetricValue::Unsigned(v) => v.fmt(f),
91 MetricValue::PackedUnsigned(v) => write_value(f, v),
92 MetricValue::Float(v) => v.fmt(f),
93 MetricValue::PackedFloat(v) => write_value(f, v),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
99pub(crate) struct MetricFormatter<'a> {
100 prefix: &'a str,
101 key: &'a str,
102 val: MetricValue,
103 type_: MetricType,
104 tags: Vec<(Option<&'a str>, &'a str)>,
105 base_size: usize,
106 kv_size: usize,
107}
108
109impl<'a> MetricFormatter<'a> {
110 const TAG_PREFIX: &'static str = "|#";
111
112 pub(crate) fn counter(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
113 Self::from_val(prefix, key, val, MetricType::Counter)
114 }
115
116 pub(crate) fn timer(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
117 Self::from_val(prefix, key, val, MetricType::Timer)
118 }
119
120 pub(crate) fn gauge(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
121 Self::from_val(prefix, key, val, MetricType::Gauge)
122 }
123
124 pub(crate) fn meter(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
125 Self::from_val(prefix, key, val, MetricType::Meter)
126 }
127
128 pub(crate) fn histogram(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
129 Self::from_val(prefix, key, val, MetricType::Histogram)
130 }
131
132 pub(crate) fn distribution(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
133 Self::from_val(prefix, key, val, MetricType::Distribution)
134 }
135
136 pub(crate) fn set(prefix: &'a str, key: &'a str, val: MetricValue) -> Self {
137 Self::from_val(prefix, key, val, MetricType::Set)
138 }
139
140 #[rustfmt::skip]
141 fn from_val(prefix: &'a str, key: &'a str, val: MetricValue, type_: MetricType) -> Self {
142 let value_count = val.count();
143 MetricFormatter {
144 prefix,
145 key,
146 type_,
147 val,
148 tags: Vec::new(),
149 kv_size: 0,
155 base_size: prefix.len() + key.len() + 1 + 10 * value_count + 1 + 2, }
157 }
158
159 fn with_tag(&mut self, key: &'a str, value: &'a str) {
160 self.tags.push((Some(key), value));
161 self.kv_size += key.len() + 1 + value.len();
162 }
163
164 fn with_tag_value(&mut self, value: &'a str) {
165 self.tags.push((None, value));
166 self.kv_size += value.len();
167 }
168
169 fn write_base_metric(&self, out: &mut String) {
170 let _ = write!(out, "{}{}:{}|{}", self.prefix, self.key, self.val, self.type_);
171 }
172
173 fn write_tags(&self, out: &mut String) {
174 if !self.tags.is_empty() {
175 out.push_str(Self::TAG_PREFIX);
176 for (i, &(key, value)) in self.tags.iter().enumerate() {
177 if i > 0 {
178 out.push(',');
179 }
180 if let Some(key) = key {
181 out.push_str(key);
182 out.push(':');
183 }
184 out.push_str(value);
185 }
186 }
187 }
188
189 fn tag_size_hint(&self) -> usize {
190 if self.tags.is_empty() {
191 return 0;
192 }
193
194 Self::TAG_PREFIX.len() + self.kv_size + self.tags.len() - 1
196 }
197
198 pub(crate) fn format(&self) -> String {
199 let size_hint = self.base_size + self.tag_size_hint();
200 let mut metric_string = String::with_capacity(size_hint);
201 self.write_base_metric(&mut metric_string);
202 self.write_tags(&mut metric_string);
203 metric_string
204 }
205}
206
207#[derive(Debug)]
213enum BuilderRepr<'m, 'c> {
214 Success(MetricFormatter<'m>, &'c StatsdClient),
215 Error(MetricError, &'c StatsdClient),
216}
217
218#[must_use = "Did you forget to call .send() after adding tags?"]
291#[derive(Debug)]
292pub struct MetricBuilder<'m, 'c, T>
293where
294 T: Metric + From<String>,
295{
296 repr: BuilderRepr<'m, 'c>,
297 type_: PhantomData<T>,
298}
299
300impl<'m, 'c, T> MetricBuilder<'m, 'c, T>
301where
302 T: Metric + From<String>,
303{
304 pub(crate) fn from_fmt(formatter: MetricFormatter<'m>, client: &'c StatsdClient) -> Self {
305 MetricBuilder {
306 repr: BuilderRepr::Success(formatter, client),
307 type_: PhantomData,
308 }
309 }
310
311 pub(crate) fn from_error(err: MetricError, client: &'c StatsdClient) -> Self {
312 MetricBuilder {
313 repr: BuilderRepr::Error(err, client),
314 type_: PhantomData,
315 }
316 }
317
318 pub fn with_tag(mut self, key: &'m str, value: &'m str) -> Self {
337 if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
338 formatter.with_tag(key, value);
339 }
340 self
341 }
342
343 pub fn with_tag_value(mut self, value: &'m str) -> Self {
362 if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
363 formatter.with_tag_value(value);
364 }
365 self
366 }
367
368 pub(crate) fn with_tags<V>(mut self, tags: V) -> Self
370 where
371 V: IntoIterator<Item = (Option<&'m str>, &'m str)>,
372 {
373 if let BuilderRepr::Success(ref mut formatter, _) = self.repr {
374 for tag in tags.into_iter() {
375 match tag {
376 (Some(key), value) => formatter.with_tag(key, value),
377 (None, value) => formatter.with_tag_value(value),
378 }
379 }
380 }
381
382 self
383 }
384
385 pub fn try_send(self) -> MetricResult<T> {
407 match self.repr {
408 BuilderRepr::Error(err, _) => Err(err),
409 BuilderRepr::Success(ref formatter, client) => {
410 let metric = T::from(formatter.format());
411 client.send_metric(&metric)?;
412 Ok(metric)
413 }
414 }
415 }
416
417 pub fn send(self) {
446 match self.repr {
447 BuilderRepr::Error(err, client) => client.consume_error(err),
448 BuilderRepr::Success(_, client) => {
449 if let Err(e) = self.try_send() {
450 client.consume_error(e);
451 }
452 }
453 }
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::{MetricBuilder, MetricFormatter, MetricValue};
460 use crate::client::StatsdClient;
461 use crate::sinks::NopMetricSink;
462 use crate::test::ErrorMetricSink;
463 use crate::types::Counter;
464 use std::sync::atomic::{AtomicU64, Ordering};
465 use std::sync::Arc;
466
467 #[test]
468 fn test_metric_formatter_tag_size_hint_no_tags() {
469 let fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
470 assert_eq!(0, fmt.tag_size_hint());
471 }
472
473 #[test]
474 fn test_metric_formatter_tag_size_hint_value() {
475 let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
476 fmt.with_tag_value("test");
477
478 assert_eq!(6, fmt.tag_size_hint());
479 }
480
481 #[test]
482 fn test_metric_formatter_tag_size_hint_key_value() {
483 let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(1));
484 fmt.with_tag("host", "web");
485 fmt.with_tag("user", "123");
486
487 assert_eq!(19, fmt.tag_size_hint());
488 }
489
490 #[test]
491 fn test_metric_formatter_counter_no_tags() {
492 let fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(4));
493 assert_eq!("prefix.some.key:4|c", &fmt.format());
494 }
495
496 #[test]
497 fn test_metric_formatter_counter_with_tags() {
498 let mut fmt = MetricFormatter::counter("prefix.", "some.key", MetricValue::Signed(4));
499 fmt.with_tag("host", "app03.example.com");
500 fmt.with_tag("bucket", "2");
501 fmt.with_tag_value("beta");
502
503 assert_eq!(
504 "prefix.some.key:4|c|#host:app03.example.com,bucket:2,beta",
505 &fmt.format()
506 );
507 }
508
509 #[test]
510 fn test_metric_formatter_timer_no_tags() {
511 let fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::Unsigned(21));
512
513 assert_eq!("prefix.some.method:21|ms", &fmt.format());
514 }
515
516 #[test]
517 fn test_metric_formatter_timer_no_tags_multiple_values() {
518 let fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::PackedUnsigned(vec![21, 22, 23]));
519
520 assert_eq!("prefix.some.method:21:22:23|ms", &fmt.format());
521 }
522
523 #[test]
524 fn test_metric_formatter_timer_with_tags() {
525 let mut fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::Unsigned(21));
526 fmt.with_tag("app", "metrics");
527 fmt.with_tag_value("async");
528
529 assert_eq!("prefix.some.method:21|ms|#app:metrics,async", &fmt.format());
530 }
531
532 #[test]
533 fn test_metric_formatter_timer_with_tags_multiple_values() {
534 let mut fmt = MetricFormatter::timer("prefix.", "some.method", MetricValue::PackedUnsigned(vec![21, 22, 23]));
535 fmt.with_tag("app", "metrics");
536 fmt.with_tag_value("async");
537
538 assert_eq!("prefix.some.method:21:22:23|ms|#app:metrics,async", &fmt.format());
539 }
540
541 #[test]
542 fn test_metric_formatter_gauge_no_tags() {
543 let fmt = MetricFormatter::gauge("prefix.", "num.failures", MetricValue::Unsigned(7));
544
545 assert_eq!("prefix.num.failures:7|g", &fmt.format());
546 }
547
548 #[test]
549 fn test_metric_formatter_gauge_with_tags() {
550 let mut fmt = MetricFormatter::gauge("prefix.", "num.failures", MetricValue::Unsigned(7));
551 fmt.with_tag("window", "300");
552 fmt.with_tag_value("best-effort");
553
554 assert_eq!("prefix.num.failures:7|g|#window:300,best-effort", &fmt.format());
555 }
556
557 #[test]
558 fn test_metric_formatter_meter_no_tags() {
559 let fmt = MetricFormatter::meter("prefix.", "user.logins", MetricValue::Unsigned(3));
560
561 assert_eq!("prefix.user.logins:3|m", &fmt.format());
562 }
563
564 #[test]
565 fn test_metric_formatter_meter_with_tags() {
566 let mut fmt = MetricFormatter::meter("prefix.", "user.logins", MetricValue::Unsigned(3));
567 fmt.with_tag("user-type", "verified");
568 fmt.with_tag_value("bucket1");
569
570 assert_eq!("prefix.user.logins:3|m|#user-type:verified,bucket1", &fmt.format());
571 }
572
573 #[test]
574 fn test_metric_formatter_histogram_no_tags() {
575 let fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::Unsigned(44));
576
577 assert_eq!("prefix.num.results:44|h", &fmt.format());
578 }
579
580 #[test]
581 fn test_metric_formatter_histogram_no_tags_multiple_values() {
582 let fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::PackedUnsigned(vec![44, 45, 46]));
583
584 assert_eq!("prefix.num.results:44:45:46|h", &fmt.format());
585 }
586
587 #[test]
588 fn test_metric_formatter_histogram_with_tags() {
589 let mut fmt = MetricFormatter::histogram("prefix.", "num.results", MetricValue::Unsigned(44));
590 fmt.with_tag("user-type", "authenticated");
591 fmt.with_tag_value("source=search");
592
593 assert_eq!(
594 "prefix.num.results:44|h|#user-type:authenticated,source=search",
595 &fmt.format()
596 );
597 }
598
599 #[test]
600 fn test_metric_formatter_histogram_with_tags_multiple_values() {
601 let mut fmt =
602 MetricFormatter::histogram("prefix.", "num.results", MetricValue::PackedUnsigned(vec![44, 45, 46]));
603 fmt.with_tag("user-type", "authenticated");
604 fmt.with_tag_value("source=search");
605
606 assert_eq!(
607 "prefix.num.results:44:45:46|h|#user-type:authenticated,source=search",
608 &fmt.format()
609 );
610 }
611
612 #[test]
613 fn test_metric_formatter_distribution_no_tags() {
614 let fmt = MetricFormatter::distribution("prefix.", "latency.milliseconds", MetricValue::Unsigned(44));
615
616 assert_eq!("prefix.latency.milliseconds:44|d", &fmt.format());
617 }
618
619 #[test]
620 fn test_metric_formatter_distribution_no_tags_multiple_values() {
621 let fmt = MetricFormatter::distribution(
622 "prefix.",
623 "latency.milliseconds",
624 MetricValue::PackedUnsigned(vec![44, 45, 46]),
625 );
626
627 assert_eq!("prefix.latency.milliseconds:44:45:46|d", &fmt.format());
628 }
629
630 #[test]
631 fn test_metric_formatter_distribution_with_tags() {
632 let mut fmt = MetricFormatter::distribution("prefix.", "latency.milliseconds", MetricValue::Unsigned(44));
633 fmt.with_tag("user-type", "authenticated");
634 fmt.with_tag_value("source=search");
635
636 assert_eq!(
637 "prefix.latency.milliseconds:44|d|#user-type:authenticated,source=search",
638 &fmt.format()
639 );
640 }
641
642 #[test]
643 fn test_metric_formatter_distribution_with_tags_multiple_values() {
644 let mut fmt = MetricFormatter::distribution(
645 "prefix.",
646 "latency.milliseconds",
647 MetricValue::PackedUnsigned(vec![44, 45, 46]),
648 );
649 fmt.with_tag("user-type", "authenticated");
650 fmt.with_tag_value("source=search");
651
652 assert_eq!(
653 "prefix.latency.milliseconds:44:45:46|d|#user-type:authenticated,source=search",
654 &fmt.format()
655 );
656 }
657
658 #[test]
659 fn test_metric_formatter_set_no_tags() {
660 let fmt = MetricFormatter::set("prefix.", "users.uniques", MetricValue::Signed(44));
661
662 assert_eq!("prefix.users.uniques:44|s", &fmt.format());
663 }
664
665 #[test]
666 fn test_metric_formatter_set_with_tags() {
667 let mut fmt = MetricFormatter::set("prefix.", "users.uniques", MetricValue::Signed(44));
668 fmt.with_tag("user-type", "authenticated");
669 fmt.with_tag_value("source=search");
670
671 assert_eq!(
672 concat!(
673 "prefix.users.uniques:44|s|#",
674 "user-type:authenticated,",
675 "source=search"
676 ),
677 &fmt.format()
678 );
679 }
680
681 #[test]
682 fn test_metric_builder_send_success() {
683 let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
684 let client = StatsdClient::builder("prefix.", NopMetricSink)
685 .with_error_handler(|e| {
686 panic!("unexpected error sending metric: {}", e);
687 })
688 .build();
689
690 let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
692 builder.send();
693 }
694
695 #[test]
696 fn test_metric_builder_send_error() {
697 let errors = Arc::new(AtomicU64::new(0));
698 let errors_ref = errors.clone();
699
700 let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
701 let client = StatsdClient::builder("prefix.", ErrorMetricSink::always())
702 .with_error_handler(move |_e| {
703 errors_ref.fetch_add(1, Ordering::Release);
704 })
705 .build();
706
707 let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
708 builder.send();
709
710 assert_eq!(1, errors.load(Ordering::Acquire));
711 }
712
713 #[test]
714 fn test_metric_builder_try_send_success() {
715 let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
716 let client = StatsdClient::from_sink("prefix.", NopMetricSink);
717
718 let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
719 let res = builder.try_send();
720
721 assert!(res.is_ok(), "expected Ok result from try_send");
722 }
723
724 #[test]
725 fn test_metric_builder_try_send_error() {
726 let fmt = MetricFormatter::counter("prefix.", "some.counter", MetricValue::Signed(11));
727 let client = StatsdClient::from_sink("prefix.", ErrorMetricSink::always());
728
729 let builder: MetricBuilder<'_, '_, Counter> = MetricBuilder::from_fmt(fmt, &client);
730 let res = builder.try_send();
731
732 assert!(res.is_err(), "expected Err result from try_send");
733 }
734}