use std::borrow::Borrow;
use std::collections::BTreeMap;
use std::ops::Deref;
use prometheus::core::{
Atomic, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, Metric, MetricVec,
MetricVecBuilder,
};
use prometheus::{Histogram, HistogramVec};
#[allow(clippy::disallowed_types)]
type PromLabelMap<'a> = std::collections::HashMap<&'a str, &'a str>;
pub trait MetricVec_: Sized {
type M: Metric;
fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<Self::M, prometheus::Error>;
fn get_metric_with(&self, labels: &PromLabelMap) -> Result<Self::M, prometheus::Error>;
fn remove_label_values(&self, vals: &[&str]) -> Result<(), prometheus::Error>;
fn remove(&self, labels: &PromLabelMap) -> Result<(), prometheus::Error>;
}
impl<P: MetricVecBuilder> MetricVec_ for MetricVec<P> {
type M = P::M;
fn get_metric_with_label_values(&self, vals: &[&str]) -> prometheus::Result<Self::M> {
self.get_metric_with_label_values(vals)
}
fn get_metric_with(&self, labels: &PromLabelMap) -> Result<Self::M, prometheus::Error> {
self.get_metric_with(labels)
}
fn remove_label_values(&self, vals: &[&str]) -> Result<(), prometheus::Error> {
self.remove_label_values(vals)
}
fn remove(&self, labels: &PromLabelMap) -> Result<(), prometheus::Error> {
self.remove(labels)
}
}
pub trait MetricVecExt: MetricVec_ {
fn get_delete_on_drop_metric<L: PromLabelsExt>(&self, labels: L)
-> DeleteOnDropMetric<Self, L>;
}
impl<V: MetricVec_ + Clone> MetricVecExt for V {
fn get_delete_on_drop_metric<L>(&self, labels: L) -> DeleteOnDropMetric<Self, L>
where
L: PromLabelsExt,
{
DeleteOnDropMetric::from_metric_vector(self.clone(), labels)
}
}
pub trait PromLabelsExt {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M;
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error>;
}
impl PromLabelsExt for &[&str] {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
vec.get_metric_with_label_values(self)
.expect("retrieving a metric by label values")
}
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
vec.remove_label_values(self)
}
}
impl PromLabelsExt for Vec<String> {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
let labels: Vec<&str> = self.iter().map(String::as_str).collect();
vec.get_metric_with_label_values(labels.as_slice())
.expect("retrieving a metric by label values")
}
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
let labels: Vec<&str> = self.iter().map(String::as_str).collect();
vec.remove_label_values(labels.as_slice())
}
}
impl PromLabelsExt for Vec<&str> {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
vec.get_metric_with_label_values(self.as_slice())
.expect("retrieving a metric by label values")
}
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
vec.remove_label_values(self.as_slice())
}
}
impl PromLabelsExt for BTreeMap<String, String> {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
let labels = self.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
vec.get_metric_with(&labels)
.expect("retrieving a metric by label values")
}
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
let labels = self.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
vec.remove(&labels)
}
}
impl PromLabelsExt for BTreeMap<&str, &str> {
fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
let labels = self.iter().map(|(k, v)| (*k, *v)).collect();
vec.get_metric_with(&labels)
.expect("retrieving a metric by label values")
}
fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
let labels = self.iter().map(|(k, v)| (*k, *v)).collect();
vec.remove(&labels)
}
}
#[derive(Debug, Clone)]
pub struct DeleteOnDropMetric<V, L>
where
V: MetricVec_,
L: PromLabelsExt,
{
inner: V::M,
labels: L,
vec: V,
}
impl<V, L> DeleteOnDropMetric<V, L>
where
V: MetricVec_,
L: PromLabelsExt,
{
fn from_metric_vector(vec: V, labels: L) -> Self {
let inner = labels.get_from_metric_vec(&vec);
Self { inner, labels, vec }
}
}
impl<V, L> Deref for DeleteOnDropMetric<V, L>
where
V: MetricVec_,
L: PromLabelsExt,
{
type Target = V::M;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<V, L> Drop for DeleteOnDropMetric<V, L>
where
V: MetricVec_,
L: PromLabelsExt,
{
fn drop(&mut self) {
if self.labels.remove_from_metric_vec(&self.vec).is_err() {
}
}
}
pub type DeleteOnDropCounter<P, L> = DeleteOnDropMetric<GenericCounterVec<P>, L>;
impl<P, L> Borrow<GenericCounter<P>> for DeleteOnDropCounter<P, L>
where
P: Atomic,
L: PromLabelsExt,
{
fn borrow(&self) -> &GenericCounter<P> {
&self.inner
}
}
pub type DeleteOnDropGauge<P, L> = DeleteOnDropMetric<GenericGaugeVec<P>, L>;
impl<P, L> Borrow<GenericGauge<P>> for DeleteOnDropGauge<P, L>
where
P: Atomic,
L: PromLabelsExt,
{
fn borrow(&self) -> &GenericGauge<P> {
&self.inner
}
}
pub type DeleteOnDropHistogram<L> = DeleteOnDropMetric<HistogramVec, L>;
impl<L> Borrow<Histogram> for DeleteOnDropHistogram<L>
where
L: PromLabelsExt,
{
fn borrow(&self) -> &Histogram {
&self.inner
}
}
#[cfg(test)]
mod test {
use prometheus::core::{AtomicI64, AtomicU64};
use prometheus::IntGaugeVec;
use crate::metric;
use crate::metrics::{IntCounterVec, MetricsRegistry};
use super::*;
#[crate::test]
fn dropping_counters() {
let reg = MetricsRegistry::new();
let vec: IntCounterVec = reg.register(metric!(
name: "test_metric",
help: "a test metric",
var_labels: ["dimension"]));
let dims: &[&str] = &["one"];
let metric_1 = vec.get_delete_on_drop_metric(dims);
metric_1.inc();
let metrics = reg.gather();
assert_eq!(metrics.len(), 1);
let reported_vec = &metrics[0];
assert_eq!(reported_vec.get_name(), "test_metric");
let dims = reported_vec.get_metric();
assert_eq!(dims.len(), 1);
assert_eq!(dims[0].get_label()[0].get_value(), "one");
drop(metric_1);
let metrics = reg.gather();
assert_eq!(metrics.len(), 0);
let string_labels: Vec<String> = ["owned"].iter().map(ToString::to_string).collect();
struct Ownership {
counter: DeleteOnDropCounter<AtomicU64, Vec<String>>,
}
let metric_owned = Ownership {
counter: vec.get_delete_on_drop_metric(string_labels),
};
metric_owned.counter.inc();
let metrics = reg.gather();
assert_eq!(metrics.len(), 1);
let reported_vec = &metrics[0];
assert_eq!(reported_vec.get_name(), "test_metric");
let dims = reported_vec.get_metric();
assert_eq!(dims.len(), 1);
assert_eq!(dims[0].get_label()[0].get_value(), "owned");
drop(metric_owned);
let metrics = reg.gather();
assert_eq!(metrics.len(), 0);
}
#[crate::test]
fn dropping_gauges() {
let reg = MetricsRegistry::new();
let vec: IntGaugeVec = reg.register(metric!(
name: "test_metric",
help: "a test metric",
var_labels: ["dimension"]));
let dims: &[&str] = &["one"];
let metric_1 = vec.get_delete_on_drop_metric(dims);
metric_1.set(666);
let metrics = reg.gather();
assert_eq!(metrics.len(), 1);
let reported_vec = &metrics[0];
assert_eq!(reported_vec.get_name(), "test_metric");
let dims = reported_vec.get_metric();
assert_eq!(dims.len(), 1);
assert_eq!(dims[0].get_label()[0].get_value(), "one");
drop(metric_1);
let metrics = reg.gather();
assert_eq!(metrics.len(), 0);
let string_labels: Vec<String> = ["owned"].iter().map(ToString::to_string).collect();
struct Ownership {
gauge: DeleteOnDropGauge<AtomicI64, Vec<String>>,
}
let metric_owned = Ownership {
gauge: vec.get_delete_on_drop_metric(string_labels),
};
metric_owned.gauge.set(666);
let metrics = reg.gather();
assert_eq!(metrics.len(), 1);
let reported_vec = &metrics[0];
assert_eq!(reported_vec.get_name(), "test_metric");
let dims = reported_vec.get_metric();
assert_eq!(dims.len(), 1);
assert_eq!(dims[0].get_label()[0].get_value(), "owned");
drop(metric_owned);
let metrics = reg.gather();
assert_eq!(metrics.len(), 0);
}
}