From ab13e73fb784312e7fa3082da358e3079d0bd29a Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 27 Mar 2026 09:58:50 -0700 Subject: [PATCH 1/6] dependencies: update prost to 0.14 Signed-off-by: John Howard --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c7b0ffa..f383417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ dtoa = "1.0" itoa = "1.0" parking_lot = "0.12" prometheus-client-derive-encode = { version = "0.5.0", path = "derive-encode" } -prost = { version = "0.12.0", optional = true } -prost-types = { version = "0.12.0", optional = true } +prost = { version = "0.14", optional = true } +prost-types = { version = "0.14", optional = true } [dev-dependencies] async-std = { version = "1", features = ["attributes"] } @@ -49,7 +49,7 @@ hyper-util = { version = "0.1.3", features = ["tokio"] } http-body-util = "0.1.1" [build-dependencies] -prost-build = { version = "0.12.0", optional = true } +prost-build = { version = "0.14", optional = true } [[bench]] name = "baseline" From 3f6dc46ca851df70a7773e71f60f1c89e7a1cac1 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 30 Mar 2026 09:35:14 -0700 Subject: [PATCH 2/6] protobuf: allow compiling without `protoc` Our goal is to have our builds compile without external dependencies. `protox` helps achieve this. Signed-off-by: John Howard --- CONTRIBUTING.md | 3 +++ Cargo.toml | 2 ++ build.rs | 23 +++++++++++++++++------ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e20a178..6433775 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,9 @@ The `build.rs` script in this library depends upon the [Protocol Buffers compiler][protoc]. Be sure that `protoc` is installed and available within your `PATH`. +If you enable the off-by-default `protobuf-protox` feature, the build uses +`protox` instead and does not require `protoc`. + [protoc]: https://docs.rs/prost-build/latest/prost_build/#sourcing-protoc ## Python Dependencies diff --git a/Cargo.toml b/Cargo.toml index f383417..e9b890e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ documentation = "https://docs.rs/prometheus-client" [features] default = [] protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] +protobuf-protox = ["protobuf", "dep:protox"] # This feature provides additional APIs for testing downstream code using # `prometheus-client`. @@ -50,6 +51,7 @@ http-body-util = "0.1.1" [build-dependencies] prost-build = { version = "0.14", optional = true } +protox = { version = "0.9.1", optional = true } [[bench]] name = "baseline" diff --git a/build.rs b/build.rs index d10b019..136856e 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,22 @@ -use std::io::Result; +use std::error::Error; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { #[cfg(feature = "protobuf")] - prost_build::compile_protos( - &["src/encoding/proto/openmetrics_data_model.proto"], - &["src/encoding/proto/"], - )?; + compile_protos()?; + + Ok(()) +} + +#[cfg(feature = "protobuf")] +fn compile_protos() -> Result<(), Box> { + let protos = ["src/encoding/proto/openmetrics_data_model.proto"]; + let includes = ["src/encoding/proto/"]; + + #[cfg(feature = "protobuf-protox")] + prost_build::compile_fds(protox::compile(protos, includes)?)?; + + #[cfg(not(feature = "protobuf-protox"))] + prost_build::compile_protos(&protos, &includes)?; Ok(()) } From 57868b73d28544341f882bbf5c322d9f3e7e851d Mon Sep 17 00:00:00 2001 From: John Howard Date: Tue, 31 Mar 2026 17:17:33 -0700 Subject: [PATCH 3/6] protobuf: move from openmetrics to protobuf Signed-off-by: John Howard --- build.rs | 9 +- derive-encode/tests/lib.rs | 24 +- src/encoding.rs | 9 +- src/encoding/proto/metrics.proto | 153 ++++ .../proto/openmetrics_data_model.proto | 214 ----- src/encoding/protobuf.rs | 812 +++++++----------- src/encoding/text.rs | 6 + 7 files changed, 508 insertions(+), 719 deletions(-) create mode 100644 src/encoding/proto/metrics.proto delete mode 100644 src/encoding/proto/openmetrics_data_model.proto diff --git a/build.rs b/build.rs index 136856e..b926f01 100644 --- a/build.rs +++ b/build.rs @@ -9,7 +9,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "protobuf")] fn compile_protos() -> Result<(), Box> { - let protos = ["src/encoding/proto/openmetrics_data_model.proto"]; + let protos = ["src/encoding/proto/metrics.proto"]; let includes = ["src/encoding/proto/"]; #[cfg(feature = "protobuf-protox")] @@ -18,5 +18,12 @@ fn compile_protos() -> Result<(), Box> { #[cfg(not(feature = "protobuf-protox"))] prost_build::compile_protos(&protos, &includes)?; + for path in &protos { + println!("cargo:rerun-if-changed={}", path); + } + for path in &includes { + println!("cargo:rerun-if-changed={}", path); + } + Ok(()) } diff --git a/derive-encode/tests/lib.rs b/derive-encode/tests/lib.rs index 5d0910f..ec8bed5 100644 --- a/derive-encode/tests/lib.rs +++ b/derive-encode/tests/lib.rs @@ -48,7 +48,7 @@ fn basic_flow() { mod protobuf { use crate::{Labels, Method}; use prometheus_client::encoding::protobuf::encode; - use prometheus_client::encoding::protobuf::openmetrics_data_model; + use prometheus_client::encoding::protobuf::prometheus_data_model; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; @@ -67,17 +67,15 @@ mod protobuf { }) .inc(); - // Encode all metrics in the registry in the OpenMetrics protobuf format. - let mut metric_set = encode(®istry).unwrap(); - let mut family: openmetrics_data_model::MetricFamily = - metric_set.metric_families.pop().unwrap(); - let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); + let mut metric_families = encode(®istry).unwrap(); + let mut family: prometheus_data_model::MetricFamily = metric_families.pop().unwrap(); + let metric: prometheus_data_model::Metric = family.metric.pop().unwrap(); - let method = &metric.labels[0]; + let method = &metric.label[0]; assert_eq!("method", method.name); assert_eq!("Get", method.value); - let path = &metric.labels[1]; + let path = &metric.label[1]; assert_eq!("path", path.name); assert_eq!("/metrics", path.value); } @@ -96,13 +94,11 @@ mod protobuf { }) .inc(); - // Encode all metrics in the registry in the OpenMetrics protobuf format. - let mut metric_set = encode(®istry).unwrap(); - let mut family: openmetrics_data_model::MetricFamily = - metric_set.metric_families.pop().unwrap(); - let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); + let mut metric_families = encode(®istry).unwrap(); + let mut family: prometheus_data_model::MetricFamily = metric_families.pop().unwrap(); + let metric: prometheus_data_model::Metric = family.metric.pop().unwrap(); - let label = &metric.labels[0]; + let label = &metric.label[0]; assert_eq!("method", label.name); assert_eq!("Get", label.value); } diff --git a/src/encoding.rs b/src/encoding.rs index 44ec0ef..e98a039 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -611,10 +611,7 @@ impl EncodeGaugeValue for i64 { impl EncodeGaugeValue for u64 { fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { - // Between forcing end users to do endless as i64 for things that are - // clearly valid i64 and having one error case for rarely used protobuf when - // a gauge is set to >i64::MAX, the latter seems like the right choice. - encoder.encode_i64(i64::try_from(*self).map_err(|_err| std::fmt::Error)?) + encoder.encode_u64(*self) } } @@ -667,6 +664,10 @@ impl GaugeValueEncoder<'_> { for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_u32(v)) } + fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { + for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_u64(v)) + } + fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_i64(v)) } diff --git a/src/encoding/proto/metrics.proto b/src/encoding/proto/metrics.proto new file mode 100644 index 0000000..a0027ff --- /dev/null +++ b/src/encoding/proto/metrics.proto @@ -0,0 +1,153 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package io.prometheus.client; +option go_package = "io_prometheus_client"; + +import "google/protobuf/timestamp.proto"; + +message LabelPair { + string name = 1; + string value = 2; +} + +enum MetricType { + // COUNTER must use the Metric field "counter". + COUNTER = 0; + // GAUGE must use the Metric field "gauge". + GAUGE = 1; + // SUMMARY must use the Metric field "summary". + SUMMARY = 2; + // UNTYPED must use the Metric field "untyped". + UNTYPED = 3; + // HISTOGRAM must use the Metric field "histogram". + HISTOGRAM = 4; + // GAUGE_HISTOGRAM must use the Metric field "histogram". + GAUGE_HISTOGRAM = 5; +} + +message Gauge { + double value = 1; +} + +message Counter { + double value = 1; + Exemplar exemplar = 2; + + google.protobuf.Timestamp start_timestamp = 3; +} + +message Quantile { + double quantile = 1; + double value = 2; +} + +message Summary { + uint64 sample_count = 1; + double sample_sum = 2; + repeated Quantile quantile = 3; + + google.protobuf.Timestamp start_timestamp = 4; +} + +message Untyped { + double value = 1; +} + +message Histogram { + uint64 sample_count = 1; + double sample_count_float = 4; // Overrides sample_count if > 0. + double sample_sum = 2; + // Buckets for the classic histogram. + repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional. + + google.protobuf.Timestamp start_timestamp = 15; + + // Everything below here is for native histograms (formerly known as sparse histograms). + + // schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8. + // They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and + // then each power of two is divided into 2^n logarithmic buckets. + // Or in other words, each bucket boundary is the previous boundary times 2^(2^-n). + // In the future, more bucket schemas may be added using numbers < -4 or > 8. + sint32 schema = 5; + double zero_threshold = 6; // Breadth of the zero bucket. + uint64 zero_count = 7; // Count in zero bucket. + double zero_count_float = 8; // Overrides sb_zero_count if > 0. + + // Negative buckets for the native histogram. + repeated BucketSpan negative_span = 9; + // Use either "negative_delta" or "negative_count", the former for + // regular histograms with integer counts, the latter for float + // histograms. + repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). + repeated double negative_count = 11; // Absolute count of each bucket. + + // Positive buckets for the native histogram. + // Use a no-op span (offset 0, length 0) for a native histogram without any + // observations yet and with a zero_threshold of 0. Otherwise, it would be + // indistinguishable from a classic histogram. + repeated BucketSpan positive_span = 12; + // Use either "positive_delta" or "positive_count", the former for + // regular histograms with integer counts, the latter for float + // histograms. + repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). + repeated double positive_count = 14; // Absolute count of each bucket. + + // Only used for native histograms. These exemplars MUST have a timestamp. + repeated Exemplar exemplars = 16; +} + +message Bucket { + uint64 cumulative_count = 1; // Cumulative in increasing order. + double cumulative_count_float = 4; // Overrides cumulative_count if > 0. + double upper_bound = 2; // Inclusive. + Exemplar exemplar = 3; +} + +// A BucketSpan defines a number of consecutive buckets in a native +// histogram with their offset. Logically, it would be more +// straightforward to include the bucket counts in the Span. However, +// the protobuf representation is more compact in the way the data is +// structured here (with all the buckets in a single array separate +// from the Spans). +message BucketSpan { + sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative). + uint32 length = 2; // Length of consecutive buckets. +} + +message Exemplar { + repeated LabelPair label = 1; + double value = 2; + google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style. +} + +message Metric { + repeated LabelPair label = 1; + Gauge gauge = 2; + Counter counter = 3; + Summary summary = 4; + Untyped untyped = 5; + Histogram histogram = 7; + int64 timestamp_ms = 6; +} + +message MetricFamily { + string name = 1; + string help = 2; + MetricType type = 3; + repeated Metric metric = 4; + string unit = 5; +} \ No newline at end of file diff --git a/src/encoding/proto/openmetrics_data_model.proto b/src/encoding/proto/openmetrics_data_model.proto deleted file mode 100644 index a95942d..0000000 --- a/src/encoding/proto/openmetrics_data_model.proto +++ /dev/null @@ -1,214 +0,0 @@ -syntax = "proto3"; - -// The OpenMetrics protobuf schema which defines the protobuf wire format. -// Ensure to interpret "required" as semantically required for a valid message. -// All string fields MUST be UTF-8 encoded strings. -package openmetrics; - -import "google/protobuf/timestamp.proto"; - -// The top-level container type that is encoded and sent over the wire. -message MetricSet { - // Each MetricFamily has one or more MetricPoints for a single Metric. - repeated MetricFamily metric_families = 1; -} - -// One or more Metrics for a single MetricFamily, where each Metric -// has one or more MetricPoints. -message MetricFamily { - // Required. - string name = 1; - - // Optional. - MetricType type = 2; - - // Optional. - string unit = 3; - - // Optional. - string help = 4; - - // Optional. - repeated Metric metrics = 5; -} - -// The type of a Metric. -enum MetricType { - // Unknown must use unknown MetricPoint values. - UNKNOWN = 0; - // Gauge must use gauge MetricPoint values. - GAUGE = 1; - // Counter must use counter MetricPoint values. - COUNTER = 2; - // State set must use state set MetricPoint values. - STATE_SET = 3; - // Info must use info MetricPoint values. - INFO = 4; - // Histogram must use histogram value MetricPoint values. - HISTOGRAM = 5; - // Gauge histogram must use histogram value MetricPoint values. - GAUGE_HISTOGRAM = 6; - // Summary quantiles must use summary value MetricPoint values. - SUMMARY = 7; -} - -// A single metric with a unique set of labels within a metric family. -message Metric { - // Optional. - repeated Label labels = 1; - - // Optional. - repeated MetricPoint metric_points = 2; -} - -// A name-value pair. These are used in multiple places: identifying -// timeseries, value of INFO metrics, and exemplars in Histograms. -message Label { - // Required. - string name = 1; - - // Required. - string value = 2; -} - -// A MetricPoint in a Metric. -message MetricPoint { - // Required. - oneof value { - UnknownValue unknown_value = 1; - GaugeValue gauge_value = 2; - CounterValue counter_value = 3; - HistogramValue histogram_value = 4; - StateSetValue state_set_value = 5; - InfoValue info_value = 6; - SummaryValue summary_value = 7; - } - - // Optional. - google.protobuf.Timestamp timestamp = 8; -} - -// Value for UNKNOWN MetricPoint. -message UnknownValue { - // Required. - oneof value { - double double_value = 1; - int64 int_value = 2; - } -} - -// Value for GAUGE MetricPoint. -message GaugeValue { - // Required. - oneof value { - double double_value = 1; - int64 int_value = 2; - } -} - -// Value for COUNTER MetricPoint. -message CounterValue { - // Required. - oneof total { - double double_value = 1; - uint64 int_value = 2; - } - - // The time values began being collected for this counter. - // Optional. - google.protobuf.Timestamp created = 3; - - // Optional. - Exemplar exemplar = 4; -} - -// Value for HISTOGRAM or GAUGE_HISTOGRAM MetricPoint. -message HistogramValue { - // Optional. - oneof sum { - double double_value = 1; - int64 int_value = 2; - } - - // Optional. - uint64 count = 3; - - // The time values began being collected for this histogram. - // Optional. - google.protobuf.Timestamp created = 4; - - // Optional. - repeated Bucket buckets = 5; - - // Bucket is the number of values for a bucket in the histogram - // with an optional exemplar. - message Bucket { - // Required. - uint64 count = 1; - - // Optional. - double upper_bound = 2; - - // Optional. - Exemplar exemplar = 3; - } -} - -message Exemplar { - // Required. - double value = 1; - - // Optional. - google.protobuf.Timestamp timestamp = 2; - - // Labels are additional information about the exemplar value (e.g. trace id). - // Optional. - repeated Label label = 3; -} - -// Value for STATE_SET MetricPoint. -message StateSetValue { - // Optional. - repeated State states = 1; - - message State { - // Required. - bool enabled = 1; - - // Required. - string name = 2; - } -} - -// Value for INFO MetricPoint. -message InfoValue { - // Optional. - repeated Label info = 1; -} - -// Value for SUMMARY MetricPoint. -message SummaryValue { - // Optional. - oneof sum { - double double_value = 1; - int64 int_value = 2; - } - - // Optional. - uint64 count = 3; - - // The time sum and count values began being collected for this summary. - // Optional. - google.protobuf.Timestamp created = 4; - - // Optional. - repeated Quantile quantile = 5; - - message Quantile { - // Required. - double quantile = 1; - - // Required. - double value = 2; - } -} diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs index 9f770f6..c8eb48a 100644 --- a/src/encoding/protobuf.rs +++ b/src/encoding/protobuf.rs @@ -1,4 +1,4 @@ -//! Open Metrics protobuf implementation. +//! Prometheus protobuf implementation. //! //! ``` //! # use prometheus_client::encoding::protobuf::encode; @@ -14,22 +14,27 @@ //! # counter.clone(), //! # ); //! # counter.inc(); -//! // Returns `MetricSet`, the top-level container type. Please refer to [openmetrics_data_model.proto](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/proto/openmetrics_data_model.proto) for details. -//! let metric_set = encode(®istry).unwrap(); +//! // Returns `Vec`, the top-level container type. Please refer to [metrics.proto](https://github.com/prometheus/prometheus/blob/main/prompb/io/prometheus/client/metrics.proto) for details. +//! let metric_families = encode(®istry).unwrap(); //! -//! let family = metric_set.metric_families.first().unwrap(); -//! assert_eq!("my_counter", family.name); +//! let family = metric_families.first().unwrap(); +//! assert_eq!("my_counter_total", family.name); //! assert_eq!("This is my counter.", family.help); //! ``` +//! +//! For wire-format exposition, serialize each returned `MetricFamily` with +//! length-delimited protobuf framing. [`encode_to_vec`] provides the exact +//! payload used with +//! `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited`. -// Allowing some lints here as the `openmetrics.rs` is an automatically generated file. +// Allowing some lints here as the `io.prometheus.client.rs` file is generated. #[allow(missing_docs, clippy::derive_partial_eq_without_eq)] -/// Data models that are automatically generated from OpenMetrics protobuf -/// format. -pub mod openmetrics_data_model { - include!(concat!(env!("OUT_DIR"), "/openmetrics.rs")); +/// Data models generated from Prometheus `io.prometheus.client` protobuf. +pub mod prometheus_data_model { + include!(concat!(env!("OUT_DIR"), "/io.prometheus.client.rs")); } +use prost::Message; use std::{borrow::Cow, collections::HashMap}; use crate::metrics::MetricType; @@ -38,40 +43,123 @@ use crate::{metrics::exemplar::Exemplar, registry::Prefix}; use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet}; -/// Encode the metrics registered with the provided [`Registry`] into MetricSet -/// using the OpenMetrics protobuf format. -pub fn encode(registry: &Registry) -> Result { - let mut metric_set = openmetrics_data_model::MetricSet::default(); - let mut descriptor_encoder = DescriptorEncoder::new(&mut metric_set.metric_families).into(); +/// Encode the metrics registered with the provided [`Registry`] into +/// Prometheus `MetricFamily` messages. +pub fn encode( + registry: &Registry, +) -> Result, std::fmt::Error> { + let mut metric_families = Vec::new(); + let mut descriptor_encoder = DescriptorEncoder::new(&mut metric_families).into(); registry.encode(&mut descriptor_encoder)?; - Ok(metric_set) + Ok(metric_families) +} + +/// Encode the metrics registered with the provided [`Registry`] into a +/// length-delimited Prometheus protobuf payload. +pub fn encode_to_vec(registry: &Registry) -> Result, EncodeError> { + let metric_families = encode(registry)?; + let mut encoded = Vec::new(); + + for metric_family in metric_families { + metric_family.encode_length_delimited(&mut encoded)?; + } + + Ok(encoded) +} + +/// Errors returned by [`encode_to_vec`]. +#[derive(Debug)] +pub enum EncodeError { + /// A metric failed to encode into the intermediate protobuf data model. + Fmt(std::fmt::Error), + /// The generated protobuf message failed to serialize. + Protobuf(prost::EncodeError), } -impl From for openmetrics_data_model::MetricType { - fn from(m: MetricType) -> Self { - match m { - MetricType::Counter => openmetrics_data_model::MetricType::Counter, - MetricType::Gauge => openmetrics_data_model::MetricType::Gauge, - MetricType::Histogram => openmetrics_data_model::MetricType::Histogram, - MetricType::Info => openmetrics_data_model::MetricType::Info, - MetricType::Unknown => openmetrics_data_model::MetricType::Unknown, +impl std::fmt::Display for EncodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EncodeError::Fmt(_) => f.write_str("failed to encode metrics into Prometheus protobuf"), + EncodeError::Protobuf(err) => err.fmt(f), } } } +impl std::error::Error for EncodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + EncodeError::Fmt(err) => Some(err), + EncodeError::Protobuf(err) => Some(err), + } + } +} + +impl From for EncodeError { + fn from(err: std::fmt::Error) -> Self { + EncodeError::Fmt(err) + } +} + +impl From for EncodeError { + fn from(err: prost::EncodeError) -> Self { + EncodeError::Protobuf(err) + } +} + +impl From for prometheus_data_model::MetricType { + fn from(metric_type: MetricType) -> Self { + match metric_type { + MetricType::Counter => prometheus_data_model::MetricType::Counter, + MetricType::Gauge => prometheus_data_model::MetricType::Gauge, + MetricType::Histogram => prometheus_data_model::MetricType::Histogram, + // Prometheus does not have a dedicated info type; expose it as the + // conventional `_info` gauge with value `1`. + MetricType::Info => prometheus_data_model::MetricType::Gauge, + MetricType::Unknown => prometheus_data_model::MetricType::Untyped, + } + } +} + +fn metric_family_name( + prefix: Option<&Prefix>, + name: &str, + unit: Option<&Unit>, + metric_type: MetricType, +) -> String { + let mut full_name = String::new(); + + if let Some(prefix) = prefix { + full_name.push_str(prefix.as_str()); + full_name.push('_'); + } + + full_name.push_str(name); + + if let Some(unit) = unit { + full_name.push('_'); + full_name.push_str(unit.as_str()); + } + + match metric_type { + MetricType::Counter => full_name.push_str("_total"), + MetricType::Info => full_name.push_str("_info"), + MetricType::Gauge | MetricType::Histogram | MetricType::Unknown => {} + } + + full_name +} + /// Metric Descriptor encoder for protobuf encoding. -/// -/// This is an inner type for [`super::DescriptorEncoder`]. #[derive(Debug)] pub(crate) struct DescriptorEncoder<'a> { - metric_families: &'a mut Vec, + metric_families: &'a mut Vec, prefix: Option<&'a Prefix>, labels: &'a [(Cow<'static, str>, Cow<'static, str>)], } impl DescriptorEncoder<'_> { pub(crate) fn new( - metric_families: &mut Vec, + metric_families: &mut Vec, ) -> DescriptorEncoder<'_> { DescriptorEncoder { metric_families, @@ -99,22 +187,12 @@ impl DescriptorEncoder<'_> { unit: Option<&Unit>, metric_type: MetricType, ) -> Result, std::fmt::Error> { - let family = openmetrics_data_model::MetricFamily { - name: { - match self.prefix { - Some(prefix) => prefix.as_str().to_string() + "_" + name, - None => name.to_string(), - } - }, - r#type: { - let metric_type: openmetrics_data_model::MetricType = metric_type.into(); - metric_type as i32 - }, - unit: if let Some(unit) = unit { - unit.as_str().to_string() - } else { - String::new() - }, + let family = prometheus_data_model::MetricFamily { + name: metric_family_name(self.prefix, name, unit, metric_type), + r#type: prometheus_data_model::MetricType::from(metric_type) as i32, + unit: unit + .map(|unit| unit.as_str().to_string()) + .unwrap_or_default(), help: help.to_string(), ..Default::default() }; @@ -132,7 +210,7 @@ impl DescriptorEncoder<'_> { .metric_families .last_mut() .expect("previous push") - .metrics, + .metric, metric_type, labels, }) @@ -147,9 +225,9 @@ pub(crate) struct MetricEncoder<'f> { /// OpenMetrics metric type of the metric. metric_type: MetricType, /// Vector of OpenMetrics metrics to which encoded metrics are added. - family: &'f mut Vec, + family: &'f mut Vec, /// Labels to be added to each metric. - labels: Vec, + labels: Vec, } impl MetricEncoder<'_> { @@ -162,22 +240,18 @@ impl MetricEncoder<'_> { v: &CounterValue, exemplar: Option<&Exemplar>, ) -> Result<(), std::fmt::Error> { - let mut value = openmetrics_data_model::counter_value::Total::IntValue(0); + let mut value = 0.0; let mut e = CounterValueEncoder { value: &mut value }.into(); v.encode(&mut e)?; - 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(value), - exemplar: exemplar.map(|e| e.try_into()).transpose()?, - ..Default::default() - }, - )), - ..Default::default() - }], + self.family.push(prometheus_data_model::Metric { + label: self.labels.clone(), + counter: Some(prometheus_data_model::Counter { + value, + exemplar: exemplar.map(TryInto::try_into).transpose()?, + start_timestamp: None, + }), + ..Default::default() }); Ok(()) @@ -187,18 +261,14 @@ impl MetricEncoder<'_> { &mut self, v: &GaugeValue, ) -> Result<(), std::fmt::Error> { - let mut value = openmetrics_data_model::gauge_value::Value::IntValue(0); + let mut value = 0.0; let mut e = GaugeValueEncoder { value: &mut value }.into(); v.encode(&mut e)?; - 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(value) }, - )), - ..Default::default() - }], + self.family.push(prometheus_data_model::Metric { + label: self.labels.clone(), + gauge: Some(prometheus_data_model::Gauge { value }), + ..Default::default() }); Ok(()) @@ -208,22 +278,18 @@ impl MetricEncoder<'_> { &mut self, label_set: &impl super::EncodeLabelSet, ) -> Result<(), std::fmt::Error> { - let mut info_labels = vec![]; + let mut labels = self.labels.clone(); label_set.encode( &mut LabelSetEncoder { - labels: &mut info_labels, + labels: &mut labels, } .into(), )?; - 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() - }], + self.family.push(prometheus_data_model::Metric { + label: labels, + gauge: Some(prometheus_data_model::Gauge { value: 1.0 }), + ..Default::default() }); Ok(()) @@ -255,13 +321,17 @@ impl MetricEncoder<'_> { buckets: &[(f64, u64)], exemplars: Option<&HashMap>>, ) -> Result<(), std::fmt::Error> { - let buckets = buckets + let mut cumulative_count = 0; + let bucket = buckets .iter() .enumerate() .map(|(i, (upper_bound, count))| { - Ok(openmetrics_data_model::histogram_value::Bucket { + cumulative_count += count; + Ok(prometheus_data_model::Bucket { + cumulative_count, + // not needed; if set would override cumulative_count. + cumulative_count_float: 0.0, upper_bound: *upper_bound, - count: *count, exemplar: exemplars .and_then(|exemplars| exemplars.get(&i).map(|exemplar| exemplar.try_into())) .transpose()?, @@ -269,21 +339,17 @@ impl MetricEncoder<'_> { }) .collect::, std::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::HistogramValue( - openmetrics_data_model::HistogramValue { - count, - created: None, - buckets, - sum: Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( - sum, - )), - }, - )), + self.family.push(prometheus_data_model::Metric { + label: self.labels.clone(), + histogram: Some(prometheus_data_model::Histogram { + sample_count: count, + sample_count_float: 0.0, + sample_sum: sum, + bucket, + start_timestamp: None, ..Default::default() - }], + }), + ..Default::default() }); Ok(()) @@ -291,7 +357,7 @@ impl MetricEncoder<'_> { } impl TryFrom<&Exemplar> - for openmetrics_data_model::Exemplar + for prometheus_data_model::Exemplar { type Error = std::fmt::Error; @@ -301,39 +367,39 @@ impl TryFrom<&Exemplar> .value .encode(ExemplarValueEncoder { value: &mut value }.into())?; - let mut labels = vec![]; - exemplar.label_set.encode( - &mut LabelSetEncoder { - labels: &mut labels, - } - .into(), - )?; + let mut label = vec![]; + exemplar + .label_set + .encode(&mut LabelSetEncoder { labels: &mut label }.into())?; - Ok(openmetrics_data_model::Exemplar { + Ok(prometheus_data_model::Exemplar { + label, value, timestamp: exemplar.timestamp.map(Into::into), - label: labels, }) } } #[derive(Debug)] pub(crate) struct GaugeValueEncoder<'a> { - value: &'a mut openmetrics_data_model::gauge_value::Value, + value: &'a mut f64, } impl GaugeValueEncoder<'_> { pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> { - self.encode_i64(v as i64) + self.encode_f64(f64::from(v)) + } + + pub fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { + self.encode_f64(v as f64) } pub fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { - *self.value = openmetrics_data_model::gauge_value::Value::IntValue(v); - Ok(()) + self.encode_f64(v as f64) } pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { - *self.value = openmetrics_data_model::gauge_value::Value::DoubleValue(v); + *self.value = v; Ok(()) } } @@ -350,9 +416,9 @@ impl ExemplarValueEncoder<'_> { } } -impl From<&(K, V)> for openmetrics_data_model::Label { +impl From<&(K, V)> for prometheus_data_model::LabelPair { fn from(kv: &(K, V)) -> Self { - openmetrics_data_model::Label { + prometheus_data_model::LabelPair { name: kv.0.to_string(), value: kv.1.to_string(), } @@ -361,24 +427,24 @@ impl From<&(K, V)> for openmetrics_data_model::Label { #[derive(Debug)] pub(crate) struct CounterValueEncoder<'a> { - value: &'a mut openmetrics_data_model::counter_value::Total, + value: &'a mut f64, } impl CounterValueEncoder<'_> { pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { - *self.value = openmetrics_data_model::counter_value::Total::DoubleValue(v); + *self.value = v; Ok(()) } pub fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { - *self.value = openmetrics_data_model::counter_value::Total::IntValue(v); + *self.value = v as f64; Ok(()) } } #[derive(Debug)] pub(crate) struct LabelSetEncoder<'a> { - labels: &'a mut Vec, + labels: &'a mut Vec, } impl LabelSetEncoder<'_> { @@ -391,12 +457,13 @@ impl LabelSetEncoder<'_> { #[derive(Debug)] pub(crate) struct LabelEncoder<'a> { - labels: &'a mut Vec, + labels: &'a mut Vec, } impl LabelEncoder<'_> { pub fn encode_label_key(&mut self) -> Result, std::fmt::Error> { - self.labels.push(openmetrics_data_model::Label::default()); + self.labels + .push(prometheus_data_model::LabelPair::default()); Ok(LabelKeyEncoder { label: self.labels.last_mut().expect("To find pushed label."), @@ -406,7 +473,7 @@ impl LabelEncoder<'_> { #[derive(Debug)] pub(crate) struct LabelKeyEncoder<'a> { - label: &'a mut openmetrics_data_model::Label, + label: &'a mut prometheus_data_model::LabelPair, } impl std::fmt::Write for LabelKeyEncoder<'_> { @@ -465,70 +532,28 @@ mod tests { registry.register("my_counter", "My counter", counter.clone()); counter.inc(); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_counter", family.name); - assert_eq!("My counter.", family.help); - - assert_eq!( - openmetrics_data_model::MetricType::Counter as i32, - extract_metric_type(&metric_set) - ); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - let expected = openmetrics_data_model::counter_value::Total::IntValue(1); - assert_eq!(Some(expected), value.total); - assert_eq!(None, value.exemplar); - assert_eq!(None, value.created); - } - _ => panic!("wrong value type"), - } - } - - #[test] - fn encode_counter_double() { - // Using `f64` - let counter: Counter = Counter::default(); - let mut registry = Registry::default(); - registry.register("my_counter", "My counter", counter.clone()); - counter.inc(); - - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_counter", family.name); + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_counter_total", family.name); assert_eq!("My counter.", family.help); - assert_eq!( - openmetrics_data_model::MetricType::Counter as i32, - extract_metric_type(&metric_set) + prometheus_data_model::MetricType::Counter as i32, + family.r#type ); - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - // The counter should be encoded as `DoubleValue` - let expected = openmetrics_data_model::counter_value::Total::DoubleValue(1.0); - assert_eq!(Some(expected), value.total); - assert_eq!(None, value.exemplar); - assert_eq!(None, value.created); - } - _ => panic!("wrong value type"), - } + let metric = family.metric.first().unwrap(); + assert_eq!(1.0, metric.counter.as_ref().unwrap().value); } #[test] fn encode_counter_with_unit() { - let mut registry = Registry::default(); let counter: Counter = Counter::default(); + let mut registry = Registry::default(); registry.register_with_unit("my_counter", "My counter", Unit::Seconds, counter); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_counter", family.name); - assert_eq!("My counter.", family.help); + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_counter_seconds_total", family.name); assert_eq!("seconds", family.unit); } @@ -538,141 +563,70 @@ mod tests { let now_ts: Timestamp = now.into(); let mut registry = Registry::default(); + let counter: CounterWithExemplar, f64> = CounterWithExemplar::default(); + registry.register("my_counter", "My counter", counter.clone()); - let counter_with_exemplar: CounterWithExemplar, f64> = - CounterWithExemplar::default(); - registry.register( - "my_counter_with_exemplar", - "My counter with exemplar", - counter_with_exemplar.clone(), - ); - - counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 42.0)]), None); - - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_counter_with_exemplar", family.name); - assert_eq!("My counter with exemplar.", family.help); - - assert_eq!( - openmetrics_data_model::MetricType::Counter as i32, - extract_metric_type(&metric_set) - ); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - // The counter should be encoded as `DoubleValue` - let expected = openmetrics_data_model::counter_value::Total::DoubleValue(1.0); - assert_eq!(Some(expected), value.total); - - let exemplar = value.exemplar.as_ref().unwrap(); - assert_eq!(1.0, exemplar.value); - - assert!(exemplar.timestamp.is_none()); - - let expected_label = { - openmetrics_data_model::Label { - name: "user_id".to_string(), - value: "42.0".to_string(), - } - }; - assert_eq!(vec![expected_label], exemplar.label); - } - _ => panic!("wrong value type"), - } - - counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 99.0)]), Some(now)); - - match extract_metric_point_value(&encode(®istry).unwrap()) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - // The counter should be encoded as `DoubleValue` - let expected = openmetrics_data_model::counter_value::Total::DoubleValue(2.0); - assert_eq!(Some(expected), value.total); + counter.inc_by(1.0, Some(vec![("user_id".to_string(), 42.0)]), None); - let exemplar = value.exemplar.as_ref().unwrap(); - assert_eq!(1.0, exemplar.value); + let metric_families = encode(®istry).unwrap(); + let exemplar = metric_families[0].metric[0] + .counter + .as_ref() + .unwrap() + .exemplar + .as_ref() + .unwrap(); + assert_eq!(1.0, exemplar.value); + assert_eq!(None, exemplar.timestamp); + assert_eq!("user_id", exemplar.label[0].name); + assert_eq!("42.0", exemplar.label[0].value); - assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap()); + counter.inc_by(1.0, Some(vec![("user_id".to_string(), 99.0)]), Some(now)); - let expected_label = { - openmetrics_data_model::Label { - name: "user_id".to_string(), - value: "99.0".to_string(), - } - }; - assert_eq!(vec![expected_label], exemplar.label); - } - _ => panic!("wrong value type"), - } + let metric_families = encode(®istry).unwrap(); + let counter = metric_families[0].metric[0].counter.as_ref().unwrap(); + assert_eq!(2.0, counter.value); + let exemplar = counter.exemplar.as_ref().unwrap(); + assert_eq!(1.0, exemplar.value); + assert_eq!(Some(now_ts), exemplar.timestamp.clone()); + assert_eq!("99.0", exemplar.label[0].value); } #[test] fn encode_gauge() { - let mut registry = Registry::default(); let gauge = Gauge::::default(); - registry.register("my_gauge", "My gauge", gauge.clone()); - gauge.inc(); - - 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); - - assert_eq!( - openmetrics_data_model::MetricType::Gauge as i32, - extract_metric_type(&metric_set) - ); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::GaugeValue(value) => { - let expected = openmetrics_data_model::gauge_value::Value::IntValue(1); - assert_eq!(Some(expected), value.value); - } - _ => panic!("wrong value type"), - } - } - - #[test] - fn encode_gauge_u64_normal() { let mut registry = Registry::default(); - let gauge = Gauge::::default(); registry.register("my_gauge", "My gauge", gauge.clone()); - gauge.set(12345); - - 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); + gauge.inc(); + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_gauge", family.name.as_str()); assert_eq!( - openmetrics_data_model::MetricType::Gauge as i32, - extract_metric_type(&metric_set) + prometheus_data_model::MetricType::Gauge as i32, + family.r#type ); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::GaugeValue(value) => { - let expected = openmetrics_data_model::gauge_value::Value::IntValue(12345); - assert_eq!(Some(expected), value.value); - } - _ => panic!("wrong value type"), - } + assert_eq!(1.0, family.metric[0].gauge.as_ref().unwrap().value); } #[test] fn encode_gauge_u64_max() { - let mut registry = Registry::default(); let gauge = Gauge::::default(); + let mut registry = Registry::default(); registry.register("my_gauge", "My gauge", gauge.clone()); gauge.set(u64::MAX); - // This expected to fail as protobuf uses i64 and u64::MAX does not fit into it. - assert!(encode(®istry).is_err()); + let metric_families = encode(®istry).unwrap(); + assert_eq!( + u64::MAX as f64, + metric_families[0].metric[0].gauge.as_ref().unwrap().value + ); } #[test] fn encode_counter_family() { let mut registry = Registry::default(); + let family = Family::, Counter>::default(); registry.register("my_counter_family", "My counter family", family.clone()); @@ -690,52 +644,32 @@ mod tests { ]) .inc(); - let metric_set = encode(®istry).unwrap(); + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_counter_family_total", family.name.as_str()); + assert_eq!(2, family.metric.len()); - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_counter_family", family.name); - assert_eq!("My counter family.", family.help); - - assert_eq!( - openmetrics_data_model::MetricType::Counter as i32, - extract_metric_type(&metric_set) - ); - - // The order of the labels is not deterministic so we are testing the - // value to be either let mut potential_method_value = HashSet::new(); potential_method_value.insert("GET"); potential_method_value.insert("POST"); - // the first metric - let metric = family.metrics.first().unwrap(); - assert_eq!(2, metric.labels.len()); - assert_eq!("method", metric.labels[0].name); - assert!(potential_method_value.remove(&metric.labels[0].value.as_str())); - assert_eq!("status", metric.labels[1].name); - assert_eq!("200", metric.labels[1].value); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - let expected = openmetrics_data_model::counter_value::Total::IntValue(1); - assert_eq!(Some(expected), value.total); - assert_eq!(None, value.exemplar); - assert_eq!(None, value.created); - } - _ => panic!("wrong value type"), - } + let metric = family.metric.first().unwrap(); + assert_eq!(2, metric.label.len()); + assert_eq!("method", metric.label[0].name); + assert!(potential_method_value.remove(metric.label[0].value.as_str())); + assert_eq!("status", metric.label[1].name); + assert_eq!("200", metric.label[1].value); - // the second metric - let metric2 = &family.metrics[1]; - assert_eq!(2, metric2.labels.len()); - assert_eq!("method", metric2.labels[0].name); - assert!(potential_method_value.remove(&metric2.labels[0].value.as_str())); - assert_eq!("status", metric2.labels[1].name); - assert_eq!("200", metric2.labels[1].value); + let metric2 = &family.metric[1]; + assert_eq!(2, metric2.label.len()); + assert_eq!("method", metric2.label[0].name); + assert!(potential_method_value.remove(metric2.label[0].value.as_str())); + assert_eq!("status", metric2.label[1].name); + assert_eq!("200", metric2.label[1].value); } #[test] - fn encode_counter_family_with_prefix_with_label() { + fn encode_counter_family_with_prefix_and_label() { let mut registry = Registry::default(); let sub_registry = registry.sub_registry_with_prefix("my_prefix"); let sub_sub_registry = sub_registry @@ -750,35 +684,17 @@ mod tests { ]) .inc(); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_prefix_my_counter_family", family.name); - assert_eq!("My counter family.", family.help); + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_prefix_my_counter_family_total", family.name.as_str()); - assert_eq!( - openmetrics_data_model::MetricType::Counter as i32, - extract_metric_type(&metric_set) - ); - - let metric = family.metrics.first().unwrap(); - assert_eq!(3, metric.labels.len()); - assert_eq!("my_key", metric.labels[0].name); - assert_eq!("my_value", metric.labels[0].value); - assert_eq!("method", metric.labels[1].name); - assert_eq!("GET", metric.labels[1].value); - assert_eq!("status", metric.labels[2].name); - assert_eq!("200", metric.labels[2].value); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::CounterValue(value) => { - let expected = openmetrics_data_model::counter_value::Total::IntValue(1); - assert_eq!(Some(expected), value.total); - assert_eq!(None, value.exemplar); - assert_eq!(None, value.created); - } - _ => panic!("wrong value type"), - } + let metric = family.metric.first().unwrap(); + assert_eq!("my_key", metric.label[0].name); + assert_eq!("my_value", metric.label[0].value); + assert_eq!("method", metric.label[1].name); + assert_eq!("GET", metric.label[1].value); + assert_eq!("status", metric.label[2].name); + assert_eq!("200", metric.label[2].value); } #[test] @@ -788,30 +704,21 @@ mod tests { registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_histogram", family.name); - assert_eq!("My histogram.", family.help); - + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_histogram", family.name.as_str()); assert_eq!( - openmetrics_data_model::MetricType::Histogram as i32, - extract_metric_type(&metric_set) + prometheus_data_model::MetricType::Histogram as i32, + family.r#type ); - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::HistogramValue(value) => { - assert_eq!( - Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( - 1.0 - )), - value.sum - ); - assert_eq!(1, value.count); - assert_eq!(11, value.buckets.len()); - } - _ => panic!("wrong value type"), - } + let histogram = family.metric[0].histogram.as_ref().unwrap(); + assert_eq!(1, histogram.sample_count); + assert_eq!(1.0, histogram.sample_sum); + assert_eq!(11, histogram.bucket.len()); + assert_eq!(1, histogram.bucket[0].cumulative_count); + assert_eq!(1.0, histogram.bucket[0].upper_bound); + assert_eq!(f64::MAX, histogram.bucket.last().unwrap().upper_bound); } #[test] @@ -819,93 +726,45 @@ mod tests { let now = SystemTime::now(); let now_ts: Timestamp = now.into(); - let mut registry = Registry::default(); let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)); + let mut registry = Registry::default(); registry.register("my_histogram", "My histogram", histogram.clone()); histogram.observe(1.0, Some(vec![("user_id".to_string(), 42u64)]), None); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_histogram", family.name); - assert_eq!("My histogram.", family.help); - - assert_eq!( - openmetrics_data_model::MetricType::Histogram as i32, - extract_metric_type(&metric_set) - ); - - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::HistogramValue(value) => { - let exemplar = value.buckets.first().unwrap().exemplar.as_ref().unwrap(); - assert_eq!(1.0, exemplar.value); - - assert!(exemplar.timestamp.is_none()); - - let expected_label = { - openmetrics_data_model::Label { - name: "user_id".to_string(), - value: "42".to_string(), - } - }; - assert_eq!(vec![expected_label], exemplar.label); - } - _ => panic!("wrong value type"), - } + let metric_families = encode(®istry).unwrap(); + let exemplar = metric_families[0].metric[0] + .histogram + .as_ref() + .unwrap() + .bucket[0] + .exemplar + .as_ref() + .unwrap(); + assert_eq!(1.0, exemplar.value); + assert_eq!(None, exemplar.timestamp); + assert_eq!("42", exemplar.label[0].value); histogram.observe(2.0, Some(vec![("user_id".to_string(), 99u64)]), Some(now)); - match extract_metric_point_value(&encode(®istry).unwrap()) { - openmetrics_data_model::metric_point::Value::HistogramValue(value) => { - let exemplar = value.buckets.get(1).unwrap().exemplar.as_ref().unwrap(); - assert_eq!(2.0, exemplar.value); - - assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap()); - - let expected_label = { - openmetrics_data_model::Label { - name: "user_id".to_string(), - value: "99".to_string(), - } - }; - assert_eq!(vec![expected_label], exemplar.label); - } - _ => panic!("wrong value type"), - } - } - - #[test] - fn encode_family_counter_histogram() { - let mut registry = Registry::default(); - - let counter_family = Family::, Counter>::default(); - let histogram_family = - Family::, Histogram>::new_with_constructor(|| { - Histogram::new(exponential_buckets(1.0, 2.0, 10)) - }); - - 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())]) - .inc(); - - histogram_family - .get_or_create(&vec![("path".to_string(), "/".to_string())]) - .observe(1.0); - - 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); + let metric_families = encode(®istry).unwrap(); + let exemplar = metric_families[0].metric[0] + .histogram + .as_ref() + .unwrap() + .bucket[1] + .exemplar + .as_ref() + .unwrap(); + assert_eq!(2.0, exemplar.value); + assert_eq!(Some(now_ts), exemplar.timestamp.clone()); + assert_eq!("99", exemplar.label[0].value); } #[test] fn encode_family_and_counter_and_histogram() { let mut registry = Registry::default(); - // Family let counter_family = Family::, Counter>::default(); let histogram_family = Family::, Histogram>::new_with_constructor(|| { @@ -927,73 +786,54 @@ mod tests { .get_or_create(&vec![("path".to_string(), "/".to_string())]) .observe(1.0); - // Counter let counter: Counter = Counter::default(); 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", histogram.clone()); histogram.observe(1.0); - 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); + let metric_families = encode(®istry).unwrap(); + assert_eq!("my_family_counter_total", metric_families[0].name); + assert_eq!("my_family_histogram", metric_families[1].name); + assert_eq!("my_counter_total", metric_families[2].name); + assert_eq!("my_histogram", metric_families[3].name); } #[test] fn encode_info() { - let mut registry = Registry::default(); let info = Info::new(vec![("os".to_string(), "GNU/linux".to_string())]); + let mut registry = Registry::default(); registry.register("my_info_metric", "My info metric", info); - let metric_set = encode(®istry).unwrap(); - - let family = metric_set.metric_families.first().unwrap(); - assert_eq!("my_info_metric", family.name); - assert_eq!("My info metric.", family.help); - + let metric_families = encode(®istry).unwrap(); + let family = metric_families.first().unwrap(); + assert_eq!("my_info_metric_info", family.name.as_str()); assert_eq!( - openmetrics_data_model::MetricType::Info as i32, - extract_metric_type(&metric_set) + prometheus_data_model::MetricType::Gauge as i32, + family.r#type ); - match extract_metric_point_value(&metric_set) { - openmetrics_data_model::metric_point::Value::InfoValue(value) => { - assert_eq!(1, value.info.len()); - - let info = value.info.first().unwrap(); - assert_eq!("os", info.name); - assert_eq!("GNU/linux", info.value); - } - _ => panic!("wrong value type"), - } + let metric = family.metric.first().unwrap(); + assert_eq!(1.0, metric.gauge.as_ref().unwrap().value); + assert_eq!("os", metric.label[0].name); + assert_eq!("GNU/linux", metric.label[0].value); } - fn extract_metric_type(metric_set: &openmetrics_data_model::MetricSet) -> i32 { - let family = metric_set.metric_families.first().unwrap(); - family.r#type - } + #[test] + fn encode_to_vec_length_delimited() { + let counter: Counter = Counter::default(); + let mut registry = Registry::default(); + registry.register("my_counter", "My counter", counter.clone()); + counter.inc(); - fn extract_metric_point_value( - metric_set: &openmetrics_data_model::MetricSet, - ) -> openmetrics_data_model::metric_point::Value { - let metric = metric_set - .metric_families - .first() - .unwrap() - .metrics - .first() - .unwrap(); + let payload = encode_to_vec(®istry).unwrap(); + let family = + prometheus_data_model::MetricFamily::decode_length_delimited(payload.as_slice()) + .unwrap(); - metric - .metric_points - .first() - .unwrap() - .value - .as_ref() - .unwrap() - .clone() + assert_eq!("my_counter_total", family.name); + assert_eq!(1.0, family.metric[0].counter.as_ref().unwrap().value); } } diff --git a/src/encoding/text.rs b/src/encoding/text.rs index fd050f4..bc3f650 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -602,6 +602,12 @@ impl GaugeValueEncoder<'_> { Ok(()) } + pub fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { + self.writer.write_str(" ")?; + self.writer.write_str(itoa::Buffer::new().format(v))?; + Ok(()) + } + pub fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { self.writer.write_str(" ")?; self.writer.write_str(itoa::Buffer::new().format(v))?; From 440d8cf9b6176bac07685e2c93dfb17e411b4be1 Mon Sep 17 00:00:00 2001 From: John Howard Date: Tue, 31 Mar 2026 17:20:29 -0700 Subject: [PATCH 4/6] Update changelog Signed-off-by: John Howard --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80654b5..a6d75e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.25.0] + +### Added + +- Added an off-by-default `protobuf-protox` feature to build protobuf support + without requiring an external `protoc` binary. + +### Changed + +- Updated `prost`, `prost-build`, and `prost-types` dependencies to `v0.14`. +- The `protobuf` feature now generates and encodes Prometheus + `io.prometheus.client` protobuf messages from `metrics.proto` rather than the + OpenMetrics protobuf data model. This is a breaking change for users of the `protobuf` feature. + See [Issue](https://github.com/prometheus/OpenMetrics/issues/296) for more context. + ## [0.24.1] ### Added diff --git a/Cargo.toml b/Cargo.toml index e9b890e..a04a8ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client" -version = "0.24.1" +version = "0.25.0" authors = ["Max Inden "] edition = "2021" description = "Open Metrics client library allowing users to natively instrument applications." From df025155db66db35289b7f7fa09c93f3186dee05 Mon Sep 17 00:00:00 2001 From: John Howard Date: Tue, 28 Apr 2026 09:46:34 -0700 Subject: [PATCH 5/6] Bring back legacy proto Signed-off-by: John Howard --- CHANGELOG.md | 2 + Cargo.toml | 1 + build.rs | 15 +- src/encoding.rs | 173 ++- src/encoding/openmetrics_protobuf.rs | 1003 +++++++++++++++++ .../{protobuf.rs => prometheus_protobuf.rs} | 0 .../proto/openmetrics_data_model.proto | 214 ++++ 7 files changed, 1365 insertions(+), 43 deletions(-) create mode 100644 src/encoding/openmetrics_protobuf.rs rename src/encoding/{protobuf.rs => prometheus_protobuf.rs} (100%) create mode 100644 src/encoding/proto/openmetrics_data_model.proto diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d75e7..2b7afb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `io.prometheus.client` protobuf messages from `metrics.proto` rather than the OpenMetrics protobuf data model. This is a breaking change for users of the `protobuf` feature. See [Issue](https://github.com/prometheus/OpenMetrics/issues/296) for more context. + The `legacy_protobuf` feature retains the old, deprecated, OpenMetrics protobuf support which will + be removed in a future release. ## [0.24.1] diff --git a/Cargo.toml b/Cargo.toml index a04a8ab..aa146cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ documentation = "https://docs.rs/prometheus-client" [features] default = [] protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] +legacy_protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] protobuf-protox = ["protobuf", "dep:protox"] # This feature provides additional APIs for testing downstream code using diff --git a/build.rs b/build.rs index b926f01..28fc516 100644 --- a/build.rs +++ b/build.rs @@ -1,19 +1,26 @@ use std::error::Error; fn main() -> Result<(), Box> { - #[cfg(feature = "protobuf")] + #[cfg(any(feature = "protobuf", feature = "legacy_protobuf"))] compile_protos()?; Ok(()) } -#[cfg(feature = "protobuf")] +#[allow(clippy::vec_init_then_push)] // False positive due to feature flags +#[cfg(any(feature = "protobuf", feature = "legacy_protobuf"))] fn compile_protos() -> Result<(), Box> { - let protos = ["src/encoding/proto/metrics.proto"]; + let mut protos = Vec::new(); + + #[cfg(feature = "protobuf")] + protos.push("src/encoding/proto/metrics.proto"); + #[cfg(feature = "legacy_protobuf")] + protos.push("src/encoding/proto/openmetrics_data_model.proto"); + let includes = ["src/encoding/proto/"]; #[cfg(feature = "protobuf-protox")] - prost_build::compile_fds(protox::compile(protos, includes)?)?; + prost_build::compile_fds(protox::compile(&protos, includes)?)?; #[cfg(not(feature = "protobuf-protox"))] prost_build::compile_protos(&protos, &includes)?; diff --git a/src/encoding.rs b/src/encoding.rs index e98a039..ce1d8b9 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -13,17 +13,27 @@ use std::rc::Rc; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(feature = "legacy_protobuf")] +#[cfg_attr(docsrs, doc(cfg(feature = "legacy_protobuf")))] +pub mod openmetrics_protobuf; #[cfg(feature = "protobuf")] #[cfg_attr(docsrs, doc(cfg(feature = "protobuf")))] -pub mod protobuf; +pub mod prometheus_protobuf; pub mod text; +#[cfg(all(feature = "legacy_protobuf", not(feature = "protobuf")))] +pub use openmetrics_protobuf as protobuf; +#[cfg(feature = "protobuf")] +pub use prometheus_protobuf as protobuf; + macro_rules! for_both_mut { ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { match &mut $self.0 { $inner::Text($pattern) => $fn, - #[cfg(feature = "protobuf")] + #[cfg(feature = "legacy_protobuf")] $inner::Protobuf($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::PrometheusProtobuf($pattern) => $fn, } }; } @@ -32,8 +42,10 @@ macro_rules! for_both { ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { match $self.0 { $inner::Text($pattern) => $fn, - #[cfg(feature = "protobuf")] + #[cfg(feature = "legacy_protobuf")] $inner::Protobuf($pattern) => $fn, + #[cfg(feature = "protobuf")] + $inner::PrometheusProtobuf($pattern) => $fn, } }; } @@ -81,8 +93,11 @@ pub struct DescriptorEncoder<'a>(DescriptorEncoderInner<'a>); enum DescriptorEncoderInner<'a> { Text(text::DescriptorEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::DescriptorEncoder<'a>), + #[cfg(feature = "protobuf")] - Protobuf(protobuf::DescriptorEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::DescriptorEncoder<'a>), } impl<'a> From> for DescriptorEncoder<'a> { @@ -91,13 +106,20 @@ impl<'a> From> for DescriptorEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for DescriptorEncoder<'a> { - fn from(e: protobuf::DescriptorEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for DescriptorEncoder<'a> { + fn from(e: openmetrics_protobuf::DescriptorEncoder<'a>) -> Self { Self(DescriptorEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for DescriptorEncoder<'a> { + fn from(e: prometheus_protobuf::DescriptorEncoder<'a>) -> Self { + Self(DescriptorEncoderInner::PrometheusProtobuf(e)) + } +} + impl DescriptorEncoder<'_> { pub(crate) fn with_prefix_and_labels<'s>( &'s mut self, @@ -138,8 +160,11 @@ pub struct MetricEncoder<'a>(MetricEncoderInner<'a>); enum MetricEncoderInner<'a> { Text(text::MetricEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::MetricEncoder<'a>), + #[cfg(feature = "protobuf")] - Protobuf(protobuf::MetricEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::MetricEncoder<'a>), } impl<'a> From> for MetricEncoder<'a> { @@ -148,13 +173,20 @@ impl<'a> From> for MetricEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for MetricEncoder<'a> { - fn from(e: protobuf::MetricEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for MetricEncoder<'a> { + fn from(e: openmetrics_protobuf::MetricEncoder<'a>) -> Self { Self(MetricEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for MetricEncoder<'a> { + fn from(e: prometheus_protobuf::MetricEncoder<'a>) -> Self { + Self(MetricEncoderInner::PrometheusProtobuf(e)) + } +} + impl MetricEncoder<'_> { /// Encode a counter. pub fn encode_counter< @@ -225,8 +257,10 @@ pub struct LabelSetEncoder<'a>(LabelSetEncoderInner<'a>); #[derive(Debug)] enum LabelSetEncoderInner<'a> { Text(text::LabelSetEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::LabelSetEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::LabelSetEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::LabelSetEncoder<'a>), } impl<'a> From> for LabelSetEncoder<'a> { @@ -235,13 +269,20 @@ impl<'a> From> for LabelSetEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for LabelSetEncoder<'a> { - fn from(e: protobuf::LabelSetEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for LabelSetEncoder<'a> { + fn from(e: openmetrics_protobuf::LabelSetEncoder<'a>) -> Self { Self(LabelSetEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelSetEncoder<'a> { + fn from(e: prometheus_protobuf::LabelSetEncoder<'a>) -> Self { + Self(LabelSetEncoderInner::PrometheusProtobuf(e)) + } +} + impl LabelSetEncoder<'_> { /// Encode the given label. pub fn encode_label(&mut self) -> LabelEncoder<'_> { @@ -314,8 +355,10 @@ pub struct LabelEncoder<'a>(LabelEncoderInner<'a>); #[derive(Debug)] enum LabelEncoderInner<'a> { Text(text::LabelEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::LabelEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::LabelEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::LabelEncoder<'a>), } impl<'a> From> for LabelEncoder<'a> { @@ -324,13 +367,20 @@ impl<'a> From> for LabelEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for LabelEncoder<'a> { - fn from(e: protobuf::LabelEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for LabelEncoder<'a> { + fn from(e: openmetrics_protobuf::LabelEncoder<'a>) -> Self { Self(LabelEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelEncoder<'a> { + fn from(e: prometheus_protobuf::LabelEncoder<'a>) -> Self { + Self(LabelEncoderInner::PrometheusProtobuf(e)) + } +} + impl LabelEncoder<'_> { /// Encode a label. pub fn encode_label_key(&mut self) -> Result, std::fmt::Error> { @@ -371,8 +421,10 @@ pub struct LabelKeyEncoder<'a>(LabelKeyEncoderInner<'a>); #[derive(Debug)] enum LabelKeyEncoderInner<'a> { Text(text::LabelKeyEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::LabelKeyEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::LabelKeyEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::LabelKeyEncoder<'a>), } impl<'a> From> for LabelKeyEncoder<'a> { @@ -381,13 +433,20 @@ impl<'a> From> for LabelKeyEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for LabelKeyEncoder<'a> { - fn from(e: protobuf::LabelKeyEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for LabelKeyEncoder<'a> { + fn from(e: openmetrics_protobuf::LabelKeyEncoder<'a>) -> Self { Self(LabelKeyEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelKeyEncoder<'a> { + fn from(e: prometheus_protobuf::LabelKeyEncoder<'a>) -> Self { + Self(LabelKeyEncoderInner::PrometheusProtobuf(e)) + } +} + impl std::fmt::Write for LabelKeyEncoder<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { for_both_mut!(self, LabelKeyEncoderInner, e, e.write_str(s)) @@ -468,8 +527,10 @@ pub struct LabelValueEncoder<'a>(LabelValueEncoderInner<'a>); #[derive(Debug)] enum LabelValueEncoderInner<'a> { Text(text::LabelValueEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::LabelValueEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::LabelValueEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::LabelValueEncoder<'a>), } impl<'a> From> for LabelValueEncoder<'a> { @@ -478,13 +539,20 @@ impl<'a> From> for LabelValueEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for LabelValueEncoder<'a> { - fn from(e: protobuf::LabelValueEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for LabelValueEncoder<'a> { + fn from(e: openmetrics_protobuf::LabelValueEncoder<'a>) -> Self { LabelValueEncoder(LabelValueEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for LabelValueEncoder<'a> { + fn from(e: prometheus_protobuf::LabelValueEncoder<'a>) -> Self { + LabelValueEncoder(LabelValueEncoderInner::PrometheusProtobuf(e)) + } +} + impl std::fmt::Write for LabelValueEncoder<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s)) @@ -655,8 +723,10 @@ pub struct GaugeValueEncoder<'a>(GaugeValueEncoderInner<'a>); #[derive(Debug)] enum GaugeValueEncoderInner<'a> { Text(text::GaugeValueEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::GaugeValueEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::GaugeValueEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::GaugeValueEncoder<'a>), } impl GaugeValueEncoder<'_> { @@ -683,13 +753,20 @@ impl<'a> From> for GaugeValueEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for GaugeValueEncoder<'a> { - fn from(e: protobuf::GaugeValueEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for GaugeValueEncoder<'a> { + fn from(e: openmetrics_protobuf::GaugeValueEncoder<'a>) -> Self { GaugeValueEncoder(GaugeValueEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for GaugeValueEncoder<'a> { + fn from(e: prometheus_protobuf::GaugeValueEncoder<'a>) -> Self { + GaugeValueEncoder(GaugeValueEncoderInner::PrometheusProtobuf(e)) + } +} + /// An encodable counter value. pub trait EncodeCounterValue { /// Encode the given instance in the OpenMetrics text encoding. @@ -734,8 +811,10 @@ pub struct CounterValueEncoder<'a>(CounterValueEncoderInner<'a>); #[derive(Debug)] enum CounterValueEncoderInner<'a> { Text(text::CounterValueEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::CounterValueEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::CounterValueEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::CounterValueEncoder<'a>), } impl<'a> From> for CounterValueEncoder<'a> { @@ -744,13 +823,20 @@ impl<'a> From> for CounterValueEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for CounterValueEncoder<'a> { - fn from(e: protobuf::CounterValueEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for CounterValueEncoder<'a> { + fn from(e: openmetrics_protobuf::CounterValueEncoder<'a>) -> Self { CounterValueEncoder(CounterValueEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for CounterValueEncoder<'a> { + fn from(e: prometheus_protobuf::CounterValueEncoder<'a>) -> Self { + CounterValueEncoder(CounterValueEncoderInner::PrometheusProtobuf(e)) + } +} + impl CounterValueEncoder<'_> { fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { for_both_mut!(self, CounterValueEncoderInner, e, e.encode_f64(v)) @@ -810,8 +896,10 @@ pub struct ExemplarValueEncoder<'a>(ExemplarValueEncoderInner<'a>); #[derive(Debug)] enum ExemplarValueEncoderInner<'a> { Text(text::ExemplarValueEncoder<'a>), + #[cfg(feature = "legacy_protobuf")] + Protobuf(openmetrics_protobuf::ExemplarValueEncoder<'a>), #[cfg(feature = "protobuf")] - Protobuf(protobuf::ExemplarValueEncoder<'a>), + PrometheusProtobuf(prometheus_protobuf::ExemplarValueEncoder<'a>), } impl<'a> From> for ExemplarValueEncoder<'a> { @@ -820,13 +908,20 @@ impl<'a> From> for ExemplarValueEncoder<'a> { } } -#[cfg(feature = "protobuf")] -impl<'a> From> for ExemplarValueEncoder<'a> { - fn from(e: protobuf::ExemplarValueEncoder<'a>) -> Self { +#[cfg(feature = "legacy_protobuf")] +impl<'a> From> for ExemplarValueEncoder<'a> { + fn from(e: openmetrics_protobuf::ExemplarValueEncoder<'a>) -> Self { ExemplarValueEncoder(ExemplarValueEncoderInner::Protobuf(e)) } } +#[cfg(feature = "protobuf")] +impl<'a> From> for ExemplarValueEncoder<'a> { + fn from(e: prometheus_protobuf::ExemplarValueEncoder<'a>) -> Self { + ExemplarValueEncoder(ExemplarValueEncoderInner::PrometheusProtobuf(e)) + } +} + impl ExemplarValueEncoder<'_> { fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> { for_both_mut!(self, ExemplarValueEncoderInner, e, e.encode(v)) diff --git a/src/encoding/openmetrics_protobuf.rs b/src/encoding/openmetrics_protobuf.rs new file mode 100644 index 0000000..ca11754 --- /dev/null +++ b/src/encoding/openmetrics_protobuf.rs @@ -0,0 +1,1003 @@ +//! Open Metrics protobuf implementation. +//! +//! ``` +//! # use prometheus_client::encoding::openmetrics_protobuf::encode; +//! # use prometheus_client::metrics::counter::Counter; +//! # use prometheus_client::registry::Registry; +//! # +//! # // Create registry and counter and register the latter with the former. +//! # let mut registry = Registry::default(); +//! # let counter: Counter = Counter::default(); +//! # registry.register( +//! # "my_counter", +//! # "This is my counter", +//! # counter.clone(), +//! # ); +//! # counter.inc(); +//! // Returns `MetricSet`, the top-level container type. Please refer to [openmetrics_data_model.proto](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/proto/openmetrics_data_model.proto) for details. +//! let metric_set = encode(®istry).unwrap(); +//! +//! let family = metric_set.metric_families.first().unwrap(); +//! assert_eq!("my_counter", family.name); +//! assert_eq!("This is my counter.", family.help); +//! ``` + +// Allowing some lints here as the `openmetrics.rs` is an automatically generated file. +#[allow(missing_docs, clippy::derive_partial_eq_without_eq)] +/// Data models that are automatically generated from OpenMetrics protobuf +/// format. +pub mod openmetrics_data_model { + include!(concat!(env!("OUT_DIR"), "/openmetrics.rs")); +} + +use std::{borrow::Cow, collections::HashMap}; + +use crate::metrics::MetricType; +use crate::registry::{Registry, Unit}; +use crate::{metrics::exemplar::Exemplar, registry::Prefix}; + +use super::{EncodeCounterValue, EncodeExemplarValue, EncodeGaugeValue, EncodeLabelSet}; + +/// Encode the metrics registered with the provided [`Registry`] into MetricSet +/// using the OpenMetrics protobuf format. +pub fn encode(registry: &Registry) -> Result { + let mut metric_set = openmetrics_data_model::MetricSet::default(); + let mut descriptor_encoder = DescriptorEncoder::new(&mut metric_set.metric_families).into(); + registry.encode(&mut descriptor_encoder)?; + Ok(metric_set) +} + +impl From for openmetrics_data_model::MetricType { + fn from(m: MetricType) -> Self { + match m { + MetricType::Counter => openmetrics_data_model::MetricType::Counter, + MetricType::Gauge => openmetrics_data_model::MetricType::Gauge, + MetricType::Histogram => openmetrics_data_model::MetricType::Histogram, + MetricType::Info => openmetrics_data_model::MetricType::Info, + MetricType::Unknown => openmetrics_data_model::MetricType::Unknown, + } + } +} + +/// Metric Descriptor encoder for protobuf encoding. +/// +/// This is an inner type for [`super::DescriptorEncoder`]. +#[derive(Debug)] +pub(crate) struct DescriptorEncoder<'a> { + metric_families: &'a mut Vec, + prefix: Option<&'a Prefix>, + labels: &'a [(Cow<'static, str>, Cow<'static, str>)], +} + +impl DescriptorEncoder<'_> { + pub(crate) fn new( + metric_families: &mut Vec, + ) -> DescriptorEncoder<'_> { + DescriptorEncoder { + metric_families, + prefix: Default::default(), + labels: Default::default(), + } + } + + pub(crate) fn with_prefix_and_labels<'s>( + &'s mut self, + prefix: Option<&'s Prefix>, + labels: &'s [(Cow<'static, str>, Cow<'static, str>)], + ) -> DescriptorEncoder<'s> { + DescriptorEncoder { + prefix, + labels, + metric_families: self.metric_families, + } + } + + pub fn encode_descriptor<'s>( + &'s mut self, + name: &str, + help: &str, + unit: Option<&Unit>, + metric_type: MetricType, + ) -> Result, std::fmt::Error> { + let family = openmetrics_data_model::MetricFamily { + name: { + match self.prefix { + Some(prefix) => prefix.as_str().to_string() + "_" + name, + None => name.to_string(), + } + }, + r#type: { + let metric_type: openmetrics_data_model::MetricType = metric_type.into(); + metric_type as i32 + }, + unit: if let Some(unit) = unit { + unit.as_str().to_string() + } else { + String::new() + }, + help: help.to_string(), + ..Default::default() + }; + let mut labels = vec![]; + self.labels.encode( + &mut LabelSetEncoder { + labels: &mut labels, + } + .into(), + )?; + self.metric_families.push(family); + + Ok(MetricEncoder { + family: &mut self + .metric_families + .last_mut() + .expect("previous push") + .metrics, + metric_type, + labels, + }) + } +} + +/// Encoder for protobuf encoding. +/// +/// This is an inner type for [`super::MetricEncoder`]. +#[derive(Debug)] +pub(crate) struct MetricEncoder<'f> { + /// OpenMetrics metric type of the metric. + metric_type: MetricType, + /// Vector of OpenMetrics metrics to which encoded metrics are added. + family: &'f mut Vec, + /// Labels to be added to each metric. + labels: Vec, +} + +impl MetricEncoder<'_> { + pub fn encode_counter< + S: EncodeLabelSet, + CounterValue: EncodeCounterValue, + ExemplarValue: EncodeExemplarValue, + >( + &mut self, + v: &CounterValue, + exemplar: Option<&Exemplar>, + ) -> Result<(), std::fmt::Error> { + let mut value = openmetrics_data_model::counter_value::Total::IntValue(0); + let mut e = CounterValueEncoder { value: &mut value }.into(); + v.encode(&mut e)?; + + 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(value), + exemplar: exemplar.map(|e| e.try_into()).transpose()?, + ..Default::default() + }, + )), + ..Default::default() + }], + }); + + Ok(()) + } + + pub fn encode_gauge( + &mut self, + v: &GaugeValue, + ) -> Result<(), std::fmt::Error> { + let mut value = openmetrics_data_model::gauge_value::Value::IntValue(0); + let mut e = GaugeValueEncoder { value: &mut value }.into(); + v.encode(&mut e)?; + + 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(value) }, + )), + ..Default::default() + }], + }); + + Ok(()) + } + + pub fn encode_info( + &mut self, + label_set: &impl super::EncodeLabelSet, + ) -> Result<(), std::fmt::Error> { + let mut info_labels = vec![]; + label_set.encode( + &mut LabelSetEncoder { + labels: &mut info_labels, + } + .into(), + )?; + + 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() + }], + }); + + Ok(()) + } + + pub fn encode_family( + &mut self, + label_set: &S, + ) -> Result, std::fmt::Error> { + let mut labels = self.labels.clone(); + label_set.encode( + &mut LabelSetEncoder { + labels: &mut labels, + } + .into(), + )?; + + Ok(MetricEncoder { + metric_type: self.metric_type, + family: self.family, + labels, + }) + } + + pub fn encode_histogram( + &mut self, + sum: f64, + count: u64, + buckets: &[(f64, u64)], + exemplars: Option<&HashMap>>, + ) -> Result<(), std::fmt::Error> { + let buckets = buckets + .iter() + .enumerate() + .map(|(i, (upper_bound, count))| { + Ok(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()?, + }) + }) + .collect::, std::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::HistogramValue( + openmetrics_data_model::HistogramValue { + count, + created: None, + buckets, + sum: Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( + sum, + )), + }, + )), + ..Default::default() + }], + }); + + Ok(()) + } +} + +impl TryFrom<&Exemplar> + for openmetrics_data_model::Exemplar +{ + type Error = std::fmt::Error; + + fn try_from(exemplar: &Exemplar) -> Result { + let mut value = f64::default(); + exemplar + .value + .encode(ExemplarValueEncoder { value: &mut value }.into())?; + + let mut labels = vec![]; + exemplar.label_set.encode( + &mut LabelSetEncoder { + labels: &mut labels, + } + .into(), + )?; + + Ok(openmetrics_data_model::Exemplar { + value, + timestamp: exemplar.timestamp.map(Into::into), + label: labels, + }) + } +} + +#[derive(Debug)] +pub(crate) struct GaugeValueEncoder<'a> { + value: &'a mut openmetrics_data_model::gauge_value::Value, +} + +impl GaugeValueEncoder<'_> { + pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> { + self.encode_i64(v as i64) + } + + pub fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { + self.encode_i64(i64::try_from(v).map_err(|_err| std::fmt::Error)?) + } + + pub fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { + *self.value = openmetrics_data_model::gauge_value::Value::IntValue(v); + Ok(()) + } + + pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { + *self.value = openmetrics_data_model::gauge_value::Value::DoubleValue(v); + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) struct ExemplarValueEncoder<'a> { + value: &'a mut f64, +} + +impl ExemplarValueEncoder<'_> { + pub fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> { + *self.value = v; + Ok(()) + } +} + +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(), + } + } +} + +#[derive(Debug)] +pub(crate) struct CounterValueEncoder<'a> { + value: &'a mut openmetrics_data_model::counter_value::Total, +} + +impl CounterValueEncoder<'_> { + pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { + *self.value = openmetrics_data_model::counter_value::Total::DoubleValue(v); + Ok(()) + } + + pub fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { + *self.value = openmetrics_data_model::counter_value::Total::IntValue(v); + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) struct LabelSetEncoder<'a> { + labels: &'a mut Vec, +} + +impl LabelSetEncoder<'_> { + pub fn encode_label(&mut self) -> LabelEncoder<'_> { + LabelEncoder { + labels: self.labels, + } + } +} + +#[derive(Debug)] +pub(crate) struct LabelEncoder<'a> { + labels: &'a mut Vec, +} + +impl LabelEncoder<'_> { + pub fn encode_label_key(&mut self) -> Result, std::fmt::Error> { + self.labels.push(openmetrics_data_model::Label::default()); + + Ok(LabelKeyEncoder { + label: self.labels.last_mut().expect("To find pushed label."), + }) + } +} + +#[derive(Debug)] +pub(crate) struct LabelKeyEncoder<'a> { + label: &'a mut openmetrics_data_model::Label, +} + +impl std::fmt::Write for LabelKeyEncoder<'_> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.label.name.write_str(s) + } +} + +impl<'a> LabelKeyEncoder<'a> { + pub fn encode_label_value(self) -> Result, std::fmt::Error> { + Ok(LabelValueEncoder { + label_value: &mut self.label.value, + }) + } +} + +#[derive(Debug)] +pub(crate) struct LabelValueEncoder<'a> { + label_value: &'a mut String, +} + +impl LabelValueEncoder<'_> { + pub fn finish(self) -> Result<(), std::fmt::Error> { + Ok(()) + } +} + +impl std::fmt::Write for LabelValueEncoder<'_> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.label_value.write_str(s) + } +} + +#[cfg(test)] +mod tests { + use prost_types::Timestamp; + + use super::*; + use crate::metrics::counter::Counter; + use crate::metrics::exemplar::{CounterWithExemplar, HistogramWithExemplars}; + use crate::metrics::family::Family; + use crate::metrics::gauge::Gauge; + use crate::metrics::histogram::{exponential_buckets, Histogram}; + use crate::metrics::info::Info; + use crate::registry::Unit; + use std::borrow::Cow; + use std::collections::HashSet; + use std::sync::atomic::AtomicI64; + use std::sync::atomic::AtomicU64; + use std::time::SystemTime; + + #[test] + fn encode_counter_int() { + let counter: Counter = Counter::default(); + let mut registry = Registry::default(); + registry.register("my_counter", "My counter", counter.clone()); + counter.inc(); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_counter", family.name); + assert_eq!("My counter.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Counter as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + let expected = openmetrics_data_model::counter_value::Total::IntValue(1); + assert_eq!(Some(expected), value.total); + assert_eq!(None, value.exemplar); + assert_eq!(None, value.created); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_counter_double() { + // Using `f64` + let counter: Counter = Counter::default(); + let mut registry = Registry::default(); + registry.register("my_counter", "My counter", counter.clone()); + counter.inc(); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_counter", family.name); + assert_eq!("My counter.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Counter as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + // The counter should be encoded as `DoubleValue` + let expected = openmetrics_data_model::counter_value::Total::DoubleValue(1.0); + assert_eq!(Some(expected), value.total); + assert_eq!(None, value.exemplar); + assert_eq!(None, value.created); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_counter_with_unit() { + let mut registry = Registry::default(); + let counter: Counter = Counter::default(); + registry.register_with_unit("my_counter", "My counter", Unit::Seconds, counter); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_counter", family.name); + assert_eq!("My counter.", family.help); + assert_eq!("seconds", family.unit); + } + + #[test] + fn encode_counter_with_exemplar() { + let now = SystemTime::now(); + let now_ts: Timestamp = now.into(); + + let mut registry = Registry::default(); + + let counter_with_exemplar: CounterWithExemplar, f64> = + CounterWithExemplar::default(); + registry.register( + "my_counter_with_exemplar", + "My counter with exemplar", + counter_with_exemplar.clone(), + ); + + counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 42.0)]), None); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_counter_with_exemplar", family.name); + assert_eq!("My counter with exemplar.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Counter as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + // The counter should be encoded as `DoubleValue` + let expected = openmetrics_data_model::counter_value::Total::DoubleValue(1.0); + assert_eq!(Some(expected), value.total); + + let exemplar = value.exemplar.as_ref().unwrap(); + assert_eq!(1.0, exemplar.value); + + assert!(exemplar.timestamp.is_none()); + + let expected_label = { + openmetrics_data_model::Label { + name: "user_id".to_string(), + value: "42.0".to_string(), + } + }; + assert_eq!(vec![expected_label], exemplar.label); + } + _ => panic!("wrong value type"), + } + + counter_with_exemplar.inc_by(1.0, Some(vec![("user_id".to_string(), 99.0)]), Some(now)); + + match extract_metric_point_value(&encode(®istry).unwrap()) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + // The counter should be encoded as `DoubleValue` + let expected = openmetrics_data_model::counter_value::Total::DoubleValue(2.0); + assert_eq!(Some(expected), value.total); + + let exemplar = value.exemplar.as_ref().unwrap(); + assert_eq!(1.0, exemplar.value); + + assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap()); + + let expected_label = { + openmetrics_data_model::Label { + name: "user_id".to_string(), + value: "99.0".to_string(), + } + }; + assert_eq!(vec![expected_label], exemplar.label); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_gauge() { + let mut registry = Registry::default(); + let gauge = Gauge::::default(); + registry.register("my_gauge", "My gauge", gauge.clone()); + gauge.inc(); + + 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); + + assert_eq!( + openmetrics_data_model::MetricType::Gauge as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::GaugeValue(value) => { + let expected = openmetrics_data_model::gauge_value::Value::IntValue(1); + assert_eq!(Some(expected), value.value); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_gauge_u64_normal() { + let mut registry = Registry::default(); + let gauge = Gauge::::default(); + registry.register("my_gauge", "My gauge", gauge.clone()); + gauge.set(12345); + + 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); + + assert_eq!( + openmetrics_data_model::MetricType::Gauge as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::GaugeValue(value) => { + let expected = openmetrics_data_model::gauge_value::Value::IntValue(12345); + assert_eq!(Some(expected), value.value); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_gauge_u64_max() { + let mut registry = Registry::default(); + let gauge = Gauge::::default(); + registry.register("my_gauge", "My gauge", gauge.clone()); + gauge.set(u64::MAX); + + // This expected to fail as protobuf uses i64 and u64::MAX does not fit into it. + assert!(encode(®istry).is_err()); + } + + #[test] + fn encode_counter_family() { + let mut registry = Registry::default(); + let family = Family::, Counter>::default(); + registry.register("my_counter_family", "My counter family", family.clone()); + + family + .get_or_create(&vec![ + ("method".to_string(), "GET".to_string()), + ("status".to_string(), "200".to_string()), + ]) + .inc(); + + family + .get_or_create(&vec![ + ("method".to_string(), "POST".to_string()), + ("status".to_string(), "200".to_string()), + ]) + .inc(); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_counter_family", family.name); + assert_eq!("My counter family.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Counter as i32, + extract_metric_type(&metric_set) + ); + + // The order of the labels is not deterministic so we are testing the + // value to be either + let mut potential_method_value = HashSet::new(); + potential_method_value.insert("GET"); + potential_method_value.insert("POST"); + + // the first metric + let metric = family.metrics.first().unwrap(); + assert_eq!(2, metric.labels.len()); + assert_eq!("method", metric.labels[0].name); + assert!(potential_method_value.remove(&metric.labels[0].value.as_str())); + assert_eq!("status", metric.labels[1].name); + assert_eq!("200", metric.labels[1].value); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + let expected = openmetrics_data_model::counter_value::Total::IntValue(1); + assert_eq!(Some(expected), value.total); + assert_eq!(None, value.exemplar); + assert_eq!(None, value.created); + } + _ => panic!("wrong value type"), + } + + // the second metric + let metric2 = &family.metrics[1]; + assert_eq!(2, metric2.labels.len()); + assert_eq!("method", metric2.labels[0].name); + assert!(potential_method_value.remove(&metric2.labels[0].value.as_str())); + assert_eq!("status", metric2.labels[1].name); + assert_eq!("200", metric2.labels[1].value); + } + + #[test] + fn encode_counter_family_with_prefix_with_label() { + let mut registry = Registry::default(); + let sub_registry = registry.sub_registry_with_prefix("my_prefix"); + let sub_sub_registry = sub_registry + .sub_registry_with_label((Cow::Borrowed("my_key"), Cow::Borrowed("my_value"))); + let family = Family::, Counter>::default(); + sub_sub_registry.register("my_counter_family", "My counter family", family.clone()); + + family + .get_or_create(&vec![ + ("method".to_string(), "GET".to_string()), + ("status".to_string(), "200".to_string()), + ]) + .inc(); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_prefix_my_counter_family", family.name); + assert_eq!("My counter family.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Counter as i32, + extract_metric_type(&metric_set) + ); + + let metric = family.metrics.first().unwrap(); + assert_eq!(3, metric.labels.len()); + assert_eq!("my_key", metric.labels[0].name); + assert_eq!("my_value", metric.labels[0].value); + assert_eq!("method", metric.labels[1].name); + assert_eq!("GET", metric.labels[1].value); + assert_eq!("status", metric.labels[2].name); + assert_eq!("200", metric.labels[2].value); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::CounterValue(value) => { + let expected = openmetrics_data_model::counter_value::Total::IntValue(1); + assert_eq!(Some(expected), value.total); + assert_eq!(None, value.exemplar); + assert_eq!(None, value.created); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_histogram() { + let mut registry = Registry::default(); + let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); + registry.register("my_histogram", "My histogram", histogram.clone()); + histogram.observe(1.0); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_histogram", family.name); + assert_eq!("My histogram.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Histogram as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::HistogramValue(value) => { + assert_eq!( + Some(openmetrics_data_model::histogram_value::Sum::DoubleValue( + 1.0 + )), + value.sum + ); + assert_eq!(1, value.count); + assert_eq!(11, value.buckets.len()); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_histogram_with_exemplars() { + let now = SystemTime::now(); + let now_ts: Timestamp = now.into(); + + 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(vec![("user_id".to_string(), 42u64)]), None); + + let metric_set = encode(®istry).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_histogram", family.name); + assert_eq!("My histogram.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Histogram as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::HistogramValue(value) => { + let exemplar = value.buckets.first().unwrap().exemplar.as_ref().unwrap(); + assert_eq!(1.0, exemplar.value); + + assert!(exemplar.timestamp.is_none()); + + let expected_label = { + openmetrics_data_model::Label { + name: "user_id".to_string(), + value: "42".to_string(), + } + }; + assert_eq!(vec![expected_label], exemplar.label); + } + _ => panic!("wrong value type"), + } + + histogram.observe(2.0, Some(vec![("user_id".to_string(), 99u64)]), Some(now)); + + match extract_metric_point_value(&encode(®istry).unwrap()) { + openmetrics_data_model::metric_point::Value::HistogramValue(value) => { + let exemplar = value.buckets.get(1).unwrap().exemplar.as_ref().unwrap(); + assert_eq!(2.0, exemplar.value); + + assert_eq!(&now_ts, exemplar.timestamp.as_ref().unwrap()); + + let expected_label = { + openmetrics_data_model::Label { + name: "user_id".to_string(), + value: "99".to_string(), + } + }; + assert_eq!(vec![expected_label], exemplar.label); + } + _ => panic!("wrong value type"), + } + } + + #[test] + fn encode_family_counter_histogram() { + let mut registry = Registry::default(); + + let counter_family = Family::, Counter>::default(); + let histogram_family = + Family::, Histogram>::new_with_constructor(|| { + Histogram::new(exponential_buckets(1.0, 2.0, 10)) + }); + + 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())]) + .inc(); + + histogram_family + .get_or_create(&vec![("path".to_string(), "/".to_string())]) + .observe(1.0); + + 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(); + + // Family + let counter_family = Family::, Counter>::default(); + let histogram_family = + Family::, Histogram>::new_with_constructor(|| { + Histogram::new(exponential_buckets(1.0, 2.0, 10)) + }); + + registry.register("my_family_counter", "My counter", counter_family.clone()); + registry.register( + "my_family_histogram", + "My histogram", + histogram_family.clone(), + ); + + counter_family + .get_or_create(&vec![("path".to_string(), "/".to_string())]) + .inc(); + + histogram_family + .get_or_create(&vec![("path".to_string(), "/".to_string())]) + .observe(1.0); + + // Counter + let counter: Counter = Counter::default(); + 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", histogram.clone()); + histogram.observe(1.0); + + 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); + } + + #[test] + fn encode_info() { + let mut registry = Registry::default(); + 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).unwrap(); + + let family = metric_set.metric_families.first().unwrap(); + assert_eq!("my_info_metric", family.name); + assert_eq!("My info metric.", family.help); + + assert_eq!( + openmetrics_data_model::MetricType::Info as i32, + extract_metric_type(&metric_set) + ); + + match extract_metric_point_value(&metric_set) { + openmetrics_data_model::metric_point::Value::InfoValue(value) => { + assert_eq!(1, value.info.len()); + + let info = value.info.first().unwrap(); + assert_eq!("os", info.name); + assert_eq!("GNU/linux", info.value); + } + _ => panic!("wrong value type"), + } + } + + fn extract_metric_type(metric_set: &openmetrics_data_model::MetricSet) -> i32 { + let family = metric_set.metric_families.first().unwrap(); + family.r#type + } + + fn extract_metric_point_value( + metric_set: &openmetrics_data_model::MetricSet, + ) -> openmetrics_data_model::metric_point::Value { + let metric = metric_set + .metric_families + .first() + .unwrap() + .metrics + .first() + .unwrap(); + + metric + .metric_points + .first() + .unwrap() + .value + .as_ref() + .unwrap() + .clone() + } +} diff --git a/src/encoding/protobuf.rs b/src/encoding/prometheus_protobuf.rs similarity index 100% rename from src/encoding/protobuf.rs rename to src/encoding/prometheus_protobuf.rs diff --git a/src/encoding/proto/openmetrics_data_model.proto b/src/encoding/proto/openmetrics_data_model.proto new file mode 100644 index 0000000..a95942d --- /dev/null +++ b/src/encoding/proto/openmetrics_data_model.proto @@ -0,0 +1,214 @@ +syntax = "proto3"; + +// The OpenMetrics protobuf schema which defines the protobuf wire format. +// Ensure to interpret "required" as semantically required for a valid message. +// All string fields MUST be UTF-8 encoded strings. +package openmetrics; + +import "google/protobuf/timestamp.proto"; + +// The top-level container type that is encoded and sent over the wire. +message MetricSet { + // Each MetricFamily has one or more MetricPoints for a single Metric. + repeated MetricFamily metric_families = 1; +} + +// One or more Metrics for a single MetricFamily, where each Metric +// has one or more MetricPoints. +message MetricFamily { + // Required. + string name = 1; + + // Optional. + MetricType type = 2; + + // Optional. + string unit = 3; + + // Optional. + string help = 4; + + // Optional. + repeated Metric metrics = 5; +} + +// The type of a Metric. +enum MetricType { + // Unknown must use unknown MetricPoint values. + UNKNOWN = 0; + // Gauge must use gauge MetricPoint values. + GAUGE = 1; + // Counter must use counter MetricPoint values. + COUNTER = 2; + // State set must use state set MetricPoint values. + STATE_SET = 3; + // Info must use info MetricPoint values. + INFO = 4; + // Histogram must use histogram value MetricPoint values. + HISTOGRAM = 5; + // Gauge histogram must use histogram value MetricPoint values. + GAUGE_HISTOGRAM = 6; + // Summary quantiles must use summary value MetricPoint values. + SUMMARY = 7; +} + +// A single metric with a unique set of labels within a metric family. +message Metric { + // Optional. + repeated Label labels = 1; + + // Optional. + repeated MetricPoint metric_points = 2; +} + +// A name-value pair. These are used in multiple places: identifying +// timeseries, value of INFO metrics, and exemplars in Histograms. +message Label { + // Required. + string name = 1; + + // Required. + string value = 2; +} + +// A MetricPoint in a Metric. +message MetricPoint { + // Required. + oneof value { + UnknownValue unknown_value = 1; + GaugeValue gauge_value = 2; + CounterValue counter_value = 3; + HistogramValue histogram_value = 4; + StateSetValue state_set_value = 5; + InfoValue info_value = 6; + SummaryValue summary_value = 7; + } + + // Optional. + google.protobuf.Timestamp timestamp = 8; +} + +// Value for UNKNOWN MetricPoint. +message UnknownValue { + // Required. + oneof value { + double double_value = 1; + int64 int_value = 2; + } +} + +// Value for GAUGE MetricPoint. +message GaugeValue { + // Required. + oneof value { + double double_value = 1; + int64 int_value = 2; + } +} + +// Value for COUNTER MetricPoint. +message CounterValue { + // Required. + oneof total { + double double_value = 1; + uint64 int_value = 2; + } + + // The time values began being collected for this counter. + // Optional. + google.protobuf.Timestamp created = 3; + + // Optional. + Exemplar exemplar = 4; +} + +// Value for HISTOGRAM or GAUGE_HISTOGRAM MetricPoint. +message HistogramValue { + // Optional. + oneof sum { + double double_value = 1; + int64 int_value = 2; + } + + // Optional. + uint64 count = 3; + + // The time values began being collected for this histogram. + // Optional. + google.protobuf.Timestamp created = 4; + + // Optional. + repeated Bucket buckets = 5; + + // Bucket is the number of values for a bucket in the histogram + // with an optional exemplar. + message Bucket { + // Required. + uint64 count = 1; + + // Optional. + double upper_bound = 2; + + // Optional. + Exemplar exemplar = 3; + } +} + +message Exemplar { + // Required. + double value = 1; + + // Optional. + google.protobuf.Timestamp timestamp = 2; + + // Labels are additional information about the exemplar value (e.g. trace id). + // Optional. + repeated Label label = 3; +} + +// Value for STATE_SET MetricPoint. +message StateSetValue { + // Optional. + repeated State states = 1; + + message State { + // Required. + bool enabled = 1; + + // Required. + string name = 2; + } +} + +// Value for INFO MetricPoint. +message InfoValue { + // Optional. + repeated Label info = 1; +} + +// Value for SUMMARY MetricPoint. +message SummaryValue { + // Optional. + oneof sum { + double double_value = 1; + int64 int_value = 2; + } + + // Optional. + uint64 count = 3; + + // The time sum and count values began being collected for this summary. + // Optional. + google.protobuf.Timestamp created = 4; + + // Optional. + repeated Quantile quantile = 5; + + message Quantile { + // Required. + double quantile = 1; + + // Required. + double value = 2; + } +} From f916bd66a4aac9d84383f953e9018476ca32f63c Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2026 10:02:46 -0700 Subject: [PATCH 6/6] Retain old 'protobuf' feature, make explicit types Signed-off-by: John Howard --- CHANGELOG.md | 6 +- Cargo.toml | 9 +-- benches/encoding/proto.rs | 4 +- build.rs | 8 +-- derive-encode/Cargo.toml | 2 +- derive-encode/tests/lib.rs | 4 +- src/encoding.rs | 101 ++++++++++++++------------- src/encoding/openmetrics_protobuf.rs | 1 + src/encoding/prometheus_protobuf.rs | 2 +- 9 files changed, 73 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7afb7..54b03a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated `prost`, `prost-build`, and `prost-types` dependencies to `v0.14`. -- The `protobuf` feature now generates and encodes Prometheus +- The `prometheus_protobuf` feature generates and encodes Prometheus `io.prometheus.client` protobuf messages from `metrics.proto` rather than the - OpenMetrics protobuf data model. This is a breaking change for users of the `protobuf` feature. + OpenMetrics protobuf data model. See [Issue](https://github.com/prometheus/OpenMetrics/issues/296) for more context. - The `legacy_protobuf` feature retains the old, deprecated, OpenMetrics protobuf support which will + The `protobuf` and `openmetrics_protobuf` features retain the old, deprecated, OpenMetrics protobuf support which will be removed in a future release. ## [0.24.1] diff --git a/Cargo.toml b/Cargo.toml index aa146cc..7ef44a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,10 @@ documentation = "https://docs.rs/prometheus-client" [features] default = [] -protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] -legacy_protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] -protobuf-protox = ["protobuf", "dep:protox"] +prometheus_protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] +openmetrics_protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] +protobuf = ["openmetrics_protobuf"] +protobuf-protox = ["dep:protox"] # This feature provides additional APIs for testing downstream code using # `prometheus-client`. @@ -76,7 +77,7 @@ required-features = [] name = "proto" path = "benches/encoding/proto.rs" harness = false -required-features = ["protobuf"] +required-features = ["prometheus_protobuf"] # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling diff --git a/benches/encoding/proto.rs b/benches/encoding/proto.rs index da3a182..5f62eed 100644 --- a/benches/encoding/proto.rs +++ b/benches/encoding/proto.rs @@ -2,7 +2,7 @@ // 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::protobuf; +use prometheus_client::encoding::prometheus_protobuf; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; @@ -75,7 +75,7 @@ pub fn proto(c: &mut Criterion) { } b.iter(|| { - let metric_set = protobuf::encode(®istry).unwrap(); + let metric_set = prometheus_protobuf::encode(®istry).unwrap(); black_box(metric_set); }) }); diff --git a/build.rs b/build.rs index 28fc516..5afe84c 100644 --- a/build.rs +++ b/build.rs @@ -1,20 +1,20 @@ use std::error::Error; fn main() -> Result<(), Box> { - #[cfg(any(feature = "protobuf", feature = "legacy_protobuf"))] + #[cfg(any(feature = "prometheus_protobuf", feature = "openmetrics_protobuf"))] compile_protos()?; Ok(()) } #[allow(clippy::vec_init_then_push)] // False positive due to feature flags -#[cfg(any(feature = "protobuf", feature = "legacy_protobuf"))] +#[cfg(any(feature = "prometheus_protobuf", feature = "openmetrics_protobuf"))] fn compile_protos() -> Result<(), Box> { let mut protos = Vec::new(); - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] protos.push("src/encoding/proto/metrics.proto"); - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] protos.push("src/encoding/proto/openmetrics_data_model.proto"); let includes = ["src/encoding/proto/"]; diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml index 064cce7..5fd04bc 100644 --- a/derive-encode/Cargo.toml +++ b/derive-encode/Cargo.toml @@ -17,7 +17,7 @@ quote = "1" syn = "2" [dev-dependencies] -prometheus-client = { path = "../", features = ["protobuf"] } +prometheus-client = { path = "../", features = ["prometheus_protobuf"] } trybuild = "1" [lib] diff --git a/derive-encode/tests/lib.rs b/derive-encode/tests/lib.rs index ec8bed5..ec30a56 100644 --- a/derive-encode/tests/lib.rs +++ b/derive-encode/tests/lib.rs @@ -47,8 +47,8 @@ fn basic_flow() { mod protobuf { use crate::{Labels, Method}; - use prometheus_client::encoding::protobuf::encode; - use prometheus_client::encoding::protobuf::prometheus_data_model; + use prometheus_client::encoding::prometheus_protobuf::encode; + use prometheus_client::encoding::prometheus_protobuf::prometheus_data_model; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; diff --git a/src/encoding.rs b/src/encoding.rs index ce1d8b9..8226c97 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,5 +1,7 @@ //! Exposition format implementations. +#![allow(deprecated)] + pub use prometheus_client_derive_encode::*; use crate::metrics::exemplar::Exemplar; @@ -13,26 +15,31 @@ use std::rc::Rc; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "legacy_protobuf")] -#[cfg_attr(docsrs, doc(cfg(feature = "legacy_protobuf")))] +#[cfg(feature = "openmetrics_protobuf")] +#[cfg_attr(docsrs, doc(cfg(feature = "openmetrics_protobuf")))] +#[cfg_attr( + not(test), + deprecated( + note = "OpenMetrics protobuf support is deprecated. Use prometheus_protobuf instead." + ) +)] pub mod openmetrics_protobuf; -#[cfg(feature = "protobuf")] -#[cfg_attr(docsrs, doc(cfg(feature = "protobuf")))] +#[cfg(feature = "prometheus_protobuf")] +#[cfg_attr(docsrs, doc(cfg(feature = "prometheus_protobuf")))] pub mod prometheus_protobuf; pub mod text; -#[cfg(all(feature = "legacy_protobuf", not(feature = "protobuf")))] +#[cfg(feature = "openmetrics_protobuf")] +#[deprecated(note = "Use openmetrics_protobuf instead.")] pub use openmetrics_protobuf as protobuf; -#[cfg(feature = "protobuf")] -pub use prometheus_protobuf as protobuf; macro_rules! for_both_mut { ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { match &mut $self.0 { $inner::Text($pattern) => $fn, - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] $inner::Protobuf($pattern) => $fn, - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] $inner::PrometheusProtobuf($pattern) => $fn, } }; @@ -42,9 +49,9 @@ macro_rules! for_both { ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { match $self.0 { $inner::Text($pattern) => $fn, - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] $inner::Protobuf($pattern) => $fn, - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] $inner::PrometheusProtobuf($pattern) => $fn, } }; @@ -93,10 +100,10 @@ pub struct DescriptorEncoder<'a>(DescriptorEncoderInner<'a>); enum DescriptorEncoderInner<'a> { Text(text::DescriptorEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::DescriptorEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::DescriptorEncoder<'a>), } @@ -106,14 +113,14 @@ impl<'a> From> for DescriptorEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for DescriptorEncoder<'a> { fn from(e: openmetrics_protobuf::DescriptorEncoder<'a>) -> Self { Self(DescriptorEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for DescriptorEncoder<'a> { fn from(e: prometheus_protobuf::DescriptorEncoder<'a>) -> Self { Self(DescriptorEncoderInner::PrometheusProtobuf(e)) @@ -160,10 +167,10 @@ pub struct MetricEncoder<'a>(MetricEncoderInner<'a>); enum MetricEncoderInner<'a> { Text(text::MetricEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::MetricEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::MetricEncoder<'a>), } @@ -173,14 +180,14 @@ impl<'a> From> for MetricEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for MetricEncoder<'a> { fn from(e: openmetrics_protobuf::MetricEncoder<'a>) -> Self { Self(MetricEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for MetricEncoder<'a> { fn from(e: prometheus_protobuf::MetricEncoder<'a>) -> Self { Self(MetricEncoderInner::PrometheusProtobuf(e)) @@ -257,9 +264,9 @@ pub struct LabelSetEncoder<'a>(LabelSetEncoderInner<'a>); #[derive(Debug)] enum LabelSetEncoderInner<'a> { Text(text::LabelSetEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::LabelSetEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::LabelSetEncoder<'a>), } @@ -269,14 +276,14 @@ impl<'a> From> for LabelSetEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for LabelSetEncoder<'a> { fn from(e: openmetrics_protobuf::LabelSetEncoder<'a>) -> Self { Self(LabelSetEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for LabelSetEncoder<'a> { fn from(e: prometheus_protobuf::LabelSetEncoder<'a>) -> Self { Self(LabelSetEncoderInner::PrometheusProtobuf(e)) @@ -355,9 +362,9 @@ pub struct LabelEncoder<'a>(LabelEncoderInner<'a>); #[derive(Debug)] enum LabelEncoderInner<'a> { Text(text::LabelEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::LabelEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::LabelEncoder<'a>), } @@ -367,14 +374,14 @@ impl<'a> From> for LabelEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for LabelEncoder<'a> { fn from(e: openmetrics_protobuf::LabelEncoder<'a>) -> Self { Self(LabelEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for LabelEncoder<'a> { fn from(e: prometheus_protobuf::LabelEncoder<'a>) -> Self { Self(LabelEncoderInner::PrometheusProtobuf(e)) @@ -421,9 +428,9 @@ pub struct LabelKeyEncoder<'a>(LabelKeyEncoderInner<'a>); #[derive(Debug)] enum LabelKeyEncoderInner<'a> { Text(text::LabelKeyEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::LabelKeyEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::LabelKeyEncoder<'a>), } @@ -433,14 +440,14 @@ impl<'a> From> for LabelKeyEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for LabelKeyEncoder<'a> { fn from(e: openmetrics_protobuf::LabelKeyEncoder<'a>) -> Self { Self(LabelKeyEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for LabelKeyEncoder<'a> { fn from(e: prometheus_protobuf::LabelKeyEncoder<'a>) -> Self { Self(LabelKeyEncoderInner::PrometheusProtobuf(e)) @@ -527,9 +534,9 @@ pub struct LabelValueEncoder<'a>(LabelValueEncoderInner<'a>); #[derive(Debug)] enum LabelValueEncoderInner<'a> { Text(text::LabelValueEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::LabelValueEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::LabelValueEncoder<'a>), } @@ -539,14 +546,14 @@ impl<'a> From> for LabelValueEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for LabelValueEncoder<'a> { fn from(e: openmetrics_protobuf::LabelValueEncoder<'a>) -> Self { LabelValueEncoder(LabelValueEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for LabelValueEncoder<'a> { fn from(e: prometheus_protobuf::LabelValueEncoder<'a>) -> Self { LabelValueEncoder(LabelValueEncoderInner::PrometheusProtobuf(e)) @@ -723,9 +730,9 @@ pub struct GaugeValueEncoder<'a>(GaugeValueEncoderInner<'a>); #[derive(Debug)] enum GaugeValueEncoderInner<'a> { Text(text::GaugeValueEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::GaugeValueEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::GaugeValueEncoder<'a>), } @@ -753,14 +760,14 @@ impl<'a> From> for GaugeValueEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for GaugeValueEncoder<'a> { fn from(e: openmetrics_protobuf::GaugeValueEncoder<'a>) -> Self { GaugeValueEncoder(GaugeValueEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for GaugeValueEncoder<'a> { fn from(e: prometheus_protobuf::GaugeValueEncoder<'a>) -> Self { GaugeValueEncoder(GaugeValueEncoderInner::PrometheusProtobuf(e)) @@ -811,9 +818,9 @@ pub struct CounterValueEncoder<'a>(CounterValueEncoderInner<'a>); #[derive(Debug)] enum CounterValueEncoderInner<'a> { Text(text::CounterValueEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::CounterValueEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::CounterValueEncoder<'a>), } @@ -823,14 +830,14 @@ impl<'a> From> for CounterValueEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for CounterValueEncoder<'a> { fn from(e: openmetrics_protobuf::CounterValueEncoder<'a>) -> Self { CounterValueEncoder(CounterValueEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for CounterValueEncoder<'a> { fn from(e: prometheus_protobuf::CounterValueEncoder<'a>) -> Self { CounterValueEncoder(CounterValueEncoderInner::PrometheusProtobuf(e)) @@ -896,9 +903,9 @@ pub struct ExemplarValueEncoder<'a>(ExemplarValueEncoderInner<'a>); #[derive(Debug)] enum ExemplarValueEncoderInner<'a> { Text(text::ExemplarValueEncoder<'a>), - #[cfg(feature = "legacy_protobuf")] + #[cfg(feature = "openmetrics_protobuf")] Protobuf(openmetrics_protobuf::ExemplarValueEncoder<'a>), - #[cfg(feature = "protobuf")] + #[cfg(feature = "prometheus_protobuf")] PrometheusProtobuf(prometheus_protobuf::ExemplarValueEncoder<'a>), } @@ -908,14 +915,14 @@ impl<'a> From> for ExemplarValueEncoder<'a> { } } -#[cfg(feature = "legacy_protobuf")] +#[cfg(feature = "openmetrics_protobuf")] impl<'a> From> for ExemplarValueEncoder<'a> { fn from(e: openmetrics_protobuf::ExemplarValueEncoder<'a>) -> Self { ExemplarValueEncoder(ExemplarValueEncoderInner::Protobuf(e)) } } -#[cfg(feature = "protobuf")] +#[cfg(feature = "prometheus_protobuf")] impl<'a> From> for ExemplarValueEncoder<'a> { fn from(e: prometheus_protobuf::ExemplarValueEncoder<'a>) -> Self { ExemplarValueEncoder(ExemplarValueEncoderInner::PrometheusProtobuf(e)) diff --git a/src/encoding/openmetrics_protobuf.rs b/src/encoding/openmetrics_protobuf.rs index ca11754..c7334c5 100644 --- a/src/encoding/openmetrics_protobuf.rs +++ b/src/encoding/openmetrics_protobuf.rs @@ -445,6 +445,7 @@ impl std::fmt::Write for LabelValueEncoder<'_> { } #[cfg(test)] +#[allow(deprecated)] mod tests { use prost_types::Timestamp; diff --git a/src/encoding/prometheus_protobuf.rs b/src/encoding/prometheus_protobuf.rs index c8eb48a..54252bd 100644 --- a/src/encoding/prometheus_protobuf.rs +++ b/src/encoding/prometheus_protobuf.rs @@ -1,7 +1,7 @@ //! Prometheus protobuf implementation. //! //! ``` -//! # use prometheus_client::encoding::protobuf::encode; +//! # use prometheus_client::encoding::prometheus_protobuf::encode; //! # use prometheus_client::metrics::counter::Counter; //! # use prometheus_client::registry::Registry; //! #