diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ad62bf..57052cb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Always use dynamic dispatch on Registry, i.e. remove generic type parameter `M` from `Registry`. + - Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83]. [PR 83]: https://github.com/prometheus/client_rust/pull/83 diff --git a/Cargo.toml b/Cargo.toml index a42a237f..fcad7708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] @@ -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"] } diff --git a/benches/encoding/proto.rs b/benches/encoding/proto.rs index 4de33a1a..89c2674b 100644 --- a/benches/encoding/proto.rs +++ b/benches/encoding/proto.rs @@ -2,24 +2,24 @@ // 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::encode; 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 prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue}; use std::fmt::{Display, Formatter}; pub fn proto(c: &mut Criterion) { c.bench_function("encode", |b| { - #[derive(Clone, Hash, PartialEq, Eq, Encode)] - struct Labels { + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelSet)] + struct CounterLabels { path: String, method: Method, some_number: u64, } - #[derive(Clone, Hash, PartialEq, Eq, Encode)] + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelValue)] enum Method { Get, #[allow(dead_code)] @@ -35,35 +35,41 @@ pub fn proto(c: &mut Criterion) { } } - #[derive(Clone, Hash, PartialEq, Eq, Encode)] + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelSet)] + struct HistogramLabels { + region: Region, + } + + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelValue)] enum Region { Africa, #[allow(dead_code)] Asia, } - let mut registry = Registry::>::default(); + let mut registry = Registry::default(); for i in 0..100 { - let counter_family = Family::::default(); - let histogram_family = Family::::new_with_constructor(|| { - Histogram::new(exponential_buckets(1.0, 2.0, 10)) - }); + let counter_family = Family::::default(); + let histogram_family = + Family::::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(), @@ -71,14 +77,16 @@ pub fn proto(c: &mut Criterion) { .inc(); histogram_family - .get_or_create(&Region::Africa) + .get_or_create(&HistogramLabels { + region: Region::Africa, + }) .observe(j.into()); } } b.iter(|| { let metric_set = encode(®istry); - black_box(metric_set); + black_box(metric_set).unwrap(); }) }); } diff --git a/benches/encoding/text.rs b/benches/encoding/text.rs index 1ab6be52..782d8d0e 100644 --- a/benches/encoding/text.rs +++ b/benches/encoding/text.rs @@ -1,31 +1,32 @@ // 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::text::encode; +use prometheus_client::encoding::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 prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue}; +use std::fmt::{Error, Write}; pub fn text(c: &mut Criterion) { c.bench_function("encode", |b| { - #[derive(Clone, Hash, PartialEq, Eq, Encode)] + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelSet)] struct Labels { method: Method, status: Status, some_number: u64, } - #[derive(Clone, Hash, PartialEq, Eq, Encode)] + #[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelValue)] enum Method { Get, #[allow(dead_code)] Put, } - #[derive(Clone, Hash, PartialEq, Eq)] + #[derive(Debug, Clone, Hash, PartialEq, Eq)] enum Status { Two, #[allow(dead_code)] @@ -34,34 +35,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> { - let status = match self { - Status::Two => b"200", - Status::Four => b"400", - Status::Five => b"500", - }; - writer.write_all(status)?; - Ok(()) - } - } + impl prometheus_client::encoding::EncodeLabelValue for Status { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), Error> { + encoder.write_str(match self { + Status::Two => "200", + Status::Four => "400", + Status::Five => "500", + })?; - #[cfg(feature = "protobuf")] - impl prometheus_client::encoding::proto::EncodeLabels for Status { - fn encode(&self, labels: &mut Vec) { - 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, - }); + Ok(()) } } - let mut registry = Registry::>::default(); + let mut registry = Registry::default(); for i in 0..100 { let counter_family = Family::::default(); @@ -72,12 +58,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 { @@ -98,7 +84,7 @@ pub fn text(c: &mut Criterion) { } } - let mut buffer = vec![]; + let mut buffer = String::new(); b.iter(|| { encode(&mut buffer, ®istry).unwrap(); diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml index 9762fd3c..55b5f37d 100644 --- a/derive-encode/Cargo.toml +++ b/derive-encode/Cargo.toml @@ -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] diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index d6f6eba1..b48b3f73 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -1,21 +1,27 @@ -extern crate proc_macro; +#![deny(dead_code)] +#![deny(missing_docs)] +#![deny(unused)] +#![forbid(unsafe_code)] +#![warn(missing_debug_implementations)] + +//! Derive crate for [`prometheus_client`]. use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::DeriveInput; -#[proc_macro_derive(Encode)] -pub fn derive_encode(input: TokenStream) -> TokenStream { +/// Derive [`prometheus_client::encoding::EncodeLabelSet`]. +#[proc_macro_derive(EncodeLabelSet)] +pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); let name = &ast.ident; - let body = match ast.clone().data { + let body: TokenStream2 = match ast.clone().data { syn::Data::Struct(s) => match s.fields { syn::Fields::Named(syn::FieldsNamed { named, .. }) => named .into_iter() - .enumerate() - .map(|(i, f)| { + .map(|f| { let ident = f.ident.unwrap(); let ident_string = KEYWORD_IDENTIFIERS .iter() @@ -23,16 +29,15 @@ pub fn derive_encode(input: TokenStream) -> TokenStream { .map(|pair| pair.0.to_string()) .unwrap_or_else(|| ident.to_string()); - let maybe_comma = if i == 0 { - TokenStream2::default() - } else { - quote! { writer.write_all(b",")?; } - }; quote! { - #maybe_comma - writer.write_all(concat!(#ident_string, "=\"").as_bytes())?; - prometheus_client::encoding::text::Encode::encode(&self.#ident, writer)?; - writer.write_all(b"\"")?; + let mut label_encoder = encoder.encode_label(); + let mut label_key_encoder = label_encoder.encode_label_key()?; + EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; + + let mut label_value_encoder = label_key_encoder.encode_label_value()?; + EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; + + label_value_encoder.finish()?; } }) .collect(), @@ -41,29 +46,19 @@ pub fn derive_encode(input: TokenStream) -> TokenStream { } syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."), }, - syn::Data::Enum(syn::DataEnum { variants, .. }) => { - let match_arms: TokenStream2 = variants - .into_iter() - .map(|v| { - let ident = v.ident; - quote! { - #name::#ident => writer.write_all(stringify!(#ident).as_bytes())?, - } - }) - .collect(); - - quote! { - match self { - #match_arms - } - } + syn::Data::Enum(syn::DataEnum { .. }) => { + panic!("Can not derive Encode for enum.") } syn::Data::Union(_) => panic!("Can not derive Encode for union."), }; let gen = quote! { - impl prometheus_client::encoding::text::Encode for #name { - fn encode(&self, writer: &mut dyn std::io::Write) -> std::result::Result<(), std::io::Error> { + impl prometheus_client::encoding::EncodeLabelSet for #name { + fn encode(&self, mut encoder: prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> { + use prometheus_client::encoding::EncodeLabel; + use prometheus_client::encoding::EncodeLabelKey; + use prometheus_client::encoding::EncodeLabelValue; + #body Ok(()) @@ -71,91 +66,52 @@ pub fn derive_encode(input: TokenStream) -> TokenStream { } }; - #[cfg(feature = "protobuf")] - let gen = { - let protobuf = derive_protobuf_encode(ast); - quote! { - #gen - - #protobuf - } - }; - gen.into() } -#[cfg(feature = "protobuf")] -fn derive_protobuf_encode(ast: DeriveInput) -> TokenStream2 { +/// Derive [`prometheus_client::encoding::EncodeLabelValue`]. +#[proc_macro_derive(EncodeLabelValue)] +pub fn derive_encode_label_value(input: TokenStream) -> TokenStream { + let ast: DeriveInput = syn::parse(input).unwrap(); let name = &ast.ident; - match ast.data { - syn::Data::Struct(s) => match s.fields { - syn::Fields::Named(syn::FieldsNamed { named, .. }) => { - let push_labels: TokenStream2 = named - .into_iter() - .map(|f| { - let ident = f.ident.unwrap(); - let ident_string = KEYWORD_IDENTIFIERS - .iter() - .find(|pair| ident == pair.1) - .map(|pair| pair.0.to_string()) - .unwrap_or_else(|| ident.to_string()); - - quote! { - let mut label = { - let mut labels = vec![]; - prometheus_client::encoding::proto::EncodeLabels::encode(&self.#ident, &mut labels); - debug_assert_eq!(1, labels.len(), "Labels encoded from {} should have only one label.", #ident_string); - labels.pop().expect("should have an element") - }; - // Override the label name with the field name of this struct. - label.name = #ident_string.to_string(); - labels.push(label); - } - }) - .collect(); - - quote! { - impl prometheus_client::encoding::proto::EncodeLabels for #name { - fn encode(&self, labels: &mut Vec) { - #push_labels - } - } - } - } - syn::Fields::Unnamed(_) => { - panic!("Can not derive Encode for struct with unnamed fields.") - } - syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."), - }, + let body = match ast.clone().data { + syn::Data::Struct(_) => { + panic!("Can not derive EncodeLabel for struct.") + } syn::Data::Enum(syn::DataEnum { variants, .. }) => { let match_arms: TokenStream2 = variants .into_iter() .map(|v| { let ident = v.ident; quote! { - #name::#ident => { - let mut label = prometheus_client::encoding::proto::Label::default(); - label.name = stringify!(#name).to_string(); - label.value = stringify!(#ident).to_string(); - labels.push(label); - } + #name::#ident => encoder.write_str(stringify!(#ident))?, } }) .collect(); quote! { - impl prometheus_client::encoding::proto::EncodeLabels for #name { - fn encode(&self, labels: &mut Vec) { - match self { - #match_arms - }; - } + match self { + #match_arms } } } syn::Data::Union(_) => panic!("Can not derive Encode for union."), - } + }; + + let gen = quote! { + impl prometheus_client::encoding::EncodeLabelValue for #name { + fn encode(&self, encoder: &mut prometheus_client::encoding::LabelValueEncoder) -> std::result::Result<(), std::fmt::Error> { + use std::fmt::Write; + + #body + + Ok(()) + } + } + }; + + gen.into() } // Copied from https://github.com/djc/askama (MIT and APACHE licensed) and diff --git a/derive-encode/tests/lib.rs b/derive-encode/tests/lib.rs index 7056aa25..446af0ba 100644 --- a/derive-encode/tests/lib.rs +++ b/derive-encode/tests/lib.rs @@ -1,16 +1,16 @@ use prometheus_client::encoding::text::encode; -use prometheus_client::encoding::Encode; +use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] struct Labels { method: Method, path: String, } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] enum Method { Get, #[allow(dead_code)] @@ -33,20 +33,20 @@ fn basic_flow() { .inc(); // Encode all metrics in the registry in the text format. - let mut buffer = vec![]; + let mut buffer = String::new(); encode(&mut buffer, ®istry).unwrap(); let expected = "# HELP my_counter This is my counter.\n".to_owned() + "# TYPE my_counter counter\n" + "my_counter_total{method=\"Get\",path=\"/metrics\"} 1\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(buffer).unwrap()); + assert_eq!(expected, buffer); } -#[cfg(feature = "protobuf")] mod protobuf { use crate::{Labels, Method}; - use prometheus_client::encoding::proto::encode; + use prometheus_client::encoding::protobuf::encode; + use prometheus_client::encoding::protobuf::openmetrics_data_model; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; @@ -66,10 +66,10 @@ mod protobuf { .inc(); // Encode all metrics in the registry in the OpenMetrics protobuf format. - let mut metric_set = encode(®istry); - let mut family: prometheus_client::encoding::proto::MetricFamily = + let mut metric_set = encode(®istry).unwrap(); + let mut family: openmetrics_data_model::MetricFamily = metric_set.metric_families.pop().unwrap(); - let metric: prometheus_client::encoding::proto::Metric = family.metrics.pop().unwrap(); + let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); let method = &metric.labels[0]; assert_eq!("method", method.name); @@ -83,27 +83,32 @@ mod protobuf { #[test] fn enums() { let mut registry = Registry::default(); - let family = Family::::default(); + let family = Family::::default(); registry.register("my_counter", "This is my counter", family.clone()); // Record a single HTTP GET request. - family.get_or_create(&Method::Get).inc(); + family + .get_or_create(&Labels { + method: Method::Get, + path: "/metrics".to_string(), + }) + .inc(); // Encode all metrics in the registry in the OpenMetrics protobuf format. - let mut metric_set = encode(®istry); - let mut family: prometheus_client::encoding::proto::MetricFamily = + let mut metric_set = encode(®istry).unwrap(); + let mut family: openmetrics_data_model::MetricFamily = metric_set.metric_families.pop().unwrap(); - let metric: prometheus_client::encoding::proto::Metric = family.metrics.pop().unwrap(); + let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); let label = &metric.labels[0]; - assert_eq!("Method", label.name); + assert_eq!("method", label.name); assert_eq!("Get", label.value); } } #[test] fn remap_keyword_identifiers() { - #[derive(Encode, Hash, Clone, Eq, PartialEq)] + #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] struct Labels { // `r#type` is problematic as `r#` is not a valid OpenMetrics label name // but one needs to use keyword identifier syntax (aka. raw identifiers) @@ -114,17 +119,20 @@ fn remap_keyword_identifiers() { r#type: u64, } - let labels = Labels { r#type: 42 }; + let mut registry = Registry::default(); + let family = Family::::default(); + registry.register("my_counter", "This is my counter", family.clone()); - let mut buffer = vec![]; + // Record a single HTTP GET request. + family.get_or_create(&Labels { r#type: 42 }).inc(); - { - use prometheus_client::encoding::text::Encode; - labels.encode(&mut buffer).unwrap(); - } + // Encode all metrics in the registry in the text format. + let mut buffer = String::new(); + encode(&mut buffer, ®istry).unwrap(); - assert_eq!( - "type=\"42\"".to_string(), - String::from_utf8(buffer).unwrap() - ); + let expected = "# HELP my_counter This is my counter.\n".to_owned() + + "# TYPE my_counter counter\n" + + "my_counter_total{type=\"42\"} 1\n" + + "# EOF\n"; + assert_eq!(expected, buffer); } diff --git a/examples/actix-web.rs b/examples/actix-web.rs index 80144aa2..d01a1027 100644 --- a/examples/actix-web.rs +++ b/examples/actix-web.rs @@ -2,18 +2,18 @@ use std::sync::Mutex; use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; use prometheus_client::encoding::text::encode; -use prometheus_client::encoding::Encode; +use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] pub enum Method { Get, Post, } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct MethodLabels { pub method: Method, } @@ -34,9 +34,8 @@ pub struct AppState { pub async fn metrics_handler(state: web::Data>) -> Result { let state = state.lock().unwrap(); - let mut buf = Vec::new(); - encode(&mut buf, &state.registry)?; - let body = std::str::from_utf8(buf.as_slice()).unwrap().to_string(); + let mut body = String::new(); + encode(&mut body, &state.registry).unwrap(); Ok(HttpResponse::Ok() .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") .body(body)) @@ -55,11 +54,9 @@ async fn main() -> std::io::Result<()> { let mut state = AppState { registry: Registry::default(), }; - state.registry.register( - "requests", - "Count of requests", - Box::new(metrics.requests.clone()), - ); + state + .registry + .register("requests", "Count of requests", metrics.requests.clone()); let state = web::Data::new(Mutex::new(state)); HttpServer::new(move || { diff --git a/examples/custom-metric.rs b/examples/custom-metric.rs index 7fcda080..83c4d356 100644 --- a/examples/custom-metric.rs +++ b/examples/custom-metric.rs @@ -1,4 +1,4 @@ -use prometheus_client::encoding::text::{encode, EncodeMetric, Encoder}; +use prometheus_client::encoding::{text::encode, EncodeMetric, MetricEncoder}; use prometheus_client::metrics::MetricType; use prometheus_client::registry::Registry; @@ -7,31 +7,24 @@ use prometheus_client::registry::Registry; /// Related to the concept of "Custom Collectors" in other implementations. /// /// [`MyCustomMetric`] generates and encodes a random number on each scrape. +#[derive(Debug)] struct MyCustomMetric {} impl EncodeMetric for MyCustomMetric { - fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> { + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { // This method is called on each Prometheus server scrape. Allowing you // to execute whatever logic is needed to generate and encode your // custom metric. // - // While the `Encoder`'s builder pattern should guide you well and makes - // many mistakes impossible at the type level, do keep in mind that - // "with great power comes great responsibility". E.g. every CPU cycle - // spend in this method delays the response send to the Prometheus - // server. - - encoder - .no_suffix()? - .no_bucket()? - .encode_value(rand::random::())? - .no_exemplar()?; - - Ok(()) + // Do keep in mind that "with great power comes great responsibility". + // E.g. every CPU cycle spend in this method delays the response send to + // the Prometheus server. + + encoder.encode_counter_u64::<()>(rand::random::(), None) } fn metric_type(&self) -> prometheus_client::metrics::MetricType { - MetricType::Unknown + MetricType::Counter } } @@ -45,8 +38,8 @@ fn main() { metric, ); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - println!("Scrape output:\n{:?}", String::from_utf8(encoded).unwrap()); + println!("Scrape output:\n{:?}", encoded); } diff --git a/examples/hyper.rs b/examples/hyper.rs index 404b6c17..f5a4009d 100644 --- a/examples/hyper.rs +++ b/examples/hyper.rs @@ -21,7 +21,7 @@ async fn main() { registry.register( "requests", "How many requests the application has received", - Box::new(request_counter.clone()), + request_counter.clone(), ); // Spawn a server to serve the OpenMetrics endpoint. @@ -59,17 +59,19 @@ pub fn make_handler( move |_req: Request| { let reg = registry.clone(); Box::pin(async move { - let mut buf = Vec::new(); - encode(&mut buf, ®.clone()).map(|_| { - let body = Body::from(buf); - Response::builder() - .header( - hyper::header::CONTENT_TYPE, - "application/openmetrics-text; version=1.0.0; charset=utf-8", - ) - .body(body) - .unwrap() - }) + let mut buf = String::new(); + encode(&mut buf, ®.clone()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) + .map(|_| { + let body = Body::from(buf); + Response::builder() + .header( + hyper::header::CONTENT_TYPE, + "application/openmetrics-text; version=1.0.0; charset=utf-8", + ) + .body(body) + .unwrap() + }) }) } } diff --git a/examples/tide.rs b/examples/tide.rs index 244db06d..68cacac6 100644 --- a/examples/tide.rs +++ b/examples/tide.rs @@ -1,5 +1,5 @@ -use prometheus_client::encoding::text::encode; -use prometheus_client::encoding::Encode; +use prometheus_client::encoding::EncodeLabelValue; +use prometheus_client::encoding::{text::encode, EncodeLabelSet}; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; @@ -31,7 +31,7 @@ async fn main() -> std::result::Result<(), std::io::Error> { app.at("/").get(|_| async { Ok("Hello, world!") }); app.at("/metrics") .get(|req: tide::Request| async move { - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, &req.state().registry).unwrap(); let response = tide::Response::builder(200) .body(encoded) @@ -44,13 +44,13 @@ async fn main() -> std::result::Result<(), std::io::Error> { Ok(()) } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] struct Labels { method: Method, path: String, } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] enum Method { Get, Put, @@ -58,7 +58,7 @@ enum Method { #[derive(Clone)] struct State { - registry: Arc>>, + registry: Arc, } #[derive(Default)] diff --git a/src/encoding.rs b/src/encoding.rs index 8d9a0c68..035fc12b 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,6 +2,425 @@ pub use prometheus_client_derive_encode::*; +use crate::metrics::exemplar::Exemplar; +use crate::metrics::MetricType; +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt::Write; +use std::ops::Deref; + #[cfg(feature = "protobuf")] -pub mod proto; +pub mod protobuf; pub mod text; + +/// Trait implemented by each metric type, e.g. [`Counter`], to implement its encoding in the OpenMetric text format. +pub trait EncodeMetric { + /// Encode the given instance in the OpenMetrics text encoding. + fn encode(&self, encoder: MetricEncoder<'_, '_>) -> Result<(), std::fmt::Error>; + + /// The OpenMetrics metric type of the instance. + // One can not use [`TypedMetric`] directly, as associated constants are not + // object safe and thus can not be used with dynamic dispatching. + fn metric_type(&self) -> MetricType; +} + +impl EncodeMetric for Box { + fn encode(&self, encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + self.deref().encode(encoder) + } + + fn metric_type(&self) -> MetricType { + self.deref().metric_type() + } +} + +/// Encoder for a metric. +/// +// `MetricEncoder` does not take a trait parameter for `writer` and `labels` +// because `EncodeMetric` which uses `MetricEncoder` needs to be usable as a +// trait object in order to be able to register different metric types with a +// `Registry`. Trait objects can not use type parameters. +// +// TODO: Alternative solutions to the above are very much appreciated. +#[derive(Debug)] +pub struct MetricEncoder<'a, 'b>(MetricEncoderInner<'a, 'b>); + +#[derive(Debug)] +enum MetricEncoderInner<'a, 'b> { + Text(text::MetricEncoder<'a, 'b>), + + #[cfg(feature = "protobuf")] + Protobuf(protobuf::MetricEncoder<'a>), +} + +impl<'a, 'b> From> for MetricEncoder<'a, 'b> { + fn from(e: text::MetricEncoder<'a, 'b>) -> Self { + Self(MetricEncoderInner::Text(e)) + } +} + +#[cfg(feature = "protobuf")] +impl<'a, 'b> From> for MetricEncoder<'a, 'b> { + fn from(e: protobuf::MetricEncoder<'a>) -> Self { + Self(MetricEncoderInner::Protobuf(e)) + } +} + +macro_rules! for_both_mut { + ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { + match &mut $self.0 { + $inner::Text($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::Protobuf($pattern) => $fn, + } + }; +} + +macro_rules! for_both { + ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { + match $self.0 { + $inner::Text($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::Protobuf($pattern) => $fn, + } + }; +} + +impl<'a, 'b> MetricEncoder<'a, 'b> { + /// Encode a counter with a double value. + pub fn encode_counter_f64( + &mut self, + v: f64, + exemplar: Option<&Exemplar>, + ) -> Result<(), std::fmt::Error> { + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_counter_f64(v, exemplar) + ) + } + + /// Encode a counter with an integer value. + pub fn encode_counter_u64( + &mut self, + v: u64, + exemplar: Option<&Exemplar>, + ) -> Result<(), std::fmt::Error> { + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_counter_u64(v, exemplar) + ) + } + + /// Encode a gauge with an integer value. + pub fn encode_gauge_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { + for_both_mut!(self, MetricEncoderInner, e, e.encode_gauge_i64(v)) + } + + /// Encode a gauge with a double value. + pub fn encode_gauge_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { + for_both_mut!(self, MetricEncoderInner, e, e.encode_gauge_f64(v)) + } + + /// Encode an info. + pub fn encode_info(&mut self, label_set: &impl EncodeLabelSet) -> Result<(), std::fmt::Error> { + for_both_mut!(self, MetricEncoderInner, e, e.encode_info(label_set)) + } + + /// Encode a histogram. + pub fn encode_histogram( + &mut self, + sum: f64, + count: u64, + buckets: &[(f64, u64)], + exemplars: Option<&HashMap>>, + ) -> Result<(), std::fmt::Error> { + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_histogram(sum, count, buckets, exemplars) + ) + } + + /// Encode a metric family. + pub fn encode_family<'c, 'd, S: EncodeLabelSet>( + &'c mut self, + label_set: &'d S, + ) -> Result, std::fmt::Error> { + for_both_mut!( + self, + MetricEncoderInner, + e, + e.encode_family(label_set).map(Into::into) + ) + } +} + +/// An encodable label set. +pub trait EncodeLabelSet { + /// Encode oneself into the given encoder. + fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error>; +} + +impl<'a> From> for LabelSetEncoder<'a> { + fn from(e: text::LabelSetEncoder<'a>) -> Self { + Self(LabelSetEncoderInner::Text(e)) + } +} + +/// Encoder for a label set. +#[derive(Debug)] +pub struct LabelSetEncoder<'a>(LabelSetEncoderInner<'a>); + +#[derive(Debug)] +enum LabelSetEncoderInner<'a> { + Text(text::LabelSetEncoder<'a>), + #[cfg(feature = "protobuf")] + Protobuf(protobuf::LabelSetEncoder<'a>), +} + +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelSetEncoder<'a> { + fn from(e: protobuf::LabelSetEncoder<'a>) -> Self { + Self(LabelSetEncoderInner::Protobuf(e)) + } +} + +impl<'a> LabelSetEncoder<'a> { + /// Encode the given label. + pub fn encode_label(&mut self) -> LabelEncoder { + for_both_mut!(self, LabelSetEncoderInner, e, e.encode_label().into()) + } +} + +/// An encodable label. +pub trait EncodeLabel { + /// Encode oneself into the given encoder. + fn encode(&self, encoder: LabelEncoder) -> Result<(), std::fmt::Error>; +} + +/// Encoder for a label. +#[derive(Debug)] +pub struct LabelEncoder<'a>(LabelEncoderInner<'a>); + +#[derive(Debug)] +enum LabelEncoderInner<'a> { + Text(text::LabelEncoder<'a>), + #[cfg(feature = "protobuf")] + Protobuf(protobuf::LabelEncoder<'a>), +} + +impl<'a> From> for LabelEncoder<'a> { + fn from(e: text::LabelEncoder<'a>) -> Self { + Self(LabelEncoderInner::Text(e)) + } +} + +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelEncoder<'a> { + fn from(e: protobuf::LabelEncoder<'a>) -> Self { + Self(LabelEncoderInner::Protobuf(e)) + } +} + +impl<'a> LabelEncoder<'a> { + /// Encode a label. + pub fn encode_label_key(&mut self) -> Result { + for_both_mut!( + self, + LabelEncoderInner, + e, + e.encode_label_key().map(Into::into) + ) + } +} + +/// An encodable label key. +pub trait EncodeLabelKey { + /// Encode oneself into the given encoder. + fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error>; +} + +/// Encoder for a label key. +#[derive(Debug)] +pub struct LabelKeyEncoder<'a>(LabelKeyEncoderInner<'a>); + +#[derive(Debug)] +enum LabelKeyEncoderInner<'a> { + Text(text::LabelKeyEncoder<'a>), + #[cfg(feature = "protobuf")] + Protobuf(protobuf::LabelKeyEncoder<'a>), +} + +impl<'a> From> for LabelKeyEncoder<'a> { + fn from(e: text::LabelKeyEncoder<'a>) -> Self { + Self(LabelKeyEncoderInner::Text(e)) + } +} + +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelKeyEncoder<'a> { + fn from(e: protobuf::LabelKeyEncoder<'a>) -> Self { + Self(LabelKeyEncoderInner::Protobuf(e)) + } +} + +impl<'a> std::fmt::Write for LabelKeyEncoder<'a> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + for_both_mut!(self, LabelKeyEncoderInner, e, e.write_str(s)) + } +} + +impl<'a> LabelKeyEncoder<'a> { + /// Encode a label value. + pub fn encode_label_value(self) -> Result, std::fmt::Error> { + for_both!( + self, + LabelKeyEncoderInner, + e, + e.encode_label_value().map(LabelValueEncoder::from) + ) + } +} + +/// An encodable label value. +pub trait EncodeLabelValue { + /// Encode oneself into the given encoder. + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error>; +} + +/// Encoder for a label value. +#[derive(Debug)] +pub struct LabelValueEncoder<'a>(LabelValueEncoderInner<'a>); + +impl<'a> From> for LabelValueEncoder<'a> { + fn from(e: text::LabelValueEncoder<'a>) -> Self { + LabelValueEncoder(LabelValueEncoderInner::Text(e)) + } +} + +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelValueEncoder<'a> { + fn from(e: protobuf::LabelValueEncoder<'a>) -> Self { + LabelValueEncoder(LabelValueEncoderInner::Protobuf(e)) + } +} + +#[derive(Debug)] +enum LabelValueEncoderInner<'a> { + Text(text::LabelValueEncoder<'a>), + #[cfg(feature = "protobuf")] + Protobuf(protobuf::LabelValueEncoder<'a>), +} + +impl<'a> LabelValueEncoder<'a> { + /// Finish encoding the label value. + pub fn finish(self) -> Result<(), std::fmt::Error> { + for_both!(self, LabelValueEncoderInner, e, e.finish()) + } +} + +impl<'a> std::fmt::Write for LabelValueEncoder<'a> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s)) + } +} + +impl EncodeLabelSet for [T; N] { + fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + self.as_ref().encode(encoder) + } +} + +impl EncodeLabelSet for &[T] { + fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + if self.is_empty() { + return Ok(()); + } + + for label in self.iter() { + label.encode(encoder.encode_label())? + } + + Ok(()) + } +} + +impl EncodeLabelSet for Vec { + fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + self.as_slice().encode(encoder) + } +} + +impl EncodeLabelSet for () { + fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + Ok(()) + } +} + +impl EncodeLabel for (K, V) { + fn encode(&self, mut encoder: LabelEncoder) -> Result<(), std::fmt::Error> { + let (key, value) = self; + + let mut label_key_encoder = encoder.encode_label_key()?; + key.encode(&mut label_key_encoder)?; + + let mut label_value_encoder = label_key_encoder.encode_label_value()?; + value.encode(&mut label_value_encoder)?; + label_value_encoder.finish()?; + + Ok(()) + } +} + +impl EncodeLabelKey for &str { + fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { + encoder.write_str(self)?; + Ok(()) + } +} +impl EncodeLabelValue for &str { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { + encoder.write_str(self)?; + Ok(()) + } +} + +impl EncodeLabelKey for String { + fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { + EncodeLabelKey::encode(&self.as_str(), encoder) + } +} +impl EncodeLabelValue for String { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { + EncodeLabelValue::encode(&self.as_str(), encoder) + } +} + +impl<'a> EncodeLabelKey for Cow<'a, str> { + fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { + EncodeLabelKey::encode(&self.as_ref(), encoder) + } +} + +impl<'a> EncodeLabelValue for Cow<'a, str> { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { + EncodeLabelValue::encode(&self.as_ref(), encoder) + } +} + +impl EncodeLabelValue for f64 { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { + encoder.write_str(dtoa::Buffer::new().format(*self)) + } +} + +impl EncodeLabelValue for u64 { + fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { + encoder.write_str(itoa::Buffer::new().format(*self)) + } +} diff --git a/src/encoding/proto.rs b/src/encoding/protobuf.rs similarity index 56% rename from src/encoding/proto.rs rename to src/encoding/protobuf.rs index 1230ee4b..f4334c7b 100644 --- a/src/encoding/proto.rs +++ b/src/encoding/protobuf.rs @@ -1,7 +1,7 @@ //! Open Metrics protobuf implementation. //! //! ``` -//! # use prometheus_client::encoding::proto::encode; +//! # use prometheus_client::encoding::protobuf::encode; //! # use prometheus_client::metrics::counter::Counter; //! # use prometheus_client::registry::Registry; //! # @@ -15,7 +15,7 @@ //! # ); //! # counter.inc(); //! // Returns `MetricSet`, the top-level container type. Please refer to [openmetrics_data_model.proto](https://github.com/OpenObservability/OpenMetrics/blob/main/proto/openmetrics_data_model.proto) for details. -//! let metric_set = encode(®istry); +//! let metric_set = encode(®istry).unwrap(); //! //! let family = metric_set.metric_families.first().unwrap(); //! assert_eq!("my_counter", family.name); @@ -30,34 +30,25 @@ pub mod openmetrics_data_model { include!(concat!(env!("OUT_DIR"), "/openmetrics.rs")); } -use crate::metrics::counter::Counter; -use crate::metrics::exemplar::{CounterWithExemplar, Exemplar, HistogramWithExemplars}; -use crate::metrics::family::{Family, MetricConstructor}; -use crate::metrics::gauge::Gauge; -use crate::metrics::histogram::Histogram; -use crate::metrics::info::Info; -use crate::metrics::{counter, gauge, MetricType, TypedMetric}; -use crate::registry::Registry; use std::collections::HashMap; -use std::ops::Deref; -use void::Void; +use std::fmt; -pub use openmetrics_data_model::*; -pub use prometheus_client_derive_encode::*; +use crate::encoding::{EncodeLabelSet, LabelSetEncoderInner}; +use crate::metrics::exemplar::Exemplar; +use crate::metrics::MetricType; +use crate::registry::Registry; /// Encode the metrics registered with the provided [`Registry`] into MetricSet /// using the OpenMetrics protobuf format. -pub fn encode(registry: &Registry) -> openmetrics_data_model::MetricSet -where - M: EncodeMetric, -{ +pub fn encode(registry: &Registry) -> Result { let mut metric_set = openmetrics_data_model::MetricSet::default(); for (desc, metric) in registry.iter() { let mut family = openmetrics_data_model::MetricFamily { name: desc.name().to_string(), r#type: { - let metric_type: openmetrics_data_model::MetricType = metric.metric_type().into(); + let metric_type: openmetrics_data_model::MetricType = + super::EncodeMetric::metric_type(metric.as_ref()).into(); metric_type as i32 }, unit: if let Some(unit) = desc.unit() { @@ -69,14 +60,34 @@ where ..Default::default() }; - let mut labels = vec![]; - desc.labels().encode(&mut labels); - metric.encode(labels, &mut family.metrics); + let mut labels = encode_labels(&desc.labels())?; + + let encoder = MetricEncoder { + family: &mut family.metrics, + metric_type: super::EncodeMetric::metric_type(metric.as_ref()), + labels: &mut labels, + }; + + super::EncodeMetric::encode(metric.as_ref(), encoder.into())?; metric_set.metric_families.push(family); } - metric_set + Ok(metric_set) +} + +fn encode_labels( + label_set: &impl EncodeLabelSet, +) -> Result, fmt::Error> { + let mut encoded = vec![]; + + label_set.encode(crate::encoding::LabelSetEncoder( + LabelSetEncoderInner::Protobuf(LabelSetEncoder { + labels: &mut encoded, + }), + ))?; + + Ok(encoded) } impl From for openmetrics_data_model::MetricType { @@ -91,444 +102,257 @@ impl From for openmetrics_data_model::MetricType { } } -/// Trait implemented by each metric type, e.g. [`Counter`], to implement its encoding. -pub trait EncodeMetric { - /// Encode to OpenMetrics protobuf encoding. - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ); - - /// The OpenMetrics metric type of the instance. - fn metric_type(&self) -> MetricType; -} +#[derive(Debug)] +pub(crate) struct MetricEncoder<'a> { + metric_type: MetricType, + family: &'a mut Vec, + labels: &'a mut Vec, +} + +impl<'a> MetricEncoder<'a> { + pub fn encode_counter_u64( + &mut self, + v: u64, + exemplar: Option<&Exemplar>, + ) -> Result<(), fmt::Error> { + self.encode_counter( + openmetrics_data_model::counter_value::Total::IntValue(v), + exemplar.map(|e| e.try_into()).transpose()?, + ) + } + + pub fn encode_counter_f64( + &mut self, + v: f64, + exemplar: Option<&Exemplar>, + ) -> Result<(), fmt::Error> { + self.encode_counter( + openmetrics_data_model::counter_value::Total::DoubleValue(v), + exemplar.map(|e| e.try_into()).transpose()?, + ) + } + + fn encode_counter( + &mut self, + total: openmetrics_data_model::counter_value::Total, + exemplar: Option, + ) -> Result<(), fmt::Error> { + self.family.push(openmetrics_data_model::Metric { + labels: self.labels.clone(), + metric_points: vec![openmetrics_data_model::MetricPoint { + value: Some(openmetrics_data_model::metric_point::Value::CounterValue( + openmetrics_data_model::CounterValue { + total: Some(total), + exemplar, + ..Default::default() + }, + )), + ..Default::default() + }], + }); -impl EncodeMetric for Box { - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - self.deref().encode(labels, family) + Ok(()) } - fn metric_type(&self) -> MetricType { - self.deref().metric_type() + pub fn encode_gauge_i64(&mut self, v: i64) -> Result<(), fmt::Error> { + self.encode_gauge(openmetrics_data_model::gauge_value::Value::IntValue(v)) } -} - -/// Trait combining [`EncodeMetric`] and [`Send`]. -pub trait SendEncodeMetric: EncodeMetric + Send {} - -impl SendEncodeMetric for T {} -/// Trait to implement its label encoding in the OpenMetrics protobuf format. -pub trait EncodeLabels { - /// Encode the given instance into Labels in the OpenMetrics protobuf - /// encoding. - fn encode(&self, labels: &mut Vec); -} - -impl From<&(K, V)> for openmetrics_data_model::Label { - fn from(kv: &(K, V)) -> Self { - openmetrics_data_model::Label { - name: kv.0.to_string(), - value: kv.1.to_string(), - } + pub fn encode_gauge_f64(&mut self, v: f64) -> Result<(), fmt::Error> { + self.encode_gauge(openmetrics_data_model::gauge_value::Value::DoubleValue(v)) } -} -impl EncodeLabels for f64 { - fn encode(&self, labels: &mut Vec) { - labels.push(openmetrics_data_model::Label { - name: self.to_string(), - value: self.to_string(), - }) - } -} + fn encode_gauge( + &mut self, + v: openmetrics_data_model::gauge_value::Value, + ) -> Result<(), fmt::Error> { + self.family.push(openmetrics_data_model::Metric { + labels: self.labels.clone(), + metric_points: vec![openmetrics_data_model::MetricPoint { + value: Some(openmetrics_data_model::metric_point::Value::GaugeValue( + openmetrics_data_model::GaugeValue { value: Some(v) }, + )), + ..Default::default() + }], + }); -impl EncodeLabels for u64 { - fn encode(&self, labels: &mut Vec) { - labels.push(openmetrics_data_model::Label { - name: self.to_string(), - value: self.to_string(), - }) + Ok(()) } -} -impl EncodeLabels for u32 { - fn encode(&self, labels: &mut Vec) { - labels.push(openmetrics_data_model::Label { - name: self.to_string(), - value: self.to_string(), - }) - } -} + pub fn encode_info( + &mut self, + label_set: &impl super::EncodeLabelSet, + ) -> Result<(), fmt::Error> { + let info_labels = encode_labels(label_set)?; -impl EncodeLabels for String { - fn encode(&self, labels: &mut Vec) { - labels.push(openmetrics_data_model::Label { - name: self.clone(), - value: self.clone(), - }) - } -} + self.family.push(openmetrics_data_model::Metric { + labels: self.labels.clone(), + metric_points: vec![openmetrics_data_model::MetricPoint { + value: Some(openmetrics_data_model::metric_point::Value::InfoValue( + openmetrics_data_model::InfoValue { info: info_labels }, + )), + ..Default::default() + }], + }); -impl EncodeLabels for Vec -where - for<'a> &'a T: Into, -{ - fn encode(&self, labels: &mut Vec) { - self.as_slice().encode(labels); + Ok(()) } -} -impl EncodeLabels for [T] -where - for<'a> &'a T: Into, -{ - fn encode(&self, labels: &mut Vec) { - labels.extend(self.iter().map(|t| t.into())); - } -} + pub fn encode_family<'b, S: EncodeLabelSet>( + &'b mut self, + label_set: &S, + ) -> Result, fmt::Error> { + label_set.encode( + LabelSetEncoder { + labels: &mut self.labels, + } + .into(), + )?; -impl EncodeLabels for Void { - fn encode(&self, _labels: &mut Vec) { - void::unreachable(*self); + Ok(MetricEncoder { + metric_type: self.metric_type, + family: self.family, + labels: self.labels, + }) } -} - -fn encode_exemplar(exemplar: &Exemplar) -> openmetrics_data_model::Exemplar -where - N: Clone, - S: EncodeLabels, - f64: From, // required because Exemplar.value is defined as `double` in protobuf -{ - let mut exemplar_proto = openmetrics_data_model::Exemplar { - value: exemplar.value.clone().into(), - ..Default::default() - }; - exemplar.label_set.encode(&mut exemplar_proto.label); - - exemplar_proto -} - -///////////////////////////////////////////////////////////////////////////////// -// Counter - -/// Trait to implement its counter value encoding in the OpenMetrics protobuf -/// format. -pub trait EncodeCounterValue { - /// Encode the given instance into counter value in the OpenMetrics protobuf - /// encoding. - fn encode(&self) -> openmetrics_data_model::counter_value::Total; -} -impl EncodeCounterValue for u64 { - fn encode(&self) -> openmetrics_data_model::counter_value::Total { - openmetrics_data_model::counter_value::Total::IntValue(*self) - } -} + pub fn encode_histogram( + &mut self, + sum: f64, + count: u64, + buckets: &[(f64, u64)], + exemplars: Option<&HashMap>>, + ) -> Result<(), fmt::Error> { + let buckets = buckets + .into_iter() + .enumerate() + .map(|(i, (upper_bound, count))| { + Ok::<_, fmt::Error>(openmetrics_data_model::histogram_value::Bucket { + upper_bound: *upper_bound, + count: *count, + exemplar: exemplars + .and_then(|exemplars| exemplars.get(&i).map(|exemplar| exemplar.try_into())) + .transpose()?, + ..Default::default() + }) + }) + .collect::, _>>()?; + + self.family.push(openmetrics_data_model::Metric { + labels: self.labels.clone(), + metric_points: vec![openmetrics_data_model::MetricPoint { + value: Some(openmetrics_data_model::metric_point::Value::HistogramValue( + openmetrics_data_model::HistogramValue { + count, + created: None, + buckets, + sum: Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( + sum, + )), + }, + )), + ..Default::default() + }], + }); -impl EncodeCounterValue for f64 { - fn encode(&self) -> openmetrics_data_model::counter_value::Total { - openmetrics_data_model::counter_value::Total::DoubleValue(*self) + Ok(()) } } -impl EncodeMetric for Counter -where - N: EncodeCounterValue, - A: counter::Atomic, -{ - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - let mut metric = encode_counter_with_maybe_exemplar(self.get(), None); - metric.labels = labels; - - family.push(metric); - } - - fn metric_type(&self) -> MetricType { - Self::TYPE - } -} +impl TryFrom<&Exemplar> for openmetrics_data_model::Exemplar { + type Error = fmt::Error; -impl EncodeMetric for CounterWithExemplar -where - S: EncodeLabels, - N: Clone + EncodeCounterValue, - A: counter::Atomic, - f64: From, -{ - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - let (value, exemplar) = self.get(); - let exemplar_proto = exemplar.as_ref().map(|e| encode_exemplar(e)); - let mut metric = encode_counter_with_maybe_exemplar(value, exemplar_proto); - metric.labels = labels; - - family.push(metric); - } - - fn metric_type(&self) -> MetricType { - Counter::::TYPE + fn try_from(exemplar: &Exemplar) -> Result { + Ok(openmetrics_data_model::Exemplar { + value: exemplar.value, + timestamp: Default::default(), + label: encode_labels(&exemplar.label_set)?, + }) } } -fn encode_counter_with_maybe_exemplar( - value: N, - exemplar: Option, -) -> openmetrics_data_model::Metric -where - N: EncodeCounterValue, -{ - openmetrics_data_model::Metric { - metric_points: { - let metric_point = openmetrics_data_model::MetricPoint { - value: { - Some(openmetrics_data_model::metric_point::Value::CounterValue( - openmetrics_data_model::CounterValue { - total: Some(value.encode()), - exemplar, - ..Default::default() - }, - )) - }, - ..Default::default() - }; +impl TryFrom<&Exemplar> for openmetrics_data_model::Exemplar { + type Error = fmt::Error; - vec![metric_point] - }, - ..Default::default() + fn try_from(exemplar: &Exemplar) -> Result { + Ok(openmetrics_data_model::Exemplar { + value: exemplar.value as f64, + timestamp: Default::default(), + label: encode_labels(&exemplar.label_set)?, + }) } } -///////////////////////////////////////////////////////////////////////////////// -// Gauge - -/// Trait to implement its gauge value encoding in the OpenMetrics protobuf -/// format. -pub trait EncodeGaugeValue { - /// Encode the given instance into gauge value in the OpenMetrics protobuf - /// encoding. - fn encode(&self) -> openmetrics_data_model::gauge_value::Value; -} - -// GaugeValue.int_value is defined as `int64` in protobuf -impl EncodeGaugeValue for i64 { - fn encode(&self) -> openmetrics_data_model::gauge_value::Value { - openmetrics_data_model::gauge_value::Value::IntValue(*self) +impl From<&(K, V)> for openmetrics_data_model::Label { + fn from(kv: &(K, V)) -> Self { + openmetrics_data_model::Label { + name: kv.0.to_string(), + value: kv.1.to_string(), + } } } -impl EncodeGaugeValue for u64 { - fn encode(&self) -> openmetrics_data_model::gauge_value::Value { - openmetrics_data_model::gauge_value::Value::IntValue(*self as i64) - } +#[derive(Debug)] +pub(crate) struct LabelSetEncoder<'a> { + labels: &'a mut Vec, } -impl EncodeGaugeValue for f64 { - fn encode(&self) -> openmetrics_data_model::gauge_value::Value { - openmetrics_data_model::gauge_value::Value::DoubleValue(*self) +impl<'a> LabelSetEncoder<'a> { + pub fn encode_label(&mut self) -> LabelEncoder { + LabelEncoder { + labels: self.labels, + } } } -impl EncodeMetric for Gauge -where - N: EncodeGaugeValue, - A: gauge::Atomic, -{ - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - let metric = openmetrics_data_model::Metric { - metric_points: { - let metric_point = openmetrics_data_model::MetricPoint { - value: { - Some(openmetrics_data_model::metric_point::Value::GaugeValue( - openmetrics_data_model::GaugeValue { - value: Some(self.get().encode()), - }, - )) - }, - ..Default::default() - }; - - vec![metric_point] - }, - labels, - }; - - family.push(metric) - } - - fn metric_type(&self) -> MetricType { - Self::TYPE - } +#[derive(Debug)] +pub(crate) struct LabelEncoder<'a> { + labels: &'a mut Vec, } -///////////////////////////////////////////////////////////////////////////////// -// Family - -impl EncodeMetric for Family -where - S: EncodeLabels + Clone + std::hash::Hash + Eq, - M: EncodeMetric + TypedMetric, - C: MetricConstructor, -{ - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - for (label_set, metric) in self.read().iter() { - let mut labels = labels.clone(); - label_set.encode(&mut labels); - metric.encode(labels, family) - } - } +impl<'a> LabelEncoder<'a> { + pub fn encode_label_key(&mut self) -> Result { + self.labels.push(openmetrics_data_model::Label::default()); - fn metric_type(&self) -> MetricType { - M::TYPE + Ok(LabelKeyEncoder { + label: self.labels.last_mut().expect("To find pushed label."), + }) } } -///////////////////////////////////////////////////////////////////////////////// -// Histogram - -impl EncodeMetric for Histogram { - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - let (sum, count, buckets) = self.get(); - // TODO: Would be better to use never type instead of `Void`. - let mut metric = encode_histogram_with_maybe_exemplars::(sum, count, &buckets, None); - metric.labels = labels; - - family.push(metric) - } - - fn metric_type(&self) -> MetricType { - Self::TYPE - } +#[derive(Debug)] +pub(crate) struct LabelKeyEncoder<'a> { + label: &'a mut openmetrics_data_model::Label, } -impl EncodeMetric for HistogramWithExemplars -where - S: EncodeLabels, -{ - fn encode( - &self, - labels: Vec, - family: &mut Vec, - ) { - let inner = self.inner(); - let (sum, count, buckets) = inner.histogram.get(); - let mut metric = - encode_histogram_with_maybe_exemplars(sum, count, &buckets, Some(&inner.exemplars)); - metric.labels = labels; - - family.push(metric) - } - - fn metric_type(&self) -> MetricType { - Histogram::TYPE +impl<'a> fmt::Write for LabelKeyEncoder<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.label.name.write_str(s) } } -fn encode_histogram_with_maybe_exemplars( - sum: f64, - count: u64, - buckets: &[(f64, u64)], - exemplars: Option<&HashMap>>, -) -> openmetrics_data_model::Metric -where - S: EncodeLabels, -{ - openmetrics_data_model::Metric { - metric_points: { - let metric_point = openmetrics_data_model::MetricPoint { - value: { - let mut histogram_value = openmetrics_data_model::HistogramValue { - sum: Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( - sum, - )), - count, - ..Default::default() - }; - - let mut cummulative = 0; - for (i, (upper_bound, count)) in buckets.iter().enumerate() { - cummulative += count; - let bucket = openmetrics_data_model::histogram_value::Bucket { - count: cummulative, - upper_bound: *upper_bound, - exemplar: exemplars - .and_then(|es| es.get(&i)) - .map(|exemplar| encode_exemplar(exemplar)), - }; - histogram_value.buckets.push(bucket); - } - Some(openmetrics_data_model::metric_point::Value::HistogramValue( - histogram_value, - )) - }, - ..Default::default() - }; - - vec![metric_point] - }, - ..Default::default() +impl<'a> LabelKeyEncoder<'a> { + pub fn encode_label_value(self) -> Result, fmt::Error> { + Ok(LabelValueEncoder { + label_value: &mut self.label.value, + }) } } -///////////////////////////////////////////////////////////////////////////////// -// Info - -impl EncodeMetric for Info -where - S: EncodeLabels, -{ - fn encode( - &self, - mut labels: Vec, - family: &mut Vec, - ) { - let metric = openmetrics_data_model::Metric { - metric_points: { - let metric_point = openmetrics_data_model::MetricPoint { - value: { - self.0.encode(&mut labels); - - Some(openmetrics_data_model::metric_point::Value::InfoValue( - openmetrics_data_model::InfoValue { info: labels }, - )) - }, - ..Default::default() - }; - - vec![metric_point] - }, - ..Default::default() - }; +#[derive(Debug)] +pub(crate) struct LabelValueEncoder<'a> { + label_value: &'a mut String, +} - family.push(metric); +impl<'a> LabelValueEncoder<'a> { + pub fn finish(self) -> Result<(), fmt::Error> { + Ok(()) } +} - fn metric_type(&self) -> MetricType { - Self::TYPE +impl<'a> fmt::Write for LabelValueEncoder<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.label_value.write_str(s) } } @@ -552,7 +376,7 @@ mod tests { registry.register("my_counter", "My counter", counter.clone()); counter.inc(); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_counter", family.name); @@ -582,7 +406,7 @@ mod tests { registry.register("my_counter", "My counter", counter.clone()); counter.inc(); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_counter", family.name); @@ -611,7 +435,7 @@ mod tests { let counter: Counter = Counter::default(); registry.register_with_unit("my_counter", "My counter", Unit::Seconds, counter); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_counter", family.name); @@ -633,7 +457,7 @@ mod tests { counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 42.0)])); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_counter_with_exemplar", family.name); @@ -656,7 +480,7 @@ mod tests { let expected_label = { openmetrics_data_model::Label { name: "user_id".to_string(), - value: "42".to_string(), + value: "42.0".to_string(), } }; assert_eq!(vec![expected_label], exemplar.label); @@ -672,7 +496,7 @@ mod tests { registry.register("my_gauge", "My gauge", gauge.clone()); gauge.inc(); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_gauge", family.name); assert_eq!("My gauge.", family.help); @@ -704,7 +528,7 @@ mod tests { ]) .inc(); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_counter_family", family.name); @@ -749,7 +573,7 @@ mod tests { ]) .inc(); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_prefix_my_counter_family", family.name); @@ -787,7 +611,7 @@ mod tests { registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_histogram", family.name); @@ -820,7 +644,7 @@ mod tests { registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0, Some(vec![("user_id".to_string(), 42u64)])); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_histogram", family.name); @@ -850,7 +674,7 @@ mod tests { #[test] fn encode_family_counter_histogram() { - let mut registry = Registry::>::default(); + let mut registry = Registry::default(); let counter_family = Family::, Counter>::default(); let histogram_family = @@ -858,12 +682,8 @@ mod tests { Histogram::new(exponential_buckets(1.0, 2.0, 10)) }); - registry.register("my_counter", "My counter", Box::new(counter_family.clone())); - registry.register( - "my_histogram", - "My histogram", - Box::new(histogram_family.clone()), - ); + registry.register("my_counter", "My counter", counter_family.clone()); + registry.register("my_histogram", "My histogram", histogram_family.clone()); counter_family .get_or_create(&vec![("path".to_string(), "/".to_string())]) @@ -873,14 +693,14 @@ mod tests { .get_or_create(&vec![("path".to_string(), "/".to_string())]) .observe(1.0); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); assert_eq!("my_counter", metric_set.metric_families[0].name); assert_eq!("my_histogram", metric_set.metric_families[1].name); } #[test] fn encode_family_and_counter_and_histogram() { - let mut registry = Registry::>::default(); + let mut registry = Registry::default(); // Family let counter_family = Family::, Counter>::default(); @@ -889,15 +709,11 @@ mod tests { Histogram::new(exponential_buckets(1.0, 2.0, 10)) }); - registry.register( - "my_family_counter", - "My counter", - Box::new(counter_family.clone()), - ); + registry.register("my_family_counter", "My counter", counter_family.clone()); registry.register( "my_family_histogram", "My histogram", - Box::new(histogram_family.clone()), + histogram_family.clone(), ); counter_family @@ -910,15 +726,15 @@ mod tests { // Counter let counter: Counter = Counter::default(); - registry.register("my_counter", "My counter", Box::new(counter.clone())); + registry.register("my_counter", "My counter", counter.clone()); counter.inc(); // Histogram let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); - registry.register("my_histogram", "My histogram", Box::new(histogram.clone())); + registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); assert_eq!("my_family_counter", metric_set.metric_families[0].name); assert_eq!("my_family_histogram", metric_set.metric_families[1].name); } @@ -929,7 +745,7 @@ mod tests { let info = Info::new(vec![("os".to_string(), "GNU/linux".to_string())]); registry.register("my_info_metric", "My info metric", info); - let metric_set = encode(®istry); + let metric_set = encode(®istry).unwrap(); let family = metric_set.metric_families.first().unwrap(); assert_eq!("my_info_metric", family.name); diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 754b08a7..7898ed29 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -14,600 +14,446 @@ //! # counter.clone(), //! # ); //! # counter.inc(); -//! let mut buffer = vec![]; +//! let mut buffer = String::new(); //! encode(&mut buffer, ®istry).unwrap(); //! //! let expected = "# HELP my_counter This is my counter.\n".to_owned() + //! "# TYPE my_counter counter\n" + //! "my_counter_total 1\n" + //! "# EOF\n"; -//! assert_eq!(expected, String::from_utf8(buffer).unwrap()); +//! assert_eq!(expected, buffer); //! ``` -use crate::metrics::counter::{self, Counter}; -use crate::metrics::exemplar::{CounterWithExemplar, Exemplar, HistogramWithExemplars}; -use crate::metrics::family::{Family, MetricConstructor}; -use crate::metrics::gauge::{self, Gauge}; -use crate::metrics::histogram::Histogram; -use crate::metrics::info::Info; -use crate::metrics::{MetricType, TypedMetric}; +use crate::encoding::{EncodeLabelSet, EncodeMetric}; +use crate::metrics::exemplar::Exemplar; use crate::registry::{Registry, Unit}; use std::borrow::Cow; use std::collections::HashMap; -use std::io::Write; -use std::ops::Deref; +use std::fmt; +use std::fmt::Write; /// Encode the metrics registered with the provided [`Registry`] into the /// provided [`Write`]r using the OpenMetrics text format. -pub fn encode(writer: &mut W, registry: &Registry) -> Result<(), std::io::Error> +pub fn encode(writer: &mut W, registry: &Registry) -> Result<(), fmt::Error> where W: Write, - M: EncodeMetric, { for (desc, metric) in registry.iter() { - writer.write_all(b"# HELP ")?; - writer.write_all(desc.name().as_bytes())?; + writer.write_str("# HELP ")?; + writer.write_str(desc.name())?; if let Some(unit) = desc.unit() { - writer.write_all(b"_")?; - unit.encode(writer)?; + writer.write_str("_")?; + writer.write_str(unit.as_str())?; } - writer.write_all(b" ")?; - writer.write_all(desc.help().as_bytes())?; - writer.write_all(b"\n")?; + writer.write_str(" ")?; + writer.write_str(desc.help())?; + writer.write_str("\n")?; - writer.write_all(b"# TYPE ")?; - writer.write_all(desc.name().as_bytes())?; + writer.write_str("# TYPE ")?; + writer.write_str(desc.name())?; if let Some(unit) = desc.unit() { - writer.write_all(b"_")?; - unit.encode(writer)?; + writer.write_str("_")?; + writer.write_str(unit.as_str())?; } - writer.write_all(b" ")?; - metric.metric_type().encode(writer)?; - writer.write_all(b"\n")?; + writer.write_str(" ")?; + writer.write_str(EncodeMetric::metric_type(metric.as_ref()).as_str())?; + writer.write_str("\n")?; if let Some(unit) = desc.unit() { - writer.write_all(b"# UNIT ")?; - writer.write_all(desc.name().as_bytes())?; - writer.write_all(b"_")?; - unit.encode(writer)?; - writer.write_all(b" ")?; - unit.encode(writer)?; - writer.write_all(b"\n")?; + writer.write_str("# UNIT ")?; + writer.write_str(desc.name())?; + writer.write_str("_")?; + writer.write_str(unit.as_str())?; + writer.write_str(" ")?; + writer.write_str(unit.as_str())?; + writer.write_str("\n")?; } - let encoder = Encoder { + let encoder = MetricEncoder { writer, name: desc.name(), unit: desc.unit(), const_labels: desc.labels(), - labels: None, - }; + family_labels: None, + } + .into(); - metric.encode(encoder)?; + EncodeMetric::encode(metric.as_ref(), encoder)?; } - writer.write_all(b"# EOF\n")?; + writer.write_str("# EOF\n")?; Ok(()) } -/// OpenMetrics text encoding for a value. -pub trait Encode { - /// Encode to OpenMetrics text encoding. - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error>; -} - -impl Encode for f64 { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - writer.write_all(dtoa::Buffer::new().format(*self).as_bytes())?; - Ok(()) - } -} - -impl Encode for u64 { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - writer.write_all(itoa::Buffer::new().format(*self).as_bytes())?; - Ok(()) - } -} - -impl Encode for u32 { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - writer.write_all(itoa::Buffer::new().format(*self).as_bytes())?; - Ok(()) - } +/// Helper type for [`EncodeMetric`], see [`EncodeMetric::encode`]. +pub(crate) struct MetricEncoder<'a, 'b> { + writer: &'a mut dyn Write, + name: &'a str, + unit: &'a Option, + const_labels: &'a [(Cow<'static, str>, Cow<'static, str>)], + family_labels: Option<&'b dyn super::EncodeLabelSet>, } -impl Encode for &[T] { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - if self.is_empty() { - return Ok(()); - } - - let mut iter = self.iter().peekable(); - while let Some(x) = iter.next() { - x.encode(writer)?; - - if iter.peek().is_some() { - writer.write_all(b",")?; - } +impl<'a, 'b> std::fmt::Debug for MetricEncoder<'a, 'b> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut labels = String::new(); + if let Some(l) = self.family_labels { + l.encode(LabelSetEncoder::new(&mut labels).into())?; } - Ok(()) + f.debug_struct("Encoder") + .field("name", &self.name) + .field("unit", &self.unit) + .field("const_labels", &self.const_labels) + .field("labels", &labels.as_str()) + .finish() } } -impl Encode for Vec { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - self.as_slice().encode(writer) +impl<'a, 'b> MetricEncoder<'a, 'b> { + pub fn encode_counter_u64( + &mut self, + v: u64, + exemplar: Option<&Exemplar>, + ) -> Result<(), fmt::Error> { + self.encode_counter(v, |w, v| w.write_fmt(format_args!("{v}")), exemplar) } -} -impl Encode for (K, V) { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - let (key, value) = self; + pub fn encode_counter_f64( + &mut self, + v: f64, + exemplar: Option<&Exemplar>, + ) -> Result<(), fmt::Error> { + self.encode_counter( + v, + |w, v| w.write_str(dtoa::Buffer::new().format(v)), + exemplar, + ) + } - key.encode(writer)?; - writer.write_all(b"=\"")?; + fn encode_counter( + &mut self, + value: V, + encode_value: impl Fn(&mut dyn Write, V) -> fmt::Result, + exemplar: Option<&Exemplar>, + ) -> Result<(), fmt::Error> + where + V: Copy, + { + self.write_name_and_unit()?; - value.encode(writer)?; - writer.write_all(b"\"")?; + self.write_suffix("total")?; - Ok(()) - } -} + self.encode_labels::<()>(None)?; -impl Encode for &str { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - // TODO: Can we do better? - writer.write_all(self.as_bytes())?; - Ok(()) - } -} + self.writer.write_str(" ")?; + encode_value(self.writer, value)?; -impl Encode for String { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - self.as_str().encode(writer) - } -} + if let Some(exemplar) = exemplar { + self.encode_exemplar(encode_value, exemplar)?; + } -impl<'a> Encode for Cow<'a, str> { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - self.as_ref().encode(writer) - } -} + self.newline()?; -impl Encode for MetricType { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - let t = match self { - MetricType::Counter => "counter", - MetricType::Gauge => "gauge", - MetricType::Histogram => "histogram", - MetricType::Info => "info", - MetricType::Unknown => "unknown", - }; - - writer.write_all(t.as_bytes())?; Ok(()) } -} -impl Encode for Unit { - fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { - writer.write_all(self.as_str().as_bytes())?; - Ok(()) + pub fn encode_gauge_i64(&mut self, v: i64) -> Result<(), fmt::Error> { + self.encode_gauge(|w| w.write_fmt(format_args!("{v}"))) } -} -impl Encode for () { - fn encode(&self, _writer: &mut dyn Write) -> Result<(), std::io::Error> { - Ok(()) + pub fn encode_gauge_f64(&mut self, v: f64) -> Result<(), fmt::Error> { + self.encode_gauge(|w| w.write_str(dtoa::Buffer::new().format(v))) } -} - -/// Helper type for [`EncodeMetric`], see [`EncodeMetric::encode`]. -/// -// `Encoder` does not take a trait parameter for `writer` and `labels` because -// `EncodeMetric` which uses `Encoder` needs to be usable as a trait object in -// order to be able to register different metric types with a `Registry`. Trait -// objects can not use type parameters. -// -// TODO: Alternative solutions to the above are very much appreciated. -#[allow(missing_debug_implementations)] -pub struct Encoder<'a, 'b> { - writer: &'a mut dyn Write, - name: &'a str, - unit: &'a Option, - const_labels: &'a [(Cow<'static, str>, Cow<'static, str>)], - labels: Option<&'b dyn Encode>, -} -impl<'a, 'b> Encoder<'a, 'b> { - /// Encode a metric suffix, e.g. in the case of [`Counter`] the suffic `_total`. - pub fn encode_suffix(&mut self, suffix: &'static str) -> Result { + fn encode_gauge( + &mut self, + encode_value: impl FnOnce(&mut dyn Write) -> fmt::Result, + ) -> Result<(), fmt::Error> { self.write_name_and_unit()?; - self.writer.write_all(b"_")?; - self.writer.write_all(suffix.as_bytes()).map(|_| ())?; + self.encode_labels::<()>(None)?; - self.encode_labels() - } + self.writer.write_str(" ")?; + encode_value(self.writer)?; - /// Signal that the metric has no suffix. - pub fn no_suffix(&mut self) -> Result { - self.write_name_and_unit()?; - - self.encode_labels() - } - - fn write_name_and_unit(&mut self) -> Result<(), std::io::Error> { - self.writer.write_all(self.name.as_bytes())?; - if let Some(unit) = self.unit { - self.writer.write_all(b"_")?; - unit.encode(self.writer)?; - } + self.newline()?; Ok(()) } - // TODO: Consider caching the encoded labels for Histograms as they stay the - // same but are currently encoded multiple times. - fn encode_labels(&mut self) -> Result { - let mut opened_curly_brackets = false; + pub fn encode_info(&mut self, label_set: &S) -> Result<(), fmt::Error> { + self.write_name_and_unit()?; - if !self.const_labels.is_empty() { - self.writer.write_all(b"{")?; - opened_curly_brackets = true; + self.write_suffix("info")?; - self.const_labels.encode(self.writer)?; - } + self.encode_labels(Some(label_set))?; - if let Some(labels) = &self.labels { - if opened_curly_brackets { - self.writer.write_all(b",")?; - } else { - opened_curly_brackets = true; - self.writer.write_all(b"{")?; - } - labels.encode(self.writer)?; - } + self.writer.write_str(" ")?; + self.writer.write_str(itoa::Buffer::new().format(1))?; - Ok(BucketEncoder { - opened_curly_brackets, - writer: self.writer, - }) + self.newline()?; + + Ok(()) } /// Encode a set of labels. Used by wrapper metric types like [`Family`]. - pub fn with_label_set<'c, 'd>(&'c mut self, label_set: &'d dyn Encode) -> Encoder<'c, 'd> { - debug_assert!(self.labels.is_none()); + pub fn encode_family<'c, 'd, S: EncodeLabelSet>( + &'c mut self, + label_set: &'d S, + ) -> Result, fmt::Error> { + debug_assert!(self.family_labels.is_none()); - Encoder { + Ok(MetricEncoder { writer: self.writer, name: self.name, unit: self.unit, const_labels: self.const_labels, - labels: Some(label_set), - } + family_labels: Some(label_set), + }) } -} -/// Used to encode an OpenMetrics Histogram bucket. -#[allow(missing_debug_implementations)] -#[must_use] -pub struct BucketEncoder<'a> { - writer: &'a mut dyn Write, - opened_curly_brackets: bool, -} + pub fn encode_histogram( + &mut self, + sum: f64, + count: u64, + buckets: &[(f64, u64)], + exemplars: Option<&HashMap>>, + ) -> Result<(), fmt::Error> { + self.write_name_and_unit()?; + self.write_suffix("sum")?; + self.encode_labels::<()>(None)?; + self.writer.write_str(" ")?; + self.writer.write_str(dtoa::Buffer::new().format(sum))?; + self.newline()?; -impl<'a> BucketEncoder<'a> { - /// Encode a bucket. Used for the [`Histogram`] metric type. - pub fn encode_bucket(&mut self, upper_bound: f64) -> Result { - if self.opened_curly_brackets { - self.writer.write_all(b",")?; - } else { - self.writer.write_all(b"{")?; - } + self.write_name_and_unit()?; + self.write_suffix("count")?; + self.encode_labels::<()>(None)?; + self.writer.write_str(" ")?; + self.writer.write_str(itoa::Buffer::new().format(count))?; + self.newline()?; - self.writer.write_all(b"le=\"")?; - if upper_bound == f64::MAX { - self.writer.write_all(b"+Inf")?; - } else { - upper_bound.encode(self.writer)?; - } - self.writer.write_all(b"\"}")?; + let mut cummulative = 0; + for (i, (upper_bound, count)) in buckets.iter().enumerate() { + cummulative += count; - Ok(ValueEncoder { - writer: self.writer, - }) - } + self.write_name_and_unit()?; + self.write_suffix("bucket")?; - /// Signal that the metric type has no bucket. - pub fn no_bucket(&mut self) -> Result { - if self.opened_curly_brackets { - self.writer.write_all(b"}")?; - } - Ok(ValueEncoder { - writer: self.writer, - }) - } -} + if *upper_bound == f64::MAX { + self.encode_labels(Some(&[("le", "+Inf")]))?; + } else { + self.encode_labels(Some(&[("le", *upper_bound)]))?; + } -/// Used to encode an OpenMetrics metric value. -#[allow(missing_debug_implementations)] -#[must_use] -pub struct ValueEncoder<'a> { - writer: &'a mut dyn Write, -} + self.writer.write_str(" ")?; + self.writer + .write_str(itoa::Buffer::new().format(cummulative))?; -impl<'a> ValueEncoder<'a> { - /// Encode the metric value. E.g. in the case of [`Counter`] the - /// monotonically increasing counter value. - pub fn encode_value(&mut self, v: V) -> Result { - self.writer.write_all(b" ")?; - v.encode(self.writer)?; - Ok(ExemplarEncoder { - writer: self.writer, - }) - } -} + if let Some(exemplar) = exemplars.and_then(|e| e.get(&i)) { + self.encode_exemplar(|w, v| w.write_str(dtoa::Buffer::new().format(v)), exemplar)?; + } -/// Used to encode an OpenMetrics Exemplar. -#[allow(missing_debug_implementations)] -#[must_use] -pub struct ExemplarEncoder<'a> { - writer: &'a mut dyn Write, -} + self.newline()?; + } + + Ok(()) + } -impl<'a> ExemplarEncoder<'a> { /// Encode an exemplar for the given metric. - pub fn encode_exemplar( + fn encode_exemplar( &mut self, + encode_value: impl Fn(&mut dyn Write, V) -> fmt::Result, exemplar: &Exemplar, - ) -> Result<(), std::io::Error> { - self.writer.write_all(b" # {")?; - exemplar.label_set.encode(self.writer)?; - self.writer.write_all(b"} ")?; - exemplar.value.encode(self.writer)?; - self.writer.write_all(b"\n")?; - Ok(()) - } + ) -> Result<(), fmt::Error> + where + V: Copy, + { + self.writer.write_str(" # {")?; + exemplar + .label_set + .encode(LabelSetEncoder::new(self.writer).into())?; + self.writer.write_str("} ")?; + encode_value(self.writer, exemplar.value)?; - /// Signal that the metric type has no exemplar. - pub fn no_exemplar(&mut self) -> Result<(), std::io::Error> { - self.writer.write_all(b"\n")?; Ok(()) } -} -/// Trait implemented by each metric type, e.g. [`Counter`], to implement its encoding in the OpenMetric text format. -pub trait EncodeMetric { - /// Encode the given instance in the OpenMetrics text encoding. - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error>; + fn newline(&mut self) -> Result<(), fmt::Error> { + self.writer.write_str("\n") + } - /// The OpenMetrics metric type of the instance. - // One can not use [`TypedMetric`] directly, as associated constants are not - // object safe and thus can not be used with dynamic dispatching. - fn metric_type(&self) -> MetricType; -} + fn write_name_and_unit(&mut self) -> Result<(), fmt::Error> { + self.writer.write_str(self.name)?; + if let Some(unit) = self.unit { + self.writer.write_str("_")?; + self.writer.write_str(unit.as_str())?; + } -impl EncodeMetric for Box { - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - self.deref().encode(encoder) + Ok(()) } - fn metric_type(&self) -> MetricType { - self.deref().metric_type() + fn write_suffix(&mut self, suffix: &'static str) -> Result<(), fmt::Error> { + self.writer.write_str("_")?; + self.writer.write_str(suffix)?; + + Ok(()) } -} -/// Trait combining [`EncodeMetric`], [`Send`] and [`Sync`]. -pub trait SendSyncEncodeMetric: EncodeMetric + Send + Sync {} + // TODO: Consider caching the encoded labels for Histograms as they stay the + // same but are currently encoded multiple times. + fn encode_labels( + &mut self, + additional_labels: Option<&S>, + ) -> Result<(), fmt::Error> { + if self.const_labels.is_empty() + && additional_labels.is_none() + && self.family_labels.is_none() + { + return Ok(()); + } -impl SendSyncEncodeMetric for T {} + self.writer.write_str("{")?; -impl EncodeMetric for Box { - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - self.deref().encode(encoder) - } + self.const_labels + .encode(LabelSetEncoder::new(self.writer).into())?; - fn metric_type(&self) -> MetricType { - self.deref().metric_type() - } -} + if let Some(additional_labels) = additional_labels { + if !self.const_labels.is_empty() { + self.writer.write_str(",")?; + } -///////////////////////////////////////////////////////////////////////////////// -// Counter + additional_labels.encode(LabelSetEncoder::new(self.writer).into())?; + } -impl EncodeMetric for Counter -where - N: Encode, - A: counter::Atomic, -{ - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - // TODO: Would be better to use never type instead of `()`. - encode_counter_with_maybe_exemplar::<(), _>(self.get(), None, encoder) - } + if let Some(labels) = &self.family_labels { + if !self.const_labels.is_empty() || additional_labels.is_some() { + self.writer.write_str(",")?; + } - fn metric_type(&self) -> MetricType { - Self::TYPE - } -} + labels.encode(LabelSetEncoder::new(self.writer).into())?; + } -// TODO: S, V, N, A are hard to grasp. -impl EncodeMetric for CounterWithExemplar -where - S: Encode, - N: Encode + Clone, - A: counter::Atomic, -{ - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - let (value, exemplar) = self.get(); - encode_counter_with_maybe_exemplar(value, exemplar.as_ref(), encoder) - } + self.writer.write_str("}")?; - fn metric_type(&self) -> MetricType { - Counter::::TYPE + Ok(()) } } -fn encode_counter_with_maybe_exemplar( - value: N, - exemplar: Option<&Exemplar>, - mut encoder: Encoder, -) -> Result<(), std::io::Error> -where - S: Encode, - N: Encode, -{ - let mut bucket_encoder = encoder.encode_suffix("total")?; - let mut value_encoder = bucket_encoder.no_bucket()?; - let mut exemplar_encoder = value_encoder.encode_value(value)?; - - match exemplar { - Some(exemplar) => exemplar_encoder.encode_exemplar(exemplar)?, - None => exemplar_encoder.no_exemplar()?, - } - - Ok(()) +pub(crate) struct LabelSetEncoder<'a> { + writer: &'a mut dyn Write, + first: bool, } -///////////////////////////////////////////////////////////////////////////////// -// Gauge - -impl EncodeMetric for Gauge -where - N: Encode, - A: gauge::Atomic, -{ - fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> { - encoder - .no_suffix()? - .no_bucket()? - .encode_value(self.get())? - .no_exemplar()?; - - Ok(()) - } - fn metric_type(&self) -> MetricType { - Self::TYPE +impl<'a> std::fmt::Debug for LabelSetEncoder<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LabelSetEncoder") + .field("first", &self.first) + .finish() } } -///////////////////////////////////////////////////////////////////////////////// -// Family - -impl EncodeMetric for Family -where - S: Clone + std::hash::Hash + Eq + Encode, - M: EncodeMetric + TypedMetric, - C: MetricConstructor, -{ - fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> { - let guard = self.read(); - for (label_set, m) in guard.iter() { - let encoder = encoder.with_label_set(label_set); - m.encode(encoder)?; +impl<'a> LabelSetEncoder<'a> { + fn new(writer: &'a mut dyn Write) -> Self { + Self { + writer, + first: true, } - Ok(()) } - fn metric_type(&self) -> MetricType { - M::TYPE + pub fn encode_label(&mut self) -> LabelEncoder { + let first = self.first; + self.first = false; + LabelEncoder { + writer: self.writer, + first, + } } } -///////////////////////////////////////////////////////////////////////////////// -// Histogram +pub(crate) struct LabelEncoder<'a> { + writer: &'a mut dyn Write, + first: bool, +} -impl EncodeMetric for Histogram { - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - let (sum, count, buckets) = self.get(); - // TODO: Would be better to use never type instead of `()`. - encode_histogram_with_maybe_exemplars::<()>(sum, count, &buckets, None, encoder) +impl<'a> std::fmt::Debug for LabelEncoder<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LabelEncoder") + .field("first", &self.first) + .finish() } +} - fn metric_type(&self) -> MetricType { - Self::TYPE +impl<'a> LabelEncoder<'a> { + pub fn encode_label_key(&mut self) -> Result { + if !self.first { + self.writer.write_str(",")?; + } + Ok(LabelKeyEncoder { + writer: self.writer, + }) } } -impl EncodeMetric for HistogramWithExemplars { - fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> { - let inner = self.inner(); - let (sum, count, buckets) = inner.histogram.get(); - encode_histogram_with_maybe_exemplars(sum, count, &buckets, Some(&inner.exemplars), encoder) - } +pub(crate) struct LabelKeyEncoder<'a> { + writer: &'a mut dyn Write, +} - fn metric_type(&self) -> MetricType { - Histogram::TYPE +impl<'a> std::fmt::Debug for LabelKeyEncoder<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LabelKeyEncoder").finish() } } -fn encode_histogram_with_maybe_exemplars( - sum: f64, - count: u64, - buckets: &[(f64, u64)], - exemplars: Option<&HashMap>>, - mut encoder: Encoder, -) -> Result<(), std::io::Error> { - encoder - .encode_suffix("sum")? - .no_bucket()? - .encode_value(sum)? - .no_exemplar()?; - encoder - .encode_suffix("count")? - .no_bucket()? - .encode_value(count)? - .no_exemplar()?; - - let mut cummulative = 0; - for (i, (upper_bound, count)) in buckets.iter().enumerate() { - cummulative += count; - let mut bucket_encoder = encoder.encode_suffix("bucket")?; - let mut value_encoder = bucket_encoder.encode_bucket(*upper_bound)?; - let mut exemplar_encoder = value_encoder.encode_value(cummulative)?; - - match exemplars.and_then(|es| es.get(&i)) { - Some(exemplar) => exemplar_encoder.encode_exemplar(exemplar)?, - None => exemplar_encoder.no_exemplar()?, - } +impl<'a> LabelKeyEncoder<'a> { + pub fn encode_label_value(self) -> Result, fmt::Error> { + self.writer.write_str("=\"")?; + Ok(LabelValueEncoder { + writer: self.writer, + }) } +} - Ok(()) +impl<'a> Write for LabelKeyEncoder<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.writer.write_str(s) + } } -///////////////////////////////////////////////////////////////////////////////// -// Info +pub(crate) struct LabelValueEncoder<'a> { + writer: &'a mut dyn Write, +} -impl EncodeMetric for Info -where - S: Clone + std::hash::Hash + Eq + Encode, -{ - fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> { - encoder - .with_label_set(&self.0) - .encode_suffix("info")? - .no_bucket()? - .encode_value(1u32)? - .no_exemplar()?; +impl<'a> std::fmt::Debug for LabelValueEncoder<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LabelValueEncoder").finish() + } +} - Ok(()) +impl<'a> LabelValueEncoder<'a> { + pub fn finish(self) -> Result<(), fmt::Error> { + self.writer.write_str("\"") } +} - fn metric_type(&self) -> MetricType { - Self::TYPE +impl<'a> Write for LabelValueEncoder<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.writer.write_str(s) } } #[cfg(test)] mod tests { use super::*; - use crate::metrics::counter::Counter; + use crate::metrics::exemplar::HistogramWithExemplars; + use crate::metrics::family::Family; use crate::metrics::gauge::Gauge; - use crate::metrics::histogram::exponential_buckets; + use crate::metrics::histogram::{exponential_buckets, Histogram}; + use crate::metrics::info::Info; + use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar}; use pyo3::{prelude::*, types::PyModule}; use std::borrow::Cow; @@ -617,11 +463,11 @@ mod tests { let mut registry = Registry::default(); registry.register("my_counter", "My counter", counter); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -630,7 +476,7 @@ mod tests { let counter: Counter = Counter::default(); registry.register_with_unit("my_counter", "My counter", Unit::Seconds, counter); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_counter_seconds My counter.\n".to_owned() @@ -638,16 +484,16 @@ mod tests { + "# UNIT my_counter_seconds seconds\n" + "my_counter_seconds_total 0\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] fn encode_counter_with_exemplar() { let mut registry = Registry::default(); - let counter_with_exemplar: CounterWithExemplar<(String, u64)> = + let counter_with_exemplar: CounterWithExemplar> = CounterWithExemplar::default(); registry.register_with_unit( "my_counter_with_exemplar", @@ -656,20 +502,20 @@ mod tests { counter_with_exemplar.clone(), ); - counter_with_exemplar.inc_by(1, Some(("user_id".to_string(), 42))); + counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), 42)])); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_counter_with_exemplar_seconds My counter with exemplar.\n" .to_owned() + "# TYPE my_counter_with_exemplar_seconds counter\n" + "# UNIT my_counter_with_exemplar_seconds seconds\n" - + "my_counter_with_exemplar_seconds_total 1 # {user_id=\"42\"} 1\n" + + "my_counter_with_exemplar_seconds_total 1 # {user_id=\"42\"} 1.0\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -678,11 +524,11 @@ mod tests { let gauge: Gauge = Gauge::default(); registry.register("my_gauge", "My gauge", gauge); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -698,11 +544,11 @@ mod tests { ]) .inc(); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -721,7 +567,7 @@ mod tests { ]) .inc(); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); @@ -730,9 +576,9 @@ mod tests { + "# TYPE my_prefix_my_counter_family counter\n" + "my_prefix_my_counter_family_total{my_key=\"my_value\",method=\"GET\",status=\"200\"} 1\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -741,16 +587,16 @@ mod tests { let info = Info::new(vec![("os".to_string(), "GNU/linux".to_string())]); registry.register("my_info_metric", "My info metric", info); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_info_metric My info metric.\n".to_owned() + "# TYPE my_info_metric info\n" + "my_info_metric_info{os=\"GNU/linux\"} 1\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -760,11 +606,11 @@ mod tests { registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -780,11 +626,11 @@ mod tests { ]) .observe(1.0); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } #[test] @@ -792,9 +638,9 @@ mod tests { let mut registry = Registry::default(); let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)); registry.register("my_histogram", "My histogram", histogram.clone()); - histogram.observe(1.0, Some(("user_id".to_string(), 42u64))); + histogram.observe(1.0, Some([("user_id".to_string(), 42u64)])); - let mut encoded = Vec::new(); + let mut encoded = String::new(); encode(&mut encoded, ®istry).unwrap(); let expected = "# HELP my_histogram My histogram.\n".to_owned() @@ -813,9 +659,9 @@ mod tests { + "my_histogram_bucket{le=\"512.0\"} 1\n" + "my_histogram_bucket{le=\"+Inf\"} 1\n" + "# EOF\n"; - assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + assert_eq!(expected, encoded); - parse_with_python_client(String::from_utf8(encoded).unwrap()); + parse_with_python_client(encoded); } fn parse_with_python_client(input: String) { @@ -841,7 +687,7 @@ def parse(input): parser .getattr("parse") .expect("`parse` to exist.") - .call1((input,)) + .call1((input.clone(),)) .map_err(|e| e.to_string()) .unwrap(); }) diff --git a/src/lib.rs b/src/lib.rs index dbc70f31..e64d60b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ //! # Examples //! //! ``` -//! use prometheus_client::encoding::Encode; +//! use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; //! use prometheus_client::encoding::text::encode; //! use prometheus_client::metrics::counter::{Atomic, Counter}; //! use prometheus_client::metrics::family::Family; @@ -30,7 +30,7 @@ //! // //! // You could as well use `(String, String)` to represent a label set, //! // instead of the custom type below. -//! #[derive(Clone, Hash, PartialEq, Eq, Encode)] +//! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] //! struct Labels { //! // Use your own enum types to represent label values. //! method: Method, @@ -38,7 +38,7 @@ //! path: String, //! }; //! -//! #[derive(Clone, Hash, PartialEq, Eq, Encode)] +//! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] //! enum Method { //! GET, //! PUT, @@ -54,7 +54,7 @@ //! "http_requests", //! // And the metric help text. //! "Number of HTTP requests received", -//! Box::new(http_requests.clone()), +//! http_requests.clone(), //! ); //! //! // Somewhere in your business logic record a single HTTP GET request. @@ -65,14 +65,14 @@ //! // When a monitoring system like Prometheus scrapes the local node, encode //! // all metrics in the registry in the text format, and send the encoded //! // metrics back. -//! let mut buffer = vec![]; +//! let mut buffer = String::new(); //! encode(&mut buffer, ®istry).unwrap(); //! //! let expected = "# HELP http_requests Number of HTTP requests received.\n".to_owned() + //! "# TYPE http_requests counter\n" + //! "http_requests_total{method=\"GET\",path=\"/metrics\"} 1\n" + //! "# EOF\n"; -//! assert_eq!(expected, String::from_utf8(buffer).unwrap()); +//! assert_eq!(expected, buffer); //! ``` //! See [examples] directory for more. //! diff --git a/src/metrics.rs b/src/metrics.rs index 647fa5c7..cd389527 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -28,3 +28,16 @@ pub enum MetricType { // StateSet, // Summary } + +impl MetricType { + /// Returns the given metric type's str representation. + pub fn as_str(&self) -> &str { + match self { + MetricType::Counter => "counter", + MetricType::Gauge => "gauge", + MetricType::Histogram => "histogram", + MetricType::Info => "info", + MetricType::Unknown => "unknown", + } + } +} diff --git a/src/metrics/counter.rs b/src/metrics/counter.rs index bd886c5e..95ecf213 100644 --- a/src/metrics/counter.rs +++ b/src/metrics/counter.rs @@ -2,6 +2,8 @@ //! //! See [`Counter`] for details. +use crate::encoding::{EncodeMetric, MetricEncoder}; + use super::{MetricType, TypedMetric}; use std::marker::PhantomData; #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))] @@ -171,6 +173,26 @@ impl TypedMetric for Counter { const TYPE: MetricType = MetricType::Counter; } +impl EncodeMetric for Counter { + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + encoder.encode_counter_u64::<()>(self.get(), None) + } + + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + +impl EncodeMetric for Counter { + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + encoder.encode_counter_f64::<()>(self.get(), None) + } + + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/metrics/exemplar.rs b/src/metrics/exemplar.rs index a2cffc03..eb6306e3 100644 --- a/src/metrics/exemplar.rs +++ b/src/metrics/exemplar.rs @@ -2,6 +2,8 @@ //! //! See [`CounterWithExemplar`] and [`HistogramWithExemplars`] for details. +use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}; + use super::counter::{self, Counter}; use super::histogram::Histogram; use super::{MetricType, TypedMetric}; @@ -37,13 +39,13 @@ pub struct Exemplar { /// # use prometheus_client::metrics::exemplar::CounterWithExemplar; /// # use prometheus_client::metrics::histogram::exponential_buckets; /// # use prometheus_client::metrics::family::Family; -/// # use prometheus_client_derive_encode::Encode; -/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// # use prometheus_client_derive_encode::EncodeLabelSet; +/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] /// pub struct ResultLabel { /// pub result: String, /// } /// -/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] /// pub struct TraceLabel { /// pub trace_id: String, /// } @@ -67,6 +69,36 @@ pub struct CounterWithExemplar { pub(crate) inner: Arc>>, } +impl CounterWithExemplar { + // TODO: Implement `fn inc`. Problematic right now as one can not produce + // value `1` of type `N`. + + /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`] + /// if a label set is provided, returning the previous value. + pub fn inc_by(&self, value: u64, label_set: Option) -> u64 { + let mut inner = self.inner.write(); + + inner.exemplar = label_set.map(|label_set| Exemplar { label_set, value }); + + inner.counter.inc_by(value) + } +} + +impl CounterWithExemplar { + // TODO: Implement `fn inc`. Problematic right now as one can not produce + // value `1` of type `N`. + + /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`] + /// if a label set is provided, returning the previous value. + pub fn inc_by(&self, value: f64, label_set: Option) -> f64 { + let mut inner = self.inner.write(); + + inner.exemplar = label_set.map(|label_set| Exemplar { label_set, value }); + + inner.counter.inc_by(value) + } +} + impl TypedMetric for CounterWithExemplar { const TYPE: MetricType = MetricType::Counter; } @@ -106,22 +138,6 @@ impl Default for CounterWithExemplar { } impl> CounterWithExemplar { - // TODO: Implement `fn inc`. Problematic right now as one can not produce - // value `1` of type `N`. - - /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`] - /// if a label set is provided, returning the previous value. - pub fn inc_by(&self, v: N, label_set: Option) -> N { - let mut inner = self.inner.write(); - - inner.exemplar = label_set.map(|label_set| Exemplar { - label_set, - value: v.clone(), - }); - - inner.counter.inc_by(v) - } - /// Get the current value of the [`CounterWithExemplar`] as well as its /// [`Exemplar`] if any. pub fn get(&self) -> (N, MappedRwLockReadGuard>>) { @@ -144,6 +160,33 @@ impl> CounterWithExemplar { } } +impl EncodeMetric for CounterWithExemplar +where + S: EncodeLabelSet, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + let (value, exemplar) = self.get(); + encoder.encode_counter_u64(value, exemplar.as_ref()) + } + + fn metric_type(&self) -> MetricType { + Counter::::TYPE + } +} +impl EncodeMetric for CounterWithExemplar +where + S: EncodeLabelSet, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + let (value, exemplar) = self.get(); + encoder.encode_counter_f64(value, exemplar.as_ref()) + } + + fn metric_type(&self) -> MetricType { + Counter::::TYPE + } +} + ///////////////////////////////////////////////////////////////////////////////// // Histogram @@ -161,13 +204,13 @@ impl> CounterWithExemplar { /// # use prometheus_client::metrics::exemplar::HistogramWithExemplars; /// # use prometheus_client::metrics::histogram::exponential_buckets; /// # use prometheus_client::metrics::family::Family; -/// # use prometheus_client_derive_encode::Encode; -/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// # use prometheus_client::encoding::EncodeLabelSet; +/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] /// pub struct ResultLabel { /// pub result: String, /// } /// -/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] /// pub struct TraceLabel { /// pub trace_id: String, /// } @@ -226,17 +269,13 @@ impl HistogramWithExemplars { /// Observe the given value, optionally providing a label set and thus /// setting the [`Exemplar`] value. - pub fn observe(&self, v: f64, label_set: Option) { + pub fn observe(&self, value: f64, label_set: Option) { let mut inner = self.inner.write(); - let bucket = inner.histogram.observe_and_bucket(v); + let bucket = inner.histogram.observe_and_bucket(value); if let (Some(bucket), Some(label_set)) = (bucket, label_set) { - inner.exemplars.insert( - bucket, - Exemplar { - label_set, - value: v, - }, - ); + inner + .exemplars + .insert(bucket, Exemplar { label_set, value }); } } @@ -244,3 +283,15 @@ impl HistogramWithExemplars { self.inner.read() } } + +impl EncodeMetric for HistogramWithExemplars { + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + let inner = self.inner(); + let (sum, count, buckets) = inner.histogram.get(); + encoder.encode_histogram(sum, count, &buckets, Some(&inner.exemplars)) + } + + fn metric_type(&self) -> MetricType { + Histogram::TYPE + } +} diff --git a/src/metrics/family.rs b/src/metrics/family.rs index bb163761..1b5a3fba 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -2,6 +2,8 @@ //! //! See [`Family`] for details. +use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}; + use super::{MetricType, TypedMetric}; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::collections::HashMap; @@ -41,14 +43,14 @@ use std::sync::Arc; /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); /// /// # // Encode all metrics in the registry in the text format. -/// # let mut buffer = vec![]; +/// # let mut buffer = String::new(); /// # encode(&mut buffer, ®istry).unwrap(); /// # /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + /// # "# TYPE my_counter counter\n" + /// # "my_counter_total{method=\"GET\"} 1\n" + /// # "# EOF\n"; -/// # assert_eq!(expected, String::from_utf8(buffer).unwrap()); +/// # assert_eq!(expected, buffer); /// ``` /// /// ### [`Family`] with custom type for performance and/or type safety @@ -57,7 +59,7 @@ use std::sync::Arc; /// [`Encode`](crate::encoding::text::Encode) implementation. /// /// ``` -/// # use prometheus_client::encoding::Encode; +/// # use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; /// # use prometheus_client::encoding::text::encode; /// # use prometheus_client::metrics::counter::{Atomic, Counter}; /// # use prometheus_client::metrics::family::Family; @@ -65,12 +67,12 @@ use std::sync::Arc; /// # use std::io::Write; /// # /// # let mut registry = Registry::default(); -/// #[derive(Clone, Hash, PartialEq, Eq, Encode)] +/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] /// struct Labels { /// method: Method, /// }; /// -/// #[derive(Clone, Hash, PartialEq, Eq, Encode)] +/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] /// enum Method { /// GET, /// PUT, @@ -87,17 +89,16 @@ use std::sync::Arc; /// family.get_or_create(&Labels { method: Method::GET }).inc(); /// # /// # // Encode all metrics in the registry in the text format. -/// # let mut buffer = vec![]; +/// # let mut buffer = String::new(); /// # encode(&mut buffer, ®istry).unwrap(); /// # /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + /// # "# TYPE my_counter counter\n" + /// # "my_counter_total{method=\"GET\"} 1\n" + /// # "# EOF\n"; -/// # assert_eq!(expected, String::from_utf8(buffer).unwrap()); +/// # assert_eq!(expected, buffer); /// ``` // TODO: Consider exposing hash algorithm. -#[derive(Debug)] pub struct Family M> { metrics: Arc>>, /// Function that when called constructs a new metric. @@ -111,6 +112,14 @@ pub struct Family M> { constructor: C, } +impl std::fmt::Debug for Family { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Family") + .field("metrics", &self.metrics) + .finish() + } +} + /// A constructor for creating new metrics in a [`Family`] when calling /// [`Family::get_or_create`]. Such constructor is provided via /// [`Family::new_with_constructor`]. @@ -296,6 +305,26 @@ impl TypedMetric for Family { const TYPE: MetricType = ::TYPE; } +impl EncodeMetric for Family +where + S: Clone + std::hash::Hash + Eq + EncodeLabelSet, + M: EncodeMetric + TypedMetric, + C: MetricConstructor, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + let guard = self.read(); + for (label_set, m) in guard.iter() { + let encoder = encoder.encode_family(label_set)?; + m.encode(encoder)?; + } + Ok(()) + } + + fn metric_type(&self) -> MetricType { + M::TYPE + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/metrics/gauge.rs b/src/metrics/gauge.rs index 297872f3..d2d576b5 100644 --- a/src/metrics/gauge.rs +++ b/src/metrics/gauge.rs @@ -2,6 +2,8 @@ //! //! See [`Gauge`] for details. +use crate::encoding::{EncodeMetric, MetricEncoder}; + use super::{MetricType, TypedMetric}; use std::marker::PhantomData; #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))] @@ -25,8 +27,8 @@ use std::sync::Arc; /// ``` /// # use prometheus_client::metrics::gauge::Gauge; /// let gauge: Gauge = Gauge::default(); -/// gauge.set(42u64); -/// let _value: u64 = gauge.get(); +/// gauge.set(42); +/// let _value = gauge.get(); /// ``` /// /// ## Using [`AtomicU64`] as storage and [`f64`] on the interface @@ -40,7 +42,7 @@ use std::sync::Arc; /// ``` #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))] #[derive(Debug)] -pub struct Gauge { +pub struct Gauge { value: Arc, phantom: PhantomData, } @@ -48,7 +50,7 @@ pub struct Gauge { /// Open Metrics [`Gauge`] to record current measurements. #[cfg(any(target_arch = "mips", target_arch = "powerpc"))] #[derive(Debug)] -pub struct Gauge { +pub struct Gauge { value: Arc, phantom: PhantomData, } @@ -265,6 +267,30 @@ impl TypedMetric for Gauge { const TYPE: MetricType = MetricType::Gauge; } +impl EncodeMetric for Gauge +where + A: Atomic, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + encoder.encode_gauge_i64(self.get()) + } + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + +impl EncodeMetric for Gauge +where + A: Atomic, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + encoder.encode_gauge_f64(self.get()) + } + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/metrics/histogram.rs b/src/metrics/histogram.rs index 5922a283..66f4496d 100644 --- a/src/metrics/histogram.rs +++ b/src/metrics/histogram.rs @@ -2,6 +2,8 @@ //! //! See [`Histogram`] for details. +use crate::encoding::{EncodeMetric, MetricEncoder}; + use super::{MetricType, TypedMetric}; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use std::iter::{self, once}; @@ -128,6 +130,17 @@ pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator Result<(), std::fmt::Error> { + let (sum, count, buckets) = self.get(); + encoder.encode_histogram::<()>(sum, count, &buckets, None) + } + + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/metrics/info.rs b/src/metrics/info.rs index 6201cdf9..6ab127c4 100644 --- a/src/metrics/info.rs +++ b/src/metrics/info.rs @@ -2,7 +2,10 @@ //! //! See [`Info`] for details. -use crate::metrics::{MetricType, TypedMetric}; +use crate::{ + encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}, + metrics::{MetricType, TypedMetric}, +}; /// Open Metrics [`Info`] metric "to expose textual information which SHOULD NOT /// change during process lifetime". @@ -25,3 +28,16 @@ impl Info { impl TypedMetric for Info { const TYPE: MetricType = MetricType::Info; } + +impl EncodeMetric for Info +where + S: Clone + std::hash::Hash + Eq + EncodeLabelSet, +{ + fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { + encoder.encode_info(&self.0) + } + + fn metric_type(&self) -> MetricType { + Self::TYPE + } +} diff --git a/src/registry.rs b/src/registry.rs index b88dc46b..39777f2a 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -19,16 +19,13 @@ use std::borrow::Cow; /// users might want to use their custom types. /// /// ``` -/// # use prometheus_client::encoding::text::{encode, EncodeMetric}; +/// # use prometheus_client::encoding::text::encode; /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; /// # use prometheus_client::metrics::gauge::{Atomic as _, Gauge}; /// # use prometheus_client::registry::Registry; /// # /// // Create a metric registry. -/// // -/// // Note the angle brackets to make sure to use the default (dynamic -/// // dispatched boxed metric) for the generic type parameter. -/// let mut registry = ::default(); +/// let mut registry = Registry::default(); /// /// let counter: Counter = Counter::default(); /// let gauge: Gauge = Gauge::default(); @@ -36,16 +33,16 @@ use std::borrow::Cow; /// registry.register( /// "my_counter", /// "This is my counter", -/// Box::new(counter.clone()), +/// counter.clone(), /// ); /// registry.register( /// "my_gauge", /// "This is my gauge", -/// Box::new(gauge.clone()), +/// gauge.clone(), /// ); /// /// # // Encode all metrics in the registry in the text format. -/// # let mut buffer = vec![]; +/// # let mut buffer = String::new(); /// # encode(&mut buffer, ®istry).unwrap(); /// # /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + @@ -55,28 +52,17 @@ use std::borrow::Cow; /// # "# TYPE my_gauge gauge\n" + /// # "my_gauge 0\n" + /// # "# EOF\n"; -/// # assert_eq!(expected, String::from_utf8(buffer).unwrap()); +/// # assert_eq!(expected, buffer); /// ``` -#[derive(Debug)] -pub struct Registry> { +#[derive(Debug, Default)] +pub struct Registry { prefix: Option, labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, - metrics: Vec<(Descriptor, M)>, - sub_registries: Vec>, -} - -impl Default for Registry { - fn default() -> Self { - Self { - prefix: None, - labels: Default::default(), - metrics: Default::default(), - sub_registries: vec![], - } - } + metrics: Vec<(Descriptor, Box)>, + sub_registries: Vec, } -impl Registry { +impl Registry { /// Creates a new default [`Registry`] with the given prefix. pub fn with_prefix(prefix: impl Into) -> Self { Self { @@ -103,12 +89,17 @@ impl Registry { /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; /// # use prometheus_client::registry::{Registry, Unit}; /// # - /// let mut registry: Registry = Registry::default(); - /// let counter = Counter::default(); + /// let mut registry = Registry::default(); + /// let counter: Counter = Counter::default(); /// /// registry.register("my_counter", "This is my counter", counter.clone()); /// ``` - pub fn register, H: Into>(&mut self, name: N, help: H, metric: M) { + pub fn register, H: Into>( + &mut self, + name: N, + help: H, + metric: impl Metric, + ) { self.priv_register(name, help, metric, None) } @@ -124,8 +115,8 @@ impl Registry { /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; /// # use prometheus_client::registry::{Registry, Unit}; /// # - /// let mut registry: Registry = Registry::default(); - /// let counter = Counter::default(); + /// let mut registry = Registry::default(); + /// let counter: Counter = Counter::default(); /// /// registry.register_with_unit( /// "my_counter", @@ -139,7 +130,7 @@ impl Registry { name: N, help: H, unit: Unit, - metric: M, + metric: impl Metric, ) { self.priv_register(name, help, metric, Some(unit)) } @@ -148,7 +139,7 @@ impl Registry { &mut self, name: N, help: H, - metric: M, + metric: impl Metric, unit: Option, ) { let name = name.into(); @@ -164,10 +155,9 @@ impl Registry { labels: self.labels.clone(), }; - self.metrics.push((descriptor, metric)); + self.metrics.push((descriptor, Box::new(metric))); } - // TODO: Update doc. /// Create a sub-registry to register metrics with a common prefix. /// /// Say you would like to prefix one set of metrics with `subsystem_a` and @@ -183,17 +173,17 @@ impl Registry { /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; /// # use prometheus_client::registry::{Registry, Unit}; /// # - /// let mut registry: Registry = Registry::default(); + /// let mut registry = Registry::default(); /// - /// let subsystem_a_counter_1 = Counter::default(); - /// let subsystem_a_counter_2 = Counter::default(); + /// let subsystem_a_counter_1: Counter = Counter::default(); + /// let subsystem_a_counter_2: Counter = Counter::default(); /// /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_a"); /// registry.register("counter_1", "", subsystem_a_counter_1.clone()); /// registry.register("counter_2", "", subsystem_a_counter_2.clone()); /// - /// let subsystem_b_counter_1 = Counter::default(); - /// let subsystem_b_counter_2 = Counter::default(); + /// let subsystem_b_counter_1: Counter = Counter::default(); + /// let subsystem_b_counter_2: Counter = Counter::default(); /// /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_b"); /// registry.register("counter_1", "", subsystem_b_counter_1.clone()); @@ -239,7 +229,7 @@ impl Registry { } /// [`Iterator`] over all metrics registered with the [`Registry`]. - pub fn iter(&self) -> RegistryIterator { + pub fn iter(&self) -> RegistryIterator { let metrics = self.metrics.iter(); let sub_registries = self.sub_registries.iter(); RegistryIterator { @@ -253,14 +243,14 @@ impl Registry { /// Iterator iterating both the metrics registered directly with the registry as /// well as all metrics registered with sub-registries. #[derive(Debug)] -pub struct RegistryIterator<'a, M> { - metrics: std::slice::Iter<'a, (Descriptor, M)>, - sub_registries: std::slice::Iter<'a, Registry>, - sub_registry: Option>>, +pub struct RegistryIterator<'a> { + metrics: std::slice::Iter<'a, (Descriptor, Box)>, + sub_registries: std::slice::Iter<'a, Registry>, + sub_registry: Option>>, } -impl<'a, M> Iterator for RegistryIterator<'a, M> { - type Item = &'a (Descriptor, M); +impl<'a> Iterator for RegistryIterator<'a> { + type Item = &'a (Descriptor, Box); fn next(&mut self) -> Option { if let Some(metric) = self.metrics.next() { @@ -365,6 +355,12 @@ impl Unit { } } +/// Super trait representing an abstract Prometheus metric. +pub trait Metric: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static {} + +impl Metric for T where T: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static +{} + #[cfg(test)] mod tests { use super::*; @@ -372,8 +368,8 @@ mod tests { #[test] fn register_and_iterate() { - let mut registry: Registry = Registry::default(); - let counter = Counter::default(); + let mut registry = Registry::default(); + let counter: Counter = Counter::default(); registry.register("my_counter", "My counter", counter); assert_eq!(1, registry.iter().count()) @@ -382,28 +378,29 @@ mod tests { #[test] fn sub_registry_with_prefix_and_label() { let top_level_metric_name = "my_top_level_metric"; - let mut registry = Registry::::default(); - registry.register(top_level_metric_name, "some help", Default::default()); + let mut registry = Registry::default(); + let counter: Counter = Counter::default(); + registry.register(top_level_metric_name, "some help", counter.clone()); let prefix_1 = "prefix_1"; let prefix_1_metric_name = "my_prefix_1_metric"; let sub_registry = registry.sub_registry_with_prefix(prefix_1); - sub_registry.register(prefix_1_metric_name, "some help", Default::default()); + sub_registry.register(prefix_1_metric_name, "some help", counter.clone()); let prefix_1_1 = "prefix_1_1"; let prefix_1_1_metric_name = "my_prefix_1_1_metric"; let sub_sub_registry = sub_registry.sub_registry_with_prefix(prefix_1_1); - sub_sub_registry.register(prefix_1_1_metric_name, "some help", Default::default()); + sub_sub_registry.register(prefix_1_1_metric_name, "some help", counter.clone()); let label_1_2 = (Cow::Borrowed("registry"), Cow::Borrowed("1_2")); let prefix_1_2_metric_name = "my_prefix_1_2_metric"; let sub_sub_registry = sub_registry.sub_registry_with_label(label_1_2.clone()); - sub_sub_registry.register(prefix_1_2_metric_name, "some help", Default::default()); + sub_sub_registry.register(prefix_1_2_metric_name, "some help", counter.clone()); let prefix_1_2_1 = "prefix_1_2_1"; let prefix_1_2_1_metric_name = "my_prefix_1_2_1_metric"; let sub_sub_sub_registry = sub_sub_registry.sub_registry_with_prefix(prefix_1_2_1); - sub_sub_sub_registry.register(prefix_1_2_1_metric_name, "some help", Default::default()); + sub_sub_sub_registry.register(prefix_1_2_1_metric_name, "some help", counter.clone()); let prefix_2 = "prefix_2"; let _ = registry.sub_registry_with_prefix(prefix_2); @@ -411,7 +408,7 @@ mod tests { let prefix_3 = "prefix_3"; let prefix_3_metric_name = "my_prefix_3_metric"; let sub_registry = registry.sub_registry_with_prefix(prefix_3); - sub_registry.register(prefix_3_metric_name, "some help", Default::default()); + sub_registry.register(prefix_3_metric_name, "some help", counter.clone()); let mut metric_iter = registry .iter()