diff --git a/CHANGELOG.md b/CHANGELOG.md index 80654b5e..a6d75e7b 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/CONTRIBUTING.md b/CONTRIBUTING.md index e20a178b..64337755 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 c7b0ffab..a04a8abd 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." @@ -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`. @@ -29,8 +30,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 +50,8 @@ 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 } +protox = { version = "0.9.1", optional = true } [[bench]] name = "baseline" diff --git a/build.rs b/build.rs index d10b0197..b926f011 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,29 @@ -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/metrics.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)?; + + 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 5d0910f3..ec8bed50 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 44ec0efd..e98a0398 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 00000000..a0027ff9 --- /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 a95942d9..00000000 --- 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 9f770f60..c8eb48a4 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 fd050f42..bc3f650b 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))?;