use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use std::hash::Hasher;
use fnv::FnvHasher;
use protobuf::RepeatedField;
use desc::Desc;
use metrics::{Collector, Metric};
use proto::{MetricFamily, MetricType};
use errors::{Result, Error};
pub trait MetricVecBuilder: Send + Sync + Clone {
type Output: Metric;
fn build(&self, &Desc, &[&str]) -> Result<Self::Output>;
}
struct MetricVecCore<T: MetricVecBuilder> {
pub children: RwLock<HashMap<u64, T::Output>>,
pub desc: Desc,
pub metric_type: MetricType,
pub new_metric: T,
}
impl<T: MetricVecBuilder> MetricVecCore<T> {
pub fn desc(&self) -> &Desc {
&self.desc
}
pub fn collect(&self) -> MetricFamily {
let mut m = MetricFamily::new();
m.set_name(self.desc.fq_name.clone());
m.set_help(self.desc.help.clone());
m.set_field_type(self.metric_type);
let children = self.children.read().unwrap();
let mut metrics = Vec::with_capacity(children.len());
for child in children.values() {
metrics.push(child.metric());
}
m.set_metric(RepeatedField::from_vec(metrics));
m
}
pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<T::Output> {
let h = try!(self.hash_label_values(&vals));
self.get_or_create_metric(h, vals)
}
pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::Output> {
let h = try!(self.hash_labels(labels));
if let Some(metric) = self.children.read().unwrap().get(&h).cloned() {
return Ok(metric);
}
let mut vals: Vec<&str> = labels.values().map(|v| v.as_ref()).collect();
vals.sort_by(|v1, v2| v1.cmp(v2));
self.get_or_create_metric(h, &vals)
}
pub fn delete_label_values(&self, vals: &[&str]) -> Result<()> {
let h = try!(self.hash_label_values(&vals));
let mut children = self.children.write().unwrap();
if children.remove(&h).is_none() {
return Err(Error::Msg(format!("missing label values {:?}", vals)));
}
Ok(())
}
pub fn delete(&self, labels: &HashMap<&str, &str>) -> Result<()> {
let h = try!(self.hash_labels(labels));
let mut children = self.children.write().unwrap();
if children.remove(&h).is_none() {
return Err(Error::Msg(format!("missing labels {:?}", labels)));
}
Ok(())
}
pub fn reset(&self) {
self.children.write().unwrap().clear();
}
fn hash_label_values(&self, vals: &[&str]) -> Result<u64> {
if vals.len() != self.desc.variable_labels.len() {
return Err(Error::InconsistentCardinality(self.desc.variable_labels.len(), vals.len()));
}
let mut h = FnvHasher::default();
for val in vals {
h.write(val.as_bytes());
}
Ok(h.finish())
}
fn hash_labels(&self, labels: &HashMap<&str, &str>) -> Result<u64> {
if labels.len() != self.desc.variable_labels.len() {
return Err(Error::InconsistentCardinality(self.desc.variable_labels.len(),
labels.len()));
}
let mut h = FnvHasher::default();
for label in &self.desc.variable_labels {
match labels.get(&label.as_ref()) {
Some(val) => h.write(val.as_bytes()),
None => {
return Err(Error::Msg(format!("label name {} missing in label map", label)))
}
}
}
Ok(h.finish())
}
fn get_or_create_metric(&self, hash: u64, label_values: &[&str]) -> Result<T::Output> {
let mut children = self.children.write().unwrap();
if let Some(metric) = children.get(&hash).cloned() {
return Ok(metric);
}
let metric = try!(self.new_metric.build(&self.desc, label_values));
children.insert(hash, metric.clone());
Ok(metric)
}
}
#[derive(Clone)]
pub struct MetricVec<T: MetricVecBuilder> {
v: Arc<MetricVecCore<T>>,
}
impl<T: MetricVecBuilder> MetricVec<T> {
pub fn create(desc: Desc, metric_type: MetricType, new_metric: T) -> MetricVec<T> {
let v = MetricVecCore {
children: RwLock::new(HashMap::new()),
desc: desc,
metric_type: metric_type,
new_metric: new_metric,
};
MetricVec { v: Arc::new(v) }
}
pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<T::Output> {
self.v.get_metric_with_label_values(vals)
}
pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::Output> {
self.v.get_metric_with(labels)
}
pub fn with_label_values(&self, vals: &[&str]) -> T::Output {
self.get_metric_with_label_values(vals).unwrap()
}
pub fn with(&self, labels: &HashMap<&str, &str>) -> T::Output {
self.get_metric_with(labels).unwrap()
}
pub fn remove_label_values(&self, vals: &[&str]) -> Result<()> {
self.v.delete_label_values(vals)
}
pub fn remove(&self, labels: &HashMap<&str, &str>) -> Result<()> {
self.v.delete(labels)
}
pub fn reset(&self) {
self.v.reset()
}
}
impl<T: MetricVecBuilder> Collector for MetricVec<T> {
fn desc(&self) -> &Desc {
&self.v.desc
}
fn collect(&self) -> MetricFamily {
self.v.collect()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use counter::CounterVec;
use gauge::GaugeVec;
use untyped::UntypedVec;
use metrics::Opts;
#[test]
fn test_counter_vec_with_labels() {
let vec = CounterVec::new(Opts::new("test_couter_vec", "test counter vec help"),
&["l1", "l2"])
.unwrap();
let mut labels = HashMap::new();
labels.insert("l1", "v1");
labels.insert("l2", "v2");
assert!(vec.remove(&labels).is_err());
vec.with(&labels).inc();
assert!(vec.remove(&labels).is_ok());
assert!(vec.remove(&labels).is_err());
let mut labels2 = HashMap::new();
labels2.insert("l1", "v2");
labels2.insert("l2", "v1");
vec.with(&labels).inc();
assert!(vec.remove(&labels2).is_err());
vec.with(&labels).inc();
let mut labels3 = HashMap::new();
labels3.insert("l1", "v1");
assert!(vec.remove(&labels3).is_err());
}
#[test]
fn test_counter_vec_with_label_values() {
let vec = CounterVec::new(Opts::new("test_vec", "test counter vec help"),
&["l1", "l2"])
.unwrap();
assert!(vec.remove_label_values(&["v1", "v2"]).is_err());
vec.with_label_values(&["v1", "v2"]).inc();
assert!(vec.remove_label_values(&["v1", "v2"]).is_ok());
vec.with_label_values(&["v1", "v2"]).inc();
assert!(vec.remove_label_values(&["v1"]).is_err());
assert!(vec.remove_label_values(&["v1", "v3"]).is_err());
}
#[test]
fn test_gauge_vec_with_labels() {
let vec = GaugeVec::new(Opts::new("test_gauge_vec", "test gauge vec help"),
&["l1", "l2"])
.unwrap();
let mut labels = HashMap::new();
labels.insert("l1", "v1");
labels.insert("l2", "v2");
assert!(vec.remove(&labels).is_err());
vec.with(&labels).inc();
vec.with(&labels).dec();
vec.with(&labels).add(42.0);
vec.with(&labels).sub(42.0);
vec.with(&labels).set(42.0);
assert!(vec.remove(&labels).is_ok());
assert!(vec.remove(&labels).is_err());
}
#[test]
fn test_gauge_vec_with_label_values() {
let vec = GaugeVec::new(Opts::new("test_gauge_vec", "test gauge vec help"),
&["l1", "l2"])
.unwrap();
assert!(vec.remove_label_values(&["v1", "v2"]).is_err());
vec.with_label_values(&["v1", "v2"]).inc();
assert!(vec.remove_label_values(&["v1", "v2"]).is_ok());
vec.with_label_values(&["v1", "v2"]).inc();
vec.with_label_values(&["v1", "v2"]).dec();
vec.with_label_values(&["v1", "v2"]).add(42.0);
vec.with_label_values(&["v1", "v2"]).sub(42.0);
vec.with_label_values(&["v1", "v2"]).set(42.0);
assert!(vec.remove_label_values(&["v1"]).is_err());
assert!(vec.remove_label_values(&["v1", "v3"]).is_err());
}
#[test]
fn test_untyped_vec_with_label_values() {
let vec = UntypedVec::new(Opts::new("test_untyped_vec", "test untyped vec help"),
&["l1", "l2"])
.unwrap();
assert!(vec.remove_label_values(&["v1", "v2"]).is_err());
vec.with_label_values(&["v1", "v2"]).inc();
assert!(vec.remove_label_values(&["v1", "v2"]).is_ok());
vec.with_label_values(&["v1", "v2"]).inc();
vec.with_label_values(&["v1", "v2"]).dec();
vec.with_label_values(&["v1", "v2"]).add(42.0);
vec.with_label_values(&["v1", "v2"]).sub(42.0);
vec.with_label_values(&["v1", "v2"]).set(42.0);
assert!(vec.remove_label_values(&["v1"]).is_err());
assert!(vec.remove_label_values(&["v1", "v3"]).is_err());
}
}