From d13bb2bf1eb14b69ef3b7d4952baef4b89bda604 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 3 Mar 2025 11:56:47 +0100 Subject: [PATCH] Use AsRef for owned label values Signed-off-by: Sander van Harmelen --- src/counter.rs | 61 +++++++++++++++- src/gauge.rs | 55 +++++++++++++- src/histogram.rs | 31 ++++++-- src/value.rs | 8 +- src/vec.rs | 185 ++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 307 insertions(+), 33 deletions(-) diff --git a/src/counter.rs b/src/counter.rs index 239e2982..e4a7689a 100644 --- a/src/counter.rs +++ b/src/counter.rs @@ -44,10 +44,10 @@ impl GenericCounter

{ /// Create a [`GenericCounter`] with the `opts` options. pub fn with_opts(opts: Opts) -> Result { - Self::with_opts_and_label_values(&opts, &[]) + Self::with_opts_and_label_values::<&str>(&opts, &[]) } - fn with_opts_and_label_values(opts: &Opts, label_values: &[&str]) -> Result { + fn with_opts_and_label_values>(opts: &Opts, label_values: &[V]) -> Result { let v = Value::new(opts, ValueType::Counter, P::T::from_i64(0), label_values)?; Ok(Self { v: Arc::new(v) }) } @@ -126,7 +126,7 @@ impl MetricVecBuilder for CounterVecBuilder

{ type M = GenericCounter

; type P = Opts; - fn build(&self, opts: &Opts, vals: &[&str]) -> Result { + fn build>(&self, opts: &Opts, vals: &[V]) -> Result { Self::M::with_opts_and_label_values(opts, vals) } } @@ -450,6 +450,40 @@ mod tests { assert!(vec.remove(&labels3).is_err()); } + #[test] + fn test_counter_vec_with_owned_labels() { + let vec = CounterVec::new( + Opts::new("test_couter_vec", "test counter vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + + let mut labels = HashMap::new(); + labels.insert("l1", v1.clone()); + labels.insert("l2", v2.clone()); + 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.clone()); + labels2.insert("l2", v1.clone()); + + vec.with(&labels).inc(); + assert!(vec.remove(&labels2).is_err()); + + vec.with(&labels).inc(); + + let mut labels3 = HashMap::new(); + labels3.insert("l1", v1.clone()); + assert!(vec.remove(&labels3).is_err()); + } + #[test] fn test_int_counter_vec() { let vec = IntCounterVec::new(Opts::new("foo", "bar"), &["l1", "l2"]).unwrap(); @@ -489,6 +523,27 @@ mod tests { assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); } + #[test] + fn test_counter_vec_with_owned_label_values() { + let vec = CounterVec::new( + Opts::new("test_vec", "test counter vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + let v3 = "v3".to_string(); + + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); + + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone()]).is_err()); + assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); + } + #[test] fn test_counter_vec_local() { let vec = CounterVec::new( diff --git a/src/gauge.rs b/src/gauge.rs index 83eba2b1..5ce5603a 100644 --- a/src/gauge.rs +++ b/src/gauge.rs @@ -43,10 +43,10 @@ impl GenericGauge

{ /// Create a [`GenericGauge`] with the `opts` options. pub fn with_opts(opts: Opts) -> Result { - Self::with_opts_and_label_values(&opts, &[]) + Self::with_opts_and_label_values::<&str>(&opts, &[]) } - fn with_opts_and_label_values(opts: &Opts, label_values: &[&str]) -> Result { + fn with_opts_and_label_values>(opts: &Opts, label_values: &[V]) -> Result { let v = Value::new(opts, ValueType::Gauge, P::T::from_i64(0), label_values)?; Ok(Self { v: Arc::new(v) }) } @@ -129,7 +129,7 @@ impl MetricVecBuilder for GaugeVecBuilder

{ type M = GenericGauge

; type P = Opts; - fn build(&self, opts: &Opts, vals: &[&str]) -> Result { + fn build>(&self, opts: &Opts, vals: &[V]) -> Result { Self::M::with_opts_and_label_values(opts, vals) } } @@ -216,6 +216,29 @@ mod tests { assert!(vec.remove(&labels).is_err()); } + #[test] + fn test_gauge_vec_with_owned_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( @@ -237,4 +260,30 @@ mod tests { assert!(vec.remove_label_values(&["v1"]).is_err()); assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); } + + #[test] + fn test_gauge_vec_with_owned_label_values() { + let vec = GaugeVec::new( + Opts::new("test_gauge_vec", "test gauge vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + let v3 = "v3".to_string(); + + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); + + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + vec.with_label_values(&[v1.clone(), v2.clone()]).dec(); + vec.with_label_values(&[v1.clone(), v2.clone()]).add(42.0); + vec.with_label_values(&[v1.clone(), v2.clone()]).sub(42.0); + vec.with_label_values(&[v1.clone(), v2.clone()]).set(42.0); + + assert!(vec.remove_label_values(&[v1.clone()]).is_err()); + assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); + } } diff --git a/src/histogram.rs b/src/histogram.rs index 78480541..5b00fed1 100644 --- a/src/histogram.rs +++ b/src/histogram.rs @@ -327,7 +327,7 @@ pub struct HistogramCore { } impl HistogramCore { - pub fn new(opts: &HistogramOpts, label_values: &[&str]) -> Result { + pub fn new>(opts: &HistogramOpts, label_values: &[V]) -> Result { let desc = opts.describe()?; for name in &desc.variable_labels { @@ -674,12 +674,12 @@ pub struct Histogram { impl Histogram { /// `with_opts` creates a [`Histogram`] with the `opts` options. pub fn with_opts(opts: HistogramOpts) -> Result { - Histogram::with_opts_and_label_values(&opts, &[]) + Histogram::with_opts_and_label_values::<&str>(&opts, &[]) } - fn with_opts_and_label_values( + fn with_opts_and_label_values>( opts: &HistogramOpts, - label_values: &[&str], + label_values: &[V], ) -> Result { let core = HistogramCore::new(opts, label_values)?; @@ -783,7 +783,7 @@ impl MetricVecBuilder for HistogramVecBuilder { type M = Histogram; type P = HistogramOpts; - fn build(&self, opts: &HistogramOpts, vals: &[&str]) -> Result { + fn build>(&self, opts: &HistogramOpts, vals: &[V]) -> Result { Histogram::with_opts_and_label_values(opts, vals) } } @@ -1386,6 +1386,27 @@ mod tests { assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); } + #[test] + fn test_histogram_vec_with_owned_label_values() { + let vec = HistogramVec::new( + HistogramOpts::new("test_histogram_vec", "test histogram vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + let v3 = "v3".to_string(); + + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); + vec.with_label_values(&[v1.clone(), v2.clone()]) + .observe(1.0); + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); + + assert!(vec.remove_label_values(&[v1.clone()]).is_err()); + assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); + } + #[test] fn test_histogram_vec_with_opts_buckets() { let labels = ["l1", "l2"]; diff --git a/src/value.rs b/src/value.rs index 87b2ede2..69fef2d3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -37,11 +37,11 @@ pub struct Value { } impl Value

{ - pub fn new( + pub fn new>( describer: &D, val_type: ValueType, val: P::T, - label_values: &[&str], + label_values: &[V], ) -> Result { let desc = describer.describe()?; let label_pairs = make_label_pairs(&desc, label_values)?; @@ -115,7 +115,7 @@ impl Value

{ } } -pub fn make_label_pairs(desc: &Desc, label_values: &[&str]) -> Result> { +pub fn make_label_pairs>(desc: &Desc, label_values: &[V]) -> Result> { if desc.variable_labels.len() != label_values.len() { return Err(Error::InconsistentCardinality { expect: desc.variable_labels.len(), @@ -136,7 +136,7 @@ pub fn make_label_pairs(desc: &Desc, label_values: &[&str]) -> Result Result; + fn build>(&self, _: &Self::P, _: &[V]) -> Result; } #[derive(Debug)] @@ -49,7 +49,10 @@ impl MetricVecCore { m } - pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result { + pub fn get_metric_with_label_values(&self, vals: &[V]) -> Result + where + V: AsRef + std::fmt::Debug, + { let h = self.hash_label_values(vals)?; if let Some(metric) = self.children.read().get(&h).cloned() { @@ -59,7 +62,10 @@ impl MetricVecCore { self.get_or_create_metric(h, vals) } - pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result { + pub fn get_metric_with(&self, labels: &HashMap<&str, V>) -> Result + where + V: AsRef + std::fmt::Debug, + { let h = self.hash_labels(labels)?; if let Some(metric) = self.children.read().get(&h).cloned() { @@ -70,7 +76,10 @@ impl MetricVecCore { self.get_or_create_metric(h, &vals) } - pub fn delete_label_values(&self, vals: &[&str]) -> Result<()> { + pub fn delete_label_values(&self, vals: &[V]) -> Result<()> + where + V: AsRef + std::fmt::Debug, + { let h = self.hash_label_values(vals)?; let mut children = self.children.write(); @@ -81,7 +90,10 @@ impl MetricVecCore { Ok(()) } - pub fn delete(&self, labels: &HashMap<&str, &str>) -> Result<()> { + pub fn delete(&self, labels: &HashMap<&str, V>) -> Result<()> + where + V: AsRef + std::fmt::Debug, + { let h = self.hash_labels(labels)?; let mut children = self.children.write(); @@ -97,7 +109,10 @@ impl MetricVecCore { self.children.write().clear(); } - pub(crate) fn hash_label_values(&self, vals: &[&str]) -> Result { + pub(crate) fn hash_label_values(&self, vals: &[V]) -> Result + where + V: AsRef + std::fmt::Debug, + { if vals.len() != self.desc.variable_labels.len() { return Err(Error::InconsistentCardinality { expect: self.desc.variable_labels.len(), @@ -107,13 +122,16 @@ impl MetricVecCore { let mut h = FnvHasher::default(); for val in vals { - h.write(val.as_bytes()); + h.write(val.as_ref().as_bytes()); } Ok(h.finish()) } - fn hash_labels(&self, labels: &HashMap<&str, &str>) -> Result { + fn hash_labels(&self, labels: &HashMap<&str, V>) -> Result + where + V: AsRef + std::fmt::Debug, + { if labels.len() != self.desc.variable_labels.len() { return Err(Error::InconsistentCardinality { expect: self.desc.variable_labels.len(), @@ -124,7 +142,7 @@ impl MetricVecCore { 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()), + Some(val) => h.write(val.as_ref().as_bytes()), None => { return Err(Error::Msg(format!( "label name {} missing in label map", @@ -137,11 +155,14 @@ impl MetricVecCore { Ok(h.finish()) } - fn get_label_values<'a>(&self, labels: &'a HashMap<&str, &str>) -> Result> { + fn get_label_values<'a, V>(&'a self, labels: &'a HashMap<&str, V>) -> Result> + where + V: AsRef + std::fmt::Debug, + { let mut values = Vec::new(); for name in &self.desc.variable_labels { match labels.get(&name.as_ref()) { - Some(val) => values.push(*val), + Some(val) => values.push(val.as_ref()), None => { return Err(Error::Msg(format!( "label name {} missing in label map", @@ -153,7 +174,10 @@ impl MetricVecCore { Ok(values) } - fn get_or_create_metric(&self, hash: u64, label_values: &[&str]) -> Result { + fn get_or_create_metric(&self, hash: u64, label_values: &[V]) -> Result + where + V: AsRef + std::fmt::Debug, + { let mut children = self.children.write(); // Check exist first. if let Some(metric) = children.get(&hash).cloned() { @@ -221,7 +245,10 @@ impl MetricVec { /// 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 { + pub fn get_metric_with_label_values(&self, vals: &[V]) -> Result + where + V: AsRef + std::fmt::Debug, + { self.v.get_metric_with_label_values(vals) } @@ -237,7 +264,10 @@ impl MetricVec { /// 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 { + pub fn get_metric_with(&self, labels: &HashMap<&str, V>) -> Result + where + V: AsRef + std::fmt::Debug, + { self.v.get_metric_with(labels) } @@ -254,14 +284,20 @@ impl MetricVec { /// ).unwrap(); /// vec.with_label_values(&["404", "POST"]).inc() /// ``` - pub fn with_label_values(&self, vals: &[&str]) -> T::M { + pub fn with_label_values(&self, vals: &[V]) -> T::M + where + V: AsRef + std::fmt::Debug, + { 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 { + pub fn with(&self, labels: &HashMap<&str, V>) -> T::M + where + V: AsRef + std::fmt::Debug, + { self.get_metric_with(labels).unwrap() } @@ -277,7 +313,10 @@ impl MetricVec { /// 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<()> { + pub fn remove_label_values(&self, vals: &[V]) -> Result<()> + where + V: AsRef + std::fmt::Debug, + { self.v.delete_label_values(vals) } @@ -289,7 +328,10 @@ impl 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<()> { + pub fn remove(&self, labels: &HashMap<&str, V>) -> Result<()> + where + V: AsRef + std::fmt::Debug, + { self.v.delete(labels) } @@ -348,6 +390,40 @@ mod tests { assert!(vec.remove(&labels3).is_err()); } + #[test] + fn test_counter_vec_with_owned_labels() { + let vec = CounterVec::new( + Opts::new("test_couter_vec", "test counter vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + + let mut labels = HashMap::new(); + labels.insert("l1", v1.clone()); + labels.insert("l2", v2.clone()); + 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.clone()); + labels2.insert("l2", v1.clone()); + + vec.with(&labels).inc(); + assert!(vec.remove(&labels2).is_err()); + + vec.with(&labels).inc(); + + let mut labels3 = HashMap::new(); + labels3.insert("l1", v1.clone()); + assert!(vec.remove(&labels3).is_err()); + } + #[test] fn test_counter_vec_with_label_values() { let vec = CounterVec::new( @@ -365,6 +441,27 @@ mod tests { assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); } + #[test] + fn test_counter_vec_with_owned_label_values() { + let vec = CounterVec::new( + Opts::new("test_vec", "test counter vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + let v3 = "v3".to_string(); + + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); + + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone()]).is_err()); + assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); + } + #[test] fn test_gauge_vec_with_labels() { let vec = GaugeVec::new( @@ -388,6 +485,32 @@ mod tests { assert!(vec.remove(&labels).is_err()); } + #[test] + fn test_gauge_vec_with_owned_labels() { + let vec = GaugeVec::new( + Opts::new("test_gauge_vec", "test gauge vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + + let mut labels = HashMap::new(); + labels.insert("l1", v1.clone()); + labels.insert("l2", v2.clone()); + 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( @@ -410,6 +533,32 @@ mod tests { assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); } + #[test] + fn test_gauge_vec_with_owned_label_values() { + let vec = GaugeVec::new( + Opts::new("test_gauge_vec", "test gauge vec help"), + &["l1", "l2"], + ) + .unwrap(); + + let v1 = "v1".to_string(); + let v2 = "v2".to_string(); + let v3 = "v3".to_string(); + + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); + + vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); + vec.with_label_values(&[v1.clone(), v2.clone()]).dec(); + vec.with_label_values(&[v1.clone(), v2.clone()]).add(42.0); + vec.with_label_values(&[v1.clone(), v2.clone()]).sub(42.0); + vec.with_label_values(&[v1.clone(), v2.clone()]).set(42.0); + + assert!(vec.remove_label_values(&[v1.clone()]).is_err()); + assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); + } + #[test] fn test_vec_get_metric_with() { let vec = CounterVec::new(