Skip to content

Commit

Permalink
encoding/: Adopt serde style encoding
Browse files Browse the repository at this point in the history
Adopt encoding style similar to serde. `EncodeXXX` for a type that can be
encoded (see serde's `Serialize`) and `XXXEncoder` for a supported encoding i.e.
text and protobuf (see serde's `Serializer`).

- Compatible with Rust's additive features. Enabling the `protobuf` feature does
not change type signatures.
- `EncodeMetric` is trait object safe, and thus `Registry` can use dynamic dispatch.
- Implement a single encoding trait `EncodeMetric` per metric type instead of
one implementation per metric AND per encoding format.
- Leverage `std::fmt::Write` instead of `std::io::Write`. The OpenMetrics text
format has to be unicode compliant. The OpenMetrics Protobuf format requires
labels to be valid strings.

Signed-off-by: Max Inden <mail@max-inden.de>
  • Loading branch information
mxinden committed Dec 2, 2022
1 parent 6cd0dba commit f0aa084
Show file tree
Hide file tree
Showing 23 changed files with 1,562 additions and 1,238 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Expand Up @@ -16,11 +16,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83].
- Always use dynamic dispatch on `Registry`, i.e. remove generic type parameter `M` from `Registry`. See [PR 105].
- Refactor encoding. See [PR 105].
- Introducing separate traits to encode
- value (e.g. `EncodeCounterValue`)
- label set (`EncodeLabelSet`), derivable for structs via `prometheus-client-derive-encode`
- label (`EncodeLabel`)
- label key (`EncodeLabelKey`)
- label value (`EncodeLabelValue`), derivable for enums via `prometheus-client-derive-encode`
- Encode as UTF-8 strings, not bytes. I.e. use `std::fmt::Write` instead of `std::io::Write`.
- Use signed integers for `Gauge` for compliance with OpenMetrics protobuf
format. See [PR 105].

[PR 83]: https://github.com/prometheus/client_rust/pull/83
[PR 85]: https://github.com/prometheus/client_rust/pull/85
[PR 96]: https://github.com/prometheus/client_rust/pull/96
[PR 105]: https://github.com/prometheus/client_rust/pull/105

## [0.18.1]

Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -11,7 +11,8 @@ homepage = "https://github.com/prometheus/client_rust"
documentation = "https://docs.rs/prometheus-client"

[features]
protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build", "dep:void", "prometheus-client-derive-encode/protobuf"]
default = []
protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"]

[workspace]
members = ["derive-encode"]
Expand All @@ -23,7 +24,6 @@ parking_lot = "0.12"
prometheus-client-derive-encode = { version = "0.3.0", path = "derive-encode" }
prost = { version = "0.11.0", optional = true }
prost-types = { version = "0.11.0", optional = true }
void = { version = "1.0", optional = true }

[dev-dependencies]
async-std = { version = "1", features = ["attributes"] }
Expand Down
46 changes: 22 additions & 24 deletions benches/encoding/proto.rs
Expand Up @@ -2,82 +2,80 @@
// https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs:write

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use prometheus_client::encoding::proto::{encode, EncodeMetric};
use prometheus_client::encoding::Encode;
use prometheus_client::encoding::protobuf;
use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::family::Family;
use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
use prometheus_client::registry::Registry;
use std::fmt::{Display, Formatter};
use prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue};

pub fn proto(c: &mut Criterion) {
c.bench_function("encode", |b| {
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
struct Labels {
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)]
struct CounterLabels {
path: String,
method: Method,
some_number: u64,
}

#[derive(Clone, Hash, PartialEq, Eq, Encode)]
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)]
enum Method {
Get,
#[allow(dead_code)]
Put,
}

impl Display for Method {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Method::Get => write!(f, "Get"),
Method::Put => write!(f, "Put"),
}
}
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)]
struct HistogramLabels {
region: Region,
}

#[derive(Clone, Hash, PartialEq, Eq, Encode)]
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)]
enum Region {
Africa,
#[allow(dead_code)]
Asia,
}

let mut registry = Registry::<Box<dyn EncodeMetric>>::default();
let mut registry = Registry::default();

for i in 0..100 {
let counter_family = Family::<Labels, Counter>::default();
let histogram_family = Family::<Region, Histogram>::new_with_constructor(|| {
Histogram::new(exponential_buckets(1.0, 2.0, 10))
});
let counter_family = Family::<CounterLabels, Counter>::default();
let histogram_family =
Family::<HistogramLabels, Histogram>::new_with_constructor(|| {
Histogram::new(exponential_buckets(1.0, 2.0, 10))
});

registry.register(
format!("my_counter{}", i),
"My counter",
Box::new(counter_family.clone()),
counter_family.clone(),
);
registry.register(
format!("my_histogram{}", i),
"My histogram",
Box::new(histogram_family.clone()),
histogram_family.clone(),
);

for j in 0_u32..100 {
counter_family
.get_or_create(&Labels {
.get_or_create(&CounterLabels {
path: format!("/path/{}", i),
method: Method::Get,
some_number: j.into(),
})
.inc();

histogram_family
.get_or_create(&Region::Africa)
.get_or_create(&HistogramLabels {
region: Region::Africa,
})
.observe(j.into());
}
}

b.iter(|| {
let metric_set = encode(&registry);
let metric_set = protobuf::encode(&registry).unwrap();
black_box(metric_set);
})
});
Expand Down
48 changes: 16 additions & 32 deletions benches/encoding/text.rs
@@ -1,31 +1,30 @@
// Benchmark inspired by https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use prometheus_client::encoding::text::{encode, EncodeMetric};
use prometheus_client::encoding::Encode;
use prometheus_client::encoding::{self, EncodeLabelSet, EncodeLabelValue, LabelValueEncoder};
use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::family::Family;
use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
use prometheus_client::registry::Registry;
use std::io::Write;
use std::fmt::Write;

pub fn text(c: &mut Criterion) {
c.bench_function("encode", |b| {
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)]
struct Labels {
method: Method,
status: Status,
some_number: u64,
}

#[derive(Clone, Hash, PartialEq, Eq, Encode)]
#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)]
enum Method {
Get,
#[allow(dead_code)]
Put,
}

#[derive(Clone, Hash, PartialEq, Eq)]
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
enum Status {
Two,
#[allow(dead_code)]
Expand All @@ -34,34 +33,19 @@ pub fn text(c: &mut Criterion) {
Five,
}

impl prometheus_client::encoding::text::Encode for Status {
fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
impl prometheus_client::encoding::EncodeLabelValue for Status {
fn encode(&self, writer: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
let status = match self {
Status::Two => b"200",
Status::Four => b"400",
Status::Five => b"500",
Status::Two => "200",
Status::Four => "400",
Status::Five => "500",
};
writer.write_all(status)?;
writer.write_str(status)?;
Ok(())
}
}

#[cfg(feature = "protobuf")]
impl prometheus_client::encoding::proto::EncodeLabels for Status {
fn encode(&self, labels: &mut Vec<prometheus_client::encoding::proto::Label>) {
let value = match self {
Status::Two => "200".to_string(),
Status::Four => "400".to_string(),
Status::Five => "500".to_string(),
};
labels.push(prometheus_client::encoding::proto::Label {
name: "status".to_string(),
value,
});
}
}

let mut registry = Registry::<Box<dyn EncodeMetric>>::default();
let mut registry = Registry::default();

for i in 0..100 {
let counter_family = Family::<Labels, Counter>::default();
Expand All @@ -72,12 +56,12 @@ pub fn text(c: &mut Criterion) {
registry.register(
format!("my_counter_{}", i),
"My counter",
Box::new(counter_family.clone()),
counter_family.clone(),
);
registry.register(
format!("my_histogram_{}", i),
"My histogram",
Box::new(histogram_family.clone()),
histogram_family.clone(),
);

for j in 0u32..100 {
Expand All @@ -98,10 +82,10 @@ pub fn text(c: &mut Criterion) {
}
}

let mut buffer = vec![];
let mut buffer = String::new();

b.iter(|| {
encode(&mut buffer, &registry).unwrap();
encoding::text::encode(&mut buffer, &registry).unwrap();
black_box(&mut buffer);
})
});
Expand Down
3 changes: 0 additions & 3 deletions derive-encode/Cargo.toml
Expand Up @@ -9,9 +9,6 @@ repository = "https://github.com/prometheus/client_rust"
homepage = "https://github.com/prometheus/client_rust"
documentation = "https://docs.rs/prometheus-client-derive-text-encode"

[features]
protobuf = []

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Expand Down

0 comments on commit f0aa084

Please sign in to comment.