From 8ed53743c18bf4cc27faf11fe74da905983148fc Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 19 Jan 2023 01:17:23 +0100 Subject: [PATCH 1/4] WIP timestamp support Signed-off-by: Lars Strojny --- examples/custom-metric.rs | 2 +- src/encoding.rs | 9 ++++++++- src/encoding/text.rs | 35 +++++++++++++++++++++++++++++++++++ src/metrics/counter.rs | 26 +++++++++++++++++++++++--- src/metrics/exemplar.rs | 2 +- 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/examples/custom-metric.rs b/examples/custom-metric.rs index 9ad17a5a..afb46e32 100644 --- a/examples/custom-metric.rs +++ b/examples/custom-metric.rs @@ -20,7 +20,7 @@ impl EncodeMetric for MyCustomMetric { // E.g. every CPU cycle spend in this method delays the response send to // the Prometheus server. - encoder.encode_counter::<(), _, u64>(&rand::random::(), None) + encoder.encode_counter::<(), _, u64>(&rand::random::(), None, None) } fn metric_type(&self) -> prometheus_client::metrics::MetricType { diff --git a/src/encoding.rs b/src/encoding.rs index b59f2450..e8bf79f3 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; use std::ops::Deref; +use std::time::SystemTime; #[cfg(feature = "protobuf")] pub mod protobuf; @@ -98,8 +99,14 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { &mut self, v: &CounterValue, exemplar: Option<&Exemplar>, + timestamp: Option, ) -> Result<(), std::fmt::Error> { - for_both_mut!(self, MetricEncoderInner, e, e.encode_counter(v, exemplar)) + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_counter(v, exemplar, timestamp) + ) } /// Encode a gauge. diff --git a/src/encoding/text.rs b/src/encoding/text.rs index dea22e05..79dc41ed 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -31,6 +31,7 @@ use crate::registry::{Descriptor, Registry, Unit}; use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; +use std::time::{SystemTime, UNIX_EPOCH}; /// Encode the metrics registered with the provided [`Registry`] into the /// provided [`Write`]r using the OpenMetrics text format. @@ -136,6 +137,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { &mut self, v: &CounterValue, exemplar: Option<&Exemplar>, + timestamp: Option, ) -> Result<(), std::fmt::Error> { self.write_name_and_unit()?; @@ -150,6 +152,10 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { .into(), )?; + if let Some(timestamp) = timestamp { + self.write_timestamp(timestamp)?; + } + if let Some(exemplar) = exemplar { self.encode_exemplar(exemplar)?; } @@ -336,6 +342,15 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { Ok(()) } + + fn write_timestamp(&mut self, timestamp: SystemTime) -> Result<(), std::fmt::Error> { + if let Ok(time) = timestamp.duration_since(UNIX_EPOCH) { + self.writer.write_char(' ')?; + return self.writer.write_str(&time.as_secs().to_string()); + } + + Ok(()) + } } pub(crate) struct CounterValueEncoder<'a> { @@ -507,6 +522,7 @@ impl<'a> std::fmt::Write for LabelValueEncoder<'a> { #[cfg(test)] mod tests { use super::*; + use crate::metrics::counter::ConstCounter; use crate::metrics::exemplar::HistogramWithExemplars; use crate::metrics::family::Family; use crate::metrics::gauge::Gauge; @@ -515,6 +531,8 @@ mod tests { use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar}; use pyo3::{prelude::*, types::PyModule}; use std::borrow::Cow; + use std::ops::Add; + use std::time::Duration; #[test] fn encode_counter() { @@ -548,6 +566,23 @@ mod tests { parse_with_python_client(encoded); } + #[test] + fn encode_const_counter_with_timestamp() { + let mut registry = Registry::default(); + let counter = + ConstCounter::new_with_timestamp(123, UNIX_EPOCH.add(Duration::from_secs(1674086890))); + registry.register("my_counter", "My counter", counter); + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP my_counter My counter.\n".to_owned() + + "# TYPE my_counter counter\n" + + "my_counter_total 123 1674086890\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + } + #[test] fn encode_counter_with_exemplar() { let mut registry = Registry::default(); diff --git a/src/metrics/counter.rs b/src/metrics/counter.rs index c1c5e511..70a6a678 100644 --- a/src/metrics/counter.rs +++ b/src/metrics/counter.rs @@ -10,6 +10,7 @@ use std::marker::PhantomData; use std::sync::atomic::AtomicU64; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; +use std::time::SystemTime; /// Open Metrics [`Counter`] to measure discrete events. /// @@ -179,7 +180,7 @@ where A: Atomic, { fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { - encoder.encode_counter::<(), _, u64>(&self.get(), None) + encoder.encode_counter::<(), _, u64>(&self.get(), None, None) } fn metric_type(&self) -> MetricType { @@ -193,12 +194,24 @@ where #[derive(Debug, Default)] pub struct ConstCounter { value: N, + timestamp: Option, } impl ConstCounter { /// Creates a new [`ConstCounter`]. pub fn new(value: N) -> Self { - Self { value } + Self { + value, + timestamp: None, + } + } + + /// Creates a new [`ConstCounter`] with a timestamp. + pub fn new_with_timestamp(value: N, timestamp: SystemTime) -> Self { + Self { + value, + timestamp: Some(timestamp), + } } } @@ -211,7 +224,7 @@ where N: crate::encoding::EncodeCounterValue, { fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { - encoder.encode_counter::<(), _, u64>(&self.value, None) + encoder.encode_counter::<(), _, u64>(&self.value, None, self.timestamp) } fn metric_type(&self) -> MetricType { @@ -223,6 +236,7 @@ where mod tests { use super::*; use quickcheck::QuickCheck; + use std::time::UNIX_EPOCH; #[test] fn inc_and_get() { @@ -250,4 +264,10 @@ mod tests { QuickCheck::new().tests(10).quickcheck(prop as fn(_)) } + + #[test] + fn const_counter_with_timestamp() { + let counter = ConstCounter::new_with_timestamp(123, UNIX_EPOCH); + assert_eq!(UNIX_EPOCH, counter.timestamp.unwrap()); + } } diff --git a/src/metrics/exemplar.rs b/src/metrics/exemplar.rs index c8478c6a..229347dd 100644 --- a/src/metrics/exemplar.rs +++ b/src/metrics/exemplar.rs @@ -157,7 +157,7 @@ where { fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { let (value, exemplar) = self.get(); - encoder.encode_counter(&value, exemplar.as_ref()) + encoder.encode_counter(&value, exemplar.as_ref(), None) } fn metric_type(&self) -> MetricType { From 766a970d70504251525df4c44b51f9532b8fa3c1 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 19 Jan 2023 09:20:27 +0100 Subject: [PATCH 2/4] Should be milliseconds according to https://prometheus.io/docs/instrumenting/exposition_formats/#:~:text=The%20timestamp%20is%20an%20int64 Signed-off-by: Lars Strojny --- src/encoding/text.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 79dc41ed..f9c4ba38 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -346,7 +346,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> { fn write_timestamp(&mut self, timestamp: SystemTime) -> Result<(), std::fmt::Error> { if let Ok(time) = timestamp.duration_since(UNIX_EPOCH) { self.writer.write_char(' ')?; - return self.writer.write_str(&time.as_secs().to_string()); + return self.writer.write_str(&time.as_millis().to_string()); } Ok(()) @@ -578,9 +578,11 @@ mod tests { let expected = "# HELP my_counter My counter.\n".to_owned() + "# TYPE my_counter counter\n" - + "my_counter_total 123 1674086890\n" + + "my_counter_total 123 1674086890000\n" + "# EOF\n"; assert_eq!(expected, encoded); + + parse_with_python_client(encoded); } #[test] From 3f135a7941233857c087475136c09067f82283c1 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 19 Jan 2023 09:21:26 +0100 Subject: [PATCH 3/4] Include millisecond precision in test case Signed-off-by: Lars Strojny --- src/encoding/text.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoding/text.rs b/src/encoding/text.rs index f9c4ba38..2d9e0e87 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -570,7 +570,7 @@ mod tests { fn encode_const_counter_with_timestamp() { let mut registry = Registry::default(); let counter = - ConstCounter::new_with_timestamp(123, UNIX_EPOCH.add(Duration::from_secs(1674086890))); + ConstCounter::new_with_timestamp(123, UNIX_EPOCH.add(Duration::from_millis(1674086890123))); registry.register("my_counter", "My counter", counter); let mut encoded = String::new(); @@ -578,7 +578,7 @@ mod tests { let expected = "# HELP my_counter My counter.\n".to_owned() + "# TYPE my_counter counter\n" - + "my_counter_total 123 1674086890000\n" + + "my_counter_total 123 1674086890123\n" + "# EOF\n"; assert_eq!(expected, encoded); From 7bfb7c4c1dc8ae8938d7f9cc651e3ba845d9cf66 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 19 Jan 2023 09:21:38 +0100 Subject: [PATCH 4/4] Formatting Signed-off-by: Lars Strojny --- src/encoding/text.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 2d9e0e87..4e9f5a3b 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -569,8 +569,10 @@ mod tests { #[test] fn encode_const_counter_with_timestamp() { let mut registry = Registry::default(); - let counter = - ConstCounter::new_with_timestamp(123, UNIX_EPOCH.add(Duration::from_millis(1674086890123))); + let counter = ConstCounter::new_with_timestamp( + 123, + UNIX_EPOCH.add(Duration::from_millis(1674086890123)), + ); registry.register("my_counter", "My counter", counter); let mut encoded = String::new();