#![cfg_attr(feature = "unstable", feature(test))]
#![deny(warnings, missing_debug_implementations, missing_copy_implementations, missing_docs)]
extern crate chrono;
use chrono::Utc;
use std::net::UdpSocket;
use std::borrow::Cow;
mod error;
pub use self::error::DogstatsdError;
mod metrics;
use self::metrics::*;
pub use self::metrics::{ServiceStatus, ServiceCheckOptions};
pub type DogstatsdResult = Result<(), DogstatsdError>;
#[derive(Debug, PartialEq)]
pub struct Options {
pub from_addr: String,
pub to_addr: String,
pub namespace: String,
}
impl Default for Options {
fn default() -> Self {
Options {
from_addr: "127.0.0.1:0".into(),
to_addr: "127.0.0.1:8125".into(),
namespace: String::new(),
}
}
}
impl Options {
pub fn new(from_addr: &str, to_addr: &str, namespace: &str) -> Self {
Options {
from_addr: from_addr.into(),
to_addr: to_addr.into(),
namespace: namespace.into(),
}
}
}
#[derive(Debug)]
pub struct Client {
socket: UdpSocket,
from_addr: String,
to_addr: String,
namespace: String,
}
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.from_addr == other.from_addr &&
self.to_addr == other.to_addr &&
self.namespace == other.namespace
}
}
impl Client {
pub fn new(options: Options) -> Result<Self, DogstatsdError> {
Ok(Client {
socket: UdpSocket::bind(&options.from_addr)?,
from_addr: options.from_addr,
to_addr: options.to_addr,
namespace: options.namespace,
})
}
pub fn incr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match stat.into() {
Cow::Borrowed(stat) => self.send(&CountMetric::Incr(stat), tags),
Cow::Owned(stat) => self.send(&CountMetric::Incr(&stat), tags)
}
}
pub fn decr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match stat.into() {
Cow::Borrowed(stat) => self.send(&CountMetric::Decr(stat), tags),
Cow::Owned(stat) => self.send(&CountMetric::Decr(&stat), tags)
}
}
pub fn count<'a, I, S, T>(&self, stat: S, count: i64, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match stat.into() {
Cow::Borrowed(stat) => self.send(&CountMetric::Arbitrary(stat, count), tags),
Cow::Owned(stat) => self.send(&CountMetric::Arbitrary(&stat, count), tags)
}
}
pub fn time<'a, F, I, S, T>(&self, stat: S, tags: I, block: F) -> DogstatsdResult
where F: FnOnce(),
I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
let start_time = Utc::now();
block();
let end_time = Utc::now();
match stat.into() {
Cow::Borrowed(stat) => self.send(&TimeMetric::new(stat, &start_time, &end_time), tags),
Cow::Owned(stat) => self.send(&TimeMetric::new(&stat, &start_time, &end_time), tags)
}
}
pub fn timing<'a, I, S, T>(&self, stat: S, ms: i64, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match stat.into() {
Cow::Borrowed(stat) => self.send(&TimingMetric::new(stat, ms), tags),
Cow::Owned(stat) => self.send(&TimingMetric::new(&stat, ms), tags)
}
}
pub fn gauge<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match (stat.into(), val.into()) {
(Cow::Borrowed(stat), Cow::Borrowed(val)) => self.send(&GaugeMetric::new(stat, val), tags),
(Cow::Owned(stat), Cow::Borrowed(val)) => self.send(&GaugeMetric::new(&stat, val), tags),
(Cow::Borrowed(stat), Cow::Owned(val)) => self.send(&GaugeMetric::new(stat, &val), tags),
(Cow::Owned(stat), Cow::Owned(val)) => self.send(&GaugeMetric::new(&stat, &val), tags)
}
}
pub fn histogram<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match (stat.into(), val.into()) {
(Cow::Borrowed(stat), Cow::Borrowed(val)) => self.send(&HistogramMetric::new(stat, val), tags),
(Cow::Owned(stat), Cow::Borrowed(val)) => self.send(&HistogramMetric::new(&stat, val), tags),
(Cow::Borrowed(stat), Cow::Owned(val)) => self.send(&HistogramMetric::new(stat, &val), tags),
(Cow::Owned(stat), Cow::Owned(val)) => self.send(&HistogramMetric::new(&stat, &val), tags)
}
}
pub fn distribution<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match (stat.into(), val.into()) {
(Cow::Borrowed(stat), Cow::Borrowed(val)) => self.send(&DistributionMetric::new(stat, val), tags),
(Cow::Owned(stat), Cow::Borrowed(val)) => self.send(&DistributionMetric::new(&stat, val), tags),
(Cow::Borrowed(stat), Cow::Owned(val)) => self.send(&DistributionMetric::new(stat, &val), tags),
(Cow::Owned(stat), Cow::Owned(val)) => self.send(&DistributionMetric::new(&stat, &val), tags)
}
}
pub fn set<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match (stat.into(), val.into()) {
(Cow::Borrowed(stat), Cow::Borrowed(val)) => self.send(&SetMetric::new(stat, val), tags),
(Cow::Owned(stat), Cow::Borrowed(val)) => self.send(&SetMetric::new(&stat, val), tags),
(Cow::Borrowed(stat), Cow::Owned(val)) => self.send(&SetMetric::new(stat, &val), tags),
(Cow::Owned(stat), Cow::Owned(val)) => self.send(&SetMetric::new(&stat, &val), tags)
}
}
pub fn service_check<'a, I, S, T>(&self, stat: S, val: ServiceStatus, tags: I, options: Option<ServiceCheckOptions>) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
T: AsRef<str>,
{
let unwrapped_options = options.unwrap_or(ServiceCheckOptions::default());
match stat.into() {
Cow::Borrowed(stat) => self.send(&ServiceCheck::new(stat, val, unwrapped_options), tags),
Cow::Owned(stat) => self.send(&ServiceCheck::new(&stat, val, unwrapped_options), tags),
}
}
pub fn event<'a, I, S, SS, T>(&self, title: S, text: SS, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=T>,
S: Into<Cow<'a, str>>,
SS: Into<Cow<'a, str>>,
T: AsRef<str>,
{
match (title.into(), text.into()) {
(Cow::Borrowed(title), Cow::Borrowed(text)) => self.send(&Event::new(title, text), tags),
(Cow::Owned(title), Cow::Borrowed(text)) => self.send(&Event::new(&title, text), tags),
(Cow::Borrowed(title), Cow::Owned(text)) => self.send(&Event::new(title, &text), tags),
(Cow::Owned(title), Cow::Owned(text)) => self.send(&Event::new(&title, &text), tags)
}
}
fn send<I, M, S>(&self, metric: &M, tags: I) -> DogstatsdResult
where I: IntoIterator<Item=S>,
M: Metric,
S: AsRef<str>,
{
let formatted_metric = format_for_send(metric, &self.namespace, tags);
self.socket.send_to(formatted_metric.as_slice(), &self.to_addr)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_options_default() {
let options = Options::default();
let expected_options = Options {
from_addr: "127.0.0.1:0".into(),
to_addr: "127.0.0.1:8125".into(),
namespace: String::new(),
};
assert_eq!(expected_options, options)
}
#[test]
fn test_new() {
let client = Client::new(Options::default()).unwrap();
let expected_client = Client {
socket: UdpSocket::bind("127.0.0.1:0").unwrap(),
from_addr: "127.0.0.1:0".into(),
to_addr: "127.0.0.1:8125".into(),
namespace: String::new(),
};
assert_eq!(expected_client, client)
}
use metrics::GaugeMetric;
#[test]
fn test_send() {
let options = Options::new("127.0.0.1:9001", "127.0.0.1:9002", "");
let client = Client::new(options).unwrap();
client.send(&GaugeMetric::new("gauge".into(), "1234".into()), &["tag1", "tag2"]).unwrap();
}
}
#[cfg(all(feature = "unstable", test))]
mod bench {
extern crate test;
use self::test::Bencher;
use super::*;
#[bench]
fn bench_incr(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = &["name1:value1"];
b.iter(|| {
client.incr("bench.incr", tags).unwrap();
})
}
#[bench]
fn bench_decr(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = &["name1:value1"];
b.iter(|| {
client.decr("bench.decr", tags).unwrap();
})
}
#[bench]
fn bench_count(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = &["name1:value1"];
let mut i = 0;
b.iter(|| {
client.count("bench.count", i, tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_timing(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = &["name1:value1"];
let mut i = 0;
b.iter(|| {
client.timing("bench.timing", i, tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_gauge(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
let mut i = 0;
b.iter(|| {
client.gauge("bench.guage", &i.to_string(), &tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_histogram(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
let mut i = 0;
b.iter(|| {
client.histogram("bench.histogram", &i.to_string(), &tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_distribution(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
let mut i = 0;
b.iter(|| {
client.distribution("bench.distribution", &i.to_string(), &tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_set(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
let mut i = 0;
b.iter(|| {
client.set("bench.set", &i.to_string(), &tags).unwrap();
i += 1;
})
}
#[bench]
fn bench_service_check(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
let all_options = ServiceCheckOptions {
hostname: Some("my-host.localhost"),
timestamp: Some(1510326433),
message: Some("Message about the check or service")
};
b.iter(|| {
client.service_check("bench.service_check", ServiceStatus::Critical, &tags, Some(all_options)).unwrap();
})
}
#[bench]
fn bench_event(b: &mut Bencher) {
let options = Options::default();
let client = Client::new(options).unwrap();
let tags = vec!["name1:value1"];
b.iter(|| {
client.event("Test Event Title", "Test Event Message", &tags).unwrap();
})
}
}