diff --git a/packages/http-tracker-core/src/statistics/mod.rs b/packages/http-tracker-core/src/statistics/mod.rs index f949babbd..7181632aa 100644 --- a/packages/http-tracker-core/src/statistics/mod.rs +++ b/packages/http-tracker-core/src/statistics/mod.rs @@ -17,7 +17,7 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(HTTP_TRACKER_CORE_REQUESTS_RECEIVED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of HTTP requests received")), + Some(MetricDescription::new("Total number of HTTP requests received")), ); metrics diff --git a/packages/metrics/src/label/set.rs b/packages/metrics/src/label/set.rs index 2b6334fc7..1c2c3e27e 100644 --- a/packages/metrics/src/label/set.rs +++ b/packages/metrics/src/label/set.rs @@ -175,6 +175,7 @@ impl PrometheusSerializable for LabelSet { mod tests { use std::collections::BTreeMap; + use std::hash::{DefaultHasher, Hash}; use pretty_assertions::assert_eq; @@ -195,54 +196,6 @@ mod tests { ] } - #[test] - fn it_should_allow_instantiation_from_an_array_of_label_pairs() { - let label_set: LabelSet = sample_array_of_label_pairs().into(); - - assert_eq!( - label_set, - LabelSet { - items: BTreeMap::from(sample_array_of_label_pairs()) - } - ); - } - - #[test] - fn it_should_allow_instantiation_from_a_vec_of_label_pairs() { - let label_set: LabelSet = sample_vec_of_label_pairs().into(); - - assert_eq!( - label_set, - LabelSet { - items: BTreeMap::from(sample_array_of_label_pairs()) - } - ); - } - - #[test] - fn it_should_allow_instantiation_from_a_b_tree_map() { - let label_set: LabelSet = BTreeMap::from(sample_array_of_label_pairs()).into(); - - assert_eq!( - label_set, - LabelSet { - items: BTreeMap::from(sample_array_of_label_pairs()) - } - ); - } - - #[test] - fn it_should_allow_instantiation_from_a_label_pair() { - let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); - - assert_eq!( - label_set, - LabelSet { - items: BTreeMap::from([(label_name!("label_name"), LabelValue::new("value"))]) - } - ); - } - #[test] fn it_should_allow_inserting_a_new_label_pair() { let mut label_set = LabelSet::default(); @@ -338,4 +291,155 @@ mod tests { assert_eq!(label_set.to_string(), r#"{label_name="label value"}"#); } + + #[test] + fn it_should_allow_instantiation_from_an_array_of_label_pairs() { + let label_set: LabelSet = sample_array_of_label_pairs().into(); + + assert_eq!( + label_set, + LabelSet { + items: BTreeMap::from(sample_array_of_label_pairs()) + } + ); + } + + #[test] + fn it_should_allow_instantiation_from_a_vec_of_label_pairs() { + let label_set: LabelSet = sample_vec_of_label_pairs().into(); + + assert_eq!( + label_set, + LabelSet { + items: BTreeMap::from(sample_array_of_label_pairs()) + } + ); + } + + #[test] + fn it_should_allow_instantiation_from_a_b_tree_map() { + let label_set: LabelSet = BTreeMap::from(sample_array_of_label_pairs()).into(); + + assert_eq!( + label_set, + LabelSet { + items: BTreeMap::from(sample_array_of_label_pairs()) + } + ); + } + + #[test] + fn it_should_allow_instantiation_from_a_label_pair() { + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + assert_eq!( + label_set, + LabelSet { + items: BTreeMap::from([(label_name!("label_name"), LabelValue::new("value"))]) + } + ); + } + + #[test] + fn it_should_allow_instantiation_from_vec_of_str_tuples() { + let label_set: LabelSet = vec![("foo", "bar"), ("baz", "qux")].into(); + + let mut expected = BTreeMap::new(); + expected.insert(LabelName::new("foo"), LabelValue::new("bar")); + expected.insert(LabelName::new("baz"), LabelValue::new("qux")); + + assert_eq!(label_set, LabelSet { items: expected }); + } + + #[test] + fn it_should_allow_instantiation_from_vec_of_string_tuples() { + let label_set: LabelSet = vec![("foo".to_string(), "bar".to_string()), ("baz".to_string(), "qux".to_string())].into(); + + let mut expected = BTreeMap::new(); + expected.insert(LabelName::new("foo"), LabelValue::new("bar")); + expected.insert(LabelName::new("baz"), LabelValue::new("qux")); + + assert_eq!(label_set, LabelSet { items: expected }); + } + + #[test] + fn it_should_allow_instantiation_from_vec_of_serialized_label() { + use super::SerializedLabel; + let label_set: LabelSet = vec![ + SerializedLabel { + name: LabelName::new("foo"), + value: LabelValue::new("bar"), + }, + SerializedLabel { + name: LabelName::new("baz"), + value: LabelValue::new("qux"), + }, + ] + .into(); + + let mut expected = BTreeMap::new(); + expected.insert(LabelName::new("foo"), LabelValue::new("bar")); + expected.insert(LabelName::new("baz"), LabelValue::new("qux")); + + assert_eq!(label_set, LabelSet { items: expected }); + } + + #[test] + fn it_should_allow_instantiation_from_array_of_string_tuples() { + let arr: [(String, String); 2] = [("foo".to_string(), "bar".to_string()), ("baz".to_string(), "qux".to_string())]; + let label_set: LabelSet = arr.into(); + + let mut expected = BTreeMap::new(); + + expected.insert(LabelName::new("foo"), LabelValue::new("bar")); + expected.insert(LabelName::new("baz"), LabelValue::new("qux")); + + assert_eq!(label_set, LabelSet { items: expected }); + } + + #[test] + fn it_should_allow_instantiation_from_array_of_str_tuples() { + let arr: [(&str, &str); 2] = [("foo", "bar"), ("baz", "qux")]; + let label_set: LabelSet = arr.into(); + + let mut expected = BTreeMap::new(); + + expected.insert(LabelName::new("foo"), LabelValue::new("bar")); + expected.insert(LabelName::new("baz"), LabelValue::new("qux")); + + assert_eq!(label_set, LabelSet { items: expected }); + } + + #[test] + fn it_should_be_comparable() { + let a: LabelSet = (label_name!("x"), LabelValue::new("1")).into(); + let b: LabelSet = (label_name!("x"), LabelValue::new("1")).into(); + let c: LabelSet = (label_name!("y"), LabelValue::new("2")).into(); + + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn it_should_be_allow_ordering() { + let a: LabelSet = (label_name!("x"), LabelValue::new("1")).into(); + let b: LabelSet = (label_name!("y"), LabelValue::new("2")).into(); + + assert!(a < b); + } + + #[test] + fn it_should_be_hashable() { + let a: LabelSet = (label_name!("x"), LabelValue::new("1")).into(); + + let mut hasher = DefaultHasher::new(); + + a.hash(&mut hasher); + } + + #[test] + fn it_should_implement_clone() { + let a: LabelSet = (label_name!("x"), LabelValue::new("1")).into(); + let _unused = a.clone(); + } } diff --git a/packages/metrics/src/label/value.rs b/packages/metrics/src/label/value.rs index ffdbce333..4f25844a8 100644 --- a/packages/metrics/src/label/value.rs +++ b/packages/metrics/src/label/value.rs @@ -33,6 +33,9 @@ impl From for LabelValue { #[cfg(test)] mod tests { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hash; + use crate::label::value::LabelValue; use crate::prometheus::PrometheusSerializable; @@ -41,4 +44,60 @@ mod tests { let label_value = LabelValue::new("value"); assert_eq!(label_value.to_prometheus(), "value"); } + + #[test] + fn it_could_be_initialized_from_str() { + let lv = LabelValue::new("abc"); + assert_eq!(lv.0, "abc"); + } + + #[test] + fn it_should_allow_to_create_an_ignored_label_value() { + let lv = LabelValue::ignore(); + assert_eq!(lv.0, ""); + } + + #[test] + fn it_should_be_converted_from_string() { + let s = String::from("foo"); + let lv: LabelValue = s.clone().into(); + assert_eq!(lv.0, s); + } + + #[test] + fn it_should_be_comparable() { + let a = LabelValue::new("x"); + let b = LabelValue::new("x"); + let c = LabelValue::new("y"); + + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn it_should_be_allow_ordering() { + let a = LabelValue::new("x"); + let b = LabelValue::new("y"); + + assert!(a < b); + } + + #[test] + fn it_should_be_hashable() { + let a = LabelValue::new("x"); + let mut hasher = DefaultHasher::new(); + a.hash(&mut hasher); + } + + #[test] + fn it_should_implement_clone() { + let a = LabelValue::new("x"); + let _unused = a.clone(); + } + + #[test] + fn it_should_implement_display() { + let a = LabelValue::new("x"); + assert_eq!(format!("{a}"), "x"); + } } diff --git a/packages/metrics/src/metric/description.rs b/packages/metrics/src/metric/description.rs index 8a50dee90..6a0ca3432 100644 --- a/packages/metrics/src/metric/description.rs +++ b/packages/metrics/src/metric/description.rs @@ -1,6 +1,8 @@ use derive_more::Display; use serde::{Deserialize, Serialize}; +use crate::prometheus::PrometheusSerializable; + #[derive(Debug, Display, Clone, Eq, PartialEq, Default, Deserialize, Serialize, Hash, Ord, PartialOrd)] pub struct MetricDescription(String); @@ -11,6 +13,11 @@ impl MetricDescription { } } +impl PrometheusSerializable for MetricDescription { + fn to_prometheus(&self) -> String { + self.0.clone() + } +} #[cfg(test)] mod tests { use super::*; @@ -21,6 +28,12 @@ mod tests { assert_eq!(metric.0, "Metric description"); } + #[test] + fn it_serializes_to_prometheus() { + let label_value = MetricDescription::new("name"); + assert_eq!(label_value.to_prometheus(), "name"); + } + #[test] fn it_should_be_displayed() { let metric = MetricDescription::new("Metric description"); diff --git a/packages/metrics/src/metric/mod.rs b/packages/metrics/src/metric/mod.rs index 2118637b8..6f254023f 100644 --- a/packages/metrics/src/metric/mod.rs +++ b/packages/metrics/src/metric/mod.rs @@ -9,7 +9,9 @@ use super::label::LabelSet; use super::prometheus::PrometheusSerializable; use super::sample_collection::SampleCollection; use crate::gauge::Gauge; +use crate::metric::description::MetricDescription; use crate::sample::Measurement; +use crate::unit::Unit; pub type MetricName = name::MetricName; @@ -17,19 +19,45 @@ pub type MetricName = name::MetricName; pub struct Metric { name: MetricName, + #[serde(rename = "unit")] + opt_unit: Option, + + #[serde(rename = "description")] + opt_description: Option, + #[serde(rename = "samples")] sample_collection: SampleCollection, } impl Metric { #[must_use] - pub fn new(name: MetricName, samples: SampleCollection) -> Self { + pub fn new( + name: MetricName, + opt_unit: Option, + opt_description: Option, + samples: SampleCollection, + ) -> Self { Self { name, + opt_unit, + opt_description, sample_collection: samples, } } + /// # Panics + /// + /// This function will panic if the empty sample collection cannot be created. + #[must_use] + pub fn new_empty_with_name(name: MetricName) -> Self { + Self { + name, + opt_unit: None, + opt_description: None, + sample_collection: SampleCollection::new(vec![]).expect("Empty sample collection creation should not fail"), + } + } + #[must_use] pub fn name(&self) -> &MetricName { &self.name @@ -75,18 +103,115 @@ impl Metric { } } -impl PrometheusSerializable for Metric { +/// `PrometheusMetricSample` is a wrapper around types that provides methods to +/// convert the metric and its measurement into a Prometheus-compatible format. +/// +/// In Prometheus, a metric is a time series that consists of a name, a set of +/// labels, and a value. The sample value needs data from the `Metric` and +/// `Measurement` structs, as well as the `LabelSet` that defines the labels for +/// the metric. +struct PrometheusMetricSample<'a, T> { + metric: &'a Metric, + measurement: &'a Measurement, + label_set: &'a LabelSet, +} + +enum PrometheusType { + Counter, + Gauge, +} + +impl PrometheusSerializable for PrometheusType { + fn to_prometheus(&self) -> String { + match self { + PrometheusType::Counter => "counter".to_string(), + PrometheusType::Gauge => "gauge".to_string(), + } + } +} + +impl PrometheusMetricSample<'_, T> { + fn to_prometheus(&self, prometheus_type: &PrometheusType) -> String { + format!( + // Format: + // # HELP + // # TYPE + // {label_set} + "{}{}{}", + self.help_line(), + self.type_line(prometheus_type), + self.metric_line() + ) + } + + fn help_line(&self) -> String { + if let Some(description) = &self.metric.opt_description { + format!( + // Format: # HELP + "# HELP {} {}\n", + self.metric.name().to_prometheus(), + description.to_prometheus() + ) + } else { + String::new() + } + } + + fn type_line(&self, kind: &PrometheusType) -> String { + format!("# TYPE {} {}\n", self.metric.name().to_prometheus(), kind.to_prometheus()) + } + + fn metric_line(&self) -> String { + format!( + // Format: {label_set} + "{}{} {}", + self.metric.name.to_prometheus(), + self.label_set.to_prometheus(), + self.measurement.value().to_prometheus() + ) + } +} + +impl<'a> PrometheusMetricSample<'a, Counter> { + pub fn new(metric: &'a Metric, measurement: &'a Measurement, label_set: &'a LabelSet) -> Self { + Self { + metric, + measurement, + label_set, + } + } +} + +impl<'a> PrometheusMetricSample<'a, Gauge> { + pub fn new(metric: &'a Metric, measurement: &'a Measurement, label_set: &'a LabelSet) -> Self { + Self { + metric, + measurement, + label_set, + } + } +} + +impl PrometheusSerializable for Metric { fn to_prometheus(&self) -> String { let samples: Vec = self .sample_collection .iter() - .map(|(label_set, sample)| { - format!( - "{}{} {}", - self.name.to_prometheus(), - label_set.to_prometheus(), - sample.value().to_prometheus() - ) + .map(|(label_set, measurement)| { + PrometheusMetricSample::::new(self, measurement, label_set).to_prometheus(&PrometheusType::Counter) + }) + .collect(); + samples.join("\n") + } +} + +impl PrometheusSerializable for Metric { + fn to_prometheus(&self) -> String { + let samples: Vec = self + .sample_collection + .iter() + .map(|(label_set, measurement)| { + PrometheusMetricSample::::new(self, measurement, label_set).to_prometheus(&PrometheusType::Gauge) }) .collect(); samples.join("\n") @@ -108,7 +233,7 @@ mod tests { let samples = SampleCollection::::default(); - let metric = Metric::::new(name.clone(), samples); + let metric = Metric::::new(name.clone(), None, None, samples); assert!(metric.is_empty()); } @@ -122,7 +247,7 @@ mod tests { let samples = SampleCollection::new(vec![Sample::new(Counter::new(1), time, label_set.clone())]).unwrap(); - Metric::::new(name.clone(), samples) + Metric::::new(name.clone(), None, None, samples) } #[test] @@ -136,7 +261,7 @@ mod tests { let samples = SampleCollection::::default(); - let metric = Metric::::new(name.clone(), samples); + let metric = Metric::::new(name.clone(), None, None, samples); assert_eq!(metric.number_of_samples(), 0); } @@ -155,20 +280,31 @@ mod tests { let samples = SampleCollection::::default(); - let _metric = Metric::::new(name, samples); + let _metric = Metric::::new(name, None, None, samples); } #[test] fn it_should_allow_incrementing_a_sample() { let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); - let name = metric_name!("test_metric"); - let label_set: LabelSet = [(label_name!("server_binding_protocol"), LabelValue::new("http"))].into(); + let samples = SampleCollection::new(vec![Sample::new(Counter::new(0), time, label_set.clone())]).unwrap(); + let mut metric = Metric::::new(name.clone(), None, None, samples); - let samples = SampleCollection::new(vec![Sample::new(Counter::new(1), time, label_set.clone())]).unwrap(); + metric.increment(&label_set, time); - let metric = Metric::::new(name.clone(), samples); + assert_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1); + } + + #[test] + fn it_should_allow_setting_to_an_absolute_value() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let name = metric_name!("test_metric"); + let label_set: LabelSet = [(label_name!("server_binding_protocol"), LabelValue::new("http"))].into(); + let samples = SampleCollection::new(vec![Sample::new(Counter::new(0), time, label_set.clone())]).unwrap(); + let mut metric = Metric::::new(name.clone(), None, None, samples); + + metric.absolute(&label_set, 1, time); assert_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1); } @@ -189,20 +325,44 @@ mod tests { let samples = SampleCollection::::default(); - let _metric = Metric::::new(name, samples); + let _metric = Metric::::new(name, None, None, samples); } #[test] - fn it_should_allow_setting_a_sample() { + fn it_should_allow_incrementing_a_sample() { let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); - let name = metric_name!("test_metric"); - let label_set: LabelSet = [(label_name!("server_binding_protocol"), LabelValue::new("http"))].into(); + let samples = SampleCollection::new(vec![Sample::new(Gauge::new(0.0), time, label_set.clone())]).unwrap(); + let mut metric = Metric::::new(name.clone(), None, None, samples); + + metric.increment(&label_set, time); + + assert_relative_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1.0); + } + #[test] + fn it_should_allow_decrement_a_sample() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let name = metric_name!("test_metric"); + let label_set: LabelSet = [(label_name!("server_binding_protocol"), LabelValue::new("http"))].into(); let samples = SampleCollection::new(vec![Sample::new(Gauge::new(1.0), time, label_set.clone())]).unwrap(); + let mut metric = Metric::::new(name.clone(), None, None, samples); + + metric.decrement(&label_set, time); + + assert_relative_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 0.0); + } + + #[test] + fn it_should_allow_setting_a_sample() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let name = metric_name!("test_metric"); + let label_set: LabelSet = [(label_name!("server_binding_protocol"), LabelValue::new("http"))].into(); + let samples = SampleCollection::new(vec![Sample::new(Gauge::new(0.0), time, label_set.clone())]).unwrap(); + let mut metric = Metric::::new(name.clone(), None, None, samples); - let metric = Metric::::new(name.clone(), samples); + metric.set(&label_set, 1.0, time); assert_relative_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1.0); } diff --git a/packages/metrics/src/metric_collection.rs b/packages/metrics/src/metric_collection.rs index 824397000..c53d02bcf 100644 --- a/packages/metrics/src/metric_collection.rs +++ b/packages/metrics/src/metric_collection.rs @@ -50,16 +50,41 @@ impl MetricCollection { /// /// Returns an error if a metric name already exists in the current collection. pub fn merge(&mut self, other: &Self) -> Result<(), Error> { + self.check_cross_type_collision(other)?; self.counters.merge(&other.counters)?; self.gauges.merge(&other.gauges)?; Ok(()) } + /// Returns a set of all metric names in this collection. + fn collect_names(&self) -> HashSet { + self.counters.names().chain(self.gauges.names()).cloned().collect() + } + + /// Checks for name collisions between this collection and another one. + fn check_cross_type_collision(&self, other: &Self) -> Result<(), Error> { + let self_names: HashSet<_> = self.collect_names(); + let other_names: HashSet<_> = other.collect_names(); + + let cross_type_collisions = self_names.intersection(&other_names).next(); + + if let Some(name) = cross_type_collisions { + return Err(Error::MetricNameCollisionInMerge { + metric_name: (*name).clone(), + }); + } + + Ok(()) + } + // Counter-specific methods - pub fn describe_counter(&mut self, name: &MetricName, opt_unit: Option, opt_description: Option<&MetricDescription>) { + pub fn describe_counter(&mut self, name: &MetricName, opt_unit: Option, opt_description: Option) { tracing::info!(target: METRICS_TARGET, type = "counter", name = name.to_string(), unit = ?opt_unit, description = ?opt_description); - self.counters.ensure_metric_exists(name); + + let metric = Metric::::new(name.clone(), opt_unit, opt_description, SampleCollection::default()); + + self.counters.insert(metric); } #[must_use] @@ -119,15 +144,14 @@ impl MetricCollection { Ok(()) } - pub fn ensure_counter_exists(&mut self, name: &MetricName) { - self.counters.ensure_metric_exists(name); - } - // Gauge-specific methods - pub fn describe_gauge(&mut self, name: &MetricName, opt_unit: Option, opt_description: Option<&MetricDescription>) { + pub fn describe_gauge(&mut self, name: &MetricName, opt_unit: Option, opt_description: Option) { tracing::info!(target: METRICS_TARGET, type = "gauge", name = name.to_string(), unit = ?opt_unit, description = ?opt_description); - self.gauges.ensure_metric_exists(name); + + let metric = Metric::::new(name.clone(), opt_unit, opt_description, SampleCollection::default()); + + self.gauges.insert(metric); } #[must_use] @@ -203,10 +227,6 @@ impl MetricCollection { Ok(()) } - - pub fn ensure_gauge_exists(&mut self, name: &MetricName) { - self.gauges.ensure_metric_exists(name); - } } #[derive(thiserror::Error, Debug, Clone)] @@ -234,7 +254,7 @@ impl Serialize for MetricCollection { S: Serializer, { #[derive(Serialize)] - #[serde(tag = "kind", rename_all = "lowercase")] + #[serde(tag = "type", rename_all = "lowercase")] enum SerializableMetric<'a> { Counter(&'a Metric), Gauge(&'a Metric), @@ -260,7 +280,7 @@ impl<'de> Deserialize<'de> for MetricCollection { D: Deserializer<'de>, { #[derive(Deserialize)] - #[serde(tag = "kind", rename_all = "lowercase")] + #[serde(tag = "type", rename_all = "lowercase")] enum MetricPayload { Counter(Metric), Gauge(Metric), @@ -336,20 +356,15 @@ impl MetricKindCollection { self.metrics.keys() } - /// # Panics - /// - /// It should not panic as long as empty sample collections are allowed. - pub fn ensure_metric_exists(&mut self, name: &MetricName) { - if !self.metrics.contains_key(name) { - self.metrics.insert( - name.clone(), - Metric::new( - name.clone(), - SampleCollection::new(vec![]).expect("Empty sample collection creation should not fail"), - ), - ); + pub fn insert_if_absent(&mut self, metric: Metric) { + if !self.metrics.contains_key(metric.name()) { + self.insert(metric); } } + + pub fn insert(&mut self, metric: Metric) { + self.metrics.insert(metric.name().clone(), metric); + } } impl MetricKindCollection { @@ -359,17 +374,18 @@ impl MetricKindCollection { /// /// Returns an error if a metric name already exists in the current collection. pub fn merge(&mut self, other: &Self) -> Result<(), Error> { - // Check for name collisions - for metric_name in other.metrics.keys() { - if self.metrics.contains_key(metric_name) { - return Err(Error::MetricNameCollisionInMerge { - metric_name: metric_name.clone(), - }); - } - } + self.check_for_name_collision(other)?; for (metric_name, metric) in &other.metrics { - if self.metrics.insert(metric_name.clone(), metric.clone()).is_some() { + self.metrics.insert(metric_name.clone(), metric.clone()); + } + + Ok(()) + } + + fn check_for_name_collision(&self, other: &Self) -> Result<(), Error> { + for metric_name in other.metrics.keys() { + if self.metrics.contains_key(metric_name) { return Err(Error::MetricNameCollisionInMerge { metric_name: metric_name.clone(), }); @@ -389,7 +405,9 @@ impl MetricKindCollection { /// /// Panics if the metric does not exist. pub fn increment(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) { - self.ensure_metric_exists(name); + let metric = Metric::::new_empty_with_name(name.clone()); + + self.insert_if_absent(metric); let metric = self.metrics.get_mut(name).expect("Counter metric should exist"); @@ -404,7 +422,9 @@ impl MetricKindCollection { /// /// Panics if the metric does not exist. pub fn absolute(&mut self, name: &MetricName, label_set: &LabelSet, value: u64, time: DurationSinceUnixEpoch) { - self.ensure_metric_exists(name); + let metric = Metric::::new_empty_with_name(name.clone()); + + self.insert_if_absent(metric); let metric = self.metrics.get_mut(name).expect("Counter metric should exist"); @@ -429,7 +449,9 @@ impl MetricKindCollection { /// /// Panics if the metric does not exist and it could not be created. pub fn set(&mut self, name: &MetricName, label_set: &LabelSet, value: f64, time: DurationSinceUnixEpoch) { - self.ensure_metric_exists(name); + let metric = Metric::::new_empty_with_name(name.clone()); + + self.insert_if_absent(metric); let metric = self.metrics.get_mut(name).expect("Gauge metric should exist"); @@ -444,7 +466,9 @@ impl MetricKindCollection { /// /// Panics if the metric does not exist and it could not be created. pub fn increment(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) { - self.ensure_metric_exists(name); + let metric = Metric::::new_empty_with_name(name.clone()); + + self.insert_if_absent(metric); let metric = self.metrics.get_mut(name).expect("Gauge metric should exist"); @@ -459,7 +483,9 @@ impl MetricKindCollection { /// /// Panics if the metric does not exist and it could not be created. pub fn decrement(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) { - self.ensure_metric_exists(name); + let metric = Metric::::new_empty_with_name(name.clone()); + + self.insert_if_absent(metric); let metric = self.metrics.get_mut(name).expect("Gauge metric should exist"); @@ -483,6 +509,7 @@ mod tests { use super::*; use crate::label::LabelValue; use crate::sample::Sample; + use crate::sample_collection::SampleCollection; use crate::tests::{format_prometheus_output, sort_lines}; use crate::{label_name, metric_name}; @@ -524,11 +551,15 @@ mod tests { MetricCollection::new( MetricKindCollection::new(vec![Metric::new( metric_name!("http_tracker_core_announce_requests_received_total"), + None, + Some(MetricDescription::new("The number of announce requests received.")), SampleCollection::new(vec![Sample::new(Counter::new(1), time, label_set_1.clone())]).unwrap(), )]) .unwrap(), MetricKindCollection::new(vec![Metric::new( metric_name!("udp_tracker_server_performance_avg_announce_processing_time_ns"), + None, + Some(MetricDescription::new("The average announce processing time in nanoseconds.")), SampleCollection::new(vec![Sample::new(Gauge::new(1.0), time, label_set_1.clone())]).unwrap(), )]) .unwrap(), @@ -540,8 +571,10 @@ mod tests { r#" [ { - "kind":"counter", + "type":"counter", "name":"http_tracker_core_announce_requests_received_total", + "unit": null, + "description": "The number of announce requests received.", "samples":[ { "value":1, @@ -564,8 +597,10 @@ mod tests { ] }, { - "kind":"gauge", + "type":"gauge", "name":"udp_tracker_server_performance_avg_announce_processing_time_ns", + "unit": null, + "description": "The average announce processing time in nanoseconds.", "samples":[ { "value":1.0, @@ -595,7 +630,11 @@ mod tests { fn prometheus() -> String { format_prometheus_output( r#" + # HELP http_tracker_core_announce_requests_received_total The number of announce requests received. + # TYPE http_tracker_core_announce_requests_received_total counter http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1 + # HELP udp_tracker_server_performance_avg_announce_processing_time_ns The average announce processing time in nanoseconds. + # TYPE udp_tracker_server_performance_avg_announce_processing_time_ns gauge udp_tracker_server_performance_avg_announce_processing_time_ns{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1 "#, ) @@ -604,10 +643,20 @@ mod tests { #[test] fn it_should_not_allow_duplicate_names_across_types() { - let counters = - MetricKindCollection::new(vec![Metric::new(metric_name!("test_metric"), SampleCollection::default())]).unwrap(); - let gauges = - MetricKindCollection::new(vec![Metric::new(metric_name!("test_metric"), SampleCollection::default())]).unwrap(); + let counters = MetricKindCollection::new(vec![Metric::new( + metric_name!("test_metric"), + None, + None, + SampleCollection::default(), + )]) + .unwrap(); + let gauges = MetricKindCollection::new(vec![Metric::new( + metric_name!("test_metric"), + None, + None, + SampleCollection::default(), + )]) + .unwrap(); assert!(MetricCollection::new(counters, gauges).is_err()); } @@ -700,6 +749,8 @@ mod tests { let metric_collection = MetricCollection::new( MetricKindCollection::new(vec![Metric::new( metric_name!("http_tracker_core_announce_requests_received_total"), + None, + None, SampleCollection::new(vec![ Sample::new(Counter::new(1), time, label_set_1.clone()), Sample::new(Counter::new(2), time, label_set_2.clone()), @@ -715,7 +766,9 @@ mod tests { let expected_prometheus_output = format_prometheus_output( r#" + # TYPE http_tracker_core_announce_requests_received_total counter http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7171",server_binding_protocol="http"} 2 + # TYPE http_tracker_core_announce_requests_received_total counter http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1 "#, ); @@ -731,8 +784,11 @@ mod tests { let mut counters = MetricKindCollection::default(); let mut gauges = MetricKindCollection::default(); - counters.ensure_metric_exists(&metric_name!("test_counter")); - gauges.ensure_metric_exists(&metric_name!("test_gauge")); + let counter = Metric::::new_empty_with_name(metric_name!("test_counter")); + counters.insert_if_absent(counter); + + let gauge = Metric::::new_empty_with_name(metric_name!("test_gauge")); + gauges.insert_if_absent(gauge); let metric_collection = MetricCollection::new(counters, gauges).unwrap(); @@ -741,6 +797,98 @@ mod tests { assert_eq!(prometheus_output, ""); } + #[test] + fn it_should_allow_merging_metric_collections() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection1 = MetricCollection::default(); + collection1 + .increase_counter(&metric_name!("test_counter"), &label_set, time) + .unwrap(); + + let mut collection2 = MetricCollection::default(); + collection2 + .set_gauge(&metric_name!("test_gauge"), &label_set, 1.0, time) + .unwrap(); + + collection1.merge(&collection2).unwrap(); + + assert!(collection1.contains_counter(&metric_name!("test_counter"))); + assert!(collection1.contains_gauge(&metric_name!("test_gauge"))); + } + + #[test] + fn it_should_not_allow_merging_metric_collections_with_name_collisions_for_the_same_metric_types() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection1 = MetricCollection::default(); + collection1 + .increase_counter(&metric_name!("test_metric"), &label_set, time) + .unwrap(); + + let mut collection2 = MetricCollection::default(); + collection2 + .increase_counter(&metric_name!("test_metric"), &label_set, time) + .unwrap(); + let result = collection1.merge(&collection2); + + assert!(result.is_err()); + } + + #[test] + fn it_should_not_allow_merging_metric_collections_with_name_collisions_for_different_metric_types() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection1 = MetricCollection::default(); + collection1 + .increase_counter(&metric_name!("test_metric"), &label_set, time) + .unwrap(); + + let mut collection2 = MetricCollection::default(); + collection2 + .set_gauge(&metric_name!("test_metric"), &label_set, 1.0, time) + .unwrap(); + + let result = collection1.merge(&collection2); + + assert!(result.is_err()); + } + + fn collection_with_one_counter(metric_name: &MetricName, label_set: &LabelSet, counter: Counter) -> MetricCollection { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + + MetricCollection::new( + MetricKindCollection::new(vec![Metric::new( + metric_name.clone(), + None, + None, + SampleCollection::new(vec![Sample::new(counter, time, label_set.clone())]).unwrap(), + )]) + .unwrap(), + MetricKindCollection::default(), + ) + .unwrap() + } + + fn collection_with_one_gauge(metric_name: &MetricName, label_set: &LabelSet, gauge: Gauge) -> MetricCollection { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + + MetricCollection::new( + MetricKindCollection::default(), + MetricKindCollection::new(vec![Metric::new( + metric_name.clone(), + None, + None, + SampleCollection::new(vec![Sample::new(gauge, time, label_set.clone())]).unwrap(), + )]) + .unwrap(), + ) + .unwrap() + } + mod for_counters { use pretty_assertions::assert_eq; @@ -748,32 +896,57 @@ mod tests { use super::*; use crate::label::LabelValue; use crate::sample::Sample; + use crate::sample_collection::SampleCollection; #[test] - fn it_should_increase_a_preexistent_counter() { + fn it_should_allow_setting_to_an_absolute_value() { let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_counter"); let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); - let mut metric_collection = MetricCollection::new( - MetricKindCollection::new(vec![Metric::new( - metric_name!("test_counter"), - SampleCollection::new(vec![Sample::new(Counter::new(0), time, label_set.clone())]).unwrap(), - )]) - .unwrap(), - MetricKindCollection::default(), - ) - .unwrap(); + let mut collection = collection_with_one_counter(&metric_name, &label_set, Counter::new(0)); - metric_collection - .increase_counter(&metric_name!("test_counter"), &label_set, time) + collection + .set_counter(&metric_name!("test_counter"), &label_set, 1, time) .unwrap(); - metric_collection + + assert_eq!( + collection.get_counter_value(&metric_name!("test_counter"), &label_set), + Some(Counter::new(1)) + ); + } + + #[test] + fn it_should_fail_setting_to_an_absolute_value_if_a_gauge_with_the_same_name_exists() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_counter"); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection = collection_with_one_gauge(&metric_name, &label_set, Gauge::new(0.0)); + + let result = collection.set_counter(&metric_name!("test_counter"), &label_set, 1, time); + + assert!( + result.is_err() + && matches!(result, Err(Error::MetricNameCollisionAdding { metric_name }) if metric_name == metric_name!("test_counter")) + ); + } + + #[test] + fn it_should_increase_a_preexistent_counter() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_counter"); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection = collection_with_one_counter(&metric_name, &label_set, Counter::new(0)); + + collection .increase_counter(&metric_name!("test_counter"), &label_set, time) .unwrap(); assert_eq!( - metric_collection.get_counter_value(&metric_name!("test_counter"), &label_set), - Some(Counter::new(2)) + collection.get_counter_value(&metric_name!("test_counter"), &label_set), + Some(Counter::new(1)) ); } @@ -798,16 +971,6 @@ mod tests { ); } - #[test] - fn it_should_allow_making_sure_a_counter_exists_without_increasing_it() { - let mut metric_collection = - MetricCollection::new(MetricKindCollection::default(), MetricKindCollection::default()).unwrap(); - - metric_collection.ensure_counter_exists(&metric_name!("test_counter")); - - assert!(metric_collection.contains_counter(&metric_name!("test_counter"))); - } - #[test] fn it_should_allow_describing_a_counter_before_using_it() { let mut metric_collection = @@ -826,10 +989,14 @@ mod tests { let result = MetricKindCollection::new(vec![ Metric::new( metric_name!("test_counter"), + None, + None, SampleCollection::new(vec![Sample::new(Counter::new(0), time, label_set.clone())]).unwrap(), ), Metric::new( metric_name!("test_counter"), + None, + None, SampleCollection::new(vec![Sample::new(Counter::new(0), time, label_set.clone())]).unwrap(), ), ]); @@ -845,58 +1012,110 @@ mod tests { use super::*; use crate::label::LabelValue; use crate::sample::Sample; + use crate::sample_collection::SampleCollection; #[test] fn it_should_set_a_preexistent_gauge() { let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_gauge"); let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); - let mut metric_collection = MetricCollection::new( - MetricKindCollection::default(), - MetricKindCollection::new(vec![Metric::new( - metric_name!("test_gauge"), - SampleCollection::new(vec![Sample::new(Gauge::new(0.0), time, label_set.clone())]).unwrap(), - )]) - .unwrap(), - ) - .unwrap(); + let mut collection = collection_with_one_gauge(&metric_name, &label_set, Gauge::new(0.0)); - metric_collection + collection .set_gauge(&metric_name!("test_gauge"), &label_set, 1.0, time) .unwrap(); assert_eq!( - metric_collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), + collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), Some(Gauge::new(1.0)) ); } #[test] - fn it_should_automatically_create_a_gauge_when_setting_if_it_does_not_exist() { + fn it_should_allow_incrementing_a_gauge() { let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_gauge"); let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); - let mut metric_collection = - MetricCollection::new(MetricKindCollection::default(), MetricKindCollection::default()).unwrap(); + let mut collection = collection_with_one_gauge(&metric_name, &label_set, Gauge::new(0.0)); - metric_collection - .set_gauge(&metric_name!("test_gauge"), &label_set, 1.0, time) + collection + .increment_gauge(&metric_name!("test_gauge"), &label_set, time) .unwrap(); assert_eq!( - metric_collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), + collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), Some(Gauge::new(1.0)) ); } #[test] - fn it_should_allow_making_sure_a_gauge_exists_without_setting_it() { + fn it_should_fail_incrementing_a_gauge_if_it_exists_a_counter_with_the_same_name() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_gauge"); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection = collection_with_one_counter(&metric_name, &label_set, Counter::new(0)); + + let result = collection.increment_gauge(&metric_name!("test_gauge"), &label_set, time); + + assert!( + result.is_err() + && matches!(result, Err(Error::MetricNameCollisionAdding { metric_name }) if metric_name == metric_name!("test_gauge")) + ); + } + + #[test] + fn it_should_allow_decrementing_a_gauge() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_gauge"); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection = collection_with_one_gauge(&metric_name, &label_set, Gauge::new(1.0)); + + collection + .decrement_gauge(&metric_name!("test_gauge"), &label_set, time) + .unwrap(); + + assert_eq!( + collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), + Some(Gauge::new(0.0)) + ); + } + + #[test] + fn it_should_fail_decrementing_a_gauge_if_it_exists_a_counter_with_the_same_name() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let metric_name = metric_name!("test_gauge"); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + + let mut collection = collection_with_one_counter(&metric_name, &label_set, Counter::new(0)); + + let result = collection.decrement_gauge(&metric_name!("test_gauge"), &label_set, time); + + assert!( + result.is_err() + && matches!(result, Err(Error::MetricNameCollisionAdding { metric_name }) if metric_name == metric_name!("test_gauge")) + ); + } + + #[test] + fn it_should_automatically_create_a_gauge_when_setting_if_it_does_not_exist() { + let time = DurationSinceUnixEpoch::from_secs(1_743_552_000); + let label_set: LabelSet = (label_name!("label_name"), LabelValue::new("value")).into(); + let mut metric_collection = MetricCollection::new(MetricKindCollection::default(), MetricKindCollection::default()).unwrap(); - metric_collection.ensure_gauge_exists(&metric_name!("test_gauge")); + metric_collection + .set_gauge(&metric_name!("test_gauge"), &label_set, 1.0, time) + .unwrap(); - assert!(metric_collection.contains_gauge(&metric_name!("test_gauge"))); + assert_eq!( + metric_collection.get_gauge_value(&metric_name!("test_gauge"), &label_set), + Some(Gauge::new(1.0)) + ); } #[test] @@ -917,10 +1136,14 @@ mod tests { let result = MetricKindCollection::new(vec![ Metric::new( metric_name!("test_gauge"), + None, + None, SampleCollection::new(vec![Sample::new(Gauge::new(0.0), time, label_set.clone())]).unwrap(), ), Metric::new( metric_name!("test_gauge"), + None, + None, SampleCollection::new(vec![Sample::new(Gauge::new(0.0), time, label_set.clone())]).unwrap(), ), ]); @@ -928,4 +1151,45 @@ mod tests { assert!(result.is_err()); } } + + mod metric_kind_collection { + + use crate::counter::Counter; + use crate::gauge::Gauge; + use crate::metric::Metric; + use crate::metric_collection::{Error, MetricKindCollection}; + use crate::metric_name; + + #[test] + fn it_should_not_allow_merging_counter_metric_collections_with_name_collisions() { + let mut collection1 = MetricKindCollection::::default(); + collection1.insert(Metric::::new_empty_with_name(metric_name!("test_metric"))); + + let mut collection2 = MetricKindCollection::::default(); + collection2.insert(Metric::::new_empty_with_name(metric_name!("test_metric"))); + + let result = collection1.merge(&collection2); + + assert!( + result.is_err() + && matches!(result, Err(Error::MetricNameCollisionInMerge { metric_name }) if metric_name == metric_name!("test_metric")) + ); + } + + #[test] + fn it_should_not_allow_merging_gauge_metric_collections_with_name_collisions() { + let mut collection1 = MetricKindCollection::::default(); + collection1.insert(Metric::::new_empty_with_name(metric_name!("test_metric"))); + + let mut collection2 = MetricKindCollection::::default(); + collection2.insert(Metric::::new_empty_with_name(metric_name!("test_metric"))); + + let result = collection1.merge(&collection2); + + assert!( + result.is_err() + && matches!(result, Err(Error::MetricNameCollisionInMerge { metric_name }) if metric_name == metric_name!("test_metric")) + ); + } + } } diff --git a/packages/metrics/src/sample_collection.rs b/packages/metrics/src/sample_collection.rs index e815f26ec..a87aacb63 100644 --- a/packages/metrics/src/sample_collection.rs +++ b/packages/metrics/src/sample_collection.rs @@ -168,10 +168,8 @@ mod tests { use crate::counter::Counter; use crate::label::LabelSet; - use crate::prometheus::PrometheusSerializable; use crate::sample::Sample; use crate::sample_collection::SampleCollection; - use crate::tests::format_prometheus_output; fn sample_update_time() -> DurationSinceUnixEpoch { DurationSinceUnixEpoch::from_secs(1_743_552_000) @@ -242,56 +240,74 @@ mod tests { assert!(!collection.is_empty()); } - #[test] - fn it_should_be_serializable_and_deserializable_for_json_format() { - let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default()); - let collection = SampleCollection::new(vec![sample]).unwrap(); + mod json_serialization { + use crate::counter::Counter; + use crate::label::LabelSet; + use crate::sample::Sample; + use crate::sample_collection::tests::sample_update_time; + use crate::sample_collection::SampleCollection; - let serialized = serde_json::to_string(&collection).unwrap(); - let deserialized: SampleCollection = serde_json::from_str(&serialized).unwrap(); + #[test] + fn it_should_be_serializable_and_deserializable_for_json_format() { + let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default()); + let collection = SampleCollection::new(vec![sample]).unwrap(); - assert_eq!(deserialized, collection); - } + let serialized = serde_json::to_string(&collection).unwrap(); + let deserialized: SampleCollection = serde_json::from_str(&serialized).unwrap(); - #[test] - fn it_should_fail_deserializing_from_json_with_duplicate_label_sets() { - let samples = vec![ - Sample::new(Counter::default(), sample_update_time(), LabelSet::default()), - Sample::new(Counter::default(), sample_update_time(), LabelSet::default()), - ]; + assert_eq!(deserialized, collection); + } - let serialized = serde_json::to_string(&samples).unwrap(); + #[test] + fn it_should_fail_deserializing_from_json_with_duplicate_label_sets() { + let samples = vec![ + Sample::new(Counter::default(), sample_update_time(), LabelSet::default()), + Sample::new(Counter::default(), sample_update_time(), LabelSet::default()), + ]; - let result: Result, _> = serde_json::from_str(&serialized); + let serialized = serde_json::to_string(&samples).unwrap(); - assert!(result.is_err()); + let result: Result, _> = serde_json::from_str(&serialized); + + assert!(result.is_err()); + } } - #[test] - fn it_should_be_exportable_to_prometheus_format_when_empty() { - let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default()); - let collection = SampleCollection::new(vec![sample]).unwrap(); + mod prometheus_serialization { + use crate::counter::Counter; + use crate::label::LabelSet; + use crate::prometheus::PrometheusSerializable; + use crate::sample::Sample; + use crate::sample_collection::tests::sample_update_time; + use crate::sample_collection::SampleCollection; + use crate::tests::format_prometheus_output; - let prometheus_output = collection.to_prometheus(); + #[test] + fn it_should_be_exportable_to_prometheus_format_when_empty() { + let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default()); + let collection = SampleCollection::new(vec![sample]).unwrap(); - assert!(!prometheus_output.is_empty()); - } + let prometheus_output = collection.to_prometheus(); - #[test] - fn it_should_be_exportable_to_prometheus_format() { - let sample = Sample::new( - Counter::new(1), - sample_update_time(), - LabelSet::from(vec![("labe_name_1", "label value value 1")]), - ); + assert!(!prometheus_output.is_empty()); + } - let collection = SampleCollection::new(vec![sample]).unwrap(); + #[test] + fn it_should_be_exportable_to_prometheus_format() { + let sample = Sample::new( + Counter::new(1), + sample_update_time(), + LabelSet::from(vec![("labe_name_1", "label value value 1")]), + ); - let prometheus_output = collection.to_prometheus(); + let collection = SampleCollection::new(vec![sample]).unwrap(); - let expected_prometheus_output = format_prometheus_output("{labe_name_1=\"label value value 1\"} 1"); + let prometheus_output = collection.to_prometheus(); - assert_eq!(prometheus_output, expected_prometheus_output); + let expected_prometheus_output = format_prometheus_output("{labe_name_1=\"label value value 1\"} 1"); + + assert_eq!(prometheus_output, expected_prometheus_output); + } } #[cfg(test)] diff --git a/packages/metrics/src/unit.rs b/packages/metrics/src/unit.rs index f7a528bed..43b42bf79 100644 --- a/packages/metrics/src/unit.rs +++ b/packages/metrics/src/unit.rs @@ -4,7 +4,11 @@ //! The `Unit` enum is used to specify the unit of measurement for metrics. //! //! They were copied from the `metrics` crate, to allow future compatibility. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum Unit { Count, Percent, diff --git a/packages/swarm-coordination-registry/src/statistics/mod.rs b/packages/swarm-coordination-registry/src/statistics/mod.rs index cfc252e34..6505a2db2 100644 --- a/packages/swarm-coordination-registry/src/statistics/mod.rs +++ b/packages/swarm-coordination-registry/src/statistics/mod.rs @@ -36,31 +36,31 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_TORRENTS_ADDED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of torrents added.")), + Some(MetricDescription::new("The total number of torrents added.")), ); metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_TORRENTS_REMOVED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of torrents removed.")), + Some(MetricDescription::new("The total number of torrents removed.")), ); metrics.metric_collection.describe_gauge( &metric_name!(TORRENT_REPOSITORY_TORRENTS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of torrents.")), + Some(MetricDescription::new("The total number of torrents.")), ); metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_TORRENTS_DOWNLOADS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of torrent downloads.")), + Some(MetricDescription::new("The total number of torrent downloads.")), ); metrics.metric_collection.describe_gauge( &metric_name!(TORRENT_REPOSITORY_TORRENTS_INACTIVE_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of inactive torrents.")), + Some(MetricDescription::new("The total number of inactive torrents.")), ); // Peers metrics @@ -68,25 +68,25 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_PEERS_ADDED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of peers added.")), + Some(MetricDescription::new("The total number of peers added.")), ); metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_PEERS_REMOVED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of peers removed.")), + Some(MetricDescription::new("The total number of peers removed.")), ); metrics.metric_collection.describe_counter( &metric_name!(TORRENT_REPOSITORY_PEERS_UPDATED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of peers updated.")), + Some(MetricDescription::new("The total number of peers updated.")), ); metrics.metric_collection.describe_gauge( &metric_name!(TORRENT_REPOSITORY_PEER_CONNECTIONS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new( + Some(MetricDescription::new( "The total number of peer connections (one connection per torrent).", )), ); @@ -94,13 +94,13 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_gauge( &metric_name!(TORRENT_REPOSITORY_UNIQUE_PEERS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of unique peers.")), + Some(MetricDescription::new("The total number of unique peers.")), ); metrics.metric_collection.describe_gauge( &metric_name!(TORRENT_REPOSITORY_PEERS_INACTIVE_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of inactive peers.")), + Some(MetricDescription::new("The total number of inactive peers.")), ); metrics diff --git a/packages/tracker-core/src/statistics/mod.rs b/packages/tracker-core/src/statistics/mod.rs index ff8187379..fdb8e8fd4 100644 --- a/packages/tracker-core/src/statistics/mod.rs +++ b/packages/tracker-core/src/statistics/mod.rs @@ -21,7 +21,7 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("The total number of torrent downloads (persisted).")), + Some(MetricDescription::new("The total number of torrent downloads (persisted).")), ); metrics diff --git a/packages/udp-tracker-core/src/statistics/mod.rs b/packages/udp-tracker-core/src/statistics/mod.rs index 9eb85d7f1..fec76069e 100644 --- a/packages/udp-tracker-core/src/statistics/mod.rs +++ b/packages/udp-tracker-core/src/statistics/mod.rs @@ -17,7 +17,7 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_CORE_REQUESTS_RECEIVED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP requests received")), + Some(MetricDescription::new("Total number of UDP requests received")), ); metrics diff --git a/packages/udp-tracker-server/src/statistics/mod.rs b/packages/udp-tracker-server/src/statistics/mod.rs index 5c30a9abc..a7da2dc63 100644 --- a/packages/udp-tracker-server/src/statistics/mod.rs +++ b/packages/udp-tracker-server/src/statistics/mod.rs @@ -24,49 +24,49 @@ pub fn describe_metrics() -> Metrics { metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_REQUESTS_ABORTED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP requests aborted")), + Some(MetricDescription::new("Total number of UDP requests aborted")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_REQUESTS_BANNED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP requests banned")), + Some(MetricDescription::new("Total number of UDP requests banned")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_CONNECTION_ID_ERRORS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of requests with connection ID errors")), + Some(MetricDescription::new("Total number of requests with connection ID errors")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_REQUESTS_RECEIVED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP requests received")), + Some(MetricDescription::new("Total number of UDP requests received")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_REQUESTS_ACCEPTED_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP requests accepted")), + Some(MetricDescription::new("Total number of UDP requests accepted")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_RESPONSES_SENT_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of UDP responses sent")), + Some(MetricDescription::new("Total number of UDP responses sent")), ); metrics.metric_collection.describe_counter( &metric_name!(UDP_TRACKER_SERVER_ERRORS_TOTAL), Some(Unit::Count), - Some(&MetricDescription::new("Total number of errors processing UDP requests")), + Some(MetricDescription::new("Total number of errors processing UDP requests")), ); metrics.metric_collection.describe_gauge( &metric_name!(UDP_TRACKER_SERVER_PERFORMANCE_AVG_PROCESSING_TIME_NS), Some(Unit::Nanoseconds), - Some(&MetricDescription::new( + Some(MetricDescription::new( "Average time to process a UDP connect request in nanoseconds", )), );