use std::str::{self, FromStr};
use std::time::{SystemTime};
use nom::{digit, is_alphanumeric, IResult};
use string_cache::DefaultAtom as Atom;
use super::super::super::super::metric::CollectedMetric;
#[derive(Debug, PartialEq)]
pub struct ParseError {
description: String,
}
#[derive(Debug, PartialEq)]
pub enum StatsdMetric {
Counter(Atom, f64, Option<f64>),
Gauge(Atom, f64),
Timer(Atom, f64, Option<f64>),
}
impl Into<CollectedMetric> for StatsdMetric {
fn into(self) -> CollectedMetric {
use self::StatsdMetric::*;
let now = SystemTime::now();
match self {
Counter(name, value, _) => CollectedMetric::Count(now, (name, vec![]), value as i32),
Gauge(name, value) => CollectedMetric::Gauge(now, (name, vec![]), value as i32),
Timer(name, value, _) => CollectedMetric::Histogram(now, (name, vec![]), value as i32),
}
}
}
pub fn parse_metrics<'a>(i: &'a [u8]) -> Result<Vec<StatsdMetric>, ParseError> {
let result = complete!(i, call!(metrics));
match result {
IResult::Done(_, metrics) => Ok(metrics),
IResult::Error(err) => Err(ParseError{ description: format!("{:?}", err) }),
IResult::Incomplete(_) => unreachable!(),
}
}
named!(metrics<Vec<StatsdMetric>>,
separated_nonempty_list_complete!(
tag!("\n"),
alt_complete!(
counter |
gauge |
timer
)
)
);
named!(counter<StatsdMetric>,
do_parse!(
name: metric_name >>
tag!(":") >>
value: double >>
tag!("|c") >>
_sample_rate: opt!(complete!(sample_rate)) >>
(StatsdMetric::Counter(Atom::from(name), value, None))
)
);
named!(gauge<StatsdMetric>,
do_parse!(
name: metric_name >>
tag!(":") >>
value: double >>
tag!("|g") >>
(StatsdMetric::Gauge(Atom::from(name), value))
)
);
named!(timer<StatsdMetric>,
do_parse!(
name: metric_name >>
tag!(":") >>
value: double >>
tag!("|ms") >>
(StatsdMetric::Timer(Atom::from(name), value, None))
)
);
named!(metric_name<&str>,
map_res!(
take_while!(call!(|c| {
is_alphanumeric(c) || c == b'.' || c == b'_'
})),
str::from_utf8
)
);
named!(sample_rate<&[u8], f64>,
preceded!(
tag!("|@"),
double
)
);
named!(double<&[u8], f64>,
map_res!(
map_res!(
recognize!(
tuple!(
opt!(tag!("-")),
alt_complete!(
delimited!(digit, tag!("."), opt!(complete!(digit))) |
delimited!(opt!(digit), tag!("."), digit) |
digit
)
)
),
str::from_utf8
),
f64::from_str
)
);
#[cfg(test)]
mod tests {
use super::*;
use std::any::Any;
use nom::IResult;
fn complete<'a, T>(value: T) -> IResult<&'a [u8], T>
where T: Any {
IResult::Done(&b""[..], value)
}
#[test]
fn it_parses_metric_names() {
assert_eq!(
metric_name(&b"foo"[..]),
complete("foo")
);
assert_eq!(
metric_name(&b"foo.bar"[..]),
complete("foo.bar")
);
assert_eq!(
metric_name(&b"foo_bar"[..]),
complete("foo_bar")
);
}
#[test]
fn it_parses_doubles() {
assert_eq!(
double(&b"23"[..]),
complete(23.0)
);
assert_eq!(
double(&b".24"[..]),
complete(0.24)
);
assert_eq!(
double(&b"2.5"[..]),
complete(2.5)
);
assert_eq!(
double(&b"-2"[..]),
complete(-2.0)
);
}
#[test]
fn it_parses_counter() {
assert_eq!(
counter(&b"foo.bar_baz:23|c"[..]),
complete(StatsdMetric::Counter(Atom::from("foo.bar_baz"), 23.0, None))
);
}
#[test]
fn it_parses_gauge() {
assert_eq!(
gauge(&b"foo.bar_baz:12|g"[..]),
complete(StatsdMetric::Gauge(Atom::from("foo.bar_baz"), 12.0))
);
}
#[test]
fn it_parses_timer() {
assert_eq!(
timer(&b"foo.bar_baz:12|ms"[..]),
complete(StatsdMetric::Timer(Atom::from("foo.bar_baz"), 12.0, None))
);
}
#[test]
fn it_parses_metrics() {
assert_eq!(
parse_metrics(&b"foo:1|g\nbar:2|c|@3\nbaz:4|ms"[..]),
Ok(vec![
StatsdMetric::Gauge(Atom::from("foo"), 1.0),
StatsdMetric::Counter(Atom::from("bar"), 2.0, None),
StatsdMetric::Timer(Atom::from("baz"), 4.0, None),
])
);
}
}