From 29f9f6bc5690511382f9564ff8731ad56843fcfb Mon Sep 17 00:00:00 2001 From: "andre.mello" Date: Thu, 19 Oct 2023 18:21:05 +0100 Subject: [PATCH 1/3] draft --- src/encoding/text.rs | 77 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/encoding/text.rs b/src/encoding/text.rs index adf42415..9736649e 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -32,6 +32,7 @@ use crate::registry::{Prefix, Registry, Unit}; use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; +use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; /// Encode the metrics registered with the provided [`Registry`] into the /// provided [`Write`]r using the OpenMetrics text format. @@ -44,10 +45,43 @@ where Ok(()) } +/// Encode the metrics registered with the provided [`Registry`] into the +/// provided [`Write`]r using the OpenMetrics text format. +pub fn encode_with_timestamps(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error> +where + W: Write, +{ + registry.encode( + &mut DescriptorEncoder::new(writer) + .with_timestamp() + .map_err(|_| std::fmt::Error)? + .into(), + )?; + writer.write_str("# EOF\n")?; + Ok(()) +} + +#[derive(Clone, Copy)] +struct UnixTimestamp(f64); + +impl UnixTimestamp { + fn now() -> Result { + let sys_time = SystemTime::now(); + sys_time + .duration_since(UNIX_EPOCH) + .map(|d| Self(d.as_secs_f64())) + } + + fn as_f64(&self) -> f64 { + self.0 + } +} + pub(crate) struct DescriptorEncoder<'a> { writer: &'a mut dyn Write, prefix: Option<&'a Prefix>, labels: &'a [(Cow<'static, str>, Cow<'static, str>)], + timestamp: Option, } impl<'a> std::fmt::Debug for DescriptorEncoder<'a> { @@ -62,6 +96,7 @@ impl DescriptorEncoder<'_> { writer, prefix: Default::default(), labels: Default::default(), + timestamp: Default::default(), } } @@ -74,6 +109,7 @@ impl DescriptorEncoder<'_> { prefix, labels, writer: self.writer, + timestamp: self.timestamp, } } @@ -133,8 +169,14 @@ impl DescriptorEncoder<'_> { unit, const_labels: self.labels, family_labels: None, + timestamp: self.timestamp, }) } + + fn with_timestamp(mut self) -> Result { + self.timestamp = Some(UnixTimestamp::now()?); + Ok(self) + } } /// Helper type for [`EncodeMetric`](super::EncodeMetric), see @@ -153,6 +195,7 @@ pub(crate) struct MetricEncoder<'a> { unit: Option<&'a Unit>, const_labels: &'a [(Cow<'static, str>, Cow<'static, str>)], family_labels: Option<&'a dyn super::EncodeLabelSet>, + timestamp: Option, } impl<'a> std::fmt::Debug for MetricEncoder<'a> { @@ -162,13 +205,20 @@ impl<'a> std::fmt::Debug for MetricEncoder<'a> { l.encode(LabelSetEncoder::new(&mut labels).into())?; } - f.debug_struct("Encoder") + let mut debug_struct = f.debug_struct("Encoder"); + + debug_struct .field("name", &self.name) .field("prefix", &self.prefix) .field("unit", &self.unit) .field("const_labels", &self.const_labels) - .field("labels", &labels.as_str()) - .finish() + .field("labels", &labels.as_str()); + + if let Some(timestamp) = &self.timestamp { + debug_struct.field("timestamp", ×tamp.as_f64()); + } + + debug_struct.finish() } } @@ -195,6 +245,8 @@ impl<'a> MetricEncoder<'a> { .into(), )?; + self.encode_timestamp()?; + if let Some(exemplar) = exemplar { self.encode_exemplar(exemplar)?; } @@ -219,6 +271,8 @@ impl<'a> MetricEncoder<'a> { .into(), )?; + self.encode_timestamp()?; + self.newline()?; Ok(()) @@ -254,6 +308,7 @@ impl<'a> MetricEncoder<'a> { unit: self.unit, const_labels: self.const_labels, family_labels: Some(label_set), + timestamp: self.timestamp, }) } @@ -269,6 +324,7 @@ impl<'a> MetricEncoder<'a> { self.encode_labels::<()>(None)?; self.writer.write_str(" ")?; self.writer.write_str(dtoa::Buffer::new().format(sum))?; + self.encode_timestamp()?; self.newline()?; self.write_prefix_name_unit()?; @@ -276,6 +332,7 @@ impl<'a> MetricEncoder<'a> { self.encode_labels::<()>(None)?; self.writer.write_str(" ")?; self.writer.write_str(itoa::Buffer::new().format(count))?; + self.encode_timestamp()?; self.newline()?; let mut cummulative = 0; @@ -295,6 +352,8 @@ impl<'a> MetricEncoder<'a> { self.writer .write_str(itoa::Buffer::new().format(cummulative))?; + self.encode_timestamp()?; + if let Some(exemplar) = exemplars.and_then(|e| e.get(&i)) { self.encode_exemplar(exemplar)? } @@ -321,12 +380,14 @@ impl<'a> MetricEncoder<'a> { } .into(), )?; + self.encode_timestamp()?; Ok(()) } fn newline(&mut self) -> Result<(), std::fmt::Error> { self.writer.write_str("\n") } + fn write_prefix_name_unit(&mut self) -> Result<(), std::fmt::Error> { if let Some(prefix) = self.prefix { self.writer.write_str(prefix.as_str())?; @@ -386,6 +447,16 @@ impl<'a> MetricEncoder<'a> { Ok(()) } + + fn encode_timestamp(&mut self) -> Result<(), std::fmt::Error> { + if let Some(timestamp) = &self.timestamp { + self.writer.write_str(" ")?; + self.writer + .write_str(dtoa::Buffer::new().format(timestamp.as_f64())) + } else { + Ok(()) + } + } } pub(crate) struct CounterValueEncoder<'a> { From 373dd5e6279aaa29bbec0c5b80ad8389953cec27 Mon Sep 17 00:00:00 2001 From: "andre.mello" Date: Fri, 20 Oct 2023 10:51:23 +0100 Subject: [PATCH 2/3] add tests --- src/encoding/text.rs | 66 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 9736649e..e2e84b29 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -644,10 +644,14 @@ mod tests { registry.register("my_counter", "My counter", counter); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -667,6 +671,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -696,6 +705,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -705,10 +719,14 @@ mod tests { registry.register("my_gauge", "My gauge", gauge); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -725,10 +743,14 @@ mod tests { .inc(); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -748,7 +770,6 @@ mod tests { .inc(); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_prefix_my_counter_family My counter family.\n" @@ -759,6 +780,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -777,6 +803,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -787,10 +818,14 @@ mod tests { histogram.observe(1.0); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -807,10 +842,14 @@ mod tests { .observe(1.0); let mut encoded = String::new(); - encode(&mut encoded, ®istry).unwrap(); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -842,6 +881,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -916,6 +960,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } #[test] @@ -975,6 +1024,11 @@ mod tests { assert_eq!(expected, encoded); parse_with_python_client(encoded); + + let mut encoded = String::new(); + encode_with_timestamps(&mut encoded, ®istry).unwrap(); + + parse_with_python_client(encoded); } fn parse_with_python_client(input: String) { From 1389ca10c611763e427e4e8b6cb576444d40e776 Mon Sep 17 00:00:00 2001 From: "andre.mello" Date: Fri, 20 Oct 2023 10:52:49 +0100 Subject: [PATCH 3/3] tweak doc-comment --- 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 e2e84b29..a5892bba 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -46,7 +46,7 @@ where } /// Encode the metrics registered with the provided [`Registry`] into the -/// provided [`Write`]r using the OpenMetrics text format. +/// provided [`Write`]r using the OpenMetrics text format (with timestamps included). pub fn encode_with_timestamps(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error> where W: Write, @@ -1027,7 +1027,7 @@ mod tests { let mut encoded = String::new(); encode_with_timestamps(&mut encoded, ®istry).unwrap(); - + parse_with_python_client(encoded); }