Skip to content

Commit ffc2ab6

Browse files
ackintoshmxindendivagant-martian
authored
encoding/: Add Protobuf support (#83)
Add support for the OpenMetrics Protobuf encoding. Co-authored-by: Max Inden <[email protected]> Co-authored-by: Diva M <[email protected]>
1 parent 16993b6 commit ffc2ab6

File tree

20 files changed

+1624
-102
lines changed

20 files changed

+1624
-102
lines changed

.github/workflows/rust.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- uses: actions-rs/cargo@v1
4040
with:
4141
command: test
42-
args: --benches
42+
args: --benches --all-features
4343

4444
test:
4545
name: Test Suite
@@ -73,7 +73,7 @@ jobs:
7373
- uses: actions-rs/cargo@v1
7474
with:
7575
command: test
76-
args: --all
76+
args: --all --all-features
7777

7878
fmt:
7979
name: Rustfmt
@@ -135,7 +135,7 @@ jobs:
135135
RUSTDOCFLAGS: "--deny broken_intra_doc_links"
136136
with:
137137
command: doc
138-
args: --verbose --workspace --no-deps --document-private-items
138+
args: --verbose --workspace --no-deps --document-private-items --all-features
139139

140140
cross-compile:
141141
name: Cross compile
@@ -159,4 +159,4 @@ jobs:
159159
with:
160160
use-cross: true
161161
command: build
162-
args: --release --target=${{ matrix.target }}
162+
args: --release --target=${{ matrix.target }} --all-features

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.19.0] - unreleased
8+
9+
### Added
10+
- Added support for the OpenMetrics protobuf format. See [PR 83].
11+
12+
### Changed
13+
14+
- Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83].
15+
16+
[PR 83]: https://github.com/prometheus/client_rust/pull/83
17+
718
## [0.18.0]
819

920
### Changed

Cargo.toml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "prometheus-client"
3-
version = "0.18.0"
3+
version = "0.19.0"
44
authors = ["Max Inden <[email protected]>"]
55
edition = "2021"
66
description = "Open Metrics client library allowing users to natively instrument applications."
@@ -10,14 +10,20 @@ repository = "https://github.com/prometheus/client_rust"
1010
homepage = "https://github.com/prometheus/client_rust"
1111
documentation = "https://docs.rs/prometheus-client"
1212

13+
[features]
14+
protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build", "dep:void", "prometheus-client-derive-encode/protobuf"]
15+
1316
[workspace]
14-
members = ["derive-text-encode"]
17+
members = ["derive-encode"]
1518

1619
[dependencies]
1720
dtoa = "1.0"
1821
itoa = "1.0"
1922
parking_lot = "0.12"
20-
prometheus-client-derive-text-encode = { version = "0.3.0", path = "derive-text-encode" }
23+
prometheus-client-derive-encode = { version = "0.3.0", path = "derive-encode" }
24+
prost = { version = "0.9.0", optional = true }
25+
prost-types = { version = "0.9.0", optional = true }
26+
void = { version = "1.0", optional = true }
2127

2228
[dev-dependencies]
2329
async-std = { version = "1", features = ["attributes"] }
@@ -29,6 +35,9 @@ rand = "0.8.4"
2935
tide = "0.16"
3036
actix-web = "4"
3137

38+
[build-dependencies]
39+
prost-build = { version = "0.9.0", optional = true }
40+
3241
[[bench]]
3342
name = "family"
3443
harness = false
@@ -37,3 +46,10 @@ harness = false
3746
name = "text"
3847
path = "benches/encoding/text.rs"
3948
harness = false
49+
required-features = []
50+
51+
[[bench]]
52+
name = "proto"
53+
path = "benches/encoding/proto.rs"
54+
harness = false
55+
required-features = ["protobuf"]

benches/encoding/proto.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Benchmark inspired by
2+
// https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs:write
3+
4+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
5+
use prometheus_client::encoding::proto::{encode, EncodeMetric};
6+
use prometheus_client::encoding::Encode;
7+
use prometheus_client::metrics::counter::Counter;
8+
use prometheus_client::metrics::family::Family;
9+
use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
10+
use prometheus_client::registry::Registry;
11+
use std::fmt::{Display, Formatter};
12+
13+
pub fn proto(c: &mut Criterion) {
14+
c.bench_function("encode", |b| {
15+
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
16+
struct Labels {
17+
path: String,
18+
method: Method,
19+
some_number: u64,
20+
}
21+
22+
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
23+
enum Method {
24+
Get,
25+
#[allow(dead_code)]
26+
Put,
27+
}
28+
29+
impl Display for Method {
30+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31+
match self {
32+
Method::Get => write!(f, "Get"),
33+
Method::Put => write!(f, "Put"),
34+
}
35+
}
36+
}
37+
38+
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
39+
enum Region {
40+
Africa,
41+
#[allow(dead_code)]
42+
Asia,
43+
}
44+
45+
let mut registry = Registry::<Box<dyn EncodeMetric>>::default();
46+
47+
for i in 0..100 {
48+
let counter_family = Family::<Labels, Counter>::default();
49+
let histogram_family = Family::<Region, Histogram>::new_with_constructor(|| {
50+
Histogram::new(exponential_buckets(1.0, 2.0, 10))
51+
});
52+
53+
registry.register(
54+
format!("my_counter{}", i),
55+
"My counter",
56+
Box::new(counter_family.clone()),
57+
);
58+
registry.register(
59+
format!("my_histogram{}", i),
60+
"My histogram",
61+
Box::new(histogram_family.clone()),
62+
);
63+
64+
for j in 0_u32..100 {
65+
counter_family
66+
.get_or_create(&Labels {
67+
path: format!("/path/{}", i),
68+
method: Method::Get,
69+
some_number: j.into(),
70+
})
71+
.inc();
72+
73+
histogram_family
74+
.get_or_create(&Region::Africa)
75+
.observe(j.into());
76+
}
77+
}
78+
79+
b.iter(|| {
80+
let metric_set = encode(&registry);
81+
black_box(metric_set);
82+
})
83+
});
84+
}
85+
86+
criterion_group!(benches, proto);
87+
criterion_main!(benches);

benches/encoding/text.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Benchmark inspired by https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs
22

33
use criterion::{black_box, criterion_group, criterion_main, Criterion};
4-
use prometheus_client::encoding::text::{encode, Encode, EncodeMetric};
4+
use prometheus_client::encoding::text::{encode, EncodeMetric};
5+
use prometheus_client::encoding::Encode;
56
use prometheus_client::metrics::counter::Counter;
67
use prometheus_client::metrics::family::Family;
78
use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
@@ -33,7 +34,7 @@ pub fn text(c: &mut Criterion) {
3334
Five,
3435
}
3536

36-
impl Encode for Status {
37+
impl prometheus_client::encoding::text::Encode for Status {
3738
fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
3839
let status = match self {
3940
Status::Two => b"200",
@@ -45,6 +46,21 @@ pub fn text(c: &mut Criterion) {
4546
}
4647
}
4748

49+
#[cfg(feature = "protobuf")]
50+
impl prometheus_client::encoding::proto::EncodeLabels for Status {
51+
fn encode(&self, labels: &mut Vec<prometheus_client::encoding::proto::Label>) {
52+
let value = match self {
53+
Status::Two => "200".to_string(),
54+
Status::Four => "400".to_string(),
55+
Status::Five => "500".to_string(),
56+
};
57+
labels.push(prometheus_client::encoding::proto::Label {
58+
name: "status".to_string(),
59+
value,
60+
});
61+
}
62+
}
63+
4864
let mut registry = Registry::<Box<dyn EncodeMetric>>::default();
4965

5066
for i in 0..100 {

build.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use std::io::Result;
2+
3+
fn main() -> Result<()> {
4+
#[cfg(feature = "protobuf")]
5+
prost_build::compile_protos(
6+
&["src/encoding/proto/openmetrics_data_model.proto"],
7+
&["src/encoding/proto/"],
8+
)?;
9+
10+
Ok(())
11+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
[package]
2-
name = "prometheus-client-derive-text-encode"
3-
version = "0.3.0"
2+
name = "prometheus-client-derive-encode"
3+
version = "0.3.1"
44
authors = ["Max Inden <[email protected]>"]
55
edition = "2021"
6-
description = "Auxiliary crate to derive text Encode trait from prometheus-client."
6+
description = "Auxiliary crate to derive Encode trait from prometheus-client."
77
license = "Apache-2.0 OR MIT"
88
repository = "https://github.com/prometheus/client_rust"
99
homepage = "https://github.com/prometheus/client_rust"
1010
documentation = "https://docs.rs/prometheus-client-derive-text-encode"
1111

12+
[features]
13+
protobuf = []
14+
1215
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1316

1417
[dependencies]
@@ -17,7 +20,7 @@ quote = "1"
1720
syn = "1"
1821

1922
[dev-dependencies]
20-
prometheus-client = { path = "../" }
23+
prometheus-client = { path = "../", features = ["protobuf"] }
2124

2225
[lib]
2326
proc-macro = true
Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
1010
let ast: DeriveInput = syn::parse(input).unwrap();
1111
let name = &ast.ident;
1212

13-
let body = match ast.data {
13+
let body = match ast.clone().data {
1414
syn::Data::Struct(s) => match s.fields {
1515
syn::Fields::Named(syn::FieldsNamed { named, .. }) => named
1616
.into_iter()
@@ -70,9 +70,94 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
7070
}
7171
}
7272
};
73+
74+
#[cfg(feature = "protobuf")]
75+
let gen = {
76+
let protobuf = derive_protobuf_encode(ast);
77+
quote! {
78+
#gen
79+
80+
#protobuf
81+
}
82+
};
83+
7384
gen.into()
7485
}
7586

87+
#[cfg(feature = "protobuf")]
88+
fn derive_protobuf_encode(ast: DeriveInput) -> TokenStream2 {
89+
let name = &ast.ident;
90+
91+
match ast.data {
92+
syn::Data::Struct(s) => match s.fields {
93+
syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
94+
let push_labels: TokenStream2 = named
95+
.into_iter()
96+
.map(|f| {
97+
let ident = f.ident.unwrap();
98+
let ident_string = KEYWORD_IDENTIFIERS
99+
.iter()
100+
.find(|pair| ident == pair.1)
101+
.map(|pair| pair.0.to_string())
102+
.unwrap_or_else(|| ident.to_string());
103+
104+
quote! {
105+
let mut label = {
106+
let mut labels = vec![];
107+
self.#ident.encode(&mut labels);
108+
debug_assert_eq!(1, labels.len(), "Labels encoded from {} should have only one label.", #ident_string);
109+
labels.pop().expect("should have an element")
110+
};
111+
// Override the label name with the field name of this struct.
112+
label.name = #ident_string.to_string();
113+
labels.push(label);
114+
}
115+
})
116+
.collect();
117+
118+
quote! {
119+
impl prometheus_client::encoding::proto::EncodeLabels for #name {
120+
fn encode(&self, labels: &mut Vec<prometheus_client::encoding::proto::Label>) {
121+
#push_labels
122+
}
123+
}
124+
}
125+
}
126+
syn::Fields::Unnamed(_) => {
127+
panic!("Can not derive Encode for struct with unnamed fields.")
128+
}
129+
syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."),
130+
},
131+
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
132+
let match_arms: TokenStream2 = variants
133+
.into_iter()
134+
.map(|v| {
135+
let ident = v.ident;
136+
quote! {
137+
#name::#ident => {
138+
let mut label = prometheus_client::encoding::proto::Label::default();
139+
label.name = stringify!(#name).to_string();
140+
label.value = stringify!(#ident).to_string();
141+
labels.push(label);
142+
}
143+
}
144+
})
145+
.collect();
146+
147+
quote! {
148+
impl prometheus_client::encoding::proto::EncodeLabels for #name {
149+
fn encode(&self, labels: &mut Vec<prometheus_client::encoding::proto::Label>) {
150+
match self {
151+
#match_arms
152+
};
153+
}
154+
}
155+
}
156+
}
157+
syn::Data::Union(_) => panic!("Can not derive Encode for union."),
158+
}
159+
}
160+
76161
// Copied from https://github.com/djc/askama (MIT and APACHE licensed) and
77162
// modified.
78163
static KEYWORD_IDENTIFIERS: [(&str, &str); 48] = [

0 commit comments

Comments
 (0)