diff --git a/packages/metrics/src/gauge.rs b/packages/metrics/src/gauge.rs index 61ff3024c..3f6089955 100644 --- a/packages/metrics/src/gauge.rs +++ b/packages/metrics/src/gauge.rs @@ -20,6 +20,14 @@ impl Gauge { pub fn set(&mut self, value: f64) { self.0 = value; } + + pub fn increment(&mut self, value: f64) { + self.0 += value; + } + + pub fn decrement(&mut self, value: f64) { + self.0 -= value; + } } impl From for Gauge { @@ -72,6 +80,20 @@ mod tests { assert_relative_eq!(gauge.value(), 1.0); } + #[test] + fn it_could_be_incremented() { + let mut gauge = Gauge::new(0.0); + gauge.increment(1.0); + assert_relative_eq!(gauge.value(), 1.0); + } + + #[test] + fn it_could_be_decremented() { + let mut gauge = Gauge::new(1.0); + gauge.decrement(1.0); + assert_relative_eq!(gauge.value(), 0.0); + } + #[test] fn it_serializes_to_prometheus() { let counter = Gauge::new(42.0); diff --git a/packages/metrics/src/metric/mod.rs b/packages/metrics/src/metric/mod.rs index ecce90f18..05779f09f 100644 --- a/packages/metrics/src/metric/mod.rs +++ b/packages/metrics/src/metric/mod.rs @@ -61,6 +61,14 @@ impl Metric { pub fn set(&mut self, label_set: &LabelSet, value: f64, time: DurationSinceUnixEpoch) { self.sample_collection.set(label_set, value, time); } + + pub fn increment(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) { + self.sample_collection.increment(label_set, time); + } + + pub fn decrement(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) { + self.sample_collection.decrement(label_set, time); + } } impl PrometheusSerializable for Metric { diff --git a/packages/metrics/src/metric_collection.rs b/packages/metrics/src/metric_collection.rs index 9e89c3c4b..438f3b03a 100644 --- a/packages/metrics/src/metric_collection.rs +++ b/packages/metrics/src/metric_collection.rs @@ -136,6 +136,38 @@ impl MetricCollection { Ok(()) } + /// # Errors + /// + /// Return an error if a metrics of a different type with the same name + /// already exists. + pub fn increase_gauge(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) -> Result<(), Error> { + if self.counters.metrics.contains_key(name) { + return Err(Error::MetricNameCollisionAdding { + metric_name: name.clone(), + }); + } + + self.gauges.increment(name, label_set, time); + + Ok(()) + } + + /// # Errors + /// + /// Return an error if a metrics of a different type with the same name + /// already exists. + pub fn decrease_gauge(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) -> Result<(), Error> { + if self.counters.metrics.contains_key(name) { + return Err(Error::MetricNameCollisionAdding { + metric_name: name.clone(), + }); + } + + self.gauges.decrement(name, label_set, time); + + Ok(()) + } + pub fn ensure_gauge_exists(&mut self, name: &MetricName) { self.gauges.ensure_metric_exists(name); } @@ -353,6 +385,36 @@ impl MetricKindCollection { metric.set(label_set, value, time); } + /// Increments the gauge for the given metric name and labels. + /// + /// If the metric name does not exist, it will be created. + /// + /// # Panics + /// + /// 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 = self.metrics.get_mut(name).expect("Gauge metric should exist"); + + metric.increment(label_set, time); + } + + /// Decrements the gauge for the given metric name and labels. + /// + /// If the metric name does not exist, it will be created. + /// + /// # Panics + /// + /// 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 = self.metrics.get_mut(name).expect("Gauge metric should exist"); + + metric.decrement(label_set, time); + } + #[must_use] pub fn get_value(&self, name: &MetricName, label_set: &LabelSet) -> Option { self.metrics diff --git a/packages/metrics/src/sample.rs b/packages/metrics/src/sample.rs index 5567dffec..4621c9906 100644 --- a/packages/metrics/src/sample.rs +++ b/packages/metrics/src/sample.rs @@ -64,6 +64,14 @@ impl Sample { pub fn set(&mut self, value: f64, time: DurationSinceUnixEpoch) { self.measurement.set(value, time); } + + pub fn increment(&mut self, time: DurationSinceUnixEpoch) { + self.measurement.increment(time); + } + + pub fn decrement(&mut self, time: DurationSinceUnixEpoch) { + self.measurement.decrement(time); + } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -121,6 +129,16 @@ impl Measurement { self.value.set(value); self.set_recorded_at(time); } + + pub fn increment(&mut self, time: DurationSinceUnixEpoch) { + self.value.increment(1.0); + self.set_recorded_at(time); + } + + pub fn decrement(&mut self, time: DurationSinceUnixEpoch) { + self.value.decrement(1.0); + self.set_recorded_at(time); + } } /// Serializes the `recorded_at` field as a string in ISO 8601 format (RFC 3339). @@ -273,7 +291,7 @@ mod tests { } #[test] - fn it_should_allow_incrementing_the_counter() { + fn it_should_allow_setting_a_value() { let mut sample = Sample::new(Gauge::default(), DurationSinceUnixEpoch::default(), LabelSet::default()); sample.set(1.0, updated_at_time()); @@ -281,6 +299,24 @@ mod tests { assert_eq!(sample.value(), &Gauge::new(1.0)); } + #[test] + fn it_should_allow_incrementing_the_value() { + let mut sample = Sample::new(Gauge::new(0.0), DurationSinceUnixEpoch::default(), LabelSet::default()); + + sample.increment(updated_at_time()); + + assert_eq!(sample.value(), &Gauge::new(1.0)); + } + + #[test] + fn it_should_allow_decrementing_the_value() { + let mut sample = Sample::new(Gauge::new(1.0), DurationSinceUnixEpoch::default(), LabelSet::default()); + + sample.decrement(updated_at_time()); + + assert_eq!(sample.value(), &Gauge::new(0.0)); + } + #[test] fn it_should_record_the_latest_update_time_when_the_counter_is_incremented() { let mut sample = Sample::new(Gauge::default(), DurationSinceUnixEpoch::default(), LabelSet::default()); diff --git a/packages/metrics/src/sample_collection.rs b/packages/metrics/src/sample_collection.rs index 49c839673..ea6b4d4af 100644 --- a/packages/metrics/src/sample_collection.rs +++ b/packages/metrics/src/sample_collection.rs @@ -90,6 +90,24 @@ impl SampleCollection { sample.set(value, time); } + + pub fn increment(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) { + let sample = self + .samples + .entry(label_set.clone()) + .or_insert_with(|| Measurement::new(Gauge::default(), time)); + + sample.increment(time); + } + + pub fn decrement(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) { + let sample = self + .samples + .entry(label_set.clone()) + .or_insert_with(|| Measurement::new(Gauge::default(), time)); + + sample.decrement(time); + } } impl Serialize for SampleCollection { @@ -278,7 +296,7 @@ mod tests { #[test] fn it_should_increment_the_counter_for_a_preexisting_label_set() { let label_set = LabelSet::default(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); // Initialize the sample collection.increment(&label_set, sample_update_time()); @@ -296,7 +314,7 @@ mod tests { #[test] fn it_should_allow_increment_the_counter_for_a_non_existent_label_set() { let label_set = LabelSet::default(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); // Increment a non-existent label collection.increment(&label_set, sample_update_time()); @@ -312,7 +330,7 @@ mod tests { let label_set = LabelSet::default(); let initial_time = sample_update_time(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); collection.increment(&label_set, initial_time); // Increment with a new time @@ -330,7 +348,7 @@ mod tests { let label2 = LabelSet::from([("name", "value2")]); let now = sample_update_time(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); collection.increment(&label1, now); collection.increment(&label2, now); @@ -351,9 +369,9 @@ mod tests { use crate::gauge::Gauge; #[test] - fn it_should_increment_the_gauge_for_a_preexisting_label_set() { + fn it_should_allow_setting_the_gauge_for_a_preexisting_label_set() { let label_set = LabelSet::default(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); // Initialize the sample collection.set(&label_set, 1.0, sample_update_time()); @@ -369,9 +387,9 @@ mod tests { } #[test] - fn it_should_allow_increment_the_gauge_for_a_non_existent_label_set() { + fn it_should_allow_setting_the_gauge_for_a_non_existent_label_set() { let label_set = LabelSet::default(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); // Set a non-existent label collection.set(&label_set, 1.0, sample_update_time()); @@ -383,11 +401,11 @@ mod tests { } #[test] - fn it_should_update_the_latest_update_time_when_incremented() { + fn it_should_update_the_latest_update_time_when_setting() { let label_set = LabelSet::default(); let initial_time = sample_update_time(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); collection.set(&label_set, 1.0, initial_time); // Set with a new time @@ -400,12 +418,12 @@ mod tests { } #[test] - fn it_should_increment_the_gauge_for_multiple_labels() { + fn it_should_allow_setting_the_gauge_for_multiple_labels() { let label1 = LabelSet::from([("name", "value1")]); let label2 = LabelSet::from([("name", "value2")]); let now = sample_update_time(); - let mut collection = SampleCollection::default(); + let mut collection = SampleCollection::::default(); collection.set(&label1, 1.0, now); collection.set(&label2, 2.0, now); @@ -414,5 +432,33 @@ mod tests { assert_eq!(collection.get(&label2).unwrap().value(), &Gauge::new(2.0)); assert_eq!(collection.len(), 2); } + + #[test] + fn it_should_allow_incrementing_the_gauge() { + let label_set = LabelSet::default(); + let mut collection = SampleCollection::::default(); + + // Initialize the sample + collection.set(&label_set, 1.0, sample_update_time()); + + // Increment + collection.increment(&label_set, sample_update_time()); + let sample = collection.get(&label_set).unwrap(); + assert_eq!(*sample.value(), Gauge::new(2.0)); + } + + #[test] + fn it_should_allow_decrementing_the_gauge() { + let label_set = LabelSet::default(); + let mut collection = SampleCollection::::default(); + + // Initialize the sample + collection.set(&label_set, 1.0, sample_update_time()); + + // Increment + collection.decrement(&label_set, sample_update_time()); + let sample = collection.get(&label_set).unwrap(); + assert_eq!(*sample.value(), Gauge::new(0.0)); + } } }