use std::fmt;
use std::iter;
use std::sync::{Arc, Weak};
use std::time::Instant;
use atomic::{AtomicF64, AtomicU64};
use default_registry;
use label::{Label, Labels, LabelsMut};
use metric::{Metric, MetricName, MetricValue};
use timestamp::{self, Timestamp, TimestampMut};
use {Collect, ErrorKind, Registry, Result};
#[derive(Debug, Clone)]
pub struct Counter(Arc<Inner>);
impl Counter {
pub fn new(name: &str) -> Result<Self> {
CounterBuilder::new(name).finish()
}
pub fn metric_name(&self) -> &MetricName {
&self.0.name
}
pub fn help(&self) -> Option<&str> {
self.0.help.as_ref().map(|h| h.as_ref())
}
pub fn labels(&self) -> &Labels {
&self.0.labels
}
pub fn labels_mut(&mut self) -> LabelsMut {
LabelsMut::new(&self.0.labels, None)
}
pub fn timestamp(&self) -> &Timestamp {
&self.0.timestamp
}
pub fn timestamp_mut(&self) -> TimestampMut {
TimestampMut::new(&self.0.timestamp)
}
#[inline]
pub fn value(&self) -> f64 {
self.0.value.get()
}
#[inline]
pub fn increment(&self) {
self.0.value.increment()
}
#[inline]
pub fn add(&self, count: f64) -> Result<()> {
track_assert!(count >= 0.0, ErrorKind::InvalidInput, "count={}", count);
self.0.value.add(count);
Ok(())
}
#[inline]
pub fn add_u64(&self, count: u64) {
self.0.value.add_u64(count);
}
#[inline]
pub fn time<F, T>(&self, f: F) -> T
where
F: FnOnce() -> T,
{
let start = Instant::now();
let result = f();
let elapsed = timestamp::duration_to_seconds(start.elapsed());
self.add(elapsed).expect("Never fails");
result
}
pub fn collector(&self) -> CounterCollector {
CounterCollector(Arc::downgrade(&self.0))
}
}
impl fmt::Display for Counter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.metric_name())?;
if !self.labels().is_empty() {
write!(f, "{}", self.labels())?;
}
write!(f, " {}", MetricValue(self.value()))?;
if let Some(timestamp) = self.timestamp().get() {
write!(f, " {}", timestamp)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct CounterBuilder {
namespace: Option<String>,
subsystem: Option<String>,
name: String,
help: Option<String>,
labels: Vec<(String, String)>,
registries: Vec<Registry>,
}
impl CounterBuilder {
pub fn new(name: &str) -> Self {
CounterBuilder {
namespace: None,
subsystem: None,
name: name.to_string(),
help: None,
labels: Vec::new(),
registries: Vec::new(),
}
}
pub fn namespace(&mut self, namespace: &str) -> &mut Self {
self.namespace = Some(namespace.to_string());
self
}
pub fn subsystem(&mut self, subsystem: &str) -> &mut Self {
self.subsystem = Some(subsystem.to_string());
self
}
pub fn help(&mut self, help: &str) -> &mut Self {
self.help = Some(help.to_string());
self
}
pub fn label(&mut self, name: &str, value: &str) -> &mut Self {
self.labels.retain(|l| l.0 != name);
self.labels.push((name.to_string(), value.to_string()));
self.labels.sort();
self
}
pub fn registry(&mut self, registry: Registry) -> &mut Self {
self.registries.push(registry);
self
}
pub fn default_registry(&mut self) -> &mut Self {
self.registry(default_registry())
}
pub fn finish(&self) -> Result<Counter> {
let name = track!(MetricName::new(
self.namespace.as_ref().map(AsRef::as_ref),
self.subsystem.as_ref().map(AsRef::as_ref),
&self.name,
))?;
let labels = track!(self
.labels
.iter()
.map(|&(ref name, ref value)| track!(Label::new(name, value)))
.collect::<Result<_>>())?;
let inner = Inner {
name,
labels: Labels::new(labels),
help: self.help.clone(),
timestamp: Timestamp::new(),
value: Value::new(),
};
let counter = Counter(Arc::new(inner));
for r in &self.registries {
r.register(counter.collector());
}
Ok(counter)
}
}
#[derive(Debug)]
pub struct CounterCollector(Weak<Inner>);
impl Collect for CounterCollector {
type Metrics = iter::Once<Metric>;
fn collect(&mut self) -> Option<Self::Metrics> {
self.0
.upgrade()
.map(|inner| iter::once(Metric::Counter(Counter(inner))))
}
}
#[derive(Debug)]
struct Inner {
name: MetricName,
labels: Labels,
help: Option<String>,
timestamp: Timestamp,
value: Value,
}
#[derive(Debug)]
struct Value {
f64: AtomicF64,
u64: AtomicU64,
}
impl Value {
fn new() -> Self {
Value {
f64: AtomicF64::new(0.0),
u64: AtomicU64::new(0),
}
}
#[inline]
fn get(&self) -> f64 {
self.f64.get() + self.u64.get() as f64
}
#[inline]
fn increment(&self) {
self.u64.inc();
}
#[inline]
fn add(&self, count: f64) {
let floor = count.floor() as u64;
let ceil = count.ceil() as u64;
if floor == ceil {
self.u64.add(floor);
} else {
self.f64.add(count);
}
}
#[inline]
fn add_u64(&self, count: u64) {
self.u64.add(count);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_works() {
let mut counter = track_try_unwrap!(CounterBuilder::new("foo_total")
.namespace("test")
.subsystem("counter")
.finish());
assert_eq!(counter.metric_name().to_string(), "test_counter_foo_total");
assert_eq!(counter.value(), 0.0);
counter.increment();
assert_eq!(counter.value(), 1.0);
counter.add(3.45).unwrap();
assert_eq!(counter.value(), 4.45);
counter.add(2.0).unwrap();
assert_eq!(counter.value(), 6.45);
counter.add_u64(2);
assert_eq!(counter.value(), 8.45);
assert_eq!(counter.to_string(), "test_counter_foo_total 8.45");
counter.labels_mut().insert("bar", "baz").unwrap();
assert_eq!(
counter.to_string(),
r#"test_counter_foo_total{bar="baz"} 8.45"#
);
}
}