1use crate::builder::{MetricBuilder, MetricFormatter, MetricValue};
12use crate::sealed::Sealed;
13use crate::sinks::MetricSink;
14use crate::types::{
15 Counter, Distribution, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer,
16};
17use std::fmt;
18use std::panic::RefUnwindSafe;
19use std::time::Duration;
20
21pub trait ToCounterValue {
30 fn try_to_value(self) -> MetricResult<MetricValue>;
31}
32
33impl ToCounterValue for i64 {
34 fn try_to_value(self) -> MetricResult<MetricValue> {
35 Ok(MetricValue::Signed(self))
36 }
37}
38
39impl ToCounterValue for i32 {
40 fn try_to_value(self) -> MetricResult<MetricValue> {
41 Ok(MetricValue::Signed(self.into()))
42 }
43}
44
45impl ToCounterValue for u64 {
46 fn try_to_value(self) -> MetricResult<MetricValue> {
47 Ok(MetricValue::Unsigned(self))
48 }
49}
50
51impl ToCounterValue for u32 {
52 fn try_to_value(self) -> MetricResult<MetricValue> {
53 Ok(MetricValue::Unsigned(self.into()))
54 }
55}
56
57pub trait ToTimerValue {
66 fn try_to_value(self) -> MetricResult<MetricValue>;
67}
68
69impl ToTimerValue for u64 {
70 fn try_to_value(self) -> MetricResult<MetricValue> {
71 Ok(MetricValue::Unsigned(self))
72 }
73}
74
75impl ToTimerValue for Vec<u64> {
76 fn try_to_value(self) -> MetricResult<MetricValue> {
77 Ok(MetricValue::PackedUnsigned(self))
78 }
79}
80
81impl ToTimerValue for Duration {
82 fn try_to_value(self) -> MetricResult<MetricValue> {
83 let as_millis = self.as_millis();
84 if as_millis > u64::MAX as u128 {
85 Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
86 } else {
87 Ok(MetricValue::Unsigned(as_millis as u64))
88 }
89 }
90}
91
92impl ToTimerValue for Vec<Duration> {
93 fn try_to_value(self) -> MetricResult<MetricValue> {
94 if self.iter().any(|x| x.as_millis() > u64::MAX as u128) {
95 Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
96 } else {
97 Ok(MetricValue::PackedUnsigned(
98 self.iter().map(|x| x.as_millis() as u64).collect(),
99 ))
100 }
101 }
102}
103
104pub trait ToGaugeValue {
113 fn try_to_value(self) -> MetricResult<MetricValue>;
114}
115
116impl ToGaugeValue for u64 {
117 fn try_to_value(self) -> MetricResult<MetricValue> {
118 Ok(MetricValue::Unsigned(self))
119 }
120}
121
122impl ToGaugeValue for f64 {
123 fn try_to_value(self) -> MetricResult<MetricValue> {
124 Ok(MetricValue::Float(self))
125 }
126}
127
128pub trait ToMeterValue {
137 fn try_to_value(self) -> MetricResult<MetricValue>;
138}
139
140impl ToMeterValue for u64 {
141 fn try_to_value(self) -> MetricResult<MetricValue> {
142 Ok(MetricValue::Unsigned(self))
143 }
144}
145
146pub trait ToHistogramValue {
155 fn try_to_value(self) -> MetricResult<MetricValue>;
156}
157
158impl ToHistogramValue for u64 {
159 fn try_to_value(self) -> MetricResult<MetricValue> {
160 Ok(MetricValue::Unsigned(self))
161 }
162}
163
164impl ToHistogramValue for f64 {
165 fn try_to_value(self) -> MetricResult<MetricValue> {
166 Ok(MetricValue::Float(self))
167 }
168}
169
170impl ToHistogramValue for Duration {
171 fn try_to_value(self) -> MetricResult<MetricValue> {
172 let as_nanos = self.as_nanos();
173 if as_nanos > u64::MAX as u128 {
174 Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
175 } else {
176 Ok(MetricValue::Unsigned(as_nanos as u64))
177 }
178 }
179}
180
181impl ToHistogramValue for Vec<u64> {
182 fn try_to_value(self) -> MetricResult<MetricValue> {
183 Ok(MetricValue::PackedUnsigned(self))
184 }
185}
186
187impl ToHistogramValue for Vec<f64> {
188 fn try_to_value(self) -> MetricResult<MetricValue> {
189 Ok(MetricValue::PackedFloat(self))
190 }
191}
192
193impl ToHistogramValue for Vec<Duration> {
194 fn try_to_value(self) -> MetricResult<MetricValue> {
195 if self.iter().any(|x| x.as_nanos() > u64::MAX as u128) {
196 Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
197 } else {
198 Ok(MetricValue::PackedUnsigned(
199 self.iter().map(|x| x.as_nanos() as u64).collect(),
200 ))
201 }
202 }
203}
204
205pub trait ToDistributionValue {
214 fn try_to_value(self) -> MetricResult<MetricValue>;
215}
216
217impl ToDistributionValue for u64 {
218 fn try_to_value(self) -> MetricResult<MetricValue> {
219 Ok(MetricValue::Unsigned(self))
220 }
221}
222
223impl ToDistributionValue for f64 {
224 fn try_to_value(self) -> MetricResult<MetricValue> {
225 Ok(MetricValue::Float(self))
226 }
227}
228
229impl ToDistributionValue for Vec<u64> {
230 fn try_to_value(self) -> MetricResult<MetricValue> {
231 Ok(MetricValue::PackedUnsigned(self))
232 }
233}
234
235impl ToDistributionValue for Vec<f64> {
236 fn try_to_value(self) -> MetricResult<MetricValue> {
237 Ok(MetricValue::PackedFloat(self))
238 }
239}
240
241pub trait ToSetValue {
250 fn try_to_value(self) -> MetricResult<MetricValue>;
251}
252
253impl ToSetValue for i64 {
254 fn try_to_value(self) -> MetricResult<MetricValue> {
255 Ok(MetricValue::Signed(self))
256 }
257}
258
259pub trait Counted<T>
275where
276 T: ToCounterValue,
277{
278 fn count(&self, key: &str, count: T) -> MetricResult<Counter> {
280 self.count_with_tags(key, count).try_send()
281 }
282
283 fn count_with_tags<'a>(&'a self, key: &'a str, count: T) -> MetricBuilder<'a, 'a, Counter>;
286}
287
288pub trait CountedExt: Counted<i64> {
293 fn incr(&self, key: &str) -> MetricResult<Counter> {
295 self.incr_with_tags(key).try_send()
296 }
297
298 fn incr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'a, 'a, Counter> {
301 self.count_with_tags(key, 1)
302 }
303
304 fn decr(&self, key: &str) -> MetricResult<Counter> {
306 self.decr_with_tags(key).try_send()
307 }
308
309 fn decr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'a, 'a, Counter> {
312 self.count_with_tags(key, -1)
313 }
314}
315
316pub trait Timed<T>
333where
334 T: ToTimerValue,
335{
336 fn time(&self, key: &str, time: T) -> MetricResult<Timer> {
338 self.time_with_tags(key, time).try_send()
339 }
340
341 fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'a, 'a, Timer>;
344}
345
346pub trait Gauged<T>
362where
363 T: ToGaugeValue,
364{
365 fn gauge(&self, key: &str, value: T) -> MetricResult<Gauge> {
367 self.gauge_with_tags(key, value).try_send()
368 }
369
370 fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Gauge>;
373}
374
375pub trait Metered<T>
392where
393 T: ToMeterValue,
394{
395 fn meter(&self, key: &str, value: T) -> MetricResult<Meter> {
397 self.meter_with_tags(key, value).try_send()
398 }
399
400 fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Meter>;
403}
404
405pub trait Histogrammed<T>
426where
427 T: ToHistogramValue,
428{
429 fn histogram(&self, key: &str, value: T) -> MetricResult<Histogram> {
431 self.histogram_with_tags(key, value).try_send()
432 }
433
434 fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Histogram>;
437}
438
439pub trait Distributed<T>
456where
457 T: ToDistributionValue,
458{
459 fn distribution(&self, key: &str, value: T) -> MetricResult<Distribution> {
461 self.distribution_with_tags(key, value).try_send()
462 }
463
464 fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Distribution>;
467}
468
469pub trait Setted<T>
480where
481 T: ToSetValue,
482{
483 fn set(&self, key: &str, value: T) -> MetricResult<Set> {
485 self.set_with_tags(key, value).try_send()
486 }
487
488 fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Set>;
491}
492
493pub trait MetricClient:
526 Counted<i64>
527 + Counted<i32>
528 + Counted<u64>
529 + Counted<u32>
530 + CountedExt
531 + Timed<u64>
532 + Timed<Duration>
533 + Timed<Vec<u64>>
534 + Timed<Vec<Duration>>
535 + Gauged<u64>
536 + Gauged<f64>
537 + Metered<u64>
538 + Histogrammed<u64>
539 + Histogrammed<f64>
540 + Histogrammed<Duration>
541 + Histogrammed<Vec<u64>>
542 + Histogrammed<Vec<f64>>
543 + Histogrammed<Vec<Duration>>
544 + Distributed<u64>
545 + Distributed<f64>
546 + Distributed<Vec<u64>>
547 + Distributed<Vec<f64>>
548 + Setted<i64>
549{
550}
551
552pub trait MetricBackend: Sealed {
622 fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
632 where
633 M: Metric;
634
635 fn consume_error(&self, err: MetricError);
645}
646
647pub struct StatsdClientBuilder {
674 prefix: String,
675 sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
676 errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
677 tags: Vec<(Option<String>, String)>,
678 container_id: Option<String>,
679}
680
681impl StatsdClientBuilder {
682 fn new<T>(prefix: &str, sink: T) -> Self
684 where
685 T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
686 {
687 StatsdClientBuilder {
688 prefix: Self::formatted_prefix(prefix),
690 sink: Box::new(sink),
691
692 errors: Box::new(nop_error_handler),
694 tags: Vec::new(),
695 container_id: None,
696 }
697 }
698
699 pub fn with_error_handler<F>(mut self, errors: F) -> Self
709 where
710 F: Fn(MetricError) + Sync + Send + RefUnwindSafe + 'static,
711 {
712 self.errors = Box::new(errors);
713 self
714 }
715
716 pub fn with_tag<K, V>(mut self, key: K, value: V) -> Self
719 where
720 K: ToString,
721 V: ToString,
722 {
723 self.tags.push((Some(key.to_string()), value.to_string()));
724 self
725 }
726
727 pub fn with_tag_value<K>(mut self, value: K) -> Self
730 where
731 K: ToString,
732 {
733 self.tags.push((None, value.to_string()));
734 self
735 }
736
737 pub fn with_container_id<K>(mut self, container_id: K) -> Self
740 where
741 K: ToString,
742 {
743 self.container_id = Some(container_id.to_string());
744 self
745 }
746
747 pub fn build(self) -> StatsdClient {
749 StatsdClient::from_builder(self)
750 }
751
752 fn formatted_prefix(prefix: &str) -> String {
753 if prefix.is_empty() {
754 String::new()
755 } else {
756 format!("{}.", prefix.trim_end_matches('.'))
757 }
758 }
759}
760
761pub struct StatsdClient {
844 prefix: String,
845 sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
846 errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
847 tags: Vec<(Option<String>, String)>,
848 container_id: Option<String>,
849}
850
851impl StatsdClient {
852 pub fn from_sink<T>(prefix: &str, sink: T) -> Self
898 where
899 T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
900 {
901 Self::builder(prefix, sink).build()
902 }
903
904 pub fn builder<T>(prefix: &str, sink: T) -> StatsdClientBuilder
942 where
943 T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
944 {
945 StatsdClientBuilder::new(prefix, sink)
946 }
947
948 pub fn flush(&self) -> MetricResult<()> {
979 Ok(self.sink.flush()?)
980 }
981
982 fn from_builder(builder: StatsdClientBuilder) -> Self {
984 StatsdClient {
985 prefix: builder.prefix,
986 sink: builder.sink,
987 errors: builder.errors,
988 tags: builder.tags,
989 container_id: builder.container_id,
990 }
991 }
992
993 fn tags(&self) -> impl IntoIterator<Item = (Option<&str>, &str)> {
994 self.tags.iter().map(|(k, v)| (k.as_deref(), v.as_str()))
995 }
996}
997
998impl Sealed for StatsdClient {}
999
1000impl MetricBackend for StatsdClient {
1001 fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
1002 where
1003 M: Metric,
1004 {
1005 let metric_string = metric.as_metric_str();
1006 self.sink.emit(metric_string)?;
1007 Ok(())
1008 }
1009
1010 fn consume_error(&self, err: MetricError) {
1011 (self.errors)(err);
1012 }
1013}
1014
1015impl fmt::Debug for StatsdClient {
1016 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017 write!(
1018 f,
1019 "StatsdClient {{ prefix: {:?}, sink: ..., errors: ..., tags: {:?} }}",
1020 self.prefix, self.tags,
1021 )
1022 }
1023}
1024
1025impl<T> Counted<T> for StatsdClient
1026where
1027 T: ToCounterValue,
1028{
1029 fn count_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Counter> {
1030 match value.try_to_value() {
1031 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::counter(&self.prefix, key, v), self)
1032 .with_tags(self.tags())
1033 .with_container_id_opt(self.container_id.as_deref()),
1034 Err(e) => MetricBuilder::from_error(e, self),
1035 }
1036 }
1037}
1038
1039impl CountedExt for StatsdClient {}
1040
1041impl<T> Timed<T> for StatsdClient
1042where
1043 T: ToTimerValue,
1044{
1045 fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'a, 'a, Timer> {
1046 match time.try_to_value() {
1047 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::timer(&self.prefix, key, v), self)
1048 .with_tags(self.tags())
1049 .with_container_id_opt(self.container_id.as_deref()),
1050 Err(e) => MetricBuilder::from_error(e, self),
1051 }
1052 }
1053}
1054
1055impl<T> Gauged<T> for StatsdClient
1056where
1057 T: ToGaugeValue,
1058{
1059 fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Gauge> {
1060 match value.try_to_value() {
1061 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::gauge(&self.prefix, key, v), self)
1062 .with_tags(self.tags())
1063 .with_container_id_opt(self.container_id.as_deref()),
1064 Err(e) => MetricBuilder::from_error(e, self),
1065 }
1066 }
1067}
1068
1069impl<T> Metered<T> for StatsdClient
1070where
1071 T: ToMeterValue,
1072{
1073 fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Meter> {
1074 match value.try_to_value() {
1075 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::meter(&self.prefix, key, v), self)
1076 .with_tags(self.tags())
1077 .with_container_id_opt(self.container_id.as_deref()),
1078 Err(e) => MetricBuilder::from_error(e, self),
1079 }
1080 }
1081}
1082
1083impl<T> Histogrammed<T> for StatsdClient
1084where
1085 T: ToHistogramValue,
1086{
1087 fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Histogram> {
1088 match value.try_to_value() {
1089 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::histogram(&self.prefix, key, v), self)
1090 .with_tags(self.tags())
1091 .with_container_id_opt(self.container_id.as_deref()),
1092 Err(e) => MetricBuilder::from_error(e, self),
1093 }
1094 }
1095}
1096
1097impl<T> Distributed<T> for StatsdClient
1098where
1099 T: ToDistributionValue,
1100{
1101 fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Distribution> {
1102 match value.try_to_value() {
1103 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::distribution(&self.prefix, key, v), self)
1104 .with_tags(self.tags())
1105 .with_container_id_opt(self.container_id.as_deref()),
1106 Err(e) => MetricBuilder::from_error(e, self),
1107 }
1108 }
1109}
1110
1111impl<T> Setted<T> for StatsdClient
1112where
1113 T: ToSetValue,
1114{
1115 fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'a, 'a, Set> {
1116 match value.try_to_value() {
1117 Ok(v) => MetricBuilder::from_fmt(MetricFormatter::set(&self.prefix, key, v), self)
1118 .with_tags(self.tags())
1119 .with_container_id_opt(self.container_id.as_deref()),
1120 Err(e) => MetricBuilder::from_error(e, self),
1121 }
1122 }
1123}
1124
1125impl MetricClient for StatsdClient {}
1126
1127#[allow(clippy::needless_pass_by_value)]
1128fn nop_error_handler(_err: MetricError) {
1129 }
1131
1132#[cfg(test)]
1133mod tests {
1134 use super::{
1135 Counted, CountedExt, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient,
1136 StatsdClientBuilder, Timed,
1137 };
1138 use crate::sinks::{MetricSink, NopMetricSink, QueuingMetricSink, SpyMetricSink};
1139 use crate::types::{ErrorKind, Metric, MetricError};
1140 use std::io;
1141 use std::panic::RefUnwindSafe;
1142 use std::sync::atomic::{AtomicUsize, Ordering};
1143 use std::sync::Arc;
1144 use std::time::Duration;
1145
1146 #[test]
1147 fn test_statsd_client_empty_prefix() {
1148 let client = StatsdClient::from_sink("", NopMetricSink);
1149 let res = client.count("some.method", 1);
1150
1151 assert_eq!("some.method:1|c", res.unwrap().as_metric_str());
1152 }
1153
1154 #[test]
1155 fn test_statsd_client_with_container_id() {
1156 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1157 .with_container_id("1234")
1158 .build();
1159 let res = client.count("some.method", 1);
1160
1161 assert_eq!("prefix.some.method:1|c|c:1234", res.unwrap().as_metric_str());
1162 }
1163
1164 #[test]
1165 fn test_statsd_client_merging_default_tags_with_tags() {
1166 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1167 .with_tag("hello", "world")
1168 .with_tag_value("production")
1169 .build();
1170 let res = client
1171 .count_with_tags("some.counter", 3)
1172 .with_tag("foo", "bar")
1173 .with_tag_value("fizz")
1174 .with_tag("bucket", "123")
1175 .try_send();
1176
1177 assert_eq!(
1178 "prefix.some.counter:3|c|#hello:world,production,foo:bar,fizz,bucket:123",
1179 res.unwrap().as_metric_str()
1180 );
1181 }
1182
1183 #[test]
1184 fn test_statsd_client_count_with_tags() {
1185 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1186 let res = client
1187 .count_with_tags("some.counter", 3)
1188 .with_tag("foo", "bar")
1189 .try_send();
1190
1191 assert_eq!("prefix.some.counter:3|c|#foo:bar", res.unwrap().as_metric_str());
1192 }
1193
1194 #[test]
1195 fn test_statsd_client_count_with_default_tags() {
1196 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1197 .with_tag("hello", "world")
1198 .build();
1199 let res = client.count_with_tags("some.counter", 3).try_send();
1200
1201 assert_eq!("prefix.some.counter:3|c|#hello:world", res.unwrap().as_metric_str());
1202 }
1203
1204 #[test]
1205 fn test_statsd_client_incr_with_tags() {
1206 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1207 let res = client.incr_with_tags("some.counter").with_tag("foo", "bar").try_send();
1208
1209 assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
1210 }
1211
1212 #[test]
1213 fn test_statsd_client_incr_with_default_tags() {
1214 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1215 .with_tag("foo", "bar")
1216 .build();
1217 let res = client.incr_with_tags("some.counter").try_send();
1218
1219 assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
1220 }
1221
1222 #[test]
1223 fn test_statsd_client_decr_with_tags() {
1224 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1225 let res = client.decr_with_tags("some.counter").with_tag("foo", "bar").try_send();
1226
1227 assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
1228 }
1229
1230 #[test]
1231 fn test_statsd_client_decr_with_default_tags() {
1232 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1233 .with_tag("foo", "bar")
1234 .build();
1235 let res = client.decr_with_tags("some.counter").try_send();
1236
1237 assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
1238 }
1239
1240 #[test]
1241 fn test_statsd_client_gauge_with_tags() {
1242 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1243 let res = client
1244 .gauge_with_tags("some.gauge", 4)
1245 .with_tag("bucket", "A")
1246 .with_tag_value("file-server")
1247 .try_send();
1248
1249 assert_eq!(
1250 "prefix.some.gauge:4|g|#bucket:A,file-server",
1251 res.unwrap().as_metric_str()
1252 );
1253 }
1254
1255 #[test]
1256 fn test_statsd_client_gauge_with_default_tags() {
1257 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1258 .with_tag("foo", "bar")
1259 .build();
1260 let res = client.gauge_with_tags("some.gauge", 4).try_send();
1261
1262 assert_eq!("prefix.some.gauge:4|g|#foo:bar", res.unwrap().as_metric_str());
1263 }
1264
1265 #[test]
1266 fn test_statsd_client_gauge_with_timestamp() {
1267 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1268 let res = client
1269 .gauge_with_tags("some.gauge", 4)
1270 .with_timestamp(1234567890)
1271 .try_send();
1272
1273 assert_eq!("prefix.some.gauge:4|g|T1234567890", res.unwrap().as_metric_str());
1274 }
1275
1276 #[test]
1277 fn test_statsd_client_time_duration() {
1278 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1279 let res = client.time("key", Duration::from_millis(157));
1280
1281 assert_eq!("prefix.key:157|ms", res.unwrap().as_metric_str());
1282 }
1283
1284 #[test]
1285 fn test_statsd_client_time_multiple_durations() {
1286 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1287 let durations = vec![
1288 Duration::from_millis(157),
1289 Duration::from_millis(158),
1290 Duration::from_millis(159),
1291 ];
1292 let res = client.time("key", durations);
1293
1294 assert_eq!("prefix.key:157:158:159|ms", res.unwrap().as_metric_str());
1295 }
1296
1297 #[test]
1298 fn test_statsd_client_time_duration_with_overflow() {
1299 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1300 let res = client.time("key", Duration::from_secs(u64::MAX));
1301
1302 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
1303 }
1304
1305 #[test]
1306 fn test_statsd_client_time_multiple_durations_with_overflow() {
1307 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1308 let durations = vec![
1309 Duration::from_millis(157),
1310 Duration::from_secs(u64::MAX),
1311 Duration::from_millis(159),
1312 ];
1313 let res = client.time("key", durations);
1314
1315 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
1316 }
1317
1318 #[test]
1319 fn test_statsd_client_time_duration_with_tags() {
1320 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1321 let res = client
1322 .time_with_tags("key", Duration::from_millis(157))
1323 .with_tag("foo", "bar")
1324 .with_tag_value("quux")
1325 .try_send();
1326
1327 assert_eq!("prefix.key:157|ms|#foo:bar,quux", res.unwrap().as_metric_str());
1328 }
1329
1330 #[test]
1331 fn test_statsd_client_time_duration_with_default_tags() {
1332 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1333 .with_tag("foo", "bar")
1334 .build();
1335 let res = client.time("key", Duration::from_millis(157));
1336
1337 assert_eq!("prefix.key:157|ms|#foo:bar", res.unwrap().as_metric_str());
1338 }
1339
1340 #[test]
1341 fn test_statsd_client_time_multiple_durations_with_tags() {
1342 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1343 let durations = vec![
1344 Duration::from_millis(157),
1345 Duration::from_millis(158),
1346 Duration::from_millis(159),
1347 ];
1348 let res = client
1349 .time_with_tags("key", durations)
1350 .with_tag("foo", "bar")
1351 .with_tag_value("quux")
1352 .try_send();
1353
1354 assert_eq!("prefix.key:157:158:159|ms|#foo:bar,quux", res.unwrap().as_metric_str());
1355 }
1356
1357 #[test]
1358 fn test_statsd_client_time_duration_with_tags_with_overflow() {
1359 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1360 let res = client
1361 .time_with_tags("key", Duration::from_secs(u64::MAX))
1362 .with_tag("foo", "bar")
1363 .with_tag_value("quux")
1364 .try_send();
1365
1366 assert!(res.is_err());
1367 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1368 }
1369
1370 #[test]
1371 fn test_statsd_client_time_multiple_durations_with_tags_with_overflow() {
1372 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1373 let durations = vec![
1374 Duration::from_millis(157),
1375 Duration::from_secs(u64::MAX),
1376 Duration::from_millis(159),
1377 ];
1378 let res = client
1379 .time_with_tags("key", durations)
1380 .with_tag("foo", "bar")
1381 .with_tag_value("quux")
1382 .try_send();
1383
1384 assert!(res.is_err());
1385 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1386 }
1387
1388 #[test]
1389 fn test_statsd_client_meter_with_tags() {
1390 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1391 let res = client
1392 .meter_with_tags("some.meter", 64)
1393 .with_tag("segment", "142")
1394 .with_tag_value("beta")
1395 .try_send();
1396
1397 assert_eq!("prefix.some.meter:64|m|#segment:142,beta", res.unwrap().as_metric_str());
1398 }
1399
1400 #[test]
1401 fn test_statsd_client_meter_with_default_tags() {
1402 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1403 .with_tag("foo", "bar")
1404 .build();
1405 let res = client.meter_with_tags("some.meter", 64).try_send();
1406
1407 assert_eq!("prefix.some.meter:64|m|#foo:bar", res.unwrap().as_metric_str());
1408 }
1409
1410 #[test]
1411 fn test_statsd_client_histogram_with_tags() {
1412 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1413 let res = client
1414 .histogram_with_tags("some.histo", 27)
1415 .with_tag("host", "www03.example.com")
1416 .with_tag_value("rc1")
1417 .try_send();
1418
1419 assert_eq!(
1420 "prefix.some.histo:27|h|#host:www03.example.com,rc1",
1421 res.unwrap().as_metric_str()
1422 );
1423 }
1424
1425 #[test]
1426 fn test_statsd_client_histogram_with_default_tags() {
1427 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1428 .with_tag("foo", "bar")
1429 .build();
1430 let res = client.histogram_with_tags("some.histo", 27).try_send();
1431
1432 assert_eq!("prefix.some.histo:27|h|#foo:bar", res.unwrap().as_metric_str());
1433 }
1434
1435 #[test]
1436 fn test_statsd_client_histogram_with_multiple_values() {
1437 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1438 let res = client.histogram_with_tags("some.histo", vec![27, 28, 29]).try_send();
1439
1440 assert_eq!("prefix.some.histo:27:28:29|h", res.unwrap().as_metric_str());
1441 }
1442
1443 #[test]
1444 fn test_statsd_client_histogram_duration() {
1445 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1446 let res = client.histogram("key", Duration::from_nanos(210));
1447
1448 assert_eq!("prefix.key:210|h", res.unwrap().as_metric_str());
1449 }
1450
1451 #[test]
1452 fn test_statsd_client_histogram_multiple_durations() {
1453 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1454 let durations = vec![
1455 Duration::from_nanos(210),
1456 Duration::from_nanos(211),
1457 Duration::from_nanos(212),
1458 ];
1459 let res = client.histogram("key", durations);
1460
1461 assert_eq!("prefix.key:210:211:212|h", res.unwrap().as_metric_str());
1462 }
1463
1464 #[test]
1465 fn test_statsd_client_histogram_duration_with_overflow() {
1466 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1467 let res = client.histogram("key", Duration::from_secs(u64::MAX));
1468
1469 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1470 }
1471
1472 #[test]
1473 fn test_statsd_client_histogram_multiple_durations_with_overflow() {
1474 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1475 let durations = vec![
1476 Duration::from_nanos(210),
1477 Duration::from_secs(u64::MAX),
1478 Duration::from_nanos(212),
1479 ];
1480
1481 let res = client.histogram("key", durations);
1482
1483 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1484 }
1485
1486 #[test]
1487 fn test_statsd_client_histogram_duration_with_tags() {
1488 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1489 let res = client
1490 .histogram_with_tags("key", Duration::from_nanos(4096))
1491 .with_tag("foo", "bar")
1492 .with_tag_value("beta")
1493 .try_send();
1494
1495 assert_eq!("prefix.key:4096|h|#foo:bar,beta", res.unwrap().as_metric_str());
1496 }
1497
1498 #[test]
1499 fn test_statsd_client_histogram_duration_with_default_tags() {
1500 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1501 .with_tag("foo", "bar")
1502 .build();
1503 let res = client.histogram_with_tags("key", Duration::from_nanos(4096)).try_send();
1504
1505 assert_eq!("prefix.key:4096|h|#foo:bar", res.unwrap().as_metric_str());
1506 }
1507
1508 #[test]
1509 fn test_statsd_client_histogram_duration_with_tags_with_overflow() {
1510 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1511 let res = client
1512 .histogram_with_tags("key", Duration::from_millis(u64::MAX))
1513 .with_tag("foo", "bar")
1514 .with_tag_value("beta")
1515 .try_send();
1516
1517 assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
1518 }
1519
1520 #[test]
1521 fn test_statsd_client_distribution_with_tags() {
1522 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1523 let res = client
1524 .distribution_with_tags("some.distr", 27)
1525 .with_tag("host", "www03.example.com")
1526 .with_tag_value("rc1")
1527 .try_send();
1528
1529 assert_eq!(
1530 "prefix.some.distr:27|d|#host:www03.example.com,rc1",
1531 res.unwrap().as_metric_str()
1532 );
1533 }
1534
1535 #[test]
1536 fn test_statsd_client_distribution_with_default_tags() {
1537 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1538 .with_tag("foo", "bar")
1539 .build();
1540 let res = client
1541 .distribution_with_tags("some.distr", 27)
1542 .with_tag("host", "www03.example.com")
1543 .with_tag_value("rc1")
1544 .try_send();
1545
1546 assert_eq!(
1547 "prefix.some.distr:27|d|#foo:bar,host:www03.example.com,rc1",
1548 res.unwrap().as_metric_str()
1549 );
1550 }
1551
1552 #[test]
1553 fn test_statsd_client_distribution_multiple_values_with_tags() {
1554 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1555 let res = client
1556 .distribution_with_tags("some.distr", vec![27, 28, 29])
1557 .with_tag("host", "www03.example.com")
1558 .with_tag_value("rc1")
1559 .try_send();
1560
1561 assert_eq!(
1562 "prefix.some.distr:27:28:29|d|#host:www03.example.com,rc1",
1563 res.unwrap().as_metric_str()
1564 );
1565 }
1566
1567 #[test]
1568 fn test_statsd_client_distribution_with_sampling_rate() {
1569 let client = StatsdClient::from_sink("prefix", NopMetricSink);
1570 let res = client
1571 .distribution_with_tags("some.distr", 4)
1572 .with_sampling_rate(0.5)
1573 .try_send();
1574
1575 assert_eq!("prefix.some.distr:4|d|@0.5", res.unwrap().as_metric_str());
1576 }
1577
1578 #[test]
1579 fn test_statsd_client_set_with_tags() {
1580 let client = StatsdClient::from_sink("myapp", NopMetricSink);
1581 let res = client.set_with_tags("some.set", 3).with_tag("foo", "bar").try_send();
1582
1583 assert_eq!("myapp.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
1584 }
1585
1586 #[test]
1587 fn test_statsd_client_set_with_default_tags() {
1588 let client = StatsdClientBuilder::new("prefix", NopMetricSink)
1589 .with_tag("foo", "bar")
1590 .build();
1591 let res = client.set_with_tags("some.set", 3).try_send();
1592
1593 assert_eq!("prefix.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
1594 }
1595
1596 #[test]
1597 fn test_statsd_client_with_tags_send_success() {
1598 let (rx, sink) = SpyMetricSink::new();
1599 let client = StatsdClient::from_sink("prefix", sink);
1600
1601 client.count_with_tags("some.key", 1).with_tag("test", "a").send();
1602 let sent = rx.recv().unwrap();
1603
1604 assert_eq!("prefix.some.key:1|c|#test:a", String::from_utf8(sent).unwrap());
1605 }
1606
1607 #[test]
1608 fn test_statsd_client_with_tags_send_error() {
1609 struct ErrorSink;
1610
1611 impl MetricSink for ErrorSink {
1612 fn emit(&self, _metric: &str) -> io::Result<usize> {
1613 Err(io::Error::from(io::ErrorKind::Other))
1614 }
1615 }
1616
1617 let count = Arc::new(AtomicUsize::new(0));
1618 let count_ref = count.clone();
1619
1620 let handler = move |_err: MetricError| {
1621 count_ref.fetch_add(1, Ordering::Release);
1622 };
1623
1624 let client = StatsdClient::builder("prefix", ErrorSink)
1625 .with_error_handler(handler)
1626 .build();
1627
1628 client.count_with_tags("some.key", 1).with_tag("tier", "web").send();
1629
1630 assert_eq!(1, count.load(Ordering::Acquire));
1631 }
1632
1633 #[test]
1638 fn test_statsd_client_as_counted_i64() {
1639 let client: Box<dyn Counted<i64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1640
1641 client.count("some.counter", 5).unwrap();
1642 }
1643
1644 #[test]
1645 fn test_statsd_client_as_counted_i32() {
1646 let client: Box<dyn Counted<i32>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1647
1648 client.count("some.counter", 10i32).unwrap();
1649 }
1650
1651 #[test]
1652 fn test_statsd_client_as_counted_u64() {
1653 let client: Box<dyn Counted<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1654
1655 client.count("some.counter", 20u64).unwrap();
1656 }
1657
1658 #[test]
1659 fn test_statsd_client_as_counted_u32() {
1660 let client: Box<dyn Counted<u32>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1661
1662 client.count("some.counter", 40u32).unwrap();
1663 }
1664
1665 #[test]
1666 fn test_statsd_client_as_countedext() {
1667 let client: Box<dyn CountedExt> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1668
1669 client.incr("some.counter").unwrap();
1670 }
1671
1672 #[test]
1673 fn test_statsd_client_as_timed_u64() {
1674 let client: Box<dyn Timed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1675
1676 client.time("some.timer", 20).unwrap();
1677 }
1678
1679 #[test]
1680 fn test_statsd_client_as_timed_duration() {
1681 let client: Box<dyn Timed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1682
1683 client.time("some.timer", Duration::from_millis(20)).unwrap();
1684 }
1685
1686 #[test]
1687 fn test_statsd_client_as_timed_packed_duration() {
1688 let client: Box<dyn Timed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1689 let durations = vec![Duration::from_millis(20), Duration::from_millis(21)];
1690
1691 client.time("some.timer", durations).unwrap();
1692 }
1693
1694 #[test]
1695 fn test_statsd_client_as_gauged_u64() {
1696 let client: Box<dyn Gauged<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1697
1698 client.gauge("some.gauge", 32).unwrap();
1699 }
1700
1701 #[test]
1702 fn test_statsd_client_as_gauged_f64() {
1703 let client: Box<dyn Gauged<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1704
1705 client.gauge("some.gauge", 3.2).unwrap();
1706 }
1707
1708 #[test]
1709 fn test_statsd_client_as_metered() {
1710 let client: Box<dyn Metered<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1711
1712 client.meter("some.meter", 9).unwrap();
1713 }
1714
1715 #[test]
1716 fn test_statsd_client_as_histogrammed_u64() {
1717 let client: Box<dyn Histogrammed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1718
1719 client.histogram("some.histogram", 4).unwrap();
1720 }
1721
1722 #[test]
1723 fn test_statsd_client_as_histogrammed_packed_u64() {
1724 let client: Box<dyn Histogrammed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1725
1726 client.histogram("some.histogram", vec![4, 5, 6]).unwrap();
1727 }
1728
1729 #[test]
1730 fn test_statsd_client_as_histogrammed_f64() {
1731 let client: Box<dyn Histogrammed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1732
1733 client.histogram("some.histogram", 4.0).unwrap();
1734 }
1735
1736 #[test]
1737 fn test_statsd_client_as_histogrammed_packed_f64() {
1738 let client: Box<dyn Histogrammed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1739
1740 client.histogram("some.histogram", vec![4.0, 5.0, 6.0]).unwrap();
1741 }
1742
1743 #[test]
1744 fn test_statsd_client_as_histogrammed_duration() {
1745 let client: Box<dyn Histogrammed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1746
1747 client.histogram("some.histogram", Duration::from_nanos(4)).unwrap();
1748 }
1749
1750 #[test]
1751 fn test_statsd_client_as_histogrammed_packed_duration() {
1752 let client: Box<dyn Histogrammed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1753 let durations = vec![Duration::from_nanos(4), Duration::from_nanos(5)];
1754
1755 client.histogram("some.histogram", durations).unwrap();
1756 }
1757
1758 #[test]
1759 fn test_statsd_client_as_distributed_u64() {
1760 let client: Box<dyn Distributed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1761
1762 client.distribution("some.distribution", 33).unwrap();
1763 }
1764
1765 #[test]
1766 fn test_statsd_client_as_distributed_packed_u64() {
1767 let client: Box<dyn Distributed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1768
1769 client.distribution("some.distribution", vec![33, 34]).unwrap();
1770 }
1771
1772 #[test]
1773 fn test_statsd_client_as_distributed_f64() {
1774 let client: Box<dyn Distributed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1775
1776 client.distribution("some.distribution", 33.0).unwrap();
1777 }
1778
1779 #[test]
1780 fn test_statsd_client_as_distributed_packed_f64() {
1781 let client: Box<dyn Distributed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
1782
1783 client.distribution("some.distribution", vec![33.0, 34.0]).unwrap();
1784 }
1785
1786 #[test]
1787 fn test_statsd_client_as_setted() {
1788 let client: Box<dyn Setted<i64>> = Box::new(StatsdClient::from_sink("myapp", NopMetricSink));
1789
1790 client.set("some.set", 5).unwrap();
1791 }
1792
1793 #[test]
1794 fn test_statsd_client_as_thread_and_panic_safe() {
1795 let client: Box<dyn MetricClient + Send + Sync + RefUnwindSafe> = Box::new(StatsdClient::from_sink(
1796 "prefix",
1797 QueuingMetricSink::from(NopMetricSink),
1798 ));
1799
1800 client.count("some.counter", 3).unwrap(); client.count("some.counter", 6i32).unwrap();
1802 client.count("some.counter", 12u64).unwrap();
1803 client.count("some.counter", 24u32).unwrap();
1804 client.time("some.timer", 198).unwrap();
1805 client.time("some.timer", Duration::from_millis(198)).unwrap();
1806 client.time("some.timer", vec![198]).unwrap();
1807 client.time("some.timer", vec![Duration::from_millis(198)]).unwrap();
1808 client.gauge("some.gauge", 4).unwrap();
1809 client.gauge("some.gauge", 4.0).unwrap();
1810 client.meter("some.meter", 29).unwrap();
1811 client.histogram("some.histogram", 32).unwrap();
1812 client.histogram("some.histogram", 32.0).unwrap();
1813 client.histogram("some.histogram", Duration::from_nanos(32)).unwrap();
1814 client.histogram("some.histogram", vec![32]).unwrap();
1815 client.histogram("some.histogram", vec![32.0]).unwrap();
1816 client
1817 .histogram("some.histogram", vec![Duration::from_nanos(32)])
1818 .unwrap();
1819 client.distribution("some.distribution", 248).unwrap();
1820 client.distribution("some.distribution", 248.0).unwrap();
1821 client.distribution("some.distribution", vec![248]).unwrap();
1822 client.distribution("some.distribution", vec![248.0]).unwrap();
1823 client.set("some.set", 5).unwrap();
1824 }
1825}