use std::net::{
ToSocketAddrs,
UdpSocket
};
use sinks::{
MetricSink,
UdpMetricSink
};
use types::{
MetricResult,
Counter,
Timer,
Gauge,
Meter,
ToMetricString
};
pub trait Counted {
fn incr(&self, key: &str) -> MetricResult<Counter>;
fn decr(&self, key: &str) -> MetricResult<Counter>;
fn count(&self, key: &str, count: i64) -> MetricResult<Counter>;
}
pub trait Timed {
fn time(&self, key: &str, time: u64) -> MetricResult<Timer>;
}
pub trait Gauged {
fn gauge(&self, key: &str, value: u64) -> MetricResult<Gauge>;
}
pub trait Metered {
fn mark(&self, key: &str) -> MetricResult<Meter>;
fn meter(&self, key: &str, value: u64) -> MetricResult<Meter>;
}
pub struct StatsdClient<T: MetricSink> {
prefix: String,
sink: T
}
impl<T: MetricSink> StatsdClient<T> {
pub fn from_sink(prefix: &str, sink: T) -> StatsdClient<T> {
StatsdClient{prefix: trim_key(prefix).to_string(), sink: sink}
}
pub fn from_udp_host<A>(prefix: &str, host: A) -> MetricResult<StatsdClient<UdpMetricSink>>
where A: ToSocketAddrs {
let socket = try!(UdpSocket::bind("0.0.0.0:0"));
let sink = try!(UdpMetricSink::new(host, socket));
Ok(StatsdClient::from_sink(prefix, sink))
}
fn send_metric<M: ToMetricString>(&self, metric: &M) -> MetricResult<()> {
let metric_string = metric.to_metric_string();
let written = try!(self.sink.emit(metric_string));
debug!("Wrote {} ({} bytes)", metric_string, written);
Ok(())
}
}
impl<T: MetricSink> Counted for StatsdClient<T> {
fn incr(&self, key: &str) -> MetricResult<Counter> {
self.count(key, 1)
}
fn decr(&self, key: &str) -> MetricResult<Counter> {
self.count(key, -1)
}
fn count(&self, key: &str, count: i64) -> MetricResult<Counter> {
let counter = Counter::new(&self.prefix, key, count);
try!(self.send_metric(&counter));
Ok(counter)
}
}
impl<T: MetricSink> Timed for StatsdClient<T> {
fn time(&self, key: &str, time: u64) -> MetricResult<Timer> {
let timer = Timer::new(&self.prefix, key, time);
try!(self.send_metric(&timer));
Ok(timer)
}
}
impl<T: MetricSink> Gauged for StatsdClient<T> {
fn gauge(&self, key: &str, value: u64) -> MetricResult<Gauge> {
let gauge = Gauge::new(&self.prefix, key, value);
try!(self.send_metric(&gauge));
Ok(gauge)
}
}
impl<T: MetricSink> Metered for StatsdClient<T> {
fn mark(&self, key: &str) -> MetricResult<Meter> {
self.meter(key, 1)
}
fn meter(&self, key: &str, value: u64) -> MetricResult<Meter> {
let meter = Meter::new(&self.prefix, key, value);
try!(self.send_metric(&meter));
Ok(meter)
}
}
fn trim_key(val: &str) -> &str {
if val.ends_with('.') {
val.trim_right_matches('.')
} else {
val
}
}
#[cfg(test)]
mod tests {
use super::trim_key;
#[test]
fn test_trim_key_with_trailing_dot() {
assert_eq!("some.prefix", trim_key("some.prefix."));
}
#[test]
fn test_trim_key_no_trailing_dot() {
assert_eq!("some.prefix", trim_key("some.prefix"));
}
}