Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timestamp support for const metrics #129

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/custom-metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u64>(), None)
encoder.encode_counter::<(), _, u64>(&rand::random::<u64>(), None, None)
}

fn metric_type(&self) -> prometheus_client::metrics::MetricType {
Expand Down
9 changes: 8 additions & 1 deletion src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,8 +99,14 @@ impl<'a, 'b> MetricEncoder<'a, 'b> {
&mut self,
v: &CounterValue,
exemplar: Option<&Exemplar<S, ExemplarValue>>,
timestamp: Option<SystemTime>,
) -> 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.
Expand Down
39 changes: 39 additions & 0 deletions src/encoding/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -136,6 +137,7 @@ impl<'a, 'b> MetricEncoder<'a, 'b> {
&mut self,
v: &CounterValue,
exemplar: Option<&Exemplar<S, ExemplarValue>>,
timestamp: Option<SystemTime>,
) -> Result<(), std::fmt::Error> {
self.write_name_and_unit()?;

Expand All @@ -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)?;
}
Expand Down Expand Up @@ -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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library follows the OpenMetrics specification. This should be compatible with recent Prometheus versions (remember to set the right content type).

Please use seconds instead of milliseconds here.

https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#timestamps-1

}
Comment on lines +347 to +350
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we panic in debug mode?


Ok(())
}
}

pub(crate) struct CounterValueEncoder<'a> {
Expand Down Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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, &registry).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();
Expand Down
26 changes: 23 additions & 3 deletions src/metrics/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -179,7 +180,7 @@ where
A: Atomic<N>,
{
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 {
Expand All @@ -193,12 +194,24 @@ where
#[derive(Debug, Default)]
pub struct ConstCounter<N = u64> {
value: N,
timestamp: Option<SystemTime>,
}

impl<N> ConstCounter<N> {
/// 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),
}
}
}

Expand All @@ -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 {
Expand All @@ -223,6 +236,7 @@ where
mod tests {
use super::*;
use quickcheck::QuickCheck;
use std::time::UNIX_EPOCH;

#[test]
fn inc_and_get() {
Expand Down Expand Up @@ -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());
}
}
2 changes: 1 addition & 1 deletion src/metrics/exemplar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down