| // Copyright 2014 The Prometheus Authors |
| // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. |
| |
| use std::collections::HashMap; |
| use std::hash::Hasher; |
| use std::sync::Arc; |
| |
| use fnv::FnvHasher; |
| use parking_lot::RwLock; |
| |
| use crate::desc::{Desc, Describer}; |
| use crate::errors::{Error, Result}; |
| use crate::metrics::{Collector, Metric}; |
| use crate::proto::{MetricFamily, MetricType}; |
| |
| /// An interface for building a metric vector. |
| pub trait MetricVecBuilder: Send + Sync + Clone { |
| /// The associated Metric collected. |
| type M: Metric; |
| /// The associated describer. |
| type P: Describer + Sync + Send + Clone; |
| |
| /// `build` builds a [`Metric`] with option and corresponding label names. |
| fn build(&self, _: &Self::P, _: &[&str]) -> Result<Self::M>; |
| } |
| |
| #[derive(Debug)] |
| pub(crate) struct MetricVecCore<T: MetricVecBuilder> { |
| pub children: RwLock<HashMap<u64, T::M>>, |
| pub desc: Desc, |
| pub metric_type: MetricType, |
| pub new_metric: T, |
| pub opts: T::P, |
| } |
| |
| impl<T: MetricVecBuilder> MetricVecCore<T> { |
| pub fn collect(&self) -> MetricFamily { |
| let mut m = MetricFamily::default(); |
| 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(); |
| let mut metrics = Vec::with_capacity(children.len()); |
| for child in children.values() { |
| metrics.push(child.metric()); |
| } |
| m.set_metric(from_vec!(metrics)); |
| m |
| } |
| |
| pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<T::M> { |
| let h = self.hash_label_values(vals)?; |
| |
| if let Some(metric) = self.children.read().get(&h).cloned() { |
| return Ok(metric); |
| } |
| |
| self.get_or_create_metric(h, vals) |
| } |
| |
| pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::M> { |
| let h = self.hash_labels(labels)?; |
| |
| if let Some(metric) = self.children.read().get(&h).cloned() { |
| return Ok(metric); |
| } |
| |
| let vals = self.get_label_values(labels)?; |
| self.get_or_create_metric(h, &vals) |
| } |
| |
| pub fn delete_label_values(&self, vals: &[&str]) -> Result<()> { |
| let h = self.hash_label_values(vals)?; |
| |
| let mut children = self.children.write(); |
| 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 = self.hash_labels(labels)?; |
| |
| let mut children = self.children.write(); |
| if children.remove(&h).is_none() { |
| return Err(Error::Msg(format!("missing labels {:?}", labels))); |
| } |
| |
| Ok(()) |
| } |
| |
| /// `reset` deletes all metrics in this vector. |
| pub fn reset(&self) { |
| self.children.write().clear(); |
| } |
| |
| pub(crate) fn hash_label_values(&self, vals: &[&str]) -> Result<u64> { |
| if vals.len() != self.desc.variable_labels.len() { |
| return Err(Error::InconsistentCardinality { |
| expect: self.desc.variable_labels.len(), |
| got: 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 { |
| expect: self.desc.variable_labels.len(), |
| got: labels.len(), |
| }); |
| } |
| |
| let mut h = FnvHasher::default(); |
| for name in &self.desc.variable_labels { |
| match labels.get(&name.as_ref()) { |
| Some(val) => h.write(val.as_bytes()), |
| None => { |
| return Err(Error::Msg(format!( |
| "label name {} missing in label map", |
| name |
| ))); |
| } |
| } |
| } |
| |
| Ok(h.finish()) |
| } |
| |
| fn get_label_values<'a>(&self, labels: &'a HashMap<&str, &str>) -> Result<Vec<&'a str>> { |
| let mut values = Vec::new(); |
| for name in &self.desc.variable_labels { |
| match labels.get(&name.as_ref()) { |
| Some(val) => values.push(*val), |
| None => { |
| return Err(Error::Msg(format!( |
| "label name {} missing in label map", |
| name |
| ))); |
| } |
| } |
| } |
| Ok(values) |
| } |
| |
| fn get_or_create_metric(&self, hash: u64, label_values: &[&str]) -> Result<T::M> { |
| let mut children = self.children.write(); |
| // Check exist first. |
| if let Some(metric) = children.get(&hash).cloned() { |
| return Ok(metric); |
| } |
| |
| let metric = self.new_metric.build(&self.opts, label_values)?; |
| children.insert(hash, metric.clone()); |
| Ok(metric) |
| } |
| } |
| |
| /// A [`Collector`] to bundle metrics of the same name that |
| /// differ in their label values. It is usually not used directly but as a |
| /// building block for implementations of vectors of a given metric |
| /// type. [`GaugeVec`](crate::GaugeVec) and [`CounterVec`](crate::CounterVec) |
| /// are examples already provided in this package. |
| #[derive(Clone)] |
| pub struct MetricVec<T: MetricVecBuilder> { |
| pub(crate) v: Arc<MetricVecCore<T>>, |
| } |
| |
| impl<T: MetricVecBuilder> std::fmt::Debug for MetricVec<T> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "MetricVec") |
| } |
| } |
| |
| impl<T: MetricVecBuilder> MetricVec<T> { |
| /// `create` creates a MetricVec with description `desc`, a metric type `metric_type` and |
| /// a MetricVecBuilder `new_metric`. |
| pub fn create(metric_type: MetricType, new_metric: T, opts: T::P) -> Result<MetricVec<T>> { |
| let desc = opts.describe()?; |
| let v = MetricVecCore { |
| children: RwLock::new(HashMap::new()), |
| desc, |
| metric_type, |
| new_metric, |
| opts, |
| }; |
| |
| Ok(MetricVec { v: Arc::new(v) }) |
| } |
| |
| /// `get_metric_with_label_values` returns the [`Metric`] for the given slice |
| /// of label values (same order as the VariableLabels in Desc). If that combination of |
| /// label values is accessed for the first time, a new [`Metric`] is created. |
| /// |
| /// It is possible to call this method without using the returned [`Metric`] |
| /// to only create the new [`Metric`] but leave it at its start value (e.g. a |
| /// [`Histogram`](crate::Histogram) without any observations). |
| /// |
| /// Keeping the [`Metric`] for later use is possible (and should be considered |
| /// if performance is critical), but keep in mind that Reset, DeleteLabelValues and Delete can |
| /// be used to delete the [`Metric`] from the MetricVec. In that case, the |
| /// [`Metric`] will still exist, but it will not be exported anymore, even if a |
| /// [`Metric`] with the same label values is created later. See also the |
| /// CounterVec example. |
| /// |
| /// An error is returned if the number of label values is not the same as the |
| /// number of VariableLabels in Desc. |
| /// |
| /// Note that for more than one label value, this method is prone to mistakes |
| /// caused by an incorrect order of arguments. Consider get_metric_with(labels) as |
| /// an alternative to avoid that type of mistake. For higher label numbers, the |
| /// latter has a much more readable (albeit more verbose) syntax, but it comes |
| /// with a performance overhead (for creating and processing the Labels map). |
| pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<T::M> { |
| self.v.get_metric_with_label_values(vals) |
| } |
| |
| /// `get_metric_with` returns the [`Metric`] for the given Labels map (the |
| /// label names must match those of the VariableLabels in Desc). If that label map is |
| /// accessed for the first time, a new [`Metric`] is created. Implications of |
| /// creating a [`Metric`] without using it and keeping the |
| /// [`Metric`] for later use are the same as for GetMetricWithLabelValues. |
| /// |
| /// An error is returned if the number and names of the Labels are inconsistent |
| /// with those of the VariableLabels in Desc. |
| /// |
| /// This method is used for the same purpose as |
| /// `get_metric_with_label_values`. See there for pros and cons of the two |
| /// methods. |
| pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::M> { |
| self.v.get_metric_with(labels) |
| } |
| |
| /// `with_label_values` works as `get_metric_with_label_values`, but panics if an error |
| /// occurs. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use prometheus::{CounterVec, Opts}; |
| /// let vec = CounterVec::new( |
| /// Opts::new("requests_total", "Number of requests."), |
| /// &["code", "http_method"] |
| /// ).unwrap(); |
| /// vec.with_label_values(&["404", "POST"]).inc() |
| /// ``` |
| pub fn with_label_values(&self, vals: &[&str]) -> T::M { |
| self.get_metric_with_label_values(vals).unwrap() |
| } |
| |
| /// `with` works as `get_metric_with`, but panics if an error occurs. The method allows |
| /// neat syntax like: |
| /// httpReqs.with(Labels{"status":"404", "method":"POST"}).inc() |
| pub fn with(&self, labels: &HashMap<&str, &str>) -> T::M { |
| self.get_metric_with(labels).unwrap() |
| } |
| |
| /// `remove_label_values` removes the metric where the variable labels are the same |
| /// as those passed in as labels (same order as the VariableLabels in Desc). It |
| /// returns true if a metric was deleted. |
| /// |
| /// It returns an error if the number of label values is not the same as the |
| /// number of VariableLabels in Desc. |
| /// |
| /// Note that for more than one label value, this method is prone to mistakes |
| /// caused by an incorrect order of arguments. Consider delete(labels) as an |
| /// alternative to avoid that type of mistake. For higher label numbers, the |
| /// latter has a much more readable (albeit more verbose) syntax, but it comes |
| /// with a performance overhead (for creating and processing the Labels map). |
| pub fn remove_label_values(&self, vals: &[&str]) -> Result<()> { |
| self.v.delete_label_values(vals) |
| } |
| |
| /// `remove` removes the metric where the variable labels are the same as those |
| /// passed in as labels. It returns true if a metric was deleted. |
| /// |
| /// It returns an error if the number and names of the Labels are inconsistent |
| /// with those of the VariableLabels in the Desc of the MetricVec. |
| /// |
| /// This method is used for the same purpose as `delete_label_values`. See |
| /// there for pros and cons of the two methods. |
| pub fn remove(&self, labels: &HashMap<&str, &str>) -> Result<()> { |
| self.v.delete(labels) |
| } |
| |
| /// `reset` deletes all metrics in this vector. |
| pub fn reset(&self) { |
| self.v.reset() |
| } |
| } |
| |
| impl<T: MetricVecBuilder> Collector for MetricVec<T> { |
| fn desc(&self) -> Vec<&Desc> { |
| vec![&self.v.desc] |
| } |
| |
| fn collect(&self) -> Vec<MetricFamily> { |
| vec![self.v.collect()] |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::collections::HashMap; |
| |
| use crate::counter::CounterVec; |
| use crate::gauge::GaugeVec; |
| use crate::metrics::{Metric, 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_vec_get_metric_with() { |
| let vec = CounterVec::new( |
| Opts::new("test_vec", "test counter vec help"), |
| &["b", "c", "a"], |
| ) |
| .unwrap(); |
| |
| // create a new metric that labels are {b" => "c", "c" => "a" "a" => "b"}. |
| let mut labels = HashMap::new(); |
| labels.insert("a", "b"); |
| labels.insert("b", "c"); |
| labels.insert("c", "a"); |
| let c = vec.get_metric_with(&labels).unwrap(); |
| let m = c.metric(); |
| let label_pairs = m.get_label(); |
| assert_eq!(label_pairs.len(), labels.len()); |
| for lp in label_pairs.iter() { |
| assert_eq!(lp.get_value(), labels[lp.get_name()]); |
| } |
| } |
| } |