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..4e9f5a3b 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_millis().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,27 @@ 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_millis(1674086890123)), + ); + 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 1674086890123\n" + + "# EOF\n"; + assert_eq!(expected, encoded); + + parse_with_python_client(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 {