Skip to content

Commit 0486185

Browse files
committed
Add support for timestamp encoding in exemplars
While timestamps are optional, they are required for native histograms: * https://github.com/prometheus/prometheus/blame/4aee718013/scrape/scrape.go#L1833 Old histograms can be converted to native histograms at the ingestion time, which in turn makes timestamps required for old histograms too. Signed-off-by: Ivan Babrou <[email protected]>
1 parent cd3c3e8 commit 0486185

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

src/encoding.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::fmt::Write;
1111
use std::ops::Deref;
1212
use std::rc::Rc;
1313
use std::sync::Arc;
14+
use std::time::{SystemTime, UNIX_EPOCH};
1415

1516
#[cfg(feature = "protobuf")]
1617
#[cfg_attr(docsrs, doc(cfg(feature = "protobuf")))]
@@ -760,6 +761,18 @@ impl EncodeExemplarValue for u32 {
760761
}
761762
}
762763

764+
/// An encodable exemplar time.
765+
pub trait EncodeExemplarTime {
766+
/// Encode the time in the OpenMetrics text encoding.
767+
fn encode(&self, encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error>;
768+
}
769+
770+
impl EncodeExemplarTime for SystemTime {
771+
fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> {
772+
encoder.encode(self.duration_since(UNIX_EPOCH).unwrap().as_secs_f64())
773+
}
774+
}
775+
763776
/// Encoder for an exemplar value.
764777
#[derive(Debug)]
765778
pub struct ExemplarValueEncoder<'a>(ExemplarValueEncoderInner<'a>);

src/encoding/protobuf.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ impl<S: EncodeLabelSet, V: EncodeExemplarValue> TryFrom<&Exemplar<S, V>>
311311

312312
Ok(openmetrics_data_model::Exemplar {
313313
value,
314-
timestamp: Default::default(),
314+
timestamp: Some(exemplar.time.into()),
315315
label: labels,
316316
})
317317
}
@@ -442,6 +442,8 @@ impl std::fmt::Write for LabelValueEncoder<'_> {
442442

443443
#[cfg(test)]
444444
mod tests {
445+
use prost_types::Timestamp;
446+
445447
use super::*;
446448
use crate::metrics::counter::Counter;
447449
use crate::metrics::exemplar::{CounterWithExemplar, HistogramWithExemplars};
@@ -454,6 +456,7 @@ mod tests {
454456
use std::collections::HashSet;
455457
use std::sync::atomic::AtomicI64;
456458
use std::sync::atomic::AtomicU64;
459+
use std::time::SystemTime;
457460

458461
#[test]
459462
fn encode_counter_int() {
@@ -531,6 +534,9 @@ mod tests {
531534

532535
#[test]
533536
fn encode_counter_with_exemplar() {
537+
let now = SystemTime::now();
538+
let now_ts: Timestamp = now.into();
539+
534540
let mut registry = Registry::default();
535541

536542
let counter_with_exemplar: CounterWithExemplar<Vec<(String, f64)>, f64> =
@@ -543,6 +549,14 @@ mod tests {
543549

544550
counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 42.0)]));
545551

552+
counter_with_exemplar
553+
.inner
554+
.write()
555+
.exemplar
556+
.as_mut()
557+
.unwrap()
558+
.time = now;
559+
546560
let metric_set = encode(&registry).unwrap();
547561

548562
let family = metric_set.metric_families.first().unwrap();
@@ -563,6 +577,8 @@ mod tests {
563577
let exemplar = value.exemplar.as_ref().unwrap();
564578
assert_eq!(1.0, exemplar.value);
565579

580+
assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap());
581+
566582
let expected_label = {
567583
openmetrics_data_model::Label {
568584
name: "user_id".to_string(),
@@ -784,11 +800,23 @@ mod tests {
784800

785801
#[test]
786802
fn encode_histogram_with_exemplars() {
803+
let now = SystemTime::now();
804+
let now_ts: Timestamp = now.into();
805+
787806
let mut registry = Registry::default();
788807
let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
789808
registry.register("my_histogram", "My histogram", histogram.clone());
790809
histogram.observe(1.0, Some(vec![("user_id".to_string(), 42u64)]));
791810

811+
histogram
812+
.inner
813+
.write()
814+
.exemplars
815+
.get_mut(&0)
816+
.as_mut()
817+
.unwrap()
818+
.time = now;
819+
792820
let metric_set = encode(&registry).unwrap();
793821

794822
let family = metric_set.metric_families.first().unwrap();
@@ -805,6 +833,8 @@ mod tests {
805833
let exemplar = value.buckets.first().unwrap().exemplar.as_ref().unwrap();
806834
assert_eq!(1.0, exemplar.value);
807835

836+
assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap());
837+
808838
let expected_label = {
809839
openmetrics_data_model::Label {
810840
name: "user_id".to_string(),

src/encoding/text.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
//! assert_eq!(expected_msg, buffer);
3838
//! ```
3939
40-
use crate::encoding::{EncodeExemplarValue, EncodeLabelSet, NoLabelSet};
40+
use crate::encoding::{EncodeExemplarTime, EncodeExemplarValue, EncodeLabelSet, NoLabelSet};
4141
use crate::metrics::exemplar::Exemplar;
4242
use crate::metrics::MetricType;
4343
use crate::registry::{Prefix, Registry, Unit};
@@ -460,6 +460,13 @@ impl MetricEncoder<'_> {
460460
}
461461
.into(),
462462
)?;
463+
self.writer.write_char(' ')?;
464+
exemplar.time.encode(
465+
ExemplarValueEncoder {
466+
writer: self.writer,
467+
}
468+
.into(),
469+
)?;
463470
Ok(())
464471
}
465472

@@ -737,6 +744,7 @@ mod tests {
737744
use std::borrow::Cow;
738745
use std::fmt::Error;
739746
use std::sync::atomic::{AtomicI32, AtomicU32};
747+
use std::time::{SystemTime, UNIX_EPOCH};
740748

741749
#[test]
742750
fn encode_counter() {
@@ -776,6 +784,8 @@ mod tests {
776784

777785
#[test]
778786
fn encode_counter_with_exemplar() {
787+
let now = SystemTime::now();
788+
779789
let mut registry = Registry::default();
780790

781791
let counter_with_exemplar: CounterWithExemplar<Vec<(String, u64)>> =
@@ -789,14 +799,24 @@ mod tests {
789799

790800
counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), 42)]));
791801

802+
counter_with_exemplar
803+
.inner
804+
.write()
805+
.exemplar
806+
.as_mut()
807+
.unwrap()
808+
.time = now;
809+
792810
let mut encoded = String::new();
793811
encode(&mut encoded, &registry).unwrap();
794812

795813
let expected = "# HELP my_counter_with_exemplar_seconds My counter with exemplar.\n"
796814
.to_owned()
797815
+ "# TYPE my_counter_with_exemplar_seconds counter\n"
798816
+ "# UNIT my_counter_with_exemplar_seconds seconds\n"
799-
+ "my_counter_with_exemplar_seconds_total 1 # {user_id=\"42\"} 1.0\n"
817+
+ "my_counter_with_exemplar_seconds_total 1 # {user_id=\"42\"} 1.0 "
818+
+ &dtoa::Buffer::new().format(now.duration_since(UNIX_EPOCH).unwrap().as_secs_f64())
819+
+ "\n"
800820
+ "# EOF\n";
801821
assert_eq!(expected, encoded);
802822

@@ -953,19 +973,32 @@ mod tests {
953973

954974
#[test]
955975
fn encode_histogram_with_exemplars() {
976+
let now = SystemTime::now();
977+
956978
let mut registry = Registry::default();
957979
let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
958980
registry.register("my_histogram", "My histogram", histogram.clone());
959981
histogram.observe(1.0, Some([("user_id".to_string(), 42u64)]));
960982

983+
histogram
984+
.inner
985+
.write()
986+
.exemplars
987+
.get_mut(&0)
988+
.as_mut()
989+
.unwrap()
990+
.time = now;
991+
961992
let mut encoded = String::new();
962993
encode(&mut encoded, &registry).unwrap();
963994

964995
let expected = "# HELP my_histogram My histogram.\n".to_owned()
965996
+ "# TYPE my_histogram histogram\n"
966997
+ "my_histogram_sum 1.0\n"
967998
+ "my_histogram_count 1\n"
968-
+ "my_histogram_bucket{le=\"1.0\"} 1 # {user_id=\"42\"} 1.0\n"
999+
+ "my_histogram_bucket{le=\"1.0\"} 1 # {user_id=\"42\"} 1.0 "
1000+
+ &dtoa::Buffer::new().format(now.duration_since(UNIX_EPOCH).unwrap().as_secs_f64())
1001+
+ "\n"
9691002
+ "my_histogram_bucket{le=\"2.0\"} 1\n"
9701003
+ "my_histogram_bucket{le=\"4.0\"} 1\n"
9711004
+ "my_histogram_bucket{le=\"8.0\"} 1\n"

src/metrics/exemplar.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ use std::sync::atomic::AtomicU32;
1616
#[cfg(target_has_atomic = "64")]
1717
use std::sync::atomic::AtomicU64;
1818
use std::sync::Arc;
19+
use std::time::SystemTime;
1920

2021
/// An OpenMetrics exemplar.
2122
#[derive(Debug)]
2223
pub struct Exemplar<S, V> {
2324
pub(crate) label_set: S,
2425
pub(crate) value: V,
26+
pub(crate) time: SystemTime,
2527
}
2628

2729
/////////////////////////////////////////////////////////////////////////////////
@@ -121,6 +123,7 @@ impl<S, N: Clone, A: counter::Atomic<N>> CounterWithExemplar<S, N, A> {
121123
inner.exemplar = label_set.map(|label_set| Exemplar {
122124
label_set,
123125
value: v.clone(),
126+
time: SystemTime::now(),
124127
});
125128

126129
inner.counter.inc_by(v)
@@ -256,6 +259,7 @@ impl<S> HistogramWithExemplars<S> {
256259
Exemplar {
257260
label_set,
258261
value: v,
262+
time: SystemTime::now(),
259263
},
260264
);
261265
}

0 commit comments

Comments
 (0)