use crate::builder::{MetricBuilder, MetricFormatter, MetricValue};
use crate::compat::Compat;
use crate::sealed::Sealed;
use crate::sinks::{MetricSink, UdpMetricSink};
use crate::types::{
Counter, Distribution, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer,
};
use std::fmt;
use std::net::{ToSocketAddrs, UdpSocket};
use std::panic::RefUnwindSafe;
use std::time::Duration;
use std::u64;
pub trait ToCounterValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToCounterValue for i64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Signed(self))
}
}
pub trait ToTimerValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToTimerValue for u64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Unsigned(self))
}
}
impl ToTimerValue for Vec<u64> {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::PackedUnsigned(self))
}
}
impl ToTimerValue for Duration {
fn try_to_value(self) -> MetricResult<MetricValue> {
let as_millis = self.as_millis();
if as_millis > u64::MAX as u128 {
Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
} else {
Ok(MetricValue::Unsigned(as_millis as u64))
}
}
}
impl ToTimerValue for Vec<Duration> {
fn try_to_value(self) -> MetricResult<MetricValue> {
if self.iter().any(|x| x.as_millis() > u64::MAX as u128) {
Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
} else {
Ok(MetricValue::PackedUnsigned(
self.iter().map(|x| x.as_millis() as u64).collect(),
))
}
}
}
pub trait ToGaugeValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToGaugeValue for u64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Unsigned(self))
}
}
impl ToGaugeValue for f64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Float(self))
}
}
pub trait ToMeterValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToMeterValue for u64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Unsigned(self))
}
}
pub trait ToHistogramValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToHistogramValue for u64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Unsigned(self))
}
}
impl ToHistogramValue for f64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Float(self))
}
}
impl ToHistogramValue for Duration {
fn try_to_value(self) -> MetricResult<MetricValue> {
let as_nanos = self.as_nanos();
if as_nanos > u64::MAX as u128 {
Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
} else {
Ok(MetricValue::Unsigned(as_nanos as u64))
}
}
}
impl ToHistogramValue for Vec<u64> {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::PackedUnsigned(self))
}
}
impl ToHistogramValue for Vec<f64> {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::PackedFloat(self))
}
}
impl ToHistogramValue for Vec<Duration> {
fn try_to_value(self) -> MetricResult<MetricValue> {
if self.iter().any(|x| x.as_nanos() > u64::MAX as u128) {
Err(MetricError::from((ErrorKind::InvalidInput, "u64 overflow")))
} else {
Ok(MetricValue::PackedUnsigned(
self.iter().map(|x| x.as_nanos() as u64).collect(),
))
}
}
}
pub trait ToDistributionValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToDistributionValue for u64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Unsigned(self))
}
}
impl ToDistributionValue for f64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Float(self))
}
}
impl ToDistributionValue for Vec<u64> {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::PackedUnsigned(self))
}
}
impl ToDistributionValue for Vec<f64> {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::PackedFloat(self))
}
}
pub trait ToSetValue {
fn try_to_value(self) -> MetricResult<MetricValue>;
}
impl ToSetValue for i64 {
fn try_to_value(self) -> MetricResult<MetricValue> {
Ok(MetricValue::Signed(self))
}
}
pub trait Counted<T>
where
T: ToCounterValue,
{
fn count(&self, key: &str, count: T) -> MetricResult<Counter> {
self.count_with_tags(key, count).try_send()
}
fn count_with_tags<'a>(&'a self, key: &'a str, count: T) -> MetricBuilder<'_, '_, Counter>;
}
pub trait CountedExt: Counted<i64> {
fn incr(&self, key: &str) -> MetricResult<Counter> {
self.incr_with_tags(key).try_send()
}
fn incr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'_, '_, Counter> {
self.count_with_tags(key, 1)
}
fn decr(&self, key: &str) -> MetricResult<Counter> {
self.decr_with_tags(key).try_send()
}
fn decr_with_tags<'a>(&'a self, key: &'a str) -> MetricBuilder<'_, '_, Counter> {
self.count_with_tags(key, -1)
}
}
pub trait Timed<T>
where
T: ToTimerValue,
{
fn time(&self, key: &str, time: T) -> MetricResult<Timer> {
self.time_with_tags(key, time).try_send()
}
fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'_, '_, Timer>;
}
pub trait Gauged<T>
where
T: ToGaugeValue,
{
fn gauge(&self, key: &str, value: T) -> MetricResult<Gauge> {
self.gauge_with_tags(key, value).try_send()
}
fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Gauge>;
}
pub trait Metered<T>
where
T: ToMeterValue,
{
fn meter(&self, key: &str, value: T) -> MetricResult<Meter> {
self.meter_with_tags(key, value).try_send()
}
fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Meter>;
}
pub trait Histogrammed<T>
where
T: ToHistogramValue,
{
fn histogram(&self, key: &str, value: T) -> MetricResult<Histogram> {
self.histogram_with_tags(key, value).try_send()
}
fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Histogram>;
}
pub trait Distributed<T>
where
T: ToDistributionValue,
{
fn distribution(&self, key: &str, value: T) -> MetricResult<Distribution> {
self.distribution_with_tags(key, value).try_send()
}
fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Distribution>;
}
pub trait Setted<T>
where
T: ToSetValue,
{
fn set(&self, key: &str, value: T) -> MetricResult<Set> {
self.set_with_tags(key, value).try_send()
}
fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Set>;
}
pub trait MetricClient:
Counted<i64>
+ CountedExt
+ Timed<u64>
+ Timed<Duration>
+ Timed<Vec<u64>>
+ Timed<Vec<Duration>>
+ Gauged<u64>
+ Gauged<f64>
+ Metered<u64>
+ Histogrammed<u64>
+ Histogrammed<f64>
+ Histogrammed<Duration>
+ Histogrammed<Vec<u64>>
+ Histogrammed<Vec<f64>>
+ Histogrammed<Vec<Duration>>
+ Distributed<u64>
+ Distributed<f64>
+ Distributed<Vec<u64>>
+ Distributed<Vec<f64>>
+ Setted<i64>
+ Compat
{
}
pub trait MetricBackend: Sealed {
fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
where
M: Metric;
fn consume_error(&self, err: MetricError);
}
pub struct StatsdClientBuilder {
prefix: String,
sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
tags: Vec<(Option<String>, String)>,
}
impl StatsdClientBuilder {
fn new<T>(prefix: &str, sink: T) -> Self
where
T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
{
StatsdClientBuilder {
prefix: Self::formatted_prefix(prefix),
sink: Box::new(sink),
errors: Box::new(nop_error_handler),
tags: Vec::new(),
}
}
pub fn with_error_handler<F>(mut self, errors: F) -> Self
where
F: Fn(MetricError) + Sync + Send + RefUnwindSafe + 'static,
{
self.errors = Box::new(errors);
self
}
pub fn with_tag<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
self.tags.push((Some(key.to_string()), value.to_string()));
self
}
pub fn with_tag_value<K>(mut self, value: K) -> Self
where
K: ToString,
{
self.tags.push((None, value.to_string()));
self
}
pub fn build(self) -> StatsdClient {
StatsdClient::from_builder(self)
}
fn formatted_prefix(prefix: &str) -> String {
if prefix.is_empty() {
String::new()
} else {
format!("{}.", prefix.trim_end_matches('.'))
}
}
}
pub struct StatsdClient {
prefix: String,
sink: Box<dyn MetricSink + Sync + Send + RefUnwindSafe>,
errors: Box<dyn Fn(MetricError) + Sync + Send + RefUnwindSafe>,
tags: Vec<(Option<String>, String)>,
}
impl StatsdClient {
pub fn from_sink<T>(prefix: &str, sink: T) -> Self
where
T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
{
Self::builder(prefix, sink).build()
}
#[deprecated(since = "0.19.0", note = "Superseded by ::from_sink() and ::builder()")]
pub fn from_udp_host<A>(prefix: &str, host: A) -> MetricResult<Self>
where
A: ToSocketAddrs,
{
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.set_nonblocking(true)?;
let sink = UdpMetricSink::from(host, socket)?;
Ok(StatsdClient::builder(prefix, sink).build())
}
pub fn builder<T>(prefix: &str, sink: T) -> StatsdClientBuilder
where
T: MetricSink + Sync + Send + RefUnwindSafe + 'static,
{
StatsdClientBuilder::new(prefix, sink)
}
fn from_builder(builder: StatsdClientBuilder) -> Self {
StatsdClient {
prefix: builder.prefix,
sink: builder.sink,
errors: builder.errors,
tags: builder.tags,
}
}
fn tags(&self) -> impl IntoIterator<Item = (Option<&str>, &str)> {
self.tags.iter().map(|(k, v)| (k.as_deref(), v.as_str()))
}
pub fn flush_sink(&self) {
if let Err(e) = self.sink.flush() {
(self.errors)(e.into());
}
}
}
impl Sealed for StatsdClient {}
impl MetricBackend for StatsdClient {
fn send_metric<M>(&self, metric: &M) -> MetricResult<()>
where
M: Metric,
{
let metric_string = metric.as_metric_str();
self.sink.emit(metric_string)?;
Ok(())
}
fn consume_error(&self, err: MetricError) {
(self.errors)(err);
}
}
impl fmt::Debug for StatsdClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"StatsdClient {{ prefix: {:?}, sink: ..., errors: ..., tags: {:?} }}",
self.prefix, self.tags,
)
}
}
impl<T> Counted<T> for StatsdClient
where
T: ToCounterValue,
{
fn count_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Counter> {
match value.try_to_value() {
Ok(v) => {
MetricBuilder::from_fmt(MetricFormatter::counter(&self.prefix, key, v), self).with_tags(self.tags())
}
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl CountedExt for StatsdClient {}
impl<T> Timed<T> for StatsdClient
where
T: ToTimerValue,
{
fn time_with_tags<'a>(&'a self, key: &'a str, time: T) -> MetricBuilder<'_, '_, Timer> {
match time.try_to_value() {
Ok(v) => MetricBuilder::from_fmt(MetricFormatter::timer(&self.prefix, key, v), self).with_tags(self.tags()),
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl<T> Gauged<T> for StatsdClient
where
T: ToGaugeValue,
{
fn gauge_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Gauge> {
match value.try_to_value() {
Ok(v) => MetricBuilder::from_fmt(MetricFormatter::gauge(&self.prefix, key, v), self).with_tags(self.tags()),
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl<T> Metered<T> for StatsdClient
where
T: ToMeterValue,
{
fn meter_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Meter> {
match value.try_to_value() {
Ok(v) => MetricBuilder::from_fmt(MetricFormatter::meter(&self.prefix, key, v), self).with_tags(self.tags()),
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl<T> Histogrammed<T> for StatsdClient
where
T: ToHistogramValue,
{
fn histogram_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Histogram> {
match value.try_to_value() {
Ok(v) => {
MetricBuilder::from_fmt(MetricFormatter::histogram(&self.prefix, key, v), self).with_tags(self.tags())
}
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl<T> Distributed<T> for StatsdClient
where
T: ToDistributionValue,
{
fn distribution_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Distribution> {
match value.try_to_value() {
Ok(v) => MetricBuilder::from_fmt(MetricFormatter::distribution(&self.prefix, key, v), self),
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl<T> Setted<T> for StatsdClient
where
T: ToSetValue,
{
fn set_with_tags<'a>(&'a self, key: &'a str, value: T) -> MetricBuilder<'_, '_, Set> {
match value.try_to_value() {
Ok(v) => MetricBuilder::from_fmt(MetricFormatter::set(&self.prefix, key, v), self).with_tags(self.tags()),
Err(e) => MetricBuilder::from_error(e, self),
}
}
}
impl MetricClient for StatsdClient {}
#[allow(clippy::needless_pass_by_value)]
fn nop_error_handler(_err: MetricError) {
}
#[cfg(test)]
mod tests {
use super::{
Counted, CountedExt, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient, Timed,
};
use crate::sinks::{MetricSink, NopMetricSink, QueuingMetricSink, SpyMetricSink};
use crate::types::{ErrorKind, Metric, MetricError};
use crate::StatsdClientBuilder;
use std::io;
use std::panic::RefUnwindSafe;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::u64;
#[test]
fn test_statsd_client_empty_prefix() {
let client = StatsdClient::from_sink("", NopMetricSink);
let res = client.count("some.method", 1);
assert_eq!("some.method:1|c", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_merging_default_tags_with_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("hello", "world")
.with_tag_value("production")
.build();
let res = client
.count_with_tags("some.counter", 3)
.with_tag("foo", "bar")
.with_tag_value("fizz")
.with_tag("bucket", "123")
.try_send();
assert_eq!(
"prefix.some.counter:3|c|#hello:world,production,foo:bar,fizz,bucket:123",
res.unwrap().as_metric_str()
);
}
#[test]
fn test_statsd_client_count_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.count_with_tags("some.counter", 3)
.with_tag("foo", "bar")
.try_send();
assert_eq!("prefix.some.counter:3|c|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_count_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("hello", "world")
.build();
let res = client.count_with_tags("some.counter", 3).try_send();
assert_eq!("prefix.some.counter:3|c|#hello:world", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_incr_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.incr_with_tags("some.counter").with_tag("foo", "bar").try_send();
assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_incr_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.incr_with_tags("some.counter").try_send();
assert_eq!("prefix.some.counter:1|c|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_decr_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.decr_with_tags("some.counter").with_tag("foo", "bar").try_send();
assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_decr_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.decr_with_tags("some.counter").try_send();
assert_eq!("prefix.some.counter:-1|c|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_gauge_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.gauge_with_tags("some.gauge", 4)
.with_tag("bucket", "A")
.with_tag_value("file-server")
.try_send();
assert_eq!(
"prefix.some.gauge:4|g|#bucket:A,file-server",
res.unwrap().as_metric_str()
);
}
#[test]
fn test_statsd_client_gauge_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.gauge_with_tags("some.gauge", 4).try_send();
assert_eq!("prefix.some.gauge:4|g|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_duration() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.time("key", Duration::from_millis(157));
assert_eq!("prefix.key:157|ms", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_multiple_durations() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_millis(157),
Duration::from_millis(158),
Duration::from_millis(159),
];
let res = client.time("key", durations);
assert_eq!("prefix.key:157:158:159|ms", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_duration_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.time("key", Duration::from_secs(u64::MAX));
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
}
#[test]
fn test_statsd_client_time_multiple_durations_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_millis(157),
Duration::from_secs(u64::MAX),
Duration::from_millis(159),
];
let res = client.time("key", durations);
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind())
}
#[test]
fn test_statsd_client_time_duration_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.time_with_tags("key", Duration::from_millis(157))
.with_tag("foo", "bar")
.with_tag_value("quux")
.try_send();
assert_eq!("prefix.key:157|ms|#foo:bar,quux", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_duration_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.time("key", Duration::from_millis(157));
assert_eq!("prefix.key:157|ms|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_multiple_durations_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_millis(157),
Duration::from_millis(158),
Duration::from_millis(159),
];
let res = client
.time_with_tags("key", durations)
.with_tag("foo", "bar")
.with_tag_value("quux")
.try_send();
assert_eq!("prefix.key:157:158:159|ms|#foo:bar,quux", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_time_duration_with_tags_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.time_with_tags("key", Duration::from_secs(u64::MAX))
.with_tag("foo", "bar")
.with_tag_value("quux")
.try_send();
assert!(res.is_err());
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}
#[test]
fn test_statsd_client_time_multiple_durations_with_tags_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_millis(157),
Duration::from_secs(u64::MAX),
Duration::from_millis(159),
];
let res = client
.time_with_tags("key", durations)
.with_tag("foo", "bar")
.with_tag_value("quux")
.try_send();
assert!(res.is_err());
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}
#[test]
fn test_statsd_client_meter_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.meter_with_tags("some.meter", 64)
.with_tag("segment", "142")
.with_tag_value("beta")
.try_send();
assert_eq!("prefix.some.meter:64|m|#segment:142,beta", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_meter_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.meter_with_tags("some.meter", 64).try_send();
assert_eq!("prefix.some.meter:64|m|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.histogram_with_tags("some.histo", 27)
.with_tag("host", "www03.example.com")
.with_tag_value("rc1")
.try_send();
assert_eq!(
"prefix.some.histo:27|h|#host:www03.example.com,rc1",
res.unwrap().as_metric_str()
);
}
#[test]
fn test_statsd_client_histogram_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.histogram_with_tags("some.histo", 27).try_send();
assert_eq!("prefix.some.histo:27|h|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_with_multiple_values() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.histogram_with_tags("some.histo", vec![27, 28, 29]).try_send();
assert_eq!("prefix.some.histo:27:28:29|h", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_duration() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.histogram("key", Duration::from_nanos(210));
assert_eq!("prefix.key:210|h", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_multiple_durations() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_nanos(210),
Duration::from_nanos(211),
Duration::from_nanos(212),
];
let res = client.histogram("key", durations);
assert_eq!("prefix.key:210:211:212|h", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_duration_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client.histogram("key", Duration::from_secs(u64::MAX));
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}
#[test]
fn test_statsd_client_histogram_multiple_durations_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let durations = vec![
Duration::from_nanos(210),
Duration::from_secs(u64::MAX),
Duration::from_nanos(212),
];
let res = client.histogram("key", durations);
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}
#[test]
fn test_statsd_client_histogram_duration_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.histogram_with_tags("key", Duration::from_nanos(4096))
.with_tag("foo", "bar")
.with_tag_value("beta")
.try_send();
assert_eq!("prefix.key:4096|h|#foo:bar,beta", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_duration_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.histogram_with_tags("key", Duration::from_nanos(4096)).try_send();
assert_eq!("prefix.key:4096|h|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_histogram_duration_with_tags_with_overflow() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.histogram_with_tags("key", Duration::from_millis(u64::MAX))
.with_tag("foo", "bar")
.with_tag_value("beta")
.try_send();
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}
#[test]
fn test_statsd_client_distribution_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.distribution_with_tags("some.distr", 27)
.with_tag("host", "www03.example.com")
.with_tag_value("rc1")
.try_send();
assert_eq!(
"prefix.some.distr:27|d|#host:www03.example.com,rc1",
res.unwrap().as_metric_str()
);
}
#[test]
fn test_statsd_client_distribution_multiple_values_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.distribution_with_tags("some.distr", vec![27, 28, 29])
.with_tag("host", "www03.example.com")
.with_tag_value("rc1")
.try_send();
assert_eq!(
"prefix.some.distr:27:28:29|d|#host:www03.example.com,rc1",
res.unwrap().as_metric_str()
);
}
#[test]
fn test_statsd_client_set_with_tags() {
let client = StatsdClient::from_sink("myapp", NopMetricSink);
let res = client.set_with_tags("some.set", 3).with_tag("foo", "bar").try_send();
assert_eq!("myapp.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_set_with_default_tags() {
let client = StatsdClientBuilder::new("prefix", NopMetricSink)
.with_tag("foo", "bar")
.build();
let res = client.set_with_tags("some.set", 3).try_send();
assert_eq!("prefix.some.set:3|s|#foo:bar", res.unwrap().as_metric_str());
}
#[test]
fn test_statsd_client_with_tags_send_success() {
let (rx, sink) = SpyMetricSink::new();
let client = StatsdClient::from_sink("prefix", sink);
client.count_with_tags("some.key", 1).with_tag("test", "a").send();
let sent = rx.recv().unwrap();
assert_eq!("prefix.some.key:1|c|#test:a", String::from_utf8(sent).unwrap());
}
#[test]
fn test_statsd_client_with_tags_send_error() {
struct ErrorSink;
impl MetricSink for ErrorSink {
fn emit(&self, _metric: &str) -> io::Result<usize> {
Err(io::Error::from(io::ErrorKind::Other))
}
}
let count = Arc::new(AtomicUsize::new(0));
let count_ref = count.clone();
let handler = move |_err: MetricError| {
count_ref.fetch_add(1, Ordering::Release);
};
let client = StatsdClient::builder("prefix", ErrorSink)
.with_error_handler(handler)
.build();
client.count_with_tags("some.key", 1).with_tag("tier", "web").send();
assert_eq!(1, count.load(Ordering::Acquire));
}
#[test]
fn test_statsd_client_as_counted() {
let client: Box<dyn Counted<i64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.count("some.counter", 5).unwrap();
}
#[test]
fn test_statsd_client_as_countedext() {
let client: Box<dyn CountedExt> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.incr("some.counter").unwrap();
}
#[test]
fn test_statsd_client_as_timed_u64() {
let client: Box<dyn Timed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.time("some.timer", 20).unwrap();
}
#[test]
fn test_statsd_client_as_timed_duration() {
let client: Box<dyn Timed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.time("some.timer", Duration::from_millis(20)).unwrap();
}
#[test]
fn test_statsd_client_as_timed_packed_duration() {
let client: Box<dyn Timed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
let durations = vec![Duration::from_millis(20), Duration::from_millis(21)];
client.time("some.timer", durations).unwrap();
}
#[test]
fn test_statsd_client_as_gauged_u64() {
let client: Box<dyn Gauged<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.gauge("some.gauge", 32).unwrap();
}
#[test]
fn test_statsd_client_as_gauged_f64() {
let client: Box<dyn Gauged<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.gauge("some.gauge", 3.2).unwrap();
}
#[test]
fn test_statsd_client_as_metered() {
let client: Box<dyn Metered<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.meter("some.meter", 9).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_u64() {
let client: Box<dyn Histogrammed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.histogram("some.histogram", 4).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_packed_u64() {
let client: Box<dyn Histogrammed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.histogram("some.histogram", vec![4, 5, 6]).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_f64() {
let client: Box<dyn Histogrammed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.histogram("some.histogram", 4.0).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_packed_f64() {
let client: Box<dyn Histogrammed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.histogram("some.histogram", vec![4.0, 5.0, 6.0]).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_duration() {
let client: Box<dyn Histogrammed<Duration>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.histogram("some.histogram", Duration::from_nanos(4)).unwrap();
}
#[test]
fn test_statsd_client_as_histogrammed_packed_duration() {
let client: Box<dyn Histogrammed<Vec<Duration>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
let durations = vec![Duration::from_nanos(4), Duration::from_nanos(5)];
client.histogram("some.histogram", durations).unwrap();
}
#[test]
fn test_statsd_client_as_distributed_u64() {
let client: Box<dyn Distributed<u64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.distribution("some.distribution", 33).unwrap();
}
#[test]
fn test_statsd_client_as_distributed_packed_u64() {
let client: Box<dyn Distributed<Vec<u64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.distribution("some.distribution", vec![33, 34]).unwrap();
}
#[test]
fn test_statsd_client_as_distributed_f64() {
let client: Box<dyn Distributed<f64>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.distribution("some.distribution", 33.0).unwrap();
}
#[test]
fn test_statsd_client_as_distributed_packed_f64() {
let client: Box<dyn Distributed<Vec<f64>>> = Box::new(StatsdClient::from_sink("prefix", NopMetricSink));
client.distribution("some.distribution", vec![33.0, 34.0]).unwrap();
}
#[test]
fn test_statsd_client_as_setted() {
let client: Box<dyn Setted<i64>> = Box::new(StatsdClient::from_sink("myapp", NopMetricSink));
client.set("some.set", 5).unwrap();
}
#[test]
fn test_statsd_client_as_thread_and_panic_safe() {
let client: Box<dyn MetricClient + Send + Sync + RefUnwindSafe> = Box::new(StatsdClient::from_sink(
"prefix",
QueuingMetricSink::from(NopMetricSink),
));
client.count("some.counter", 3).unwrap();
client.time("some.timer", 198).unwrap();
client.time("some.timer", Duration::from_millis(198)).unwrap();
client.time("some.timer", vec![198]).unwrap();
client.time("some.timer", vec![Duration::from_millis(198)]).unwrap();
client.gauge("some.gauge", 4).unwrap();
client.gauge("some.gauge", 4.0).unwrap();
client.meter("some.meter", 29).unwrap();
client.histogram("some.histogram", 32).unwrap();
client.histogram("some.histogram", 32.0).unwrap();
client.histogram("some.histogram", Duration::from_nanos(32)).unwrap();
client.histogram("some.histogram", vec![32]).unwrap();
client.histogram("some.histogram", vec![32.0]).unwrap();
client
.histogram("some.histogram", vec![Duration::from_nanos(32)])
.unwrap();
client.distribution("some.distribution", 248).unwrap();
client.distribution("some.distribution", 248.0).unwrap();
client.distribution("some.distribution", vec![248]).unwrap();
client.distribution("some.distribution", vec![248.0]).unwrap();
client.set("some.set", 5).unwrap();
}
}