├── .gitignore ├── MAINTAINERS.md ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── CODE_OF_CONDUCT.md ├── SECURITY.md ├── benches ├── baseline.rs ├── exemplars.rs ├── family.rs └── encoding │ ├── proto.rs │ └── text.rs ├── derive-encode ├── Cargo.toml ├── tests │ ├── build │ │ └── redefine-prelude-symbols.rs │ └── lib.rs ├── LICENSE-MIT ├── src │ └── lib.rs └── LICENSE-APACHE ├── CONTRIBUTING.md ├── LICENSE-MIT ├── src ├── metrics.rs ├── metrics │ ├── info.rs │ ├── histogram.rs │ ├── counter.rs │ ├── exemplar.rs │ ├── gauge.rs │ └── family.rs ├── collector.rs ├── lib.rs ├── encoding │ └── proto │ │ └── openmetrics_data_model.proto ├── registry.rs └── encoding.rs ├── examples ├── io_write.rs ├── custom-metric.rs ├── actix-web.rs ├── tide.rs ├── axum.rs └── hyper.rs ├── Cargo.toml ├── README.md ├── LICENSE-APACHE └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | - Max Inden ( / @mxinden) 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Prometheus Community Code of Conduct 2 | 3 | Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a security issue 2 | 3 | The Prometheus security policy, including how to report vulnerabilities, can be 4 | found here: 5 | 6 | 7 | -------------------------------------------------------------------------------- /benches/baseline.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use prometheus_client::metrics::counter::Counter; 3 | use prometheus_client::metrics::family::Family; 4 | 5 | pub fn baseline(c: &mut Criterion) { 6 | c.bench_function("counter", |b| { 7 | let counter: Counter = Counter::default(); 8 | 9 | b.iter(|| { 10 | counter.inc(); 11 | }) 12 | }); 13 | 14 | c.bench_function("counter via family lookup", |b| { 15 | let family = Family::<(), Counter>::default(); 16 | 17 | b.iter(|| { 18 | family.get_or_create(&()).inc(); 19 | }) 20 | }); 21 | } 22 | 23 | criterion_group!(benches, baseline); 24 | criterion_main!(benches); 25 | -------------------------------------------------------------------------------- /derive-encode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prometheus-client-derive-encode" 3 | version = "0.5.0" 4 | authors = ["Max Inden "] 5 | edition = "2021" 6 | description = "Auxiliary crate to derive Encode trait from prometheus-client." 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/prometheus/client_rust" 9 | homepage = "https://github.com/prometheus/client_rust" 10 | documentation = "https://docs.rs/prometheus-client-derive-text-encode" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | proc-macro2 = "1" 16 | quote = "1" 17 | syn = "2" 18 | 19 | [dev-dependencies] 20 | prometheus-client = { path = "../", features = ["protobuf"] } 21 | trybuild = "1" 22 | 23 | [lib] 24 | proc-macro = true 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Protocol Buffers 4 | 5 | The `build.rs` script in this library depends upon the 6 | [Protocol Buffers compiler][protoc]. Be sure that `protoc` is installed and 7 | available within your `PATH`. 8 | 9 | [protoc]: https://docs.rs/prost-build/latest/prost_build/#sourcing-protoc 10 | 11 | ## Python Dependencies 12 | 13 | This repository uses the [`prometheus-client`][client-python] Python client 14 | library in its test suite. 15 | 16 | You may create and activate a virtual environment with this dependency 17 | installed by running the following shell commands from the root of this 18 | repository: 19 | 20 | ```shell 21 | python -m venv ./venv 22 | source venv/bin/activate 23 | pip install prometheus-client 24 | ``` 25 | 26 | [client-python]: https://github.com/prometheus/client_python 27 | -------------------------------------------------------------------------------- /derive-encode/tests/build/redefine-prelude-symbols.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | // Empty module has nothing and can be used to redefine symbols. 4 | mod empty {} 5 | 6 | // redefine the prelude `::std` 7 | use empty as std; 8 | 9 | // redefine the dependency `::prometheus_client` 10 | use empty as prometheus_client; 11 | 12 | // redefine the prelude `::core::result::Result`. 13 | type Result = (); 14 | 15 | enum TResult { 16 | Ok, 17 | Err, 18 | } 19 | 20 | // redefine the prelude `::core::result::Result::Ok/Err`. 21 | use TResult::Ok; 22 | use TResult::Err; 23 | 24 | #[derive(Debug, Clone, PartialEq, Eq, Hash, ::prometheus_client::encoding::EncodeLabelSet)] 25 | struct LableSet { 26 | a: String, 27 | b: LabelEnum, 28 | } 29 | 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ::prometheus_client::encoding::EncodeLabelValue)] 31 | enum LabelEnum { 32 | A, 33 | B, 34 | } 35 | 36 | fn main() {} 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Max Inden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | //! Metric type implementations. 2 | 3 | pub mod counter; 4 | pub mod exemplar; 5 | pub mod family; 6 | pub mod gauge; 7 | pub mod histogram; 8 | pub mod info; 9 | 10 | /// A metric that is aware of its Open Metrics metric type. 11 | pub trait TypedMetric { 12 | /// The OpenMetrics metric type. 13 | const TYPE: MetricType = MetricType::Unknown; 14 | } 15 | 16 | /// OpenMetrics metric type. 17 | #[derive(Clone, Copy, Debug)] 18 | #[allow(missing_docs)] 19 | pub enum MetricType { 20 | Counter, 21 | Gauge, 22 | Histogram, 23 | Info, 24 | Unknown, 25 | // Not (yet) supported metric types. 26 | // 27 | // GaugeHistogram, 28 | // StateSet, 29 | // Summary 30 | } 31 | 32 | impl MetricType { 33 | /// Returns the given metric type's str representation. 34 | pub fn as_str(&self) -> &str { 35 | match self { 36 | MetricType::Counter => "counter", 37 | MetricType::Gauge => "gauge", 38 | MetricType::Histogram => "histogram", 39 | MetricType::Info => "info", 40 | MetricType::Unknown => "unknown", 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /derive-encode/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Max Inden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/metrics/info.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics info metric. 2 | //! 3 | //! See [`Info`] for details. 4 | 5 | use crate::{ 6 | encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}, 7 | metrics::{MetricType, TypedMetric}, 8 | }; 9 | 10 | /// Open Metrics [`Info`] metric "to expose textual information which SHOULD NOT 11 | /// change during process lifetime". 12 | /// 13 | /// ``` 14 | /// # use prometheus_client::metrics::info::Info; 15 | /// 16 | /// let _info = Info::new(vec![("os", "GNU/linux")]); 17 | /// ``` 18 | #[derive(Debug)] 19 | pub struct Info(pub(crate) S); 20 | 21 | impl Info { 22 | /// Create [`Info`] metric with the provided label set. 23 | pub fn new(label_set: S) -> Self { 24 | Self(label_set) 25 | } 26 | } 27 | 28 | impl TypedMetric for Info { 29 | const TYPE: MetricType = MetricType::Info; 30 | } 31 | 32 | impl EncodeMetric for Info 33 | where 34 | S: Clone + std::hash::Hash + Eq + EncodeLabelSet, 35 | { 36 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 37 | encoder.encode_info(&self.0) 38 | } 39 | 40 | fn metric_type(&self) -> MetricType { 41 | Self::TYPE 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/io_write.rs: -------------------------------------------------------------------------------- 1 | //! Example showing how one could write to a file or socket instead of a string. 2 | //! For large metrics registries this will be more memory efficient. 3 | 4 | use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry}; 5 | use std::io::Write; 6 | 7 | fn main() { 8 | let mut registry = ::with_prefix("stream"); 9 | let request_counter: Counter = Default::default(); 10 | 11 | registry.register( 12 | "requests", 13 | "How many requests the application has received", 14 | request_counter.clone(), 15 | ); 16 | 17 | let mut buf = String::new(); 18 | encode(&mut buf, ®istry).unwrap(); 19 | 20 | let mut file = Vec::new(); 21 | let mut writer = IoWriterWrapper(&mut file); 22 | encode(&mut writer, ®istry).unwrap(); 23 | 24 | assert!(buf.as_bytes() == file); 25 | } 26 | 27 | pub struct IoWriterWrapper(W); 28 | 29 | impl std::fmt::Write for IoWriterWrapper 30 | where 31 | W: Write, 32 | { 33 | fn write_str(&mut self, input: &str) -> std::fmt::Result { 34 | self.0 35 | .write_all(input.as_bytes()) 36 | .map(|_| ()) 37 | .map_err(|_| std::fmt::Error) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /benches/exemplars.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | use prometheus_client::metrics::exemplar::HistogramWithExemplars; 5 | use prometheus_client::metrics::histogram::Histogram; 6 | 7 | type Exemplar = Vec<(String, String)>; 8 | 9 | const BUCKETS: &[f64] = &[1.0, 2.0, 3.0]; 10 | 11 | pub fn exemplars(c: &mut Criterion) { 12 | c.bench_function("histogram without exemplars", |b| { 13 | let histogram = Histogram::new(BUCKETS.iter().copied()); 14 | 15 | b.iter(|| { 16 | histogram.observe(1.0); 17 | }); 18 | }); 19 | 20 | c.bench_function("histogram with exemplars (no exemplar passed)", |b| { 21 | let histogram = HistogramWithExemplars::::new(BUCKETS.iter().copied()); 22 | 23 | b.iter(|| { 24 | histogram.observe(1.0, None, None); 25 | }); 26 | }); 27 | 28 | c.bench_function("histogram with exemplars (some exemplar passed)", |b| { 29 | let histogram = HistogramWithExemplars::::new(BUCKETS.iter().copied()); 30 | let exemplar = vec![("TraceID".to_owned(), "deadfeed".to_owned())]; 31 | 32 | b.iter(|| { 33 | histogram.observe(1.0, Some(exemplar.clone()), Some(SystemTime::now())); 34 | }); 35 | }); 36 | } 37 | 38 | criterion_group!(benches, exemplars); 39 | criterion_main!(benches); 40 | -------------------------------------------------------------------------------- /examples/custom-metric.rs: -------------------------------------------------------------------------------- 1 | use prometheus_client::encoding::{text::encode, EncodeMetric, MetricEncoder, NoLabelSet}; 2 | use prometheus_client::metrics::MetricType; 3 | use prometheus_client::registry::Registry; 4 | 5 | /// Showcasing encoding of custom metrics. 6 | /// 7 | /// Related to the concept of "Custom Collectors" in other implementations. 8 | /// 9 | /// [`MyCustomMetric`] generates and encodes a random number on each scrape. 10 | #[derive(Debug)] 11 | struct MyCustomMetric {} 12 | 13 | impl EncodeMetric for MyCustomMetric { 14 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 15 | // This method is called on each Prometheus server scrape. Allowing you 16 | // to execute whatever logic is needed to generate and encode your 17 | // custom metric. 18 | // 19 | // Do keep in mind that "with great power comes great responsibility". 20 | // E.g. every CPU cycle spend in this method delays the response send to 21 | // the Prometheus server. 22 | 23 | encoder.encode_counter::(&rand::random::(), None) 24 | } 25 | 26 | fn metric_type(&self) -> prometheus_client::metrics::MetricType { 27 | MetricType::Counter 28 | } 29 | } 30 | 31 | fn main() { 32 | let mut registry = Registry::default(); 33 | 34 | let metric = MyCustomMetric {}; 35 | registry.register( 36 | "my_custom_metric", 37 | "Custom metric returning a random number on each scrape", 38 | metric, 39 | ); 40 | 41 | let mut encoded = String::new(); 42 | encode(&mut encoded, ®istry).unwrap(); 43 | 44 | println!("Scrape output:\n{encoded:?}"); 45 | } 46 | -------------------------------------------------------------------------------- /src/collector.rs: -------------------------------------------------------------------------------- 1 | //! Metric collector implementation. 2 | //! 3 | //! See [`Collector`] for details. 4 | 5 | use crate::encoding::DescriptorEncoder; 6 | 7 | /// The [`Collector`] abstraction allows users to provide additional metrics and 8 | /// their description on each scrape. 9 | /// 10 | /// An example use-case is an exporter that retrieves a set of operating system metrics 11 | /// ad-hoc on each scrape. 12 | /// 13 | /// Register a [`Collector`] with a [`Registry`](crate::registry::Registry) via 14 | /// [`Registry::register_collector`](crate::registry::Registry::register_collector). 15 | /// 16 | /// ``` 17 | /// # use prometheus_client::metrics::counter::ConstCounter; 18 | /// # use prometheus_client::collector::Collector; 19 | /// # use prometheus_client::encoding::{DescriptorEncoder, EncodeMetric}; 20 | /// # 21 | /// #[derive(Debug)] 22 | /// struct MyCollector {} 23 | /// 24 | /// impl Collector for MyCollector { 25 | /// fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { 26 | /// let counter = ConstCounter::new(42u64); 27 | /// let metric_encoder = encoder.encode_descriptor( 28 | /// "my_counter", 29 | /// "some help", 30 | /// None, 31 | /// counter.metric_type(), 32 | /// )?; 33 | /// counter.encode(metric_encoder)?; 34 | /// Ok(()) 35 | /// } 36 | /// } 37 | /// ``` 38 | pub trait Collector: std::fmt::Debug + Send + Sync + 'static { 39 | /// Once the [`Collector`] is registered, this method is called on each scrape. 40 | fn encode(&self, encoder: DescriptorEncoder) -> Result<(), std::fmt::Error>; 41 | } 42 | 43 | impl Collector for std::sync::Arc { 44 | fn encode(&self, encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { 45 | self.as_ref().encode(encoder) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prometheus-client" 3 | version = "0.24.0" 4 | authors = ["Max Inden "] 5 | edition = "2021" 6 | description = "Open Metrics client library allowing users to natively instrument applications." 7 | license = "Apache-2.0 OR MIT" 8 | keywords = ["openmetrics", "prometheus", "metrics", "instrumentation", "monitoring"] 9 | repository = "https://github.com/prometheus/client_rust" 10 | homepage = "https://github.com/prometheus/client_rust" 11 | documentation = "https://docs.rs/prometheus-client" 12 | 13 | [features] 14 | default = [] 15 | protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] 16 | 17 | [workspace] 18 | members = ["derive-encode"] 19 | 20 | [dependencies] 21 | dtoa = "1.0" 22 | itoa = "1.0" 23 | parking_lot = "0.12" 24 | prometheus-client-derive-encode = { version = "0.5.0", path = "derive-encode" } 25 | prost = { version = "0.12.0", optional = true } 26 | prost-types = { version = "0.12.0", optional = true } 27 | 28 | [dev-dependencies] 29 | async-std = { version = "1", features = ["attributes"] } 30 | axum = "0.7" 31 | criterion = "0.5" 32 | futures = "0.3" 33 | http-types = "2" 34 | pyo3 = "0.27" 35 | quickcheck = "1" 36 | rand = "0.8.4" 37 | tide = "0.16" 38 | actix-web = "4" 39 | tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal"] } 40 | hyper = { version = "1.3.1", features = ["server", "http1"] } 41 | hyper-util = { version = "0.1.3", features = ["tokio"] } 42 | http-body-util = "0.1.1" 43 | 44 | [build-dependencies] 45 | prost-build = { version = "0.12.0", optional = true } 46 | 47 | [[bench]] 48 | name = "baseline" 49 | harness = false 50 | 51 | [[bench]] 52 | name = "exemplars" 53 | harness = false 54 | 55 | [[bench]] 56 | name = "family" 57 | harness = false 58 | 59 | [[bench]] 60 | name = "text" 61 | path = "benches/encoding/text.rs" 62 | harness = false 63 | required-features = [] 64 | 65 | [[bench]] 66 | name = "proto" 67 | path = "benches/encoding/proto.rs" 68 | harness = false 69 | required-features = ["protobuf"] 70 | 71 | # Passing arguments to the docsrs builder in order to properly document cfg's. 72 | # More information: https://docs.rs/about/builds#cross-compiling 73 | [package.metadata.docs.rs] 74 | all-features = true 75 | -------------------------------------------------------------------------------- /examples/actix-web.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use actix_web::middleware::Compress; 4 | use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; 5 | use prometheus_client::encoding::text::encode; 6 | use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; 7 | use prometheus_client::metrics::counter::Counter; 8 | use prometheus_client::metrics::family::Family; 9 | use prometheus_client::registry::Registry; 10 | 11 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 12 | pub enum Method { 13 | Get, 14 | Post, 15 | } 16 | 17 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 18 | pub struct MethodLabels { 19 | pub method: Method, 20 | } 21 | 22 | pub struct Metrics { 23 | requests: Family, 24 | } 25 | 26 | impl Metrics { 27 | pub fn inc_requests(&self, method: Method) { 28 | self.requests.get_or_create(&MethodLabels { method }).inc(); 29 | } 30 | } 31 | 32 | pub struct AppState { 33 | pub registry: Registry, 34 | } 35 | 36 | pub async fn metrics_handler(state: web::Data>) -> Result { 37 | let state = state.lock().unwrap(); 38 | let mut body = String::new(); 39 | encode(&mut body, &state.registry).unwrap(); 40 | Ok(HttpResponse::Ok() 41 | .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") 42 | .body(body)) 43 | } 44 | 45 | pub async fn some_handler(metrics: web::Data) -> impl Responder { 46 | metrics.inc_requests(Method::Get); 47 | "okay".to_string() 48 | } 49 | 50 | #[actix_web::main] 51 | async fn main() -> std::io::Result<()> { 52 | let metrics = web::Data::new(Metrics { 53 | requests: Family::default(), 54 | }); 55 | let mut state = AppState { 56 | registry: Registry::default(), 57 | }; 58 | state 59 | .registry 60 | .register("requests", "Count of requests", metrics.requests.clone()); 61 | let state = web::Data::new(Mutex::new(state)); 62 | 63 | HttpServer::new(move || { 64 | App::new() 65 | .wrap(Compress::default()) 66 | .app_data(metrics.clone()) 67 | .app_data(state.clone()) 68 | .service(web::resource("/metrics").route(web::get().to(metrics_handler))) 69 | .service(web::resource("/handler").route(web::get().to(some_handler))) 70 | }) 71 | .bind(("127.0.0.1", 8080))? 72 | .run() 73 | .await 74 | } 75 | -------------------------------------------------------------------------------- /benches/family.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use prometheus_client::metrics::counter::Counter; 3 | use prometheus_client::metrics::family::Family; 4 | 5 | pub fn family(c: &mut Criterion) { 6 | c.bench_function( 7 | "counter family with [(&'static str, &'static str)] label set", 8 | |b| { 9 | let family = Family::<[(&'static str, &'static str); 2], Counter>::default(); 10 | 11 | b.iter(|| { 12 | family 13 | .get_or_create(&[("method", "GET"), ("status", "200")]) 14 | .inc(); 15 | }) 16 | }, 17 | ); 18 | 19 | c.bench_function( 20 | "counter family with Vec<(&'static str, &'static str)> label set", 21 | |b| { 22 | let family = Family::, Counter>::default(); 23 | 24 | b.iter(|| { 25 | family 26 | .get_or_create(&vec![("method", "GET"), ("status", "200")]) 27 | .inc(); 28 | }) 29 | }, 30 | ); 31 | 32 | c.bench_function("counter family with Vec<(String, String)> label set", |b| { 33 | let family = Family::, Counter>::default(); 34 | 35 | b.iter(|| { 36 | family 37 | .get_or_create(&vec![ 38 | ("method".to_owned(), "GET".to_owned()), 39 | ("status".to_owned(), "200".to_owned()), 40 | ]) 41 | .inc(); 42 | }) 43 | }); 44 | 45 | c.bench_function("counter family with custom type label set", |b| { 46 | #[derive(Clone, Hash, PartialEq, Eq)] 47 | struct Labels { 48 | method: Method, 49 | status: Status, 50 | } 51 | 52 | #[derive(Clone, Hash, PartialEq, Eq)] 53 | enum Method { 54 | Get, 55 | #[allow(dead_code)] 56 | Put, 57 | } 58 | 59 | #[derive(Clone, Hash, PartialEq, Eq)] 60 | enum Status { 61 | Two, 62 | #[allow(dead_code)] 63 | Four, 64 | #[allow(dead_code)] 65 | Five, 66 | } 67 | let family = Family::::default(); 68 | 69 | b.iter(|| { 70 | family 71 | .get_or_create(&Labels { 72 | method: Method::Get, 73 | status: Status::Two, 74 | }) 75 | .inc(); 76 | }) 77 | }); 78 | } 79 | 80 | criterion_group!(benches, family); 81 | criterion_main!(benches); 82 | -------------------------------------------------------------------------------- /examples/tide.rs: -------------------------------------------------------------------------------- 1 | use prometheus_client::encoding::EncodeLabelValue; 2 | use prometheus_client::encoding::{text::encode, EncodeLabelSet}; 3 | use prometheus_client::metrics::counter::Counter; 4 | use prometheus_client::metrics::family::Family; 5 | use prometheus_client::registry::Registry; 6 | 7 | use std::sync::Arc; 8 | 9 | use tide::{Middleware, Next, Request, Result}; 10 | 11 | #[async_std::main] 12 | async fn main() -> std::result::Result<(), std::io::Error> { 13 | tide::log::start(); 14 | 15 | let mut registry = Registry::default(); 16 | let http_requests_total = Family::::default(); 17 | registry.register( 18 | "http_requests_total", 19 | "Number of HTTP requests", 20 | http_requests_total.clone(), 21 | ); 22 | 23 | let middleware = MetricsMiddleware { 24 | http_requests_total, 25 | }; 26 | let mut app = tide::with_state(State { 27 | registry: Arc::new(registry), 28 | }); 29 | 30 | app.with(middleware); 31 | app.at("/").get(|_| async { Ok("Hello, world!") }); 32 | app.at("/metrics") 33 | .get(|req: tide::Request| async move { 34 | let mut encoded = String::new(); 35 | encode(&mut encoded, &req.state().registry).unwrap(); 36 | let response = tide::Response::builder(200) 37 | .body(encoded) 38 | .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") 39 | .build(); 40 | Ok(response) 41 | }); 42 | app.listen("127.0.0.1:8080").await?; 43 | 44 | Ok(()) 45 | } 46 | 47 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 48 | struct Labels { 49 | method: Method, 50 | path: String, 51 | } 52 | 53 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 54 | enum Method { 55 | Get, 56 | Put, 57 | } 58 | 59 | #[derive(Clone)] 60 | struct State { 61 | registry: Arc, 62 | } 63 | 64 | #[derive(Default)] 65 | struct MetricsMiddleware { 66 | http_requests_total: Family, 67 | } 68 | 69 | #[tide::utils::async_trait] 70 | impl Middleware for MetricsMiddleware { 71 | async fn handle(&self, req: Request, next: Next<'_, State>) -> Result { 72 | let method = match req.method() { 73 | http_types::Method::Get => Method::Get, 74 | http_types::Method::Put => Method::Put, 75 | _ => todo!(), 76 | }; 77 | let path = req.url().path().to_string(); 78 | let _count = self 79 | .http_requests_total 80 | .get_or_create(&Labels { method, path }) 81 | .inc(); 82 | 83 | let res = next.run(req).await; 84 | Ok(res) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/axum.rs: -------------------------------------------------------------------------------- 1 | use axum::body::Body; 2 | use axum::extract::State; 3 | use axum::http::header::CONTENT_TYPE; 4 | use axum::http::StatusCode; 5 | use axum::response::{IntoResponse, Response}; 6 | use axum::routing::get; 7 | use axum::Router; 8 | use prometheus_client::encoding::text::encode; 9 | use prometheus_client::metrics::counter::Counter; 10 | use prometheus_client::metrics::family::Family; 11 | use prometheus_client::registry::Registry; 12 | use prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue}; 13 | use std::sync::Arc; 14 | use tokio::sync::Mutex; 15 | 16 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 17 | pub enum Method { 18 | Get, 19 | Post, 20 | } 21 | 22 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 23 | pub struct MethodLabels { 24 | pub method: Method, 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct Metrics { 29 | requests: Family, 30 | } 31 | 32 | impl Metrics { 33 | pub fn inc_requests(&self, method: Method) { 34 | self.requests.get_or_create(&MethodLabels { method }).inc(); 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct AppState { 40 | pub registry: Registry, 41 | } 42 | 43 | pub async fn metrics_handler(State(state): State>>) -> impl IntoResponse { 44 | let state = state.lock().await; 45 | let mut buffer = String::new(); 46 | encode(&mut buffer, &state.registry).unwrap(); 47 | 48 | Response::builder() 49 | .status(StatusCode::OK) 50 | .header( 51 | CONTENT_TYPE, 52 | "application/openmetrics-text; version=1.0.0; charset=utf-8", 53 | ) 54 | .body(Body::from(buffer)) 55 | .unwrap() 56 | } 57 | 58 | pub async fn some_handler(State(metrics): State>>) -> impl IntoResponse { 59 | metrics.lock().await.inc_requests(Method::Get); 60 | "okay".to_string() 61 | } 62 | 63 | #[tokio::main] 64 | async fn main() { 65 | let metrics = Metrics { 66 | requests: Family::default(), 67 | }; 68 | let mut state = AppState { 69 | registry: Registry::default(), 70 | }; 71 | state 72 | .registry 73 | .register("requests", "Count of requests", metrics.requests.clone()); 74 | let metrics = Arc::new(Mutex::new(metrics)); 75 | let state = Arc::new(Mutex::new(state)); 76 | 77 | let router = Router::new() 78 | .route("/metrics", get(metrics_handler)) 79 | .with_state(state) 80 | .route("/handler", get(some_handler)) 81 | .with_state(metrics); 82 | let port = 8080; 83 | let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")) 84 | .await 85 | .unwrap(); 86 | 87 | axum::serve(listener, router).await.unwrap(); 88 | } 89 | -------------------------------------------------------------------------------- /benches/encoding/proto.rs: -------------------------------------------------------------------------------- 1 | // Benchmark inspired by 2 | // https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs:write 3 | 4 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 5 | use prometheus_client::encoding::protobuf; 6 | use prometheus_client::metrics::counter::Counter; 7 | use prometheus_client::metrics::family::Family; 8 | use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; 9 | use prometheus_client::registry::Registry; 10 | use prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue}; 11 | 12 | pub fn proto(c: &mut Criterion) { 13 | c.bench_function("encode", |b| { 14 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] 15 | struct CounterLabels { 16 | path: String, 17 | method: Method, 18 | some_number: u64, 19 | } 20 | 21 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] 22 | enum Method { 23 | Get, 24 | #[allow(dead_code)] 25 | Put, 26 | } 27 | 28 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] 29 | struct HistogramLabels { 30 | region: Region, 31 | } 32 | 33 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] 34 | enum Region { 35 | Africa, 36 | #[allow(dead_code)] 37 | Asia, 38 | } 39 | 40 | let mut registry = Registry::default(); 41 | 42 | for i in 0..100 { 43 | let counter_family = Family::::default(); 44 | let histogram_family = 45 | Family::::new_with_constructor(|| { 46 | Histogram::new(exponential_buckets(1.0, 2.0, 10)) 47 | }); 48 | 49 | registry.register( 50 | format!("my_counter{i}"), 51 | "My counter", 52 | counter_family.clone(), 53 | ); 54 | registry.register( 55 | format!("my_histogram{i}"), 56 | "My histogram", 57 | histogram_family.clone(), 58 | ); 59 | 60 | for j in 0_u32..100 { 61 | counter_family 62 | .get_or_create(&CounterLabels { 63 | path: format!("/path/{i}"), 64 | method: Method::Get, 65 | some_number: j.into(), 66 | }) 67 | .inc(); 68 | 69 | histogram_family 70 | .get_or_create(&HistogramLabels { 71 | region: Region::Africa, 72 | }) 73 | .observe(j.into()); 74 | } 75 | } 76 | 77 | b.iter(|| { 78 | let metric_set = protobuf::encode(®istry).unwrap(); 79 | black_box(metric_set); 80 | }) 81 | }); 82 | } 83 | 84 | criterion_group!(benches, proto); 85 | criterion_main!(benches); 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prometheus Rust client library 2 | 3 | [![Test Status](https://github.com/prometheus/client_rust/actions/workflows/rust.yml/badge.svg?event=push)](https://github.com/prometheus/client_rust/actions) 4 | [![Crate](https://img.shields.io/crates/v/prometheus-client.svg)](https://crates.io/crates/prometheus-client) 5 | [![API](https://docs.rs/prometheus-client/badge.svg)](https://docs.rs/prometheus-client) 6 | 7 | [Rust](https://github.com/rust-lang/) client library implementation of the [Open 8 | Metrics specification](https://github.com/OpenObservability/OpenMetrics). Allows 9 | developers to instrument applications and thus enables operators to monitor said 10 | applications with monitoring systems like [Prometheus](https://prometheus.io/). 11 | 12 | **Documentation**: https://docs.rs/prometheus-client/ 13 | 14 | ## Goals 15 | 16 | - No `unsafe`. Don't use unsafe Rust within the library itself. 17 | 18 | - Type safe. Leverage Rust's type system to catch common instrumentation 19 | mistakes at compile time. 20 | 21 | - Fast. Don't force users to worry about the performance impact of 22 | instrumentation. Instead encourage users to instrument often and extensively. 23 | 24 | ## Specification Compliance 25 | 26 | Below is a list of properties where this client library implementation lags 27 | behind the Open Metrics specification. Not being compliant with all requirements 28 | (`MUST` and `MUST NOT`) of the specification is considered a bug and likely to 29 | be fixed in the future. Contributions in all forms are most welcome. 30 | 31 | - State set metric. 32 | 33 | - Enforce "A Histogram MetricPoint MUST contain at least one bucket". 34 | 35 | - Enforce "A MetricFamily MUST have a [...] UNIT metadata". 36 | 37 | - Enforce "MetricFamily names [...] MUST be unique within a MetricSet." 38 | 39 | - Enforce "Names SHOULD be in snake_case". 40 | 41 | - Enforce "MetricFamily names beginning with underscores are RESERVED and MUST 42 | NOT be used unless specified by this standard". 43 | 44 | - Enforce "Exposers SHOULD avoid names that could be confused with the suffixes 45 | that text format sample metric names use". 46 | 47 | - Gauge histogram metric. 48 | 49 | - Allow "A MetricPoint in a Metric with the type [Counter, Histogram] SHOULD have a Timestamp 50 | value called Created". 51 | 52 | - Summary metric. 53 | 54 | ## Related Libraries 55 | 56 | - [rust-prometheus](https://github.com/tikv/rust-prometheus/): See [tikv/rust-prometheus/#392](https://github.com/tikv/rust-prometheus/issues/392) for a high-level comparison. 57 | 58 | ## License 59 | 60 | Licensed under either of 61 | 62 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 63 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 64 | 65 | at your option. 66 | 67 | #### Contribution 68 | 69 | Unless you explicitly state otherwise, any contribution intentionally submitted 70 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 71 | dual licensed as above, without any additional terms or conditions. 72 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(dead_code)] 2 | #![deny(missing_docs)] 3 | #![deny(unused)] 4 | #![forbid(unsafe_code)] 5 | #![warn(missing_debug_implementations)] 6 | #![cfg_attr(docsrs, feature(doc_cfg))] 7 | 8 | //! Client library implementation of the [Open Metrics 9 | //! specification](https://github.com/OpenObservability/OpenMetrics). Allows 10 | //! developers to instrument applications and thus enables operators to monitor 11 | //! said applications with monitoring systems like 12 | //! [Prometheus](https://prometheus.io/). 13 | //! 14 | //! # Examples 15 | //! 16 | //! ``` 17 | //! use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; 18 | //! use prometheus_client::encoding::text::encode; 19 | //! use prometheus_client::metrics::counter::{Atomic, Counter}; 20 | //! use prometheus_client::metrics::family::Family; 21 | //! use prometheus_client::registry::Registry; 22 | //! use std::io::Write; 23 | //! 24 | //! // Create a metric registry. 25 | //! // 26 | //! // Note the angle brackets to make sure to use the default (dynamic 27 | //! // dispatched boxed metric) for the generic type parameter. 28 | //! let mut registry = ::default(); 29 | //! 30 | //! // Define a type representing a metric label set, i.e. a key value pair. 31 | //! // 32 | //! // You could as well use `(String, String)` to represent a label set, 33 | //! // instead of the custom type below. 34 | //! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 35 | //! struct Labels { 36 | //! // Use your own enum types to represent label values. 37 | //! method: Method, 38 | //! // Or just a plain string. 39 | //! path: String, 40 | //! }; 41 | //! 42 | //! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 43 | //! enum Method { 44 | //! GET, 45 | //! PUT, 46 | //! }; 47 | //! 48 | //! // Create a sample counter metric family utilizing the above custom label 49 | //! // type, representing the number of HTTP requests received. 50 | //! let http_requests = Family::::default(); 51 | //! 52 | //! // Register the metric family with the registry. 53 | //! registry.register( 54 | //! // With the metric name. 55 | //! "http_requests", 56 | //! // And the metric help text. 57 | //! "Number of HTTP requests received", 58 | //! http_requests.clone(), 59 | //! ); 60 | //! 61 | //! // Somewhere in your business logic record a single HTTP GET request. 62 | //! http_requests.get_or_create( 63 | //! &Labels { method: Method::GET, path: "/metrics".to_string() } 64 | //! ).inc(); 65 | //! 66 | //! // When a monitoring system like Prometheus scrapes the local node, encode 67 | //! // all metrics in the registry in the text format, and send the encoded 68 | //! // metrics back. 69 | //! let mut buffer = String::new(); 70 | //! encode(&mut buffer, ®istry).unwrap(); 71 | //! 72 | //! let expected = "# HELP http_requests Number of HTTP requests received.\n".to_owned() + 73 | //! "# TYPE http_requests counter\n" + 74 | //! "http_requests_total{method=\"GET\",path=\"/metrics\"} 1\n" + 75 | //! "# EOF\n"; 76 | //! assert_eq!(expected, buffer); 77 | //! ``` 78 | //! See [examples] directory for more. 79 | //! 80 | //! [examples]: https://github.com/prometheus/client_rust/tree/master/examples 81 | 82 | pub mod collector; 83 | pub mod encoding; 84 | pub mod metrics; 85 | pub mod registry; 86 | -------------------------------------------------------------------------------- /benches/encoding/text.rs: -------------------------------------------------------------------------------- 1 | // Benchmark inspired by https://github.com/tikv/rust-prometheus/blob/ab1ca7285d3463504381a5025ae1951e020d6796/benches/text_encoder.rs 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | use prometheus_client::encoding::{self, EncodeLabelSet, EncodeLabelValue, LabelValueEncoder}; 5 | use prometheus_client::metrics::counter::Counter; 6 | use prometheus_client::metrics::family::Family; 7 | use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; 8 | use prometheus_client::registry::Registry; 9 | use std::fmt::Write; 10 | 11 | pub fn text(c: &mut Criterion) { 12 | c.bench_function("encode", |b| { 13 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] 14 | struct Labels { 15 | method: Method, 16 | status: Status, 17 | some_number: u64, 18 | } 19 | 20 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] 21 | enum Method { 22 | Get, 23 | #[allow(dead_code)] 24 | Put, 25 | } 26 | 27 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] 28 | enum Status { 29 | Two, 30 | #[allow(dead_code)] 31 | Four, 32 | #[allow(dead_code)] 33 | Five, 34 | } 35 | 36 | impl prometheus_client::encoding::EncodeLabelValue for Status { 37 | fn encode(&self, writer: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 38 | let status = match self { 39 | Status::Two => "200", 40 | Status::Four => "400", 41 | Status::Five => "500", 42 | }; 43 | writer.write_str(status)?; 44 | Ok(()) 45 | } 46 | } 47 | 48 | let mut registry = Registry::default(); 49 | 50 | for i in 0..100 { 51 | let counter_family = Family::::default(); 52 | let histogram_family = Family::::new_with_constructor(|| { 53 | Histogram::new(exponential_buckets(1.0, 2.0, 10)) 54 | }); 55 | 56 | registry.register( 57 | format!("my_counter_{i}"), 58 | "My counter", 59 | counter_family.clone(), 60 | ); 61 | registry.register( 62 | format!("my_histogram_{i}"), 63 | "My histogram", 64 | histogram_family.clone(), 65 | ); 66 | 67 | for j in 0u32..100 { 68 | counter_family 69 | .get_or_create(&Labels { 70 | method: Method::Get, 71 | status: Status::Two, 72 | some_number: j.into(), 73 | }) 74 | .inc(); 75 | histogram_family 76 | .get_or_create(&Labels { 77 | method: Method::Get, 78 | status: Status::Two, 79 | some_number: j.into(), 80 | }) 81 | .observe(j.into()); 82 | } 83 | } 84 | 85 | let mut buffer = String::new(); 86 | 87 | b.iter(|| { 88 | encoding::text::encode(&mut buffer, ®istry).unwrap(); 89 | black_box(&mut buffer); 90 | }) 91 | }); 92 | } 93 | 94 | criterion_group!(benches, text); 95 | criterion_main!(benches); 96 | -------------------------------------------------------------------------------- /examples/hyper.rs: -------------------------------------------------------------------------------- 1 | use futures::future::BoxFuture; 2 | use http_body_util::{combinators, BodyExt, Full}; 3 | use hyper::{ 4 | body::{Bytes, Incoming}, 5 | server::conn::http1, 6 | service::service_fn, 7 | Request, Response, 8 | }; 9 | use hyper_util::rt::TokioIo; 10 | use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry}; 11 | use std::{ 12 | io, 13 | net::{IpAddr, Ipv4Addr, SocketAddr}, 14 | sync::Arc, 15 | }; 16 | use tokio::{ 17 | net::TcpListener, 18 | pin, 19 | signal::unix::{signal, SignalKind}, 20 | }; 21 | 22 | #[tokio::main] 23 | async fn main() { 24 | let request_counter: Counter = Default::default(); 25 | 26 | let mut registry = ::with_prefix("tokio_hyper_example"); 27 | 28 | registry.register( 29 | "requests", 30 | "How many requests the application has received", 31 | request_counter.clone(), 32 | ); 33 | 34 | // Spawn a server to serve the OpenMetrics endpoint. 35 | let metrics_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8001); 36 | start_metrics_server(metrics_addr, registry).await 37 | } 38 | 39 | /// Start a HTTP server to report metrics. 40 | pub async fn start_metrics_server(metrics_addr: SocketAddr, registry: Registry) { 41 | eprintln!("Starting metrics server on {metrics_addr}"); 42 | 43 | let registry = Arc::new(registry); 44 | 45 | let tcp_listener = TcpListener::bind(metrics_addr).await.unwrap(); 46 | let server = http1::Builder::new(); 47 | while let Ok((stream, _)) = tcp_listener.accept().await { 48 | let mut shutdown_stream = signal(SignalKind::terminate()).unwrap(); 49 | let io = TokioIo::new(stream); 50 | let server_clone = server.clone(); 51 | let registry_clone = registry.clone(); 52 | tokio::task::spawn(async move { 53 | let conn = server_clone.serve_connection(io, service_fn(make_handler(registry_clone))); 54 | pin!(conn); 55 | tokio::select! { 56 | _ = conn.as_mut() => {} 57 | _ = shutdown_stream.recv() => { 58 | conn.as_mut().graceful_shutdown(); 59 | } 60 | } 61 | }); 62 | } 63 | } 64 | 65 | /// Boxed HTTP body for responses 66 | type BoxBody = combinators::BoxBody; 67 | 68 | /// This function returns a HTTP handler (i.e. another function) 69 | pub fn make_handler( 70 | registry: Arc, 71 | ) -> impl Fn(Request) -> BoxFuture<'static, io::Result>> { 72 | // This closure accepts a request and responds with the OpenMetrics encoding of our metrics. 73 | move |_req: Request| { 74 | let reg = registry.clone(); 75 | 76 | Box::pin(async move { 77 | let mut buf = String::new(); 78 | encode(&mut buf, ®.clone()) 79 | .map_err(std::io::Error::other) 80 | .map(|_| { 81 | let body = full(Bytes::from(buf)); 82 | Response::builder() 83 | .header( 84 | hyper::header::CONTENT_TYPE, 85 | "application/openmetrics-text; version=1.0.0; charset=utf-8", 86 | ) 87 | .body(body) 88 | .unwrap() 89 | }) 90 | }) 91 | } 92 | } 93 | 94 | /// helper function to build a full boxed body 95 | pub fn full(body: Bytes) -> BoxBody { 96 | Full::new(body).map_err(|never| match never {}).boxed() 97 | } 98 | -------------------------------------------------------------------------------- /src/encoding/proto/openmetrics_data_model.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // The OpenMetrics protobuf schema which defines the protobuf wire format. 4 | // Ensure to interpret "required" as semantically required for a valid message. 5 | // All string fields MUST be UTF-8 encoded strings. 6 | package openmetrics; 7 | 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | // The top-level container type that is encoded and sent over the wire. 11 | message MetricSet { 12 | // Each MetricFamily has one or more MetricPoints for a single Metric. 13 | repeated MetricFamily metric_families = 1; 14 | } 15 | 16 | // One or more Metrics for a single MetricFamily, where each Metric 17 | // has one or more MetricPoints. 18 | message MetricFamily { 19 | // Required. 20 | string name = 1; 21 | 22 | // Optional. 23 | MetricType type = 2; 24 | 25 | // Optional. 26 | string unit = 3; 27 | 28 | // Optional. 29 | string help = 4; 30 | 31 | // Optional. 32 | repeated Metric metrics = 5; 33 | } 34 | 35 | // The type of a Metric. 36 | enum MetricType { 37 | // Unknown must use unknown MetricPoint values. 38 | UNKNOWN = 0; 39 | // Gauge must use gauge MetricPoint values. 40 | GAUGE = 1; 41 | // Counter must use counter MetricPoint values. 42 | COUNTER = 2; 43 | // State set must use state set MetricPoint values. 44 | STATE_SET = 3; 45 | // Info must use info MetricPoint values. 46 | INFO = 4; 47 | // Histogram must use histogram value MetricPoint values. 48 | HISTOGRAM = 5; 49 | // Gauge histogram must use histogram value MetricPoint values. 50 | GAUGE_HISTOGRAM = 6; 51 | // Summary quantiles must use summary value MetricPoint values. 52 | SUMMARY = 7; 53 | } 54 | 55 | // A single metric with a unique set of labels within a metric family. 56 | message Metric { 57 | // Optional. 58 | repeated Label labels = 1; 59 | 60 | // Optional. 61 | repeated MetricPoint metric_points = 2; 62 | } 63 | 64 | // A name-value pair. These are used in multiple places: identifying 65 | // timeseries, value of INFO metrics, and exemplars in Histograms. 66 | message Label { 67 | // Required. 68 | string name = 1; 69 | 70 | // Required. 71 | string value = 2; 72 | } 73 | 74 | // A MetricPoint in a Metric. 75 | message MetricPoint { 76 | // Required. 77 | oneof value { 78 | UnknownValue unknown_value = 1; 79 | GaugeValue gauge_value = 2; 80 | CounterValue counter_value = 3; 81 | HistogramValue histogram_value = 4; 82 | StateSetValue state_set_value = 5; 83 | InfoValue info_value = 6; 84 | SummaryValue summary_value = 7; 85 | } 86 | 87 | // Optional. 88 | google.protobuf.Timestamp timestamp = 8; 89 | } 90 | 91 | // Value for UNKNOWN MetricPoint. 92 | message UnknownValue { 93 | // Required. 94 | oneof value { 95 | double double_value = 1; 96 | int64 int_value = 2; 97 | } 98 | } 99 | 100 | // Value for GAUGE MetricPoint. 101 | message GaugeValue { 102 | // Required. 103 | oneof value { 104 | double double_value = 1; 105 | int64 int_value = 2; 106 | } 107 | } 108 | 109 | // Value for COUNTER MetricPoint. 110 | message CounterValue { 111 | // Required. 112 | oneof total { 113 | double double_value = 1; 114 | uint64 int_value = 2; 115 | } 116 | 117 | // The time values began being collected for this counter. 118 | // Optional. 119 | google.protobuf.Timestamp created = 3; 120 | 121 | // Optional. 122 | Exemplar exemplar = 4; 123 | } 124 | 125 | // Value for HISTOGRAM or GAUGE_HISTOGRAM MetricPoint. 126 | message HistogramValue { 127 | // Optional. 128 | oneof sum { 129 | double double_value = 1; 130 | int64 int_value = 2; 131 | } 132 | 133 | // Optional. 134 | uint64 count = 3; 135 | 136 | // The time values began being collected for this histogram. 137 | // Optional. 138 | google.protobuf.Timestamp created = 4; 139 | 140 | // Optional. 141 | repeated Bucket buckets = 5; 142 | 143 | // Bucket is the number of values for a bucket in the histogram 144 | // with an optional exemplar. 145 | message Bucket { 146 | // Required. 147 | uint64 count = 1; 148 | 149 | // Optional. 150 | double upper_bound = 2; 151 | 152 | // Optional. 153 | Exemplar exemplar = 3; 154 | } 155 | } 156 | 157 | message Exemplar { 158 | // Required. 159 | double value = 1; 160 | 161 | // Optional. 162 | google.protobuf.Timestamp timestamp = 2; 163 | 164 | // Labels are additional information about the exemplar value (e.g. trace id). 165 | // Optional. 166 | repeated Label label = 3; 167 | } 168 | 169 | // Value for STATE_SET MetricPoint. 170 | message StateSetValue { 171 | // Optional. 172 | repeated State states = 1; 173 | 174 | message State { 175 | // Required. 176 | bool enabled = 1; 177 | 178 | // Required. 179 | string name = 2; 180 | } 181 | } 182 | 183 | // Value for INFO MetricPoint. 184 | message InfoValue { 185 | // Optional. 186 | repeated Label info = 1; 187 | } 188 | 189 | // Value for SUMMARY MetricPoint. 190 | message SummaryValue { 191 | // Optional. 192 | oneof sum { 193 | double double_value = 1; 194 | int64 int_value = 2; 195 | } 196 | 197 | // Optional. 198 | uint64 count = 3; 199 | 200 | // The time sum and count values began being collected for this summary. 201 | // Optional. 202 | google.protobuf.Timestamp created = 4; 203 | 204 | // Optional. 205 | repeated Quantile quantile = 5; 206 | 207 | message Quantile { 208 | // Required. 209 | double quantile = 1; 210 | 211 | // Required. 212 | double value = 2; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install Protoc 11 | uses: arduino/setup-protoc@v1 12 | with: 13 | repo-token: ${{ secrets.GITHUB_TOKEN }} 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | 21 | # Install prometheus_client python library. 22 | - uses: actions/setup-python@v2 23 | with: 24 | python-version: '3.x' 25 | - run: pip3 install prometheus_client 26 | 27 | # Caching 28 | - name: Cache cargo registry 29 | uses: actions/cache@v4 30 | with: 31 | path: ~/.cargo/registry 32 | key: cargo-registry-${{ hashFiles('Cargo.toml') }} 33 | - name: Cache cargo index 34 | uses: actions/cache@v4 35 | with: 36 | path: ~/.cargo/git 37 | key: cargo-index-${{ hashFiles('Cargo.toml') }} 38 | 39 | - uses: actions-rs/cargo@v1 40 | with: 41 | command: check 42 | 43 | - uses: actions-rs/cargo@v1 44 | with: 45 | command: test 46 | args: --benches --all-features 47 | 48 | test: 49 | name: Test Suite 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Install Protoc 53 | uses: arduino/setup-protoc@v1 54 | with: 55 | repo-token: ${{ secrets.GITHUB_TOKEN }} 56 | - uses: actions/checkout@v2 57 | - uses: actions-rs/toolchain@v1 58 | with: 59 | profile: minimal 60 | toolchain: stable 61 | override: true 62 | 63 | # Install prometheus_client python library. 64 | - uses: actions/setup-python@v2 65 | with: 66 | python-version: '3.x' 67 | - run: pip3 install prometheus_client 68 | 69 | # Caching 70 | - name: Cache cargo registry 71 | uses: actions/cache@v4 72 | with: 73 | path: ~/.cargo/registry 74 | key: cargo-registry-${{ hashFiles('Cargo.toml') }} 75 | - name: Cache cargo index 76 | uses: actions/cache@v4 77 | with: 78 | path: ~/.cargo/git 79 | key: cargo-index-${{ hashFiles('Cargo.toml') }} 80 | 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | command: test 84 | args: --all --all-features 85 | 86 | fmt: 87 | name: Rustfmt 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Install Protoc 91 | uses: arduino/setup-protoc@v1 92 | with: 93 | repo-token: ${{ secrets.GITHUB_TOKEN }} 94 | - uses: actions/checkout@v2 95 | - uses: actions-rs/toolchain@v1 96 | with: 97 | profile: minimal 98 | toolchain: stable 99 | override: true 100 | - run: rustup component add rustfmt 101 | - uses: actions-rs/cargo@v1 102 | with: 103 | command: fmt 104 | args: --all -- --check 105 | 106 | clippy: 107 | name: Clippy 108 | runs-on: ubuntu-latest 109 | steps: 110 | - name: Install Protoc 111 | uses: arduino/setup-protoc@v1 112 | with: 113 | repo-token: ${{ secrets.GITHUB_TOKEN }} 114 | - uses: actions/checkout@v2 115 | - uses: actions-rs/toolchain@v1 116 | with: 117 | profile: minimal 118 | toolchain: stable 119 | override: true 120 | - run: rustup component add clippy 121 | 122 | # Caching 123 | - name: Cache cargo registry 124 | uses: actions/cache@v4 125 | with: 126 | path: ~/.cargo/registry 127 | key: cargo-registry-${{ hashFiles('Cargo.toml') }} 128 | - name: Cache cargo index 129 | uses: actions/cache@v4 130 | with: 131 | path: ~/.cargo/git 132 | key: cargo-index-${{ hashFiles('Cargo.toml') }} 133 | 134 | - uses: actions-rs/cargo@v1 135 | with: 136 | command: clippy 137 | args: --workspace --all-targets -- -D warnings 138 | 139 | check-rustdoc-links: 140 | name: Check rustdoc intra-doc links 141 | runs-on: ubuntu-latest 142 | steps: 143 | - name: Install Protoc 144 | uses: arduino/setup-protoc@v1 145 | with: 146 | repo-token: ${{ secrets.GITHUB_TOKEN }} 147 | - uses: actions/checkout@v2 148 | - uses: actions-rs/toolchain@v1 149 | with: 150 | profile: minimal 151 | toolchain: stable 152 | override: true 153 | - uses: actions-rs/cargo@v1 154 | env: 155 | RUSTDOCFLAGS: "--deny broken_intra_doc_links" 156 | with: 157 | command: doc 158 | args: --verbose --workspace --no-deps --document-private-items --all-features 159 | 160 | cross-compile: 161 | name: Cross compile 162 | runs-on: ubuntu-latest 163 | strategy: 164 | matrix: 165 | target: 166 | - armv7-unknown-linux-gnueabihf 167 | - powerpc-unknown-linux-gnu 168 | - powerpc64-unknown-linux-gnu 169 | - wasm32-unknown-unknown 170 | steps: 171 | - uses: actions/checkout@v2 172 | 173 | - uses: dtolnay/rust-toolchain@stable 174 | with: 175 | target: ${{ matrix.target }} 176 | 177 | # Note that this does not test the `protobuf` feature (for now). See reasoning in https://github.com/prometheus/client_rust/pull/98/. 178 | - run: cargo check --target=${{ matrix.target }} 179 | -------------------------------------------------------------------------------- /derive-encode/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(dead_code)] 2 | #![deny(missing_docs)] 3 | #![deny(unused)] 4 | #![forbid(unsafe_code)] 5 | #![warn(missing_debug_implementations)] 6 | 7 | //! Derive crate for `prometheus_client`. 8 | 9 | use proc_macro::TokenStream; 10 | use proc_macro2::TokenStream as TokenStream2; 11 | use quote::quote; 12 | use syn::DeriveInput; 13 | 14 | /// Derive `prometheus_client::encoding::EncodeLabelSet`. 15 | #[proc_macro_derive(EncodeLabelSet, attributes(prometheus))] 16 | pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { 17 | let ast: DeriveInput = syn::parse(input).unwrap(); 18 | let name = &ast.ident; 19 | 20 | let body: TokenStream2 = match ast.clone().data { 21 | syn::Data::Struct(s) => match s.fields { 22 | syn::Fields::Named(syn::FieldsNamed { named, .. }) => named 23 | .into_iter() 24 | .map(|f| { 25 | let attribute = f 26 | .attrs 27 | .iter() 28 | .find(|a| a.path().is_ident("prometheus")) 29 | .map(|a| a.parse_args::().unwrap().to_string()); 30 | let flatten = match attribute.as_deref() { 31 | Some("flatten") => true, 32 | Some(other) => { 33 | panic!("Provided attribute '{other}', but only 'flatten' is supported") 34 | } 35 | None => false, 36 | }; 37 | let ident = f.ident.unwrap(); 38 | if flatten { 39 | quote! { 40 | EncodeLabelSet::encode(&self.#ident, encoder)?; 41 | } 42 | } else { 43 | let ident_string = KEYWORD_IDENTIFIERS 44 | .iter() 45 | .find(|pair| ident == pair.1) 46 | .map(|pair| pair.0.to_string()) 47 | .unwrap_or_else(|| ident.to_string()); 48 | 49 | quote! { 50 | let mut label_encoder = encoder.encode_label(); 51 | let mut label_key_encoder = label_encoder.encode_label_key()?; 52 | EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; 53 | 54 | let mut label_value_encoder = label_key_encoder.encode_label_value()?; 55 | EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; 56 | 57 | label_value_encoder.finish()?; 58 | } 59 | } 60 | }) 61 | .collect(), 62 | syn::Fields::Unnamed(_) => { 63 | panic!("Can not derive Encode for struct with unnamed fields.") 64 | } 65 | syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."), 66 | }, 67 | syn::Data::Enum(syn::DataEnum { .. }) => { 68 | panic!("Can not derive Encode for enum.") 69 | } 70 | syn::Data::Union(_) => panic!("Can not derive Encode for union."), 71 | }; 72 | 73 | let gen = quote! { 74 | impl ::prometheus_client::encoding::EncodeLabelSet for #name { 75 | fn encode(&self, encoder: &mut ::prometheus_client::encoding::LabelSetEncoder) -> ::core::result::Result<(), ::core::fmt::Error> { 76 | use ::prometheus_client::encoding::EncodeLabel; 77 | use ::prometheus_client::encoding::EncodeLabelKey; 78 | use ::prometheus_client::encoding::EncodeLabelValue; 79 | 80 | #body 81 | 82 | ::core::result::Result::Ok(()) 83 | } 84 | } 85 | }; 86 | 87 | gen.into() 88 | } 89 | 90 | /// Derive `prometheus_client::encoding::EncodeLabelValue`. 91 | #[proc_macro_derive(EncodeLabelValue)] 92 | pub fn derive_encode_label_value(input: TokenStream) -> TokenStream { 93 | let ast: DeriveInput = syn::parse(input).unwrap(); 94 | let name = &ast.ident; 95 | 96 | let body = match ast.clone().data { 97 | syn::Data::Struct(_) => { 98 | panic!("Can not derive EncodeLabel for struct.") 99 | } 100 | syn::Data::Enum(syn::DataEnum { variants, .. }) => { 101 | let match_arms: TokenStream2 = variants 102 | .into_iter() 103 | .map(|v| { 104 | let ident = v.ident; 105 | quote! { 106 | #name::#ident => encoder.write_str(stringify!(#ident))?, 107 | } 108 | }) 109 | .collect(); 110 | 111 | quote! { 112 | match self { 113 | #match_arms 114 | } 115 | } 116 | } 117 | syn::Data::Union(_) => panic!("Can not derive Encode for union."), 118 | }; 119 | 120 | let gen = quote! { 121 | impl ::prometheus_client::encoding::EncodeLabelValue for #name { 122 | fn encode(&self, encoder: &mut ::prometheus_client::encoding::LabelValueEncoder) -> ::core::result::Result<(), ::core::fmt::Error> { 123 | use ::core::fmt::Write; 124 | 125 | #body 126 | 127 | ::core::result::Result::Ok(()) 128 | } 129 | } 130 | }; 131 | 132 | gen.into() 133 | } 134 | 135 | // Copied from https://github.com/djc/askama (MIT and APACHE licensed) and 136 | // modified. 137 | static KEYWORD_IDENTIFIERS: [(&str, &str); 48] = [ 138 | ("as", "r#as"), 139 | ("break", "r#break"), 140 | ("const", "r#const"), 141 | ("continue", "r#continue"), 142 | ("crate", "r#crate"), 143 | ("else", "r#else"), 144 | ("enum", "r#enum"), 145 | ("extern", "r#extern"), 146 | ("false", "r#false"), 147 | ("fn", "r#fn"), 148 | ("for", "r#for"), 149 | ("if", "r#if"), 150 | ("impl", "r#impl"), 151 | ("in", "r#in"), 152 | ("let", "r#let"), 153 | ("loop", "r#loop"), 154 | ("match", "r#match"), 155 | ("mod", "r#mod"), 156 | ("move", "r#move"), 157 | ("mut", "r#mut"), 158 | ("pub", "r#pub"), 159 | ("ref", "r#ref"), 160 | ("return", "r#return"), 161 | ("static", "r#static"), 162 | ("struct", "r#struct"), 163 | ("trait", "r#trait"), 164 | ("true", "r#true"), 165 | ("type", "r#type"), 166 | ("unsafe", "r#unsafe"), 167 | ("use", "r#use"), 168 | ("where", "r#where"), 169 | ("while", "r#while"), 170 | ("async", "r#async"), 171 | ("await", "r#await"), 172 | ("dyn", "r#dyn"), 173 | ("abstract", "r#abstract"), 174 | ("become", "r#become"), 175 | ("box", "r#box"), 176 | ("do", "r#do"), 177 | ("final", "r#final"), 178 | ("macro", "r#macro"), 179 | ("override", "r#override"), 180 | ("priv", "r#priv"), 181 | ("typeof", "r#typeof"), 182 | ("unsized", "r#unsized"), 183 | ("virtual", "r#virtual"), 184 | ("yield", "r#yield"), 185 | ("try", "r#try"), 186 | ]; 187 | -------------------------------------------------------------------------------- /src/metrics/histogram.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics histogram. 2 | //! 3 | //! See [`Histogram`] for details. 4 | 5 | use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet}; 6 | 7 | use super::{MetricType, TypedMetric}; 8 | use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; 9 | use std::iter::{self, once}; 10 | use std::sync::Arc; 11 | 12 | /// Open Metrics [`Histogram`] to measure distributions of discrete events. 13 | /// 14 | /// ``` 15 | /// # use prometheus_client::metrics::histogram::{Histogram, exponential_buckets}; 16 | /// let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); 17 | /// histogram.observe(4.2); 18 | /// ``` 19 | /// 20 | /// [`Histogram`] does not implement [`Default`], given that the choice of 21 | /// bucket values depends on the situation [`Histogram`] is used in. As an 22 | /// example, to measure HTTP request latency, the values suggested in the 23 | /// Golang implementation might work for you: 24 | /// 25 | /// ``` 26 | /// # use prometheus_client::metrics::histogram::Histogram; 27 | /// // Default values from go client(https://github.com/prometheus/client_golang/blob/5d584e2717ef525673736d72cd1d12e304f243d7/prometheus/histogram.go#L68) 28 | /// let custom_buckets = [ 29 | /// 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30 | /// ]; 31 | /// let histogram = Histogram::new(custom_buckets); 32 | /// histogram.observe(4.2); 33 | /// ``` 34 | // TODO: Consider using atomics. See 35 | // https://github.com/tikv/rust-prometheus/pull/314. 36 | #[derive(Debug)] 37 | pub struct Histogram { 38 | inner: Arc>, 39 | } 40 | 41 | impl Clone for Histogram { 42 | fn clone(&self) -> Self { 43 | Histogram { 44 | inner: self.inner.clone(), 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | pub(crate) struct Inner { 51 | // TODO: Consider allowing integer observe values. 52 | sum: f64, 53 | count: u64, 54 | // TODO: Consider being generic over the bucket length. 55 | buckets: Vec<(f64, u64)>, 56 | } 57 | 58 | impl Histogram { 59 | /// Create a new [`Histogram`]. 60 | /// 61 | /// ```rust 62 | /// # use prometheus_client::metrics::histogram::Histogram; 63 | /// let histogram = Histogram::new([10.0, 100.0, 1_000.0]); 64 | /// ``` 65 | pub fn new(buckets: impl IntoIterator) -> Self { 66 | Self { 67 | inner: Arc::new(RwLock::new(Inner { 68 | sum: Default::default(), 69 | count: Default::default(), 70 | buckets: buckets 71 | .into_iter() 72 | .chain(once(f64::MAX)) 73 | .map(|upper_bound| (upper_bound, 0)) 74 | .collect(), 75 | })), 76 | } 77 | } 78 | 79 | /// Observe the given value. 80 | pub fn observe(&self, v: f64) { 81 | self.observe_and_bucket(v); 82 | } 83 | 84 | /// Observes the given value, returning the index of the first bucket the 85 | /// value is added to. 86 | /// 87 | /// Needed in 88 | /// [`HistogramWithExemplars`](crate::metrics::exemplar::HistogramWithExemplars). 89 | pub(crate) fn observe_and_bucket(&self, v: f64) -> Option { 90 | let mut inner = self.inner.write(); 91 | inner.sum += v; 92 | inner.count += 1; 93 | 94 | let first_bucket = inner 95 | .buckets 96 | .iter_mut() 97 | .enumerate() 98 | .find(|(_i, (upper_bound, _value))| upper_bound >= &v); 99 | 100 | match first_bucket { 101 | Some((i, (_upper_bound, value))) => { 102 | *value += 1; 103 | Some(i) 104 | } 105 | None => None, 106 | } 107 | } 108 | 109 | pub(crate) fn get(&self) -> (f64, u64, MappedRwLockReadGuard<'_, Vec<(f64, u64)>>) { 110 | let inner = self.inner.read(); 111 | let sum = inner.sum; 112 | let count = inner.count; 113 | let buckets = RwLockReadGuard::map(inner, |inner| &inner.buckets); 114 | (sum, count, buckets) 115 | } 116 | } 117 | 118 | impl TypedMetric for Histogram { 119 | const TYPE: MetricType = MetricType::Histogram; 120 | } 121 | 122 | /// Exponential bucket distribution. 123 | pub fn exponential_buckets(start: f64, factor: f64, length: u16) -> impl Iterator { 124 | iter::repeat(()) 125 | .enumerate() 126 | .map(move |(i, _)| start * factor.powf(i as f64)) 127 | .take(length.into()) 128 | } 129 | 130 | /// Exponential bucket distribution within a range 131 | /// 132 | /// Creates `length` buckets, where the lowest bucket is `min` and the highest bucket is `max`. 133 | /// 134 | /// If `length` is less than 1, or `min` is less than or equal to 0, an empty iterator is returned. 135 | pub fn exponential_buckets_range(min: f64, max: f64, length: u16) -> impl Iterator { 136 | let mut len_observed = length; 137 | let mut min_bucket = min; 138 | // length needs a positive length and min needs to be greater than 0 139 | // set len_observed to 0 and min_bucket to 1.0 140 | // this will return an empty iterator in the result 141 | if length < 1 || min <= 0.0 { 142 | len_observed = 0; 143 | min_bucket = 1.0; 144 | } 145 | // We know max/min and highest bucket. Solve for growth_factor. 146 | let growth_factor = (max / min_bucket).powf(1.0 / (len_observed as f64 - 1.0)); 147 | 148 | iter::repeat(()) 149 | .enumerate() 150 | .map(move |(i, _)| min_bucket * growth_factor.powf(i as f64)) 151 | .take(len_observed.into()) 152 | } 153 | 154 | /// Linear bucket distribution. 155 | pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator { 156 | iter::repeat(()) 157 | .enumerate() 158 | .map(move |(i, _)| start + (width * (i as f64))) 159 | .take(length.into()) 160 | } 161 | 162 | impl EncodeMetric for Histogram { 163 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 164 | let (sum, count, buckets) = self.get(); 165 | encoder.encode_histogram::(sum, count, &buckets, None) 166 | } 167 | 168 | fn metric_type(&self) -> MetricType { 169 | Self::TYPE 170 | } 171 | } 172 | 173 | #[cfg(test)] 174 | mod tests { 175 | use super::*; 176 | 177 | #[test] 178 | fn histogram() { 179 | let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); 180 | histogram.observe(1.0); 181 | } 182 | 183 | #[test] 184 | fn exponential() { 185 | assert_eq!( 186 | vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0], 187 | exponential_buckets(1.0, 2.0, 10).collect::>() 188 | ); 189 | } 190 | 191 | #[test] 192 | fn linear() { 193 | assert_eq!( 194 | vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], 195 | linear_buckets(0.0, 1.0, 10).collect::>() 196 | ); 197 | } 198 | 199 | #[test] 200 | fn exponential_range() { 201 | assert_eq!( 202 | vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0], 203 | exponential_buckets_range(1.0, 32.0, 6).collect::>() 204 | ); 205 | } 206 | 207 | #[test] 208 | fn exponential_range_incorrect() { 209 | let res = exponential_buckets_range(1.0, 32.0, 0).collect::>(); 210 | assert!(res.is_empty()); 211 | 212 | let res = exponential_buckets_range(0.0, 32.0, 6).collect::>(); 213 | assert!(res.is_empty()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /derive-encode/tests/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use prometheus_client::encoding::text::encode; 4 | use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; 5 | use prometheus_client::metrics::counter::Counter; 6 | use prometheus_client::metrics::family::Family; 7 | use prometheus_client::registry::Registry; 8 | 9 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] 10 | struct Labels { 11 | method: Method, 12 | path: String, 13 | } 14 | 15 | #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] 16 | enum Method { 17 | Get, 18 | #[allow(dead_code)] 19 | Put, 20 | } 21 | 22 | #[test] 23 | fn basic_flow() { 24 | let mut registry = Registry::default(); 25 | 26 | let family = Family::::default(); 27 | registry.register("my_counter", "This is my counter", family.clone()); 28 | 29 | // Record a single HTTP GET request. 30 | family 31 | .get_or_create(&Labels { 32 | method: Method::Get, 33 | path: "/metrics".to_string(), 34 | }) 35 | .inc(); 36 | 37 | // Encode all metrics in the registry in the text format. 38 | let mut buffer = String::new(); 39 | encode(&mut buffer, ®istry).unwrap(); 40 | 41 | let expected = "# HELP my_counter This is my counter.\n".to_owned() 42 | + "# TYPE my_counter counter\n" 43 | + "my_counter_total{method=\"Get\",path=\"/metrics\"} 1\n" 44 | + "# EOF\n"; 45 | assert_eq!(expected, buffer); 46 | } 47 | 48 | mod protobuf { 49 | use crate::{Labels, Method}; 50 | use prometheus_client::encoding::protobuf::encode; 51 | use prometheus_client::encoding::protobuf::openmetrics_data_model; 52 | use prometheus_client::metrics::counter::Counter; 53 | use prometheus_client::metrics::family::Family; 54 | use prometheus_client::registry::Registry; 55 | 56 | #[test] 57 | fn structs() { 58 | let mut registry = Registry::default(); 59 | let family = Family::::default(); 60 | registry.register("my_counter", "This is my counter", family.clone()); 61 | 62 | // Record a single HTTP GET request. 63 | family 64 | .get_or_create(&Labels { 65 | method: Method::Get, 66 | path: "/metrics".to_string(), 67 | }) 68 | .inc(); 69 | 70 | // Encode all metrics in the registry in the OpenMetrics protobuf format. 71 | let mut metric_set = encode(®istry).unwrap(); 72 | let mut family: openmetrics_data_model::MetricFamily = 73 | metric_set.metric_families.pop().unwrap(); 74 | let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); 75 | 76 | let method = &metric.labels[0]; 77 | assert_eq!("method", method.name); 78 | assert_eq!("Get", method.value); 79 | 80 | let path = &metric.labels[1]; 81 | assert_eq!("path", path.name); 82 | assert_eq!("/metrics", path.value); 83 | } 84 | 85 | #[test] 86 | fn enums() { 87 | let mut registry = Registry::default(); 88 | let family = Family::::default(); 89 | registry.register("my_counter", "This is my counter", family.clone()); 90 | 91 | // Record a single HTTP GET request. 92 | family 93 | .get_or_create(&Labels { 94 | method: Method::Get, 95 | path: "/metrics".to_string(), 96 | }) 97 | .inc(); 98 | 99 | // Encode all metrics in the registry in the OpenMetrics protobuf format. 100 | let mut metric_set = encode(®istry).unwrap(); 101 | let mut family: openmetrics_data_model::MetricFamily = 102 | metric_set.metric_families.pop().unwrap(); 103 | let metric: openmetrics_data_model::Metric = family.metrics.pop().unwrap(); 104 | 105 | let label = &metric.labels[0]; 106 | assert_eq!("method", label.name); 107 | assert_eq!("Get", label.value); 108 | } 109 | } 110 | 111 | #[test] 112 | fn remap_keyword_identifiers() { 113 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 114 | struct Labels { 115 | // `r#type` is problematic as `r#` is not a valid OpenMetrics label name 116 | // but one needs to use keyword identifier syntax (aka. raw identifiers) 117 | // as `type` is a keyword. 118 | // 119 | // Test makes sure `r#type` is replaced by `type` in the OpenMetrics 120 | // output. 121 | r#type: u64, 122 | } 123 | 124 | let mut registry = Registry::default(); 125 | let family = Family::::default(); 126 | registry.register("my_counter", "This is my counter", family.clone()); 127 | 128 | // Record a single HTTP GET request. 129 | family.get_or_create(&Labels { r#type: 42 }).inc(); 130 | 131 | // Encode all metrics in the registry in the text format. 132 | let mut buffer = String::new(); 133 | encode(&mut buffer, ®istry).unwrap(); 134 | 135 | let expected = "# HELP my_counter This is my counter.\n".to_owned() 136 | + "# TYPE my_counter counter\n" 137 | + "my_counter_total{type=\"42\"} 1\n" 138 | + "# EOF\n"; 139 | assert_eq!(expected, buffer); 140 | } 141 | 142 | #[test] 143 | fn arc_string() { 144 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 145 | struct Labels { 146 | client_id: Arc, 147 | } 148 | 149 | let mut registry = Registry::default(); 150 | let family = Family::::default(); 151 | registry.register("my_counter", "This is my counter", family.clone()); 152 | 153 | // Record a single HTTP GET request. 154 | let client_id = Arc::new("client_id".to_string()); 155 | family 156 | .get_or_create(&Labels { 157 | client_id: client_id.clone(), 158 | }) 159 | .inc(); 160 | 161 | // Encode all metrics in the registry in the text format. 162 | let mut buffer = String::new(); 163 | encode(&mut buffer, ®istry).unwrap(); 164 | 165 | let expected = "# HELP my_counter This is my counter.\n".to_owned() 166 | + "# TYPE my_counter counter\n" 167 | + "my_counter_total{client_id=\"client_id\"} 1\n" 168 | + "# EOF\n"; 169 | assert_eq!(expected, buffer); 170 | } 171 | 172 | #[test] 173 | fn flatten() { 174 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 175 | struct CommonLabels { 176 | a: u64, 177 | b: u64, 178 | } 179 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 180 | struct Labels { 181 | unique: u64, 182 | #[prometheus(flatten)] 183 | common: CommonLabels, 184 | } 185 | 186 | let mut registry = Registry::default(); 187 | let family = Family::::default(); 188 | registry.register("my_counter", "This is my counter", family.clone()); 189 | 190 | // Record a single HTTP GET request. 191 | family 192 | .get_or_create(&Labels { 193 | unique: 1, 194 | common: CommonLabels { a: 2, b: 3 }, 195 | }) 196 | .inc(); 197 | 198 | // Encode all metrics in the registry in the text format. 199 | let mut buffer = String::new(); 200 | encode(&mut buffer, ®istry).unwrap(); 201 | 202 | let expected = "# HELP my_counter This is my counter.\n".to_owned() 203 | + "# TYPE my_counter counter\n" 204 | + "my_counter_total{unique=\"1\",a=\"2\",b=\"3\"} 1\n" 205 | + "# EOF\n"; 206 | assert_eq!(expected, buffer); 207 | } 208 | 209 | #[test] 210 | fn build() { 211 | let t = trybuild::TestCases::new(); 212 | t.pass("tests/build/redefine-prelude-symbols.rs") 213 | } 214 | -------------------------------------------------------------------------------- /src/metrics/counter.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics counter. 2 | //! 3 | //! See [`Counter`] for details. 4 | 5 | use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet}; 6 | 7 | use super::{MetricType, TypedMetric}; 8 | use std::marker::PhantomData; 9 | #[cfg(target_has_atomic = "64")] 10 | use std::sync::atomic::AtomicU64; 11 | use std::sync::atomic::{AtomicU32, Ordering}; 12 | use std::sync::Arc; 13 | 14 | /// Open Metrics [`Counter`] to measure discrete events. 15 | /// 16 | /// Single monotonically increasing value metric. 17 | /// 18 | /// [`Counter`] is generic over the actual data type tracking the [`Counter`] 19 | /// state as well as the data type used to interact with the [`Counter`]. Out of 20 | /// convenience the generic type parameters are set to use an [`AtomicU64`] as a 21 | /// storage and [`u64`] on the interface by default. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ## Using [`AtomicU64`] as storage and [`u64`] on the interface 26 | /// 27 | /// ``` 28 | /// # use prometheus_client::metrics::counter::Counter; 29 | /// let counter: Counter = Counter::default(); 30 | /// counter.inc(); 31 | /// let _value: u64 = counter.get(); 32 | /// ``` 33 | /// 34 | /// ## Using [`AtomicU64`] as storage and [`f64`] on the interface 35 | /// 36 | /// ``` 37 | /// # use prometheus_client::metrics::counter::Counter; 38 | /// # use std::sync::atomic::AtomicU64; 39 | /// let counter = Counter::::default(); 40 | /// counter.inc(); 41 | /// let _value: f64 = counter.get(); 42 | /// ``` 43 | #[cfg(target_has_atomic = "64")] 44 | #[derive(Debug)] 45 | pub struct Counter { 46 | value: Arc, 47 | phantom: PhantomData, 48 | } 49 | 50 | /// Open Metrics [`Counter`] to measure discrete events. 51 | #[cfg(not(target_has_atomic = "64"))] 52 | #[derive(Debug)] 53 | pub struct Counter { 54 | value: Arc, 55 | phantom: PhantomData, 56 | } 57 | 58 | impl Clone for Counter { 59 | fn clone(&self) -> Self { 60 | Self { 61 | value: self.value.clone(), 62 | phantom: PhantomData, 63 | } 64 | } 65 | } 66 | 67 | impl Default for Counter { 68 | fn default() -> Self { 69 | Counter { 70 | value: Arc::new(A::default()), 71 | phantom: PhantomData, 72 | } 73 | } 74 | } 75 | 76 | impl> Counter { 77 | /// Increase the [`Counter`] by 1, returning the previous value. 78 | pub fn inc(&self) -> N { 79 | self.value.inc() 80 | } 81 | 82 | /// Increase the [`Counter`] by `v`, returning the previous value. 83 | pub fn inc_by(&self, v: N) -> N { 84 | self.value.inc_by(v) 85 | } 86 | 87 | /// Get the current value of the [`Counter`]. 88 | pub fn get(&self) -> N { 89 | self.value.get() 90 | } 91 | 92 | /// Exposes the inner atomic type of the [`Counter`]. 93 | /// 94 | /// This should only be used for advanced use-cases which are not directly 95 | /// supported by the library. 96 | /// 97 | /// The caller of this function has to uphold the property of an Open 98 | /// Metrics counter namely that the value is monotonically increasing, i.e. 99 | /// either stays the same or increases. 100 | pub fn inner(&self) -> &A { 101 | &self.value 102 | } 103 | } 104 | 105 | /// Atomic operations for a [`Counter`] value store. 106 | pub trait Atomic { 107 | /// Increase the value by `1`. 108 | fn inc(&self) -> N; 109 | 110 | /// Increase the value. 111 | fn inc_by(&self, v: N) -> N; 112 | 113 | /// Get the the value. 114 | fn get(&self) -> N; 115 | } 116 | 117 | #[cfg(target_has_atomic = "64")] 118 | impl Atomic for AtomicU64 { 119 | fn inc(&self) -> u64 { 120 | self.inc_by(1) 121 | } 122 | 123 | fn inc_by(&self, v: u64) -> u64 { 124 | self.fetch_add(v, Ordering::Relaxed) 125 | } 126 | 127 | fn get(&self) -> u64 { 128 | self.load(Ordering::Relaxed) 129 | } 130 | } 131 | 132 | impl Atomic for AtomicU32 { 133 | fn inc(&self) -> u32 { 134 | self.inc_by(1) 135 | } 136 | 137 | fn inc_by(&self, v: u32) -> u32 { 138 | self.fetch_add(v, Ordering::Relaxed) 139 | } 140 | 141 | fn get(&self) -> u32 { 142 | self.load(Ordering::Relaxed) 143 | } 144 | } 145 | 146 | #[cfg(target_has_atomic = "64")] 147 | impl Atomic for AtomicU64 { 148 | fn inc(&self) -> f64 { 149 | self.inc_by(1.0) 150 | } 151 | 152 | fn inc_by(&self, v: f64) -> f64 { 153 | let mut old_u64 = self.load(Ordering::Relaxed); 154 | let mut old_f64; 155 | loop { 156 | old_f64 = f64::from_bits(old_u64); 157 | let new = f64::to_bits(old_f64 + v); 158 | match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) { 159 | Ok(_) => break, 160 | Err(x) => old_u64 = x, 161 | } 162 | } 163 | 164 | old_f64 165 | } 166 | 167 | fn get(&self) -> f64 { 168 | f64::from_bits(self.load(Ordering::Relaxed)) 169 | } 170 | } 171 | 172 | impl Atomic for AtomicU32 { 173 | fn inc(&self) -> f32 { 174 | self.inc_by(1.0) 175 | } 176 | 177 | fn inc_by(&self, v: f32) -> f32 { 178 | let mut old_u32 = self.load(Ordering::Relaxed); 179 | let mut old_f32; 180 | loop { 181 | old_f32 = f32::from_bits(old_u32); 182 | let new = f32::to_bits(old_f32 + v); 183 | match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) { 184 | Ok(_) => break, 185 | Err(x) => old_u32 = x, 186 | } 187 | } 188 | 189 | old_f32 190 | } 191 | 192 | fn get(&self) -> f32 { 193 | f32::from_bits(self.load(Ordering::Relaxed)) 194 | } 195 | } 196 | 197 | impl TypedMetric for Counter { 198 | const TYPE: MetricType = MetricType::Counter; 199 | } 200 | 201 | impl EncodeMetric for Counter 202 | where 203 | N: crate::encoding::EncodeCounterValue, 204 | A: Atomic, 205 | { 206 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 207 | encoder.encode_counter::(&self.get(), None) 208 | } 209 | 210 | fn metric_type(&self) -> MetricType { 211 | Self::TYPE 212 | } 213 | } 214 | 215 | /// As a [`Counter`], but constant, meaning it cannot change once created. 216 | /// 217 | /// Needed for advanced use-cases, e.g. in combination with [`Collector`](crate::collector::Collector). 218 | #[derive(Debug, Default)] 219 | pub struct ConstCounter { 220 | value: N, 221 | } 222 | 223 | impl ConstCounter { 224 | /// Creates a new [`ConstCounter`]. 225 | pub fn new(value: N) -> Self { 226 | Self { value } 227 | } 228 | } 229 | 230 | impl TypedMetric for ConstCounter { 231 | const TYPE: MetricType = MetricType::Counter; 232 | } 233 | 234 | impl EncodeMetric for ConstCounter 235 | where 236 | N: crate::encoding::EncodeCounterValue, 237 | { 238 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 239 | encoder.encode_counter::(&self.value, None) 240 | } 241 | 242 | fn metric_type(&self) -> MetricType { 243 | Self::TYPE 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | use super::*; 250 | use quickcheck::QuickCheck; 251 | 252 | #[test] 253 | fn inc_and_get() { 254 | let counter: Counter = Counter::default(); 255 | assert_eq!(0, counter.inc()); 256 | assert_eq!(1, counter.get()); 257 | } 258 | 259 | #[cfg(target_has_atomic = "64")] 260 | #[test] 261 | fn f64_stored_in_atomic_u64() { 262 | fn prop(fs: Vec) { 263 | let fs: Vec = fs 264 | .into_iter() 265 | // Map infinite, subnormal and NaN to 0.0. 266 | .map(|f| if f.is_normal() { f } else { 0.0 }) 267 | .collect(); 268 | let sum: f64 = fs.iter().sum(); 269 | let counter = Counter::::default(); 270 | for f in fs { 271 | counter.inc_by(f); 272 | } 273 | assert_eq!(counter.get(), sum) 274 | } 275 | 276 | QuickCheck::new().tests(10).quickcheck(prop as fn(_)) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/metrics/exemplar.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics exemplars for counters and histograms. 2 | //! 3 | //! See [`CounterWithExemplar`] and [`HistogramWithExemplars`] for details. 4 | 5 | use crate::encoding::{ 6 | EncodeCounterValue, EncodeExemplarValue, EncodeLabelSet, EncodeMetric, MetricEncoder, 7 | }; 8 | 9 | use super::counter::{self, Counter}; 10 | use super::histogram::Histogram; 11 | use super::{MetricType, TypedMetric}; 12 | use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; 13 | use std::collections::HashMap; 14 | #[cfg(not(target_has_atomic = "64"))] 15 | use std::sync::atomic::AtomicU32; 16 | #[cfg(target_has_atomic = "64")] 17 | use std::sync::atomic::AtomicU64; 18 | use std::sync::Arc; 19 | use std::time::SystemTime; 20 | 21 | /// An OpenMetrics exemplar. 22 | #[derive(Debug)] 23 | pub struct Exemplar { 24 | pub(crate) label_set: S, 25 | pub(crate) value: V, 26 | pub(crate) timestamp: Option, 27 | } 28 | 29 | ///////////////////////////////////////////////////////////////////////////////// 30 | // Counter 31 | 32 | /// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete 33 | /// events and track references to data outside of the metric set. 34 | /// 35 | /// ``` 36 | /// # use prometheus_client::metrics::exemplar::CounterWithExemplar; 37 | /// let counter_with_exemplar = CounterWithExemplar::>::default(); 38 | /// counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), "42".to_string())]), None); 39 | /// let _value: (u64, _) = counter_with_exemplar.get(); 40 | /// ``` 41 | /// You can also use exemplars with families. Just wrap the exemplar in a Family. 42 | /// ``` 43 | /// # use prometheus_client::metrics::exemplar::CounterWithExemplar; 44 | /// # use prometheus_client::metrics::histogram::exponential_buckets; 45 | /// # use prometheus_client::metrics::family::Family; 46 | /// # use prometheus_client_derive_encode::EncodeLabelSet; 47 | /// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] 48 | /// pub struct ResultLabel { 49 | /// pub result: String, 50 | /// } 51 | /// 52 | /// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] 53 | /// pub struct TraceLabel { 54 | /// pub trace_id: String, 55 | /// } 56 | /// 57 | /// let latency: Family> = Family::default(); 58 | /// 59 | /// latency 60 | /// .get_or_create(&ResultLabel { 61 | /// result: "success".to_owned(), 62 | /// }) 63 | /// .inc_by( 64 | /// 1, 65 | /// Some(TraceLabel { 66 | /// trace_id: "3a2f90c9f80b894f".to_owned(), 67 | /// }), 68 | /// None, 69 | /// ); 70 | /// ``` 71 | #[cfg(target_has_atomic = "64")] 72 | #[derive(Debug)] 73 | pub struct CounterWithExemplar { 74 | pub(crate) inner: Arc>>, 75 | } 76 | 77 | impl TypedMetric for CounterWithExemplar { 78 | const TYPE: MetricType = MetricType::Counter; 79 | } 80 | 81 | /// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete 82 | /// events and track references to data outside of the metric set. 83 | #[cfg(not(target_has_atomic = "64"))] 84 | #[derive(Debug)] 85 | pub struct CounterWithExemplar { 86 | pub(crate) inner: Arc>>, 87 | } 88 | 89 | impl Clone for CounterWithExemplar { 90 | fn clone(&self) -> Self { 91 | CounterWithExemplar { 92 | inner: self.inner.clone(), 93 | } 94 | } 95 | } 96 | 97 | /// An OpenMetrics [`Counter`] in combination with an OpenMetrics [`Exemplar`]. 98 | #[derive(Debug)] 99 | pub struct CounterWithExemplarInner { 100 | pub(crate) exemplar: Option>, 101 | pub(crate) counter: Counter, 102 | } 103 | 104 | impl Default for CounterWithExemplar { 105 | fn default() -> Self { 106 | Self { 107 | inner: Arc::new(RwLock::new(CounterWithExemplarInner { 108 | exemplar: None, 109 | counter: Default::default(), 110 | })), 111 | } 112 | } 113 | } 114 | 115 | impl> CounterWithExemplar { 116 | // TODO: Implement `fn inc`. Problematic right now as one can not produce 117 | // value `1` of type `N`. 118 | 119 | /// Increase the [`CounterWithExemplar`] by `v`, updating the [`Exemplar`] 120 | /// if a label set is provided, returning the previous value. 121 | pub fn inc_by(&self, v: N, label_set: Option, timestamp: Option) -> N { 122 | let mut inner = self.inner.write(); 123 | 124 | inner.exemplar = label_set.map(|label_set| Exemplar { 125 | label_set, 126 | value: v.clone(), 127 | timestamp, 128 | }); 129 | 130 | inner.counter.inc_by(v) 131 | } 132 | 133 | /// Get the current value of the [`CounterWithExemplar`] as well as its 134 | /// [`Exemplar`] if any. 135 | pub fn get(&self) -> (N, MappedRwLockReadGuard<'_, Option>>) { 136 | let inner = self.inner.read(); 137 | let value = inner.counter.get(); 138 | let exemplar = RwLockReadGuard::map(inner, |inner| &inner.exemplar); 139 | (value, exemplar) 140 | } 141 | 142 | /// Exposes the inner atomic type of the [`CounterWithExemplar`]. 143 | /// 144 | /// This should only be used for advanced use-cases which are not directly 145 | /// supported by the library. 146 | /// 147 | /// The caller of this function has to uphold the property of an Open 148 | /// Metrics counter namely that the value is monotonically increasing, i.e. 149 | /// either stays the same or increases. 150 | pub fn inner(&self) -> MappedRwLockReadGuard<'_, A> { 151 | RwLockReadGuard::map(self.inner.read(), |inner| inner.counter.inner()) 152 | } 153 | } 154 | 155 | // TODO: S, V, N, A are hard to grasp. 156 | impl EncodeMetric for crate::metrics::exemplar::CounterWithExemplar 157 | where 158 | S: EncodeLabelSet, 159 | N: EncodeCounterValue + EncodeExemplarValue + Clone, 160 | A: counter::Atomic, 161 | { 162 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 163 | let (value, exemplar) = self.get(); 164 | encoder.encode_counter(&value, exemplar.as_ref()) 165 | } 166 | 167 | fn metric_type(&self) -> MetricType { 168 | Counter::::TYPE 169 | } 170 | } 171 | 172 | ///////////////////////////////////////////////////////////////////////////////// 173 | // Histogram 174 | 175 | /// Open Metrics [`Histogram`] to both measure distributions of discrete events. 176 | /// and track references to data outside of the metric set. 177 | /// 178 | /// ``` 179 | /// # use prometheus_client::metrics::exemplar::HistogramWithExemplars; 180 | /// # use prometheus_client::metrics::histogram::exponential_buckets; 181 | /// let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)); 182 | /// histogram.observe(4.2, Some(vec![("user_id".to_string(), "42".to_string())]), None); 183 | /// ``` 184 | /// You can also use exemplars with families. Just wrap the exemplar in a Family. 185 | /// ``` 186 | /// # use prometheus_client::metrics::exemplar::HistogramWithExemplars; 187 | /// # use prometheus_client::metrics::histogram::exponential_buckets; 188 | /// # use prometheus_client::metrics::family::Family; 189 | /// # use prometheus_client::encoding::EncodeLabelSet; 190 | /// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] 191 | /// pub struct ResultLabel { 192 | /// pub result: String, 193 | /// } 194 | /// 195 | /// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)] 196 | /// pub struct TraceLabel { 197 | /// pub trace_id: String, 198 | /// } 199 | /// 200 | /// let latency: Family> = 201 | /// Family::new_with_constructor(|| { 202 | /// HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)) 203 | /// }); 204 | /// 205 | /// latency 206 | /// .get_or_create(&ResultLabel { 207 | /// result: "success".to_owned(), 208 | /// }) 209 | /// .observe( 210 | /// 0.001345422, 211 | /// Some(TraceLabel { 212 | /// trace_id: "3a2f90c9f80b894f".to_owned(), 213 | /// }), 214 | /// None, 215 | /// ); 216 | /// ``` 217 | #[derive(Debug)] 218 | pub struct HistogramWithExemplars { 219 | // TODO: Not ideal, as Histogram has a Mutex as well. 220 | pub(crate) inner: Arc>>, 221 | } 222 | 223 | impl TypedMetric for HistogramWithExemplars { 224 | const TYPE: MetricType = MetricType::Histogram; 225 | } 226 | 227 | impl Clone for HistogramWithExemplars { 228 | fn clone(&self) -> Self { 229 | Self { 230 | inner: self.inner.clone(), 231 | } 232 | } 233 | } 234 | 235 | /// An OpenMetrics [`Histogram`] in combination with an OpenMetrics [`Exemplar`]. 236 | #[derive(Debug)] 237 | pub struct HistogramWithExemplarsInner { 238 | pub(crate) exemplars: HashMap>, 239 | pub(crate) histogram: Histogram, 240 | } 241 | 242 | impl HistogramWithExemplars { 243 | /// Create a new [`HistogramWithExemplars`]. 244 | pub fn new(buckets: impl Iterator) -> Self { 245 | Self { 246 | inner: Arc::new(RwLock::new(HistogramWithExemplarsInner { 247 | exemplars: Default::default(), 248 | histogram: Histogram::new(buckets), 249 | })), 250 | } 251 | } 252 | 253 | /// Observe the given value, optionally providing a label set and thus 254 | /// setting the [`Exemplar`] value. 255 | pub fn observe(&self, v: f64, label_set: Option, timestamp: Option) { 256 | let mut inner = self.inner.write(); 257 | let bucket = inner.histogram.observe_and_bucket(v); 258 | if let (Some(bucket), Some(label_set)) = (bucket, label_set) { 259 | inner.exemplars.insert( 260 | bucket, 261 | Exemplar { 262 | label_set, 263 | value: v, 264 | timestamp, 265 | }, 266 | ); 267 | } 268 | } 269 | 270 | pub(crate) fn inner(&self) -> RwLockReadGuard<'_, HistogramWithExemplarsInner> { 271 | self.inner.read() 272 | } 273 | } 274 | 275 | impl EncodeMetric for HistogramWithExemplars { 276 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 277 | let inner = self.inner(); 278 | let (sum, count, buckets) = inner.histogram.get(); 279 | encoder.encode_histogram(sum, count, &buckets, Some(&inner.exemplars)) 280 | } 281 | 282 | fn metric_type(&self) -> MetricType { 283 | Histogram::TYPE 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/metrics/gauge.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics gauge. 2 | //! 3 | //! See [`Gauge`] for details. 4 | 5 | use crate::encoding::{EncodeGaugeValue, EncodeMetric, MetricEncoder}; 6 | 7 | use super::{MetricType, TypedMetric}; 8 | use std::marker::PhantomData; 9 | use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; 10 | #[cfg(target_has_atomic = "64")] 11 | use std::sync::atomic::{AtomicI64, AtomicU64}; 12 | use std::sync::Arc; 13 | 14 | /// Open Metrics [`Gauge`] to record current measurements. 15 | /// 16 | /// Single increasing, decreasing or constant value metric. 17 | /// 18 | /// [`Gauge`] is generic over the actual data type tracking the [`Gauge`] state 19 | /// as well as the data type used to interact with the [`Gauge`]. Out of 20 | /// convenience the generic type parameters are set to use an [`AtomicI64`] as a 21 | /// storage and [`i64`] on the interface by default. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ## Using [`AtomicI64`] as storage and [`i64`] on the interface 26 | /// 27 | /// ``` 28 | /// # use prometheus_client::metrics::gauge::Gauge; 29 | /// let gauge: Gauge = Gauge::default(); 30 | /// gauge.set(42); 31 | /// let _value = gauge.get(); 32 | /// ``` 33 | /// 34 | /// ## Using [`AtomicU64`] as storage and [`f64`] on the interface 35 | /// 36 | /// ``` 37 | /// # use prometheus_client::metrics::gauge::Gauge; 38 | /// # use std::sync::atomic::AtomicU64; 39 | /// let gauge = Gauge::::default(); 40 | /// gauge.set(42.0); 41 | /// let _value: f64 = gauge.get(); 42 | /// ``` 43 | #[cfg(target_has_atomic = "64")] 44 | #[derive(Debug)] 45 | pub struct Gauge { 46 | value: Arc, 47 | phantom: PhantomData, 48 | } 49 | 50 | /// Open Metrics [`Gauge`] to record current measurements. 51 | #[cfg(not(target_has_atomic = "64"))] 52 | #[derive(Debug)] 53 | pub struct Gauge { 54 | value: Arc, 55 | phantom: PhantomData, 56 | } 57 | 58 | impl Clone for Gauge { 59 | fn clone(&self) -> Self { 60 | Self { 61 | value: self.value.clone(), 62 | phantom: PhantomData, 63 | } 64 | } 65 | } 66 | 67 | impl Default for Gauge { 68 | fn default() -> Self { 69 | Self { 70 | value: Arc::new(A::default()), 71 | phantom: PhantomData, 72 | } 73 | } 74 | } 75 | 76 | impl> Gauge { 77 | /// Increase the [`Gauge`] by 1, returning the previous value. 78 | pub fn inc(&self) -> N { 79 | self.value.inc() 80 | } 81 | 82 | /// Increase the [`Gauge`] by `v`, returning the previous value. 83 | pub fn inc_by(&self, v: N) -> N { 84 | self.value.inc_by(v) 85 | } 86 | 87 | /// Decrease the [`Gauge`] by 1, returning the previous value. 88 | pub fn dec(&self) -> N { 89 | self.value.dec() 90 | } 91 | 92 | /// Decrease the [`Gauge`] by `v`, returning the previous value. 93 | pub fn dec_by(&self, v: N) -> N { 94 | self.value.dec_by(v) 95 | } 96 | 97 | /// Sets the [`Gauge`] to `v`, returning the previous value. 98 | pub fn set(&self, v: N) -> N { 99 | self.value.set(v) 100 | } 101 | 102 | /// Get the current value of the [`Gauge`]. 103 | pub fn get(&self) -> N { 104 | self.value.get() 105 | } 106 | 107 | /// Exposes the inner atomic type of the [`Gauge`]. 108 | /// 109 | /// This should only be used for advanced use-cases which are not directly 110 | /// supported by the library. 111 | pub fn inner(&self) -> &A { 112 | &self.value 113 | } 114 | } 115 | 116 | /// Atomic operations for a [`Gauge`] value store. 117 | pub trait Atomic { 118 | /// Increase the value by `1`. 119 | fn inc(&self) -> N; 120 | 121 | /// Increase the value. 122 | fn inc_by(&self, v: N) -> N; 123 | 124 | /// Decrease the value by `1`. 125 | fn dec(&self) -> N; 126 | 127 | /// Decrease the value. 128 | fn dec_by(&self, v: N) -> N; 129 | 130 | /// Set the value. 131 | fn set(&self, v: N) -> N; 132 | 133 | /// Get the value. 134 | fn get(&self) -> N; 135 | } 136 | 137 | impl Atomic for AtomicI32 { 138 | fn inc(&self) -> i32 { 139 | self.inc_by(1) 140 | } 141 | 142 | fn inc_by(&self, v: i32) -> i32 { 143 | self.fetch_add(v, Ordering::Relaxed) 144 | } 145 | 146 | fn dec(&self) -> i32 { 147 | self.dec_by(1) 148 | } 149 | 150 | fn dec_by(&self, v: i32) -> i32 { 151 | self.fetch_sub(v, Ordering::Relaxed) 152 | } 153 | 154 | fn set(&self, v: i32) -> i32 { 155 | self.swap(v, Ordering::Relaxed) 156 | } 157 | 158 | fn get(&self) -> i32 { 159 | self.load(Ordering::Relaxed) 160 | } 161 | } 162 | 163 | impl Atomic for AtomicU32 { 164 | fn inc(&self) -> u32 { 165 | self.inc_by(1) 166 | } 167 | 168 | fn inc_by(&self, v: u32) -> u32 { 169 | self.fetch_add(v, Ordering::Relaxed) 170 | } 171 | 172 | fn dec(&self) -> u32 { 173 | self.dec_by(1) 174 | } 175 | 176 | fn dec_by(&self, v: u32) -> u32 { 177 | self.fetch_sub(v, Ordering::Relaxed) 178 | } 179 | 180 | fn set(&self, v: u32) -> u32 { 181 | self.swap(v, Ordering::Relaxed) 182 | } 183 | 184 | fn get(&self) -> u32 { 185 | self.load(Ordering::Relaxed) 186 | } 187 | } 188 | 189 | #[cfg(target_has_atomic = "64")] 190 | impl Atomic for AtomicI64 { 191 | fn inc(&self) -> i64 { 192 | self.inc_by(1) 193 | } 194 | 195 | fn inc_by(&self, v: i64) -> i64 { 196 | self.fetch_add(v, Ordering::Relaxed) 197 | } 198 | 199 | fn dec(&self) -> i64 { 200 | self.dec_by(1) 201 | } 202 | 203 | fn dec_by(&self, v: i64) -> i64 { 204 | self.fetch_sub(v, Ordering::Relaxed) 205 | } 206 | 207 | fn set(&self, v: i64) -> i64 { 208 | self.swap(v, Ordering::Relaxed) 209 | } 210 | 211 | fn get(&self) -> i64 { 212 | self.load(Ordering::Relaxed) 213 | } 214 | } 215 | 216 | #[cfg(target_has_atomic = "64")] 217 | impl Atomic for AtomicU64 { 218 | fn inc(&self) -> u64 { 219 | self.inc_by(1) 220 | } 221 | 222 | fn inc_by(&self, v: u64) -> u64 { 223 | self.fetch_add(v, Ordering::Relaxed) 224 | } 225 | 226 | fn dec(&self) -> u64 { 227 | self.dec_by(1) 228 | } 229 | 230 | fn dec_by(&self, v: u64) -> u64 { 231 | self.fetch_sub(v, Ordering::Relaxed) 232 | } 233 | 234 | fn set(&self, v: u64) -> u64 { 235 | self.swap(v, Ordering::Relaxed) 236 | } 237 | 238 | fn get(&self) -> u64 { 239 | self.load(Ordering::Relaxed) 240 | } 241 | } 242 | 243 | #[cfg(target_has_atomic = "64")] 244 | impl Atomic for AtomicU64 { 245 | fn inc(&self) -> f64 { 246 | self.inc_by(1.0) 247 | } 248 | 249 | fn inc_by(&self, v: f64) -> f64 { 250 | let mut old_u64 = self.load(Ordering::Relaxed); 251 | let mut old_f64; 252 | loop { 253 | old_f64 = f64::from_bits(old_u64); 254 | let new = f64::to_bits(old_f64 + v); 255 | match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) { 256 | Ok(_) => break, 257 | Err(x) => old_u64 = x, 258 | } 259 | } 260 | 261 | old_f64 262 | } 263 | 264 | fn dec(&self) -> f64 { 265 | self.dec_by(1.0) 266 | } 267 | 268 | fn dec_by(&self, v: f64) -> f64 { 269 | let mut old_u64 = self.load(Ordering::Relaxed); 270 | let mut old_f64; 271 | loop { 272 | old_f64 = f64::from_bits(old_u64); 273 | let new = f64::to_bits(old_f64 - v); 274 | match self.compare_exchange_weak(old_u64, new, Ordering::Relaxed, Ordering::Relaxed) { 275 | Ok(_) => break, 276 | Err(x) => old_u64 = x, 277 | } 278 | } 279 | 280 | old_f64 281 | } 282 | 283 | fn set(&self, v: f64) -> f64 { 284 | f64::from_bits(self.swap(f64::to_bits(v), Ordering::Relaxed)) 285 | } 286 | 287 | fn get(&self) -> f64 { 288 | f64::from_bits(self.load(Ordering::Relaxed)) 289 | } 290 | } 291 | 292 | impl Atomic for AtomicU32 { 293 | fn inc(&self) -> f32 { 294 | self.inc_by(1.0) 295 | } 296 | 297 | fn inc_by(&self, v: f32) -> f32 { 298 | let mut old_u32 = self.load(Ordering::Relaxed); 299 | let mut old_f32; 300 | loop { 301 | old_f32 = f32::from_bits(old_u32); 302 | let new = f32::to_bits(old_f32 + v); 303 | match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) { 304 | Ok(_) => break, 305 | Err(x) => old_u32 = x, 306 | } 307 | } 308 | 309 | old_f32 310 | } 311 | 312 | fn dec(&self) -> f32 { 313 | self.dec_by(1.0) 314 | } 315 | 316 | fn dec_by(&self, v: f32) -> f32 { 317 | let mut old_u32 = self.load(Ordering::Relaxed); 318 | let mut old_f32; 319 | loop { 320 | old_f32 = f32::from_bits(old_u32); 321 | let new = f32::to_bits(old_f32 - v); 322 | match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) { 323 | Ok(_) => break, 324 | Err(x) => old_u32 = x, 325 | } 326 | } 327 | 328 | old_f32 329 | } 330 | 331 | fn set(&self, v: f32) -> f32 { 332 | f32::from_bits(self.swap(f32::to_bits(v), Ordering::Relaxed)) 333 | } 334 | 335 | fn get(&self) -> f32 { 336 | f32::from_bits(self.load(Ordering::Relaxed)) 337 | } 338 | } 339 | 340 | impl TypedMetric for Gauge { 341 | const TYPE: MetricType = MetricType::Gauge; 342 | } 343 | 344 | impl EncodeMetric for Gauge 345 | where 346 | N: EncodeGaugeValue, 347 | A: Atomic, 348 | { 349 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 350 | encoder.encode_gauge(&self.get()) 351 | } 352 | fn metric_type(&self) -> MetricType { 353 | Self::TYPE 354 | } 355 | } 356 | 357 | /// As a [`Gauge`], but constant, meaning it cannot change once created. 358 | /// 359 | /// Needed for advanced use-cases, e.g. in combination with [`Collector`](crate::collector::Collector). 360 | #[derive(Debug, Default)] 361 | pub struct ConstGauge { 362 | value: N, 363 | } 364 | 365 | impl ConstGauge { 366 | /// Creates a new [`ConstGauge`]. 367 | pub fn new(value: N) -> Self { 368 | Self { value } 369 | } 370 | } 371 | 372 | impl TypedMetric for ConstGauge { 373 | const TYPE: MetricType = MetricType::Gauge; 374 | } 375 | 376 | impl EncodeMetric for ConstGauge 377 | where 378 | N: EncodeGaugeValue, 379 | { 380 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 381 | encoder.encode_gauge(&self.value) 382 | } 383 | 384 | fn metric_type(&self) -> MetricType { 385 | Self::TYPE 386 | } 387 | } 388 | 389 | #[cfg(test)] 390 | mod tests { 391 | use super::*; 392 | 393 | #[test] 394 | fn inc_dec_and_get() { 395 | let gauge: Gauge = Gauge::default(); 396 | assert_eq!(0, gauge.inc()); 397 | assert_eq!(1, gauge.get()); 398 | 399 | assert_eq!(1, gauge.dec()); 400 | assert_eq!(0, gauge.get()); 401 | 402 | assert_eq!(0, gauge.set(10)); 403 | assert_eq!(10, gauge.get()); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /derive-encode/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.24.1] 8 | 9 | ### Fixed 10 | 11 | - `EncodeGaugeValue`, `EncodeCounterValue` and `EncodeExemplarValue` now use 12 | fewer `as` casts in their implementation. This caught an issue where 13 | `EncodeGaugeValue` would not error when encoding some `u64`s that don't fit 14 | in a `i64`. See [PR 281]. 15 | - Filter out empty metric families, to match the go client. See [PR 279]. 16 | 17 | [PR 279]: https://github.com/prometheus/client_rust/pull/279 18 | [PR 281]: https://github.com/prometheus/client_rust/pull/281 19 | 20 | ## [0.24.0] 21 | 22 | ### Added 23 | 24 | - `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`. 25 | See [PR 257]. 26 | 27 | - `Family::get_or_create_owned` can access a metric in a labeled family. This 28 | method avoids the risk of runtime deadlocks at the expense of creating an 29 | owned type. See [PR 244]. 30 | 31 | - `impl Collector for std::sync::Arc`. 32 | See [PR 273]. 33 | 34 | [PR 244]: https://github.com/prometheus/client_rust/pull/244 35 | [PR 257]: https://github.com/prometheus/client_rust/pull/257 36 | [PR 273]: https://github.com/prometheus/client_rust/pull/273 37 | 38 | ### Changed 39 | 40 | - `EncodeLabelSet::encode()` now accepts a mutable reference to its encoder parameter. 41 | - Exemplar timestamps can now be passed, which are required for `convert_classic_histograms_to_nhcb: true` 42 | in Prometheus scraping. See [PR 276]. 43 | 44 | [PR 276]: https://github.com/prometheus/client_rust/pull/276 45 | 46 | ## [0.23.1] 47 | 48 | ### Changed 49 | 50 | - `Histogram::new` now accepts an `IntoIterator` argument, rather than an `Iterator`. 51 | See [PR 243]. 52 | 53 | [PR 243]: https://github.com/prometheus/client_rust/pull/243 54 | 55 | ## [0.23.0] 56 | 57 | ### Changed 58 | 59 | - `ConstCounter::new` now requires specifying the type of literal arguments, like this: `ConstCounter::new(42u64);`. 60 | See [PR 173]. 61 | 62 | - Update `prost` dependencies to `v0.12`. 63 | See [PR 198]. 64 | 65 | - Implement `Atomic` for `AtomicU64` for gauges. 66 | See [PR 226]. 67 | 68 | - Implement `EnableLabelValue` for `bool`. 69 | See [PR 237] 70 | 71 | [PR 173]: https://github.com/prometheus/client_rust/pull/173 72 | [PR 198]: https://github.com/prometheus/client_rust/pull/198 73 | [PR 226]: https://github.com/prometheus/client_rust/pull/226 74 | [PR 237]: https://github.com/prometheus/client_rust/pull/237 75 | 76 | ### Added 77 | 78 | - Support `i32`/`f32` for `Gauge` and `u32`/`f32` for `Counter`/`CounterWithExemplar`. 79 | See [PR 173] and [PR 216]. 80 | 81 | - Supoort `Arc` for `EncodeLabelValue`. 82 | See [PR 217]. 83 | 84 | - Add `histogram::exponential_buckets_range`. 85 | See [PR 233]. 86 | 87 | - Added `get` method to `Family`. 88 | See [PR 234]. 89 | 90 | [PR 173]: https://github.com/prometheus/client_rust/pull/173 91 | [PR 216]: https://github.com/prometheus/client_rust/pull/216 92 | [PR 217]: https://github.com/prometheus/client_rust/pull/217 93 | [PR 233]: https://github.com/prometheus/client_rust/pull/233 94 | [PR 234]: https://github.com/prometheus/client_rust/pull/234 95 | 96 | ### Fixed 97 | 98 | - Don't prepend `,` when encoding empty family label set. 99 | See [PR 175]. 100 | 101 | [PR 175]: https://github.com/prometheus/client_rust/pull/175 102 | 103 | ## [0.22.3] 104 | 105 | ### Added 106 | 107 | - Added `encode_registry` and `encode_eof` functions to `text` module. 108 | See [PR 205]. 109 | 110 | [PR 205]: https://github.com/prometheus/client_rust/pull/205 111 | 112 | - Support all platforms with 32 bit atomics lacking 64 bit atomics. 113 | See [PR 203]. 114 | 115 | [PR 203]: https://github.com/prometheus/client_rust/pull/203 116 | 117 | ## [0.22.2] 118 | 119 | ### Added 120 | 121 | - Added `Gauge` implementation. 122 | See [PR 191]. 123 | 124 | [PR 191]: https://github.com/prometheus/client_rust/pull/191 125 | 126 | ## [0.22.1] 127 | 128 | ### Added 129 | 130 | - Added `EncodeLabelValue` and `EncodeLabelKey` implementations for `Arc`, 131 | `Rc`, and `Box`. 132 | See [PR 188]. 133 | 134 | [PR 188]: https://github.com/prometheus/client_rust/pull/188 135 | 136 | ## [0.22.0] 137 | 138 | ### Changed 139 | 140 | - Simplify `Collector` `trait` by enabling `Collector::collect` to encode metrics directly with a `DescriptorEncoder`. 141 | See [PR 149] for details. 142 | 143 | [PR 149]: https://github.com/prometheus/client_rust/pull/149 144 | 145 | ## [0.21.2] 146 | 147 | ### Added 148 | 149 | - Added `sub_registry_with_labels` method to `Registry`. 150 | See [PR 145]. 151 | - Added `with_labels` and `with_prefix_and_labels` constructors to `Registry`. 152 | See [PR 147]. 153 | 154 | [PR 145]: https://github.com/prometheus/client_rust/pull/145 155 | [PR 147]: https://github.com/prometheus/client_rust/pull/147 156 | 157 | ## [0.21.1] 158 | 159 | ### Added 160 | 161 | - Implement `EncodeLabelValue` for `Option`. 162 | See [PR 137]. 163 | 164 | [PR 137]: https://github.com/prometheus/client_rust/pull/137 165 | 166 | ## [0.21.0] 167 | 168 | ### Changed 169 | 170 | - Replace `impl EncodeMetric for RefCell` with a new type `ConstFamily` implementing `EncodeMetric`. 171 | 172 | ## [0.20.0] 173 | 174 | ### Added 175 | 176 | - Introduce `Collector` abstraction allowing users to provide additional metrics 177 | and their description on each scrape. See [PR 82]. 178 | 179 | - Introduce a `#[prometheus(flatten)]` attribute which can be used when deriving `EncodeLabelSet`, allowing 180 | a nested struct to be flattened during encoding. See [PR 118]. 181 | 182 | For example: 183 | 184 | ```rust 185 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 186 | struct CommonLabels { 187 | a: u64, 188 | b: u64, 189 | } 190 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] 191 | struct Labels { 192 | unique: u64, 193 | #[prometheus(flatten)] 194 | common: CommonLabels, 195 | } 196 | ``` 197 | 198 | Would be encoded as: 199 | 200 | ``` 201 | my_metric{a="42",b="42",unique="42"} 42 202 | ``` 203 | 204 | ### Fixed 205 | 206 | - Fix label encoding in protobuf feature. See [PR 123]. 207 | 208 | [PR 82]: https://github.com/prometheus/client_rust/pull/82 209 | [PR 118]: https://github.com/prometheus/client_rust/pull/118 210 | [PR 123]: https://github.com/prometheus/client_rust/pull/123 211 | 212 | ## [0.19.0] 213 | 214 | This is a large release including multiple breaking changes. Major user-facing 215 | improvement of this release is support for the OpenMetrics Protobuf format. 216 | 217 | ### Upgrade guide: 218 | 219 | - Don't box before registering. 220 | 221 | ```diff 222 | registry.register( 223 | "my_metric", 224 | "This is my metric", 225 | - Box::new(my_metric.clone()), 226 | + my_metric.clone(), 227 | ); 228 | ``` 229 | 230 | - Gauge uses `i64` instead of `u64`. 231 | 232 | ```diff 233 | my_gauge 234 | - .set(42u64); 235 | + .set(42i64); 236 | ``` 237 | 238 | - Derive `EncodeLabelSet` for `struct` and `EncodeLabelValue` for `enum` instead of just `Encode` for all and require `Debug`. 239 | 240 | ```diff 241 | - #[derive(Clone, Hash, PartialEq, Eq, Encode)] 242 | + #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] 243 | struct Labels { 244 | path: String, 245 | method: Method, 246 | some_number: u64, 247 | } 248 | 249 | - #[derive(Clone, Hash, PartialEq, Eq, Encode)] 250 | + #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] 251 | enum Method { 252 | Get, 253 | #[allow(dead_code)] 254 | Put, 255 | } 256 | ``` 257 | 258 | - Encode as utf-8 and not as `[u8]`. 259 | 260 | ```diff 261 | - let mut buffer = vec![]; 262 | + let mut buffer = String::new(); 263 | encode(&mut buffer, ®istry).unwrap(); 264 | ``` 265 | 266 | For details on each of these, see changelog entries below. 267 | 268 | ### Added 269 | 270 | - Added support for the OpenMetrics protobuf format. See [PR 83]. 271 | - Added a `remove` method to `Family` to allow the removal of a specified label 272 | set from a family. See [PR 85]. 273 | - Added a `clear` method to `Family` to allow the removal of all label sets 274 | from a family. See [PR 85]. 275 | - Impl `TypedMetric` for `CounterWithExemplar` and `HistogramWithExemplar`, so that they can be used with `Family`. See [PR 96]. 276 | 277 | ### Changed 278 | 279 | - Always use dynamic dispatch on `Registry`, i.e. remove generic type parameter `M` from `Registry`. See [PR 105]. 280 | - Refactor encoding. See [PR 105]. 281 | - Introducing separate traits to encode 282 | - value (e.g. `EncodeCounterValue`) 283 | - label set (`EncodeLabelSet`), derivable for structs via `prometheus-client-derive-encode` 284 | - label (`EncodeLabel`) 285 | - label key (`EncodeLabelKey`) 286 | - label value (`EncodeLabelValue`), derivable for enums via `prometheus-client-derive-encode` 287 | - Encode as UTF-8 strings, not bytes. I.e. use `std::fmt::Write` instead of `std::io::Write`. 288 | - Use signed integers for `Gauge` for compliance with OpenMetrics protobuf 289 | format. See [PR 105]. 290 | 291 | [PR 83]: https://github.com/prometheus/client_rust/pull/83 292 | [PR 85]: https://github.com/prometheus/client_rust/pull/85 293 | [PR 96]: https://github.com/prometheus/client_rust/pull/96 294 | [PR 105]: https://github.com/prometheus/client_rust/pull/105 295 | 296 | ## [0.18.1] 297 | 298 | ### Fixed 299 | 300 | - Fix race condition in `Family::get_or_create`. See [PR 102]. 301 | 302 | [PR 102]: https://github.com/prometheus/client_rust/pull/102 303 | 304 | ## [0.18.0] 305 | 306 | ### Changed 307 | 308 | - Use `parking_lot` instead of `std::sync::*`. 309 | 310 | Before `proemtheus-client` would use the `owning_ref` crate to map the target 311 | of a `std::sync::RwLockReadGuard`. `owning_ref` has multiple unsoundness 312 | issues, see https://rustsec.org/advisories/RUSTSEC-2022-0040.html. Instead of 313 | replacing `owning_ref` with a similar crate, we switch to locking via 314 | `parking_lot` which supports the above mapping natively. 315 | 316 | See [PR 78] and [issue 77]. 317 | 318 | [PR 78]: https://github.com/prometheus/client_rust/pull/78 319 | [issue 77]: https://github.com/prometheus/client_rust/issues/77 320 | 321 | ## [0.17.0] 322 | 323 | ### Changed 324 | - Updates to Rust 2021 Edition. See [PR 65]. 325 | 326 | ### Added 327 | - Added a `with_prefix` method to `Registry` to allow initializing a registry with a prefix. See [PR 70]. 328 | - Added `Debug` implementations on most public types that were missing them. See [PR 71]. 329 | - Added example for actix-web framework. See [PR 76]. 330 | 331 | ### Removed 332 | - Remove `Add` trait implementation for a private type which lead to compile time conflicts with existing `Add` implementations e.g. on `String`. See [PR 69]. 333 | 334 | [PR 65]: https://github.com/prometheus/client_rust/pull/65 335 | [PR 69]: https://github.com/prometheus/client_rust/pull/69 336 | [PR 70]: https://github.com/prometheus/client_rust/pull/70 337 | [PR 71]: https://github.com/prometheus/client_rust/pull/71 338 | [PR 76]: https://github.com/prometheus/client_rust/pull/76 339 | 340 | ## [0.16.0] 341 | 342 | ### Changed 343 | 344 | - Require `Registry` default generic type `SendEncodeMetric` to be `Sync`. See [PR 58]. 345 | 346 | [PR 58]: https://github.com/prometheus/client_rust/pull/58 347 | 348 | ## [0.15.1] - 2022-02-04 349 | 350 | ### Added 351 | 352 | - Expose `Encoder` methods. See [PR 41]. 353 | 354 | ### Changed 355 | 356 | - Use `AtomicU32` on platforms that don't support `AtomicU64`. See [PR 42]. 357 | 358 | [PR 41]: https://github.com/prometheus/client_rust/pull/41 359 | [PR 42]: https://github.com/prometheus/client_rust/pull/42 360 | 361 | ## [0.15.0] - 2022-01-16 362 | 363 | ### Changed 364 | 365 | - Release as `prometheus-client` and `prometheus-client-derive-text-encode`. 366 | 367 | ## [0.14.0] - 2021-12-29 368 | 369 | ### Changed 370 | 371 | - Update to `itoa` `v1`. See [PR 28]. 372 | - Update to `dtoa` `v1`. See [PR 27]. 373 | 374 | ### Added 375 | 376 | - Implement `Gauge::dec` and `Gauge::dec_by`. See [PR 30]. 377 | 378 | [PR 28]: https://github.com/prometheus/client_rust/pull/28 379 | [PR 27]: https://github.com/prometheus/client_rust/pull/27 380 | [PR 30]: https://github.com/prometheus/client_rust/pull/30 381 | 382 | ## [0.13.0] - 2021-11-21 383 | 384 | _Note: This was initially released as `v0.12.1` but later on yanked due to it 385 | including a breaking change. See [PR 24] for details._ 386 | 387 | ### Added 388 | 389 | - Allow family to use constructors that do not coerce to function pointers. See [PR 21]. 390 | 391 | [PR 21]: https://github.com/prometheus/client_rust/pull/21 392 | [PR 24]: https://github.com/prometheus/client_rust/pull/24 393 | 394 | ## [0.12.0] - 2021-08-07 395 | 396 | ### Added 397 | 398 | - Add `Registry::sub_registry_with_label`. See [PR 20]. 399 | 400 | ### Changed 401 | 402 | - Rename `Registry::sub_registry` to `Registry::sub_registry_with_prefix`. See 403 | [PR 20]. 404 | 405 | [PR 20]: https://github.com/prometheus/client_rust/pull/20 406 | 407 | ## [0.11.2] - 2021-06-09 408 | ### Fixed 409 | - Do not separate labels with spaces. 410 | 411 | ## [0.11.1] - 2021-06-08 412 | ### Fixed 413 | - Encode Info metric labels. 414 | 415 | ## [0.11.0] - 2021-06-08 416 | ### Added 417 | - Add support for OpenMetrics Info metrics (see [PR 18]). 418 | 419 | [PR 18]: https://github.com/prometheus/client_rust/pull/18 420 | 421 | ## [0.10.1] - 2021-05-31 422 | ### Added 423 | - Implement `Encode` for `u32`. 424 | 425 | ### Fixed 426 | - Update to prometheus-client-derive-text-encode v0.1.1 which handles keyword 427 | identifiers aka raw identifiers 428 | 429 | https://github.com/prometheus/client_rust/pull/16 430 | 431 | ## [0.10.0] - 2021-04-29 432 | ### Added 433 | - Added `metrics::histogram::linear_buckets`. 434 | https://github.com/prometheus/client_rust/issues/13 435 | 436 | ### Changed 437 | - Renamed `metrics::histogram::exponential_series` to 438 | `metrics::histogram::exponential_buckets`. 439 | https://github.com/prometheus/client_rust/issues/13 440 | -------------------------------------------------------------------------------- /src/registry.rs: -------------------------------------------------------------------------------- 1 | //! Metric registry implementation. 2 | //! 3 | //! See [`Registry`] for details. 4 | 5 | use std::borrow::Cow; 6 | 7 | use crate::collector::Collector; 8 | use crate::encoding::{DescriptorEncoder, EncodeMetric}; 9 | 10 | /// A metric registry. 11 | /// 12 | /// First off one registers metrics with the registry via 13 | /// [`Registry::register`]. Later on the [`Registry`] is passed to an encoder 14 | /// collecting samples of each metric by iterating all metrics in the 15 | /// [`Registry`]. 16 | /// 17 | /// [`Registry`] is the core building block, generic over the metric type being 18 | /// registered. Out of convenience, the generic type parameter is set to use 19 | /// dynamic dispatching by default to be able to register different types of 20 | /// metrics (e.g. [`Counter`](crate::metrics::counter::Counter) and 21 | /// [`Gauge`](crate::metrics::gauge::Gauge)) with the same registry. Advanced 22 | /// users might want to use their custom types. 23 | /// 24 | /// ``` 25 | /// # use prometheus_client::encoding::text::encode; 26 | /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; 27 | /// # use prometheus_client::metrics::gauge::{Atomic as _, Gauge}; 28 | /// # use prometheus_client::registry::Registry; 29 | /// # 30 | /// // Create a metric registry. 31 | /// let mut registry = Registry::default(); 32 | /// 33 | /// let counter: Counter = Counter::default(); 34 | /// let gauge: Gauge = Gauge::default(); 35 | /// 36 | /// registry.register( 37 | /// "my_counter", 38 | /// "This is my counter", 39 | /// counter.clone(), 40 | /// ); 41 | /// registry.register( 42 | /// "my_gauge", 43 | /// "This is my gauge", 44 | /// gauge.clone(), 45 | /// ); 46 | /// 47 | /// # // Encode all metrics in the registry in the text format. 48 | /// # let mut buffer = String::new(); 49 | /// # encode(&mut buffer, ®istry).unwrap(); 50 | /// # 51 | /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + 52 | /// # "# TYPE my_counter counter\n" + 53 | /// # "my_counter_total 0\n" + 54 | /// # "# HELP my_gauge This is my gauge.\n" + 55 | /// # "# TYPE my_gauge gauge\n" + 56 | /// # "my_gauge 0\n" + 57 | /// # "# EOF\n"; 58 | /// # assert_eq!(expected, buffer); 59 | /// ``` 60 | #[derive(Debug, Default)] 61 | pub struct Registry { 62 | prefix: Option, 63 | labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, 64 | metrics: Vec<(Descriptor, Box)>, 65 | collectors: Vec>, 66 | sub_registries: Vec, 67 | } 68 | 69 | impl Registry { 70 | /// Creates a new default [`Registry`] with the given prefix. 71 | pub fn with_prefix(prefix: impl Into) -> Self { 72 | Self { 73 | prefix: Some(Prefix(prefix.into())), 74 | ..Default::default() 75 | } 76 | } 77 | 78 | /// Creates a new default [`Registry`] with the given labels. 79 | pub fn with_labels( 80 | labels: impl Iterator, Cow<'static, str>)>, 81 | ) -> Self { 82 | Self { 83 | labels: labels.into_iter().collect(), 84 | ..Default::default() 85 | } 86 | } 87 | 88 | /// Creates a new default [`Registry`] with the given prefix and labels. 89 | pub fn with_prefix_and_labels( 90 | prefix: impl Into, 91 | labels: impl Iterator, Cow<'static, str>)>, 92 | ) -> Self { 93 | Self { 94 | prefix: Some(Prefix(prefix.into())), 95 | labels: labels.into_iter().collect(), 96 | ..Default::default() 97 | } 98 | } 99 | 100 | /// Register a metric with the [`Registry`]. 101 | /// 102 | /// Note: In the Open Metrics text exposition format some metric types have 103 | /// a special suffix, e.g. the 104 | /// [`Counter`](crate::metrics::counter::Counter`) metric with `_total`. 105 | /// These suffixes are inferred through the metric type and must not be 106 | /// appended to the metric name manually by the user. 107 | /// 108 | /// Note: A full stop punctuation mark (`.`) is automatically added to the 109 | /// passed help text. 110 | /// 111 | /// Use [`Registry::register_with_unit`] whenever a unit for the given 112 | /// metric is known. 113 | /// 114 | /// ``` 115 | /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; 116 | /// # use prometheus_client::registry::{Registry, Unit}; 117 | /// # 118 | /// let mut registry = Registry::default(); 119 | /// let counter: Counter = Counter::default(); 120 | /// 121 | /// registry.register("my_counter", "This is my counter", counter.clone()); 122 | /// ``` 123 | pub fn register, H: Into>( 124 | &mut self, 125 | name: N, 126 | help: H, 127 | metric: impl Metric, 128 | ) { 129 | self.priv_register(name, help, metric, None) 130 | } 131 | 132 | /// Register a metric with the [`Registry`] specifying the metric's unit. 133 | /// 134 | /// See [`Registry::register`] for additional documentation. 135 | /// 136 | /// Note: In the Open Metrics text exposition format units are appended to 137 | /// the metric name. This is done automatically. Users must not append the 138 | /// unit to the name manually. 139 | /// 140 | /// ``` 141 | /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; 142 | /// # use prometheus_client::registry::{Registry, Unit}; 143 | /// # 144 | /// let mut registry = Registry::default(); 145 | /// let counter: Counter = Counter::default(); 146 | /// 147 | /// registry.register_with_unit( 148 | /// "my_counter", 149 | /// "This is my counter", 150 | /// Unit::Seconds, 151 | /// counter.clone(), 152 | /// ); 153 | /// ``` 154 | pub fn register_with_unit, H: Into>( 155 | &mut self, 156 | name: N, 157 | help: H, 158 | unit: Unit, 159 | metric: impl Metric, 160 | ) { 161 | self.priv_register(name, help, metric, Some(unit)) 162 | } 163 | 164 | fn priv_register, H: Into>( 165 | &mut self, 166 | name: N, 167 | help: H, 168 | metric: impl Metric, 169 | unit: Option, 170 | ) { 171 | let descriptor = Descriptor::new(name, help, unit); 172 | self.metrics.push((descriptor, Box::new(metric))); 173 | } 174 | 175 | /// Register a [`Collector`]. 176 | /// 177 | /// ``` 178 | /// # use prometheus_client::metrics::counter::ConstCounter; 179 | /// # use prometheus_client::registry::Registry; 180 | /// # use prometheus_client::collector::Collector; 181 | /// # use prometheus_client::encoding::{DescriptorEncoder, EncodeMetric}; 182 | /// # 183 | /// #[derive(Debug)] 184 | /// struct MyCollector {} 185 | /// 186 | /// impl Collector for MyCollector { 187 | /// fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { 188 | /// let counter = ConstCounter::new(42u64); 189 | /// let metric_encoder = encoder.encode_descriptor( 190 | /// "my_counter", 191 | /// "some help", 192 | /// None, 193 | /// counter.metric_type(), 194 | /// )?; 195 | /// counter.encode(metric_encoder)?; 196 | /// Ok(()) 197 | /// } 198 | /// } 199 | /// 200 | /// let my_collector = Box::new(MyCollector{}); 201 | /// 202 | /// let mut registry = Registry::default(); 203 | /// 204 | /// registry.register_collector(my_collector); 205 | /// ``` 206 | pub fn register_collector(&mut self, collector: Box) { 207 | self.collectors.push(collector); 208 | } 209 | 210 | /// Create a sub-registry to register metrics with a common prefix. 211 | /// 212 | /// Say you would like to prefix one set of metrics with `subsystem_a` and 213 | /// one set of metrics with `subsystem_b`. Instead of prefixing each metric 214 | /// with the corresponding subsystem string individually, you can create two 215 | /// sub-registries like demonstrated below. 216 | /// 217 | /// This can be used to pass a prefixed sub-registry down to a subsystem of 218 | /// your architecture automatically adding a prefix to each metric the 219 | /// subsystem registers. 220 | /// 221 | /// ``` 222 | /// # use prometheus_client::metrics::counter::{Atomic as _, Counter}; 223 | /// # use prometheus_client::registry::{Registry, Unit}; 224 | /// # 225 | /// let mut registry = Registry::default(); 226 | /// 227 | /// let subsystem_a_counter_1: Counter = Counter::default(); 228 | /// let subsystem_a_counter_2: Counter = Counter::default(); 229 | /// 230 | /// let subsystem_a_registry = registry.sub_registry_with_prefix("subsystem_a"); 231 | /// subsystem_a_registry.register("counter_1", "", subsystem_a_counter_1.clone()); 232 | /// subsystem_a_registry.register("counter_2", "", subsystem_a_counter_2.clone()); 233 | /// 234 | /// let subsystem_b_counter_1: Counter = Counter::default(); 235 | /// let subsystem_b_counter_2: Counter = Counter::default(); 236 | /// 237 | /// let subsystem_b_registry = registry.sub_registry_with_prefix("subsystem_b"); 238 | /// subsystem_b_registry.register("counter_1", "", subsystem_b_counter_1.clone()); 239 | /// subsystem_b_registry.register("counter_2", "", subsystem_b_counter_2.clone()); 240 | /// ``` 241 | /// 242 | /// See [`Registry::sub_registry_with_label`] for the same functionality, 243 | /// but namespacing with a label instead of a metric name prefix. 244 | pub fn sub_registry_with_prefix>(&mut self, prefix: P) -> &mut Self { 245 | let sub_registry = Registry { 246 | prefix: Some(Prefix( 247 | self.prefix.clone().map(|p| p.0 + "_").unwrap_or_default() + prefix.as_ref(), 248 | )), 249 | labels: self.labels.clone(), 250 | ..Default::default() 251 | }; 252 | 253 | self.priv_sub_registry(sub_registry) 254 | } 255 | 256 | /// Like [`Registry::sub_registry_with_prefix`] but with a label instead. 257 | pub fn sub_registry_with_label( 258 | &mut self, 259 | label: (Cow<'static, str>, Cow<'static, str>), 260 | ) -> &mut Self { 261 | self.sub_registry_with_labels(std::iter::once(label)) 262 | } 263 | 264 | /// Like [`Registry::sub_registry_with_prefix`] but with multiple labels instead. 265 | pub fn sub_registry_with_labels( 266 | &mut self, 267 | labels: impl Iterator, Cow<'static, str>)>, 268 | ) -> &mut Self { 269 | let mut new_labels = self.labels.clone(); 270 | new_labels.extend(labels); 271 | 272 | let sub_registry = Registry { 273 | prefix: self.prefix.clone(), 274 | labels: new_labels, 275 | ..Default::default() 276 | }; 277 | 278 | self.priv_sub_registry(sub_registry) 279 | } 280 | 281 | fn priv_sub_registry(&mut self, sub_registry: Self) -> &mut Self { 282 | self.sub_registries.push(sub_registry); 283 | 284 | self.sub_registries 285 | .last_mut() 286 | .expect("sub_registries not to be empty.") 287 | } 288 | 289 | pub(crate) fn encode(&self, encoder: &mut DescriptorEncoder) -> Result<(), std::fmt::Error> { 290 | for (descriptor, metric) in self.metrics.iter().filter(|(_, m)| !m.is_empty()) { 291 | let mut descriptor_encoder = 292 | encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels); 293 | let metric_encoder = descriptor_encoder.encode_descriptor( 294 | &descriptor.name, 295 | &descriptor.help, 296 | descriptor.unit.as_ref(), 297 | EncodeMetric::metric_type(metric.as_ref()), 298 | )?; 299 | metric.encode(metric_encoder)?; 300 | } 301 | 302 | for collector in self.collectors.iter() { 303 | let descriptor_encoder = 304 | encoder.with_prefix_and_labels(self.prefix.as_ref(), &self.labels); 305 | collector.encode(descriptor_encoder)?; 306 | } 307 | 308 | for registry in self.sub_registries.iter() { 309 | registry.encode(encoder)?; 310 | } 311 | 312 | Ok(()) 313 | } 314 | } 315 | 316 | /// Metric prefix 317 | #[derive(Clone, Debug)] 318 | pub(crate) struct Prefix(String); 319 | 320 | impl Prefix { 321 | pub(crate) fn as_str(&self) -> &str { 322 | self.0.as_str() 323 | } 324 | } 325 | 326 | impl From for Prefix { 327 | fn from(s: String) -> Self { 328 | Prefix(s) 329 | } 330 | } 331 | 332 | /// OpenMetrics metric descriptor. 333 | #[derive(Debug, Clone)] 334 | struct Descriptor { 335 | name: String, 336 | help: String, 337 | unit: Option, 338 | } 339 | 340 | impl Descriptor { 341 | /// Create new [`Descriptor`]. 342 | fn new, H: Into>(name: N, help: H, unit: Option) -> Self { 343 | Self { 344 | name: name.into(), 345 | help: help.into() + ".", 346 | unit, 347 | } 348 | } 349 | } 350 | 351 | /// Metric units recommended by Open Metrics. 352 | /// 353 | /// See [`Unit::Other`] to specify alternative units. 354 | #[derive(Debug, Clone)] 355 | #[allow(missing_docs)] 356 | pub enum Unit { 357 | Amperes, 358 | Bytes, 359 | Celsius, 360 | Grams, 361 | Joules, 362 | Meters, 363 | Ratios, 364 | Seconds, 365 | Volts, 366 | Other(String), 367 | } 368 | 369 | impl Unit { 370 | /// Returns the given Unit's str representation. 371 | pub fn as_str(&self) -> &str { 372 | match self { 373 | Unit::Amperes => "amperes", 374 | Unit::Bytes => "bytes", 375 | Unit::Celsius => "celsius", 376 | Unit::Grams => "grams", 377 | Unit::Joules => "joules", 378 | Unit::Meters => "meters", 379 | Unit::Ratios => "ratios", 380 | Unit::Seconds => "seconds", 381 | Unit::Volts => "volts", 382 | Unit::Other(other) => other.as_str(), 383 | } 384 | } 385 | } 386 | 387 | /// Super trait representing an abstract Prometheus metric. 388 | pub trait Metric: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static {} 389 | 390 | impl Metric for T where T: crate::encoding::EncodeMetric + Send + Sync + std::fmt::Debug + 'static 391 | {} 392 | -------------------------------------------------------------------------------- /src/metrics/family.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing an Open Metrics metric family. 2 | //! 3 | //! See [`Family`] for details. 4 | 5 | use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder}; 6 | 7 | use super::{MetricType, TypedMetric}; 8 | use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; 9 | use std::collections::HashMap; 10 | use std::sync::Arc; 11 | 12 | /// Representation of the OpenMetrics *MetricFamily* data type. 13 | /// 14 | /// A [`Family`] is a set of metrics with the same name, help text and 15 | /// type, differentiated by their label values thus spanning a multidimensional 16 | /// space. 17 | /// 18 | /// # Generic over the label set 19 | /// 20 | /// A [`Family`] is generic over the label type. For convenience one might 21 | /// choose a `Vec<(String, String)>`, for performance and/or type safety one might 22 | /// define a custom type. 23 | /// 24 | /// ## Examples 25 | /// 26 | /// ### [`Family`] with `Vec<(String, String)>` for convenience 27 | /// 28 | /// ``` 29 | /// # use prometheus_client::encoding::text::encode; 30 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 31 | /// # use prometheus_client::metrics::family::Family; 32 | /// # use prometheus_client::registry::Registry; 33 | /// # 34 | /// # let mut registry = Registry::default(); 35 | /// let family = Family::, Counter>::default(); 36 | /// # registry.register( 37 | /// # "my_counter", 38 | /// # "This is my counter", 39 | /// # family.clone(), 40 | /// # ); 41 | /// 42 | /// // Record a single HTTP GET request. 43 | /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 44 | /// 45 | /// # // Encode all metrics in the registry in the text format. 46 | /// # let mut buffer = String::new(); 47 | /// # encode(&mut buffer, ®istry).unwrap(); 48 | /// # 49 | /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + 50 | /// # "# TYPE my_counter counter\n" + 51 | /// # "my_counter_total{method=\"GET\"} 1\n" + 52 | /// # "# EOF\n"; 53 | /// # assert_eq!(expected, buffer); 54 | /// ``` 55 | /// 56 | /// ### [`Family`] with custom type for performance and/or type safety 57 | /// 58 | /// Using `EncodeLabelSet` and `EncodeLabelValue` derive macro to generate 59 | /// [`EncodeLabelSet`] for `struct`s and 60 | /// [`EncodeLabelValue`](crate::encoding::EncodeLabelValue) for `enum`s. 61 | /// 62 | /// ``` 63 | /// # use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; 64 | /// # use prometheus_client::encoding::text::encode; 65 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 66 | /// # use prometheus_client::metrics::family::Family; 67 | /// # use prometheus_client::registry::Registry; 68 | /// # use std::io::Write; 69 | /// # 70 | /// # let mut registry = Registry::default(); 71 | /// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 72 | /// struct Labels { 73 | /// method: Method, 74 | /// }; 75 | /// 76 | /// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 77 | /// enum Method { 78 | /// GET, 79 | /// PUT, 80 | /// }; 81 | /// 82 | /// let family = Family::::default(); 83 | /// # registry.register( 84 | /// # "my_counter", 85 | /// # "This is my counter", 86 | /// # family.clone(), 87 | /// # ); 88 | /// 89 | /// // Record a single HTTP GET request. 90 | /// family.get_or_create(&Labels { method: Method::GET }).inc(); 91 | /// # 92 | /// # // Encode all metrics in the registry in the text format. 93 | /// # let mut buffer = String::new(); 94 | /// # encode(&mut buffer, ®istry).unwrap(); 95 | /// # 96 | /// # let expected = "# HELP my_counter This is my counter.\n".to_owned() + 97 | /// # "# TYPE my_counter counter\n" + 98 | /// # "my_counter_total{method=\"GET\"} 1\n" + 99 | /// # "# EOF\n"; 100 | /// # assert_eq!(expected, buffer); 101 | /// ``` 102 | // TODO: Consider exposing hash algorithm. 103 | pub struct Family M> { 104 | metrics: Arc>>, 105 | /// Function that when called constructs a new metric. 106 | /// 107 | /// For most metric types this would simply be its [`Default`] 108 | /// implementation set through [`Family::default`]. For metric types that 109 | /// need custom construction logic like 110 | /// [`Histogram`](crate::metrics::histogram::Histogram) in order to set 111 | /// specific buckets, a custom constructor is set via 112 | /// [`Family::new_with_constructor`]. 113 | constructor: C, 114 | } 115 | 116 | impl std::fmt::Debug for Family { 117 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 118 | f.debug_struct("Family") 119 | .field("metrics", &self.metrics) 120 | .finish() 121 | } 122 | } 123 | 124 | /// A constructor for creating new metrics in a [`Family`] when calling 125 | /// [`Family::get_or_create`]. Such constructor is provided via 126 | /// [`Family::new_with_constructor`]. 127 | /// 128 | /// This is mostly used when creating histograms using constructors that need to 129 | /// capture variables. 130 | /// 131 | /// ``` 132 | /// # use prometheus_client::metrics::family::{Family, MetricConstructor}; 133 | /// # use prometheus_client::metrics::histogram::Histogram; 134 | /// struct CustomBuilder { 135 | /// buckets: Vec, 136 | /// } 137 | /// 138 | /// impl MetricConstructor for CustomBuilder { 139 | /// fn new_metric(&self) -> Histogram { 140 | /// // When a new histogram is created, this function will be called. 141 | /// Histogram::new(self.buckets.iter().cloned()) 142 | /// } 143 | /// } 144 | /// 145 | /// let custom_builder = CustomBuilder { buckets: vec![0.0, 10.0, 100.0] }; 146 | /// let metric = Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder); 147 | /// ``` 148 | pub trait MetricConstructor { 149 | /// Create a new instance of the metric type. 150 | fn new_metric(&self) -> M; 151 | } 152 | 153 | /// In cases in which the explicit type of the metric is not required, it is 154 | /// posible to directly provide a closure even if it captures variables. 155 | /// 156 | /// ``` 157 | /// # use prometheus_client::metrics::family::{Family}; 158 | /// # use prometheus_client::metrics::histogram::Histogram; 159 | /// let custom_buckets = [0.0, 10.0, 100.0]; 160 | /// let metric = Family::<(), Histogram, _>::new_with_constructor(|| { 161 | /// Histogram::new(custom_buckets.into_iter()) 162 | /// }); 163 | /// # metric.get_or_create(&()); 164 | /// ``` 165 | impl M> MetricConstructor for F { 166 | fn new_metric(&self) -> M { 167 | self() 168 | } 169 | } 170 | 171 | impl Default for Family { 172 | fn default() -> Self { 173 | Self { 174 | metrics: Arc::new(RwLock::new(Default::default())), 175 | constructor: M::default, 176 | } 177 | } 178 | } 179 | 180 | impl Family { 181 | /// Create a metric family using a custom constructor to construct new 182 | /// metrics. 183 | /// 184 | /// When calling [`Family::get_or_create`] a [`Family`] needs to be able to 185 | /// construct a new metric in case none exists for the given label set. In 186 | /// most cases, e.g. for [`Counter`](crate::metrics::counter::Counter) 187 | /// [`Family`] can just use the [`Default::default`] implementation for the 188 | /// metric type. For metric types such as 189 | /// [`Histogram`](crate::metrics::histogram::Histogram) one might want 190 | /// [`Family`] to construct a 191 | /// [`Histogram`](crate::metrics::histogram::Histogram) with custom buckets 192 | /// (see example below). For such case one can use this method. For more 193 | /// involved constructors see [`MetricConstructor`]. 194 | /// 195 | /// ``` 196 | /// # use prometheus_client::metrics::family::Family; 197 | /// # use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; 198 | /// Family::, Histogram>::new_with_constructor(|| { 199 | /// Histogram::new(exponential_buckets(1.0, 2.0, 10)) 200 | /// }); 201 | /// ``` 202 | pub fn new_with_constructor(constructor: C) -> Self { 203 | Self { 204 | metrics: Arc::new(RwLock::new(Default::default())), 205 | constructor, 206 | } 207 | } 208 | } 209 | 210 | impl> Family 211 | where 212 | S: Clone + std::hash::Hash + Eq, 213 | M: Clone, 214 | C: MetricConstructor, 215 | { 216 | /// Access a metric with the given label set, creating it if one does not yet exist. 217 | /// 218 | /// ``` 219 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 220 | /// # use prometheus_client::metrics::family::Family; 221 | /// # 222 | /// let family = Family::, Counter>::default(); 223 | /// 224 | /// // Will create and return the metric with label `method="GET"` when first called. 225 | /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 226 | /// 227 | /// // Will return a clone of the existing metric on all subsequent calls. 228 | /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 229 | /// ``` 230 | /// 231 | /// Callers wishing to avoid a clone of the metric `M` can call [`Family::get_or_create()`] to 232 | /// return a reference to the metric instead. 233 | pub fn get_or_create_owned(&self, label_set: &S) -> M { 234 | use std::ops::Deref; 235 | 236 | let guard = self.get_or_create(label_set); 237 | let metric = guard.deref().to_owned(); 238 | drop(guard); 239 | 240 | metric 241 | } 242 | } 243 | 244 | impl> Family { 245 | /// Access a metric with the given label set, creating it if one does not 246 | /// yet exist. 247 | /// 248 | /// ``` 249 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 250 | /// # use prometheus_client::metrics::family::Family; 251 | /// # 252 | /// let family = Family::, Counter>::default(); 253 | /// 254 | /// // Will create the metric with label `method="GET"` on first call and 255 | /// // return a reference. 256 | /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 257 | /// 258 | /// // Will return a reference to the existing metric on all subsequent 259 | /// // calls. 260 | /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 261 | /// ``` 262 | /// 263 | /// NB: This method can cause deadlocks if multiple metrics within this family are read at 264 | /// once. Use [`Family::get_or_create_owned()`] if you would like to avoid this by cloning the 265 | /// metric `M`. 266 | pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<'_, M> { 267 | if let Some(metric) = self.get(label_set) { 268 | return metric; 269 | } 270 | 271 | let mut write_guard = self.metrics.write(); 272 | 273 | write_guard 274 | .entry(label_set.clone()) 275 | .or_insert_with(|| self.constructor.new_metric()); 276 | 277 | let read_guard = RwLockWriteGuard::downgrade(write_guard); 278 | 279 | RwLockReadGuard::map(read_guard, |metrics| { 280 | metrics 281 | .get(label_set) 282 | .expect("Metric to exist after creating it.") 283 | }) 284 | } 285 | 286 | /// Access a metric with the given label set, returning None if one 287 | /// does not yet exist. 288 | /// 289 | /// ``` 290 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 291 | /// # use prometheus_client::metrics::family::Family; 292 | /// # 293 | /// let family = Family::, Counter>::default(); 294 | /// 295 | /// if let Some(metric) = family.get(&vec![("method".to_owned(), "GET".to_owned())]) { 296 | /// metric.inc(); 297 | /// }; 298 | /// ``` 299 | pub fn get(&self, label_set: &S) -> Option> { 300 | RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set)).ok() 301 | } 302 | 303 | /// Remove a label set from the metric family. 304 | /// 305 | /// Returns a bool indicating if a label set was removed or not. 306 | /// 307 | /// ``` 308 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 309 | /// # use prometheus_client::metrics::family::Family; 310 | /// # 311 | /// let family = Family::, Counter>::default(); 312 | /// 313 | /// // Will create the metric with label `method="GET"` on first call and 314 | /// // return a reference. 315 | /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 316 | /// 317 | /// // Will return `true`, indicating that the `method="GET"` label set was 318 | /// // removed. 319 | /// assert!(family.remove(&vec![("method".to_owned(), "GET".to_owned())])); 320 | /// ``` 321 | pub fn remove(&self, label_set: &S) -> bool { 322 | self.metrics.write().remove(label_set).is_some() 323 | } 324 | 325 | /// Clear all label sets from the metric family. 326 | /// 327 | /// ``` 328 | /// # use prometheus_client::metrics::counter::{Atomic, Counter}; 329 | /// # use prometheus_client::metrics::family::Family; 330 | /// # 331 | /// let family = Family::, Counter>::default(); 332 | /// 333 | /// // Will create the metric with label `method="GET"` on first call and 334 | /// // return a reference. 335 | /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); 336 | /// 337 | /// // Clear the family of all label sets. 338 | /// family.clear(); 339 | /// ``` 340 | pub fn clear(&self) { 341 | self.metrics.write().clear() 342 | } 343 | 344 | pub(crate) fn read(&self) -> RwLockReadGuard<'_, HashMap> { 345 | self.metrics.read() 346 | } 347 | } 348 | 349 | impl Clone for Family { 350 | fn clone(&self) -> Self { 351 | Family { 352 | metrics: self.metrics.clone(), 353 | constructor: self.constructor.clone(), 354 | } 355 | } 356 | } 357 | 358 | impl TypedMetric for Family { 359 | const TYPE: MetricType = ::TYPE; 360 | } 361 | 362 | impl EncodeMetric for Family 363 | where 364 | S: Clone + std::hash::Hash + Eq + EncodeLabelSet, 365 | M: EncodeMetric + TypedMetric, 366 | C: MetricConstructor, 367 | { 368 | fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 369 | let guard = self.read(); 370 | for (label_set, m) in guard.iter() { 371 | let encoder = encoder.encode_family(label_set)?; 372 | m.encode(encoder)?; 373 | } 374 | Ok(()) 375 | } 376 | 377 | fn metric_type(&self) -> MetricType { 378 | M::TYPE 379 | } 380 | 381 | fn is_empty(&self) -> bool { 382 | self.metrics.read().is_empty() 383 | } 384 | } 385 | 386 | #[cfg(test)] 387 | mod tests { 388 | use super::*; 389 | use crate::metrics::counter::Counter; 390 | use crate::metrics::histogram::{exponential_buckets, Histogram}; 391 | 392 | #[test] 393 | fn counter_family() { 394 | let family = Family::, Counter>::default(); 395 | 396 | family 397 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 398 | .inc(); 399 | 400 | assert_eq!( 401 | 1, 402 | family 403 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 404 | .get() 405 | ); 406 | } 407 | 408 | #[test] 409 | fn histogram_family() { 410 | Family::<(), Histogram>::new_with_constructor(|| { 411 | Histogram::new(exponential_buckets(1.0, 2.0, 10)) 412 | }); 413 | } 414 | 415 | #[test] 416 | fn histogram_family_with_struct_constructor() { 417 | struct CustomBuilder { 418 | custom_start: f64, 419 | } 420 | impl MetricConstructor for CustomBuilder { 421 | fn new_metric(&self) -> Histogram { 422 | Histogram::new(exponential_buckets(self.custom_start, 2.0, 10)) 423 | } 424 | } 425 | 426 | let custom_builder = CustomBuilder { custom_start: 1.0 }; 427 | Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder); 428 | } 429 | 430 | #[test] 431 | fn counter_family_remove() { 432 | let family = Family::, Counter>::default(); 433 | 434 | family 435 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 436 | .inc(); 437 | 438 | assert_eq!( 439 | 1, 440 | family 441 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 442 | .get() 443 | ); 444 | 445 | family 446 | .get_or_create(&vec![("method".to_string(), "POST".to_string())]) 447 | .inc_by(2); 448 | 449 | assert_eq!( 450 | 2, 451 | family 452 | .get_or_create(&vec![("method".to_string(), "POST".to_string())]) 453 | .get() 454 | ); 455 | 456 | // Attempt to remove it twice, showing it really was removed on the 457 | // first attempt. 458 | assert!(family.remove(&vec![("method".to_string(), "POST".to_string())])); 459 | assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())])); 460 | 461 | // This should make a new POST label. 462 | family 463 | .get_or_create(&vec![("method".to_string(), "POST".to_string())]) 464 | .inc(); 465 | 466 | assert_eq!( 467 | 1, 468 | family 469 | .get_or_create(&vec![("method".to_string(), "POST".to_string())]) 470 | .get() 471 | ); 472 | 473 | // GET label should have be untouched. 474 | assert_eq!( 475 | 1, 476 | family 477 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 478 | .get() 479 | ); 480 | } 481 | 482 | #[test] 483 | fn counter_family_clear() { 484 | let family = Family::, Counter>::default(); 485 | 486 | // Create a label and check it. 487 | family 488 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 489 | .inc(); 490 | 491 | assert_eq!( 492 | 1, 493 | family 494 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 495 | .get() 496 | ); 497 | 498 | // Clear it, then try recreating and checking it again. 499 | family.clear(); 500 | 501 | family 502 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 503 | .inc(); 504 | 505 | assert_eq!( 506 | 1, 507 | family 508 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 509 | .get() 510 | ); 511 | } 512 | 513 | #[test] 514 | fn test_get() { 515 | let family = Family::, Counter>::default(); 516 | 517 | // Test getting a non-existent metric. 518 | let non_existent = family.get(&vec![("method".to_string(), "GET".to_string())]); 519 | assert!(non_existent.is_none()); 520 | 521 | // Create a metric. 522 | family 523 | .get_or_create(&vec![("method".to_string(), "GET".to_string())]) 524 | .inc(); 525 | 526 | // Test getting an existing metric. 527 | let existing = family.get(&vec![("method".to_string(), "GET".to_string())]); 528 | assert!(existing.is_some()); 529 | assert_eq!(existing.unwrap().get(), 1); 530 | 531 | // Test getting a different non-existent metric. 532 | let another_non_existent = family.get(&vec![("method".to_string(), "POST".to_string())]); 533 | assert!(another_non_existent.is_none()); 534 | 535 | // Test modifying the metric through the returned reference. 536 | if let Some(metric) = family.get(&vec![("method".to_string(), "GET".to_string())]) { 537 | metric.inc(); 538 | } 539 | 540 | // Verify the modification. 541 | let modified = family.get(&vec![("method".to_string(), "GET".to_string())]); 542 | assert_eq!(modified.unwrap().get(), 2); 543 | 544 | // Test with a different label set type. 545 | let string_family = Family::::default(); 546 | string_family.get_or_create(&"test".to_string()).inc(); 547 | 548 | let string_metric = string_family.get(&"test".to_string()); 549 | assert!(string_metric.is_some()); 550 | assert_eq!(string_metric.unwrap().get(), 1); 551 | 552 | let non_existent_string = string_family.get(&"non_existent".to_string()); 553 | assert!(non_existent_string.is_none()); 554 | } 555 | 556 | /// Tests that [`Family::get_or_create_owned()`] does not cause deadlocks. 557 | #[test] 558 | fn counter_family_does_not_deadlock() { 559 | /// A structure we'll place two counters into, within a single expression. 560 | struct S { 561 | apples: Counter, 562 | oranges: Counter, 563 | } 564 | 565 | let family = Family::<(&str, &str), Counter>::default(); 566 | let s = S { 567 | apples: family.get_or_create_owned(&("kind", "apple")), 568 | oranges: family.get_or_create_owned(&("kind", "orange")), 569 | }; 570 | 571 | s.apples.inc(); 572 | s.oranges.inc_by(2); 573 | } 574 | } 575 | -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | //! Exposition format implementations. 2 | 3 | pub use prometheus_client_derive_encode::*; 4 | 5 | use crate::metrics::exemplar::Exemplar; 6 | use crate::metrics::MetricType; 7 | use crate::registry::{Prefix, Unit}; 8 | use std::borrow::Cow; 9 | use std::collections::HashMap; 10 | use std::fmt::Write; 11 | use std::ops::Deref; 12 | use std::rc::Rc; 13 | use std::sync::Arc; 14 | use std::time::{SystemTime, UNIX_EPOCH}; 15 | 16 | #[cfg(feature = "protobuf")] 17 | #[cfg_attr(docsrs, doc(cfg(feature = "protobuf")))] 18 | pub mod protobuf; 19 | pub mod text; 20 | 21 | macro_rules! for_both_mut { 22 | ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { 23 | match &mut $self.0 { 24 | $inner::Text($pattern) => $fn, 25 | #[cfg(feature = "protobuf")] 26 | $inner::Protobuf($pattern) => $fn, 27 | } 28 | }; 29 | } 30 | 31 | macro_rules! for_both { 32 | ($self:expr, $inner:ident, $pattern:pat, $fn:expr) => { 33 | match $self.0 { 34 | $inner::Text($pattern) => $fn, 35 | #[cfg(feature = "protobuf")] 36 | $inner::Protobuf($pattern) => $fn, 37 | } 38 | }; 39 | } 40 | 41 | /// Trait implemented by each metric type, e.g. 42 | /// [`Counter`](crate::metrics::counter::Counter), to implement its encoding in 43 | /// the OpenMetric text format. 44 | pub trait EncodeMetric { 45 | /// Encode the given instance in the OpenMetrics text encoding. 46 | // TODO: Lifetimes on MetricEncoder needed? 47 | fn encode(&self, encoder: MetricEncoder) -> Result<(), std::fmt::Error>; 48 | 49 | /// The OpenMetrics metric type of the instance. 50 | // One can not use [`TypedMetric`] directly, as associated constants are not 51 | // object safe and thus can not be used with dynamic dispatching. 52 | fn metric_type(&self) -> MetricType; 53 | 54 | /// Check if the metric is empty. 55 | /// 56 | /// An empty metric is a metric that has no data to encode, and thus should not have any 57 | /// descriptor in the final output. 58 | /// 59 | /// By default, this returns `false`, ensuring the metric and its description is always 60 | /// encoded. 61 | fn is_empty(&self) -> bool { 62 | false 63 | } 64 | } 65 | 66 | impl EncodeMetric for Box { 67 | fn encode(&self, encoder: MetricEncoder) -> Result<(), std::fmt::Error> { 68 | self.deref().encode(encoder) 69 | } 70 | 71 | fn metric_type(&self) -> MetricType { 72 | self.deref().metric_type() 73 | } 74 | } 75 | 76 | /// Encoder for a Metric Descriptor. 77 | #[derive(Debug)] 78 | pub struct DescriptorEncoder<'a>(DescriptorEncoderInner<'a>); 79 | 80 | #[derive(Debug)] 81 | enum DescriptorEncoderInner<'a> { 82 | Text(text::DescriptorEncoder<'a>), 83 | 84 | #[cfg(feature = "protobuf")] 85 | Protobuf(protobuf::DescriptorEncoder<'a>), 86 | } 87 | 88 | impl<'a> From> for DescriptorEncoder<'a> { 89 | fn from(e: text::DescriptorEncoder<'a>) -> Self { 90 | Self(DescriptorEncoderInner::Text(e)) 91 | } 92 | } 93 | 94 | #[cfg(feature = "protobuf")] 95 | impl<'a> From> for DescriptorEncoder<'a> { 96 | fn from(e: protobuf::DescriptorEncoder<'a>) -> Self { 97 | Self(DescriptorEncoderInner::Protobuf(e)) 98 | } 99 | } 100 | 101 | impl DescriptorEncoder<'_> { 102 | pub(crate) fn with_prefix_and_labels<'s>( 103 | &'s mut self, 104 | prefix: Option<&'s Prefix>, 105 | labels: &'s [(Cow<'static, str>, Cow<'static, str>)], 106 | // TODO: result needed? 107 | ) -> DescriptorEncoder<'s> { 108 | for_both_mut!( 109 | self, 110 | DescriptorEncoderInner, 111 | e, 112 | e.with_prefix_and_labels(prefix, labels).into() 113 | ) 114 | } 115 | 116 | /// Encode a descriptor. 117 | pub fn encode_descriptor<'s>( 118 | &'s mut self, 119 | name: &'s str, 120 | help: &str, 121 | unit: Option<&'s Unit>, 122 | metric_type: MetricType, 123 | ) -> Result, std::fmt::Error> { 124 | for_both_mut!( 125 | self, 126 | DescriptorEncoderInner, 127 | e, 128 | Ok(e.encode_descriptor(name, help, unit, metric_type)?.into()) 129 | ) 130 | } 131 | } 132 | 133 | /// Encoder for a metric. 134 | #[derive(Debug)] 135 | pub struct MetricEncoder<'a>(MetricEncoderInner<'a>); 136 | 137 | #[derive(Debug)] 138 | enum MetricEncoderInner<'a> { 139 | Text(text::MetricEncoder<'a>), 140 | 141 | #[cfg(feature = "protobuf")] 142 | Protobuf(protobuf::MetricEncoder<'a>), 143 | } 144 | 145 | impl<'a> From> for MetricEncoder<'a> { 146 | fn from(e: text::MetricEncoder<'a>) -> Self { 147 | Self(MetricEncoderInner::Text(e)) 148 | } 149 | } 150 | 151 | #[cfg(feature = "protobuf")] 152 | impl<'a> From> for MetricEncoder<'a> { 153 | fn from(e: protobuf::MetricEncoder<'a>) -> Self { 154 | Self(MetricEncoderInner::Protobuf(e)) 155 | } 156 | } 157 | 158 | impl MetricEncoder<'_> { 159 | /// Encode a counter. 160 | pub fn encode_counter< 161 | S: EncodeLabelSet, 162 | CounterValue: EncodeCounterValue, 163 | ExemplarValue: EncodeExemplarValue, 164 | >( 165 | &mut self, 166 | v: &CounterValue, 167 | exemplar: Option<&Exemplar>, 168 | ) -> Result<(), std::fmt::Error> { 169 | for_both_mut!(self, MetricEncoderInner, e, e.encode_counter(v, exemplar)) 170 | } 171 | 172 | /// Encode a gauge. 173 | pub fn encode_gauge( 174 | &mut self, 175 | v: &GaugeValue, 176 | ) -> Result<(), std::fmt::Error> { 177 | for_both_mut!(self, MetricEncoderInner, e, e.encode_gauge(v)) 178 | } 179 | 180 | /// Encode an info. 181 | pub fn encode_info(&mut self, label_set: &impl EncodeLabelSet) -> Result<(), std::fmt::Error> { 182 | for_both_mut!(self, MetricEncoderInner, e, e.encode_info(label_set)) 183 | } 184 | 185 | /// Encode a histogram. 186 | pub fn encode_histogram( 187 | &mut self, 188 | sum: f64, 189 | count: u64, 190 | buckets: &[(f64, u64)], 191 | exemplars: Option<&HashMap>>, 192 | ) -> Result<(), std::fmt::Error> { 193 | for_both_mut!( 194 | self, 195 | MetricEncoderInner, 196 | e, 197 | e.encode_histogram(sum, count, buckets, exemplars) 198 | ) 199 | } 200 | 201 | /// Encode a metric family. 202 | pub fn encode_family<'s, S: EncodeLabelSet>( 203 | &'s mut self, 204 | label_set: &'s S, 205 | ) -> Result, std::fmt::Error> { 206 | for_both_mut!( 207 | self, 208 | MetricEncoderInner, 209 | e, 210 | e.encode_family(label_set).map(Into::into) 211 | ) 212 | } 213 | } 214 | 215 | /// An encodable label set. 216 | pub trait EncodeLabelSet { 217 | /// Encode oneself into the given encoder. 218 | fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error>; 219 | } 220 | 221 | /// Encoder for a label set. 222 | #[derive(Debug)] 223 | pub struct LabelSetEncoder<'a>(LabelSetEncoderInner<'a>); 224 | 225 | #[derive(Debug)] 226 | enum LabelSetEncoderInner<'a> { 227 | Text(text::LabelSetEncoder<'a>), 228 | #[cfg(feature = "protobuf")] 229 | Protobuf(protobuf::LabelSetEncoder<'a>), 230 | } 231 | 232 | impl<'a> From> for LabelSetEncoder<'a> { 233 | fn from(e: text::LabelSetEncoder<'a>) -> Self { 234 | Self(LabelSetEncoderInner::Text(e)) 235 | } 236 | } 237 | 238 | #[cfg(feature = "protobuf")] 239 | impl<'a> From> for LabelSetEncoder<'a> { 240 | fn from(e: protobuf::LabelSetEncoder<'a>) -> Self { 241 | Self(LabelSetEncoderInner::Protobuf(e)) 242 | } 243 | } 244 | 245 | impl LabelSetEncoder<'_> { 246 | /// Encode the given label. 247 | pub fn encode_label(&mut self) -> LabelEncoder<'_> { 248 | for_both_mut!(self, LabelSetEncoderInner, e, e.encode_label().into()) 249 | } 250 | } 251 | 252 | impl EncodeLabelSet for [T; N] { 253 | fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { 254 | self.as_ref().encode(encoder) 255 | } 256 | } 257 | 258 | impl EncodeLabelSet for &[T] { 259 | fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { 260 | if self.is_empty() { 261 | return Ok(()); 262 | } 263 | 264 | for label in self.iter() { 265 | let encoder = encoder.encode_label(); 266 | label.encode(encoder)? 267 | } 268 | 269 | Ok(()) 270 | } 271 | } 272 | 273 | impl EncodeLabelSet for Vec { 274 | fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { 275 | self.as_slice().encode(encoder) 276 | } 277 | } 278 | 279 | impl EncodeLabelSet for (A, B) 280 | where 281 | A: EncodeLabelSet, 282 | B: EncodeLabelSet, 283 | { 284 | fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { 285 | let (a, b) = self; 286 | 287 | a.encode(encoder)?; 288 | b.encode(encoder)?; 289 | 290 | Ok(()) 291 | } 292 | } 293 | 294 | /// Uninhabited type to represent the lack of a label set for a metric 295 | #[derive(Debug)] 296 | pub enum NoLabelSet {} 297 | 298 | impl EncodeLabelSet for NoLabelSet { 299 | fn encode(&self, _encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { 300 | Ok(()) 301 | } 302 | } 303 | 304 | /// An encodable label. 305 | pub trait EncodeLabel { 306 | /// Encode oneself into the given encoder. 307 | fn encode(&self, encoder: LabelEncoder) -> Result<(), std::fmt::Error>; 308 | } 309 | 310 | /// Encoder for a label. 311 | #[derive(Debug)] 312 | pub struct LabelEncoder<'a>(LabelEncoderInner<'a>); 313 | 314 | #[derive(Debug)] 315 | enum LabelEncoderInner<'a> { 316 | Text(text::LabelEncoder<'a>), 317 | #[cfg(feature = "protobuf")] 318 | Protobuf(protobuf::LabelEncoder<'a>), 319 | } 320 | 321 | impl<'a> From> for LabelEncoder<'a> { 322 | fn from(e: text::LabelEncoder<'a>) -> Self { 323 | Self(LabelEncoderInner::Text(e)) 324 | } 325 | } 326 | 327 | #[cfg(feature = "protobuf")] 328 | impl<'a> From> for LabelEncoder<'a> { 329 | fn from(e: protobuf::LabelEncoder<'a>) -> Self { 330 | Self(LabelEncoderInner::Protobuf(e)) 331 | } 332 | } 333 | 334 | impl LabelEncoder<'_> { 335 | /// Encode a label. 336 | pub fn encode_label_key(&mut self) -> Result, std::fmt::Error> { 337 | for_both_mut!( 338 | self, 339 | LabelEncoderInner, 340 | e, 341 | e.encode_label_key().map(Into::into) 342 | ) 343 | } 344 | } 345 | 346 | impl EncodeLabel for (K, V) { 347 | fn encode(&self, mut encoder: LabelEncoder) -> Result<(), std::fmt::Error> { 348 | let (key, value) = self; 349 | 350 | let mut label_key_encoder = encoder.encode_label_key()?; 351 | key.encode(&mut label_key_encoder)?; 352 | 353 | let mut label_value_encoder = label_key_encoder.encode_label_value()?; 354 | value.encode(&mut label_value_encoder)?; 355 | label_value_encoder.finish()?; 356 | 357 | Ok(()) 358 | } 359 | } 360 | 361 | /// An encodable label key. 362 | pub trait EncodeLabelKey { 363 | /// Encode oneself into the given encoder. 364 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error>; 365 | } 366 | 367 | /// Encoder for a label key. 368 | #[derive(Debug)] 369 | pub struct LabelKeyEncoder<'a>(LabelKeyEncoderInner<'a>); 370 | 371 | #[derive(Debug)] 372 | enum LabelKeyEncoderInner<'a> { 373 | Text(text::LabelKeyEncoder<'a>), 374 | #[cfg(feature = "protobuf")] 375 | Protobuf(protobuf::LabelKeyEncoder<'a>), 376 | } 377 | 378 | impl<'a> From> for LabelKeyEncoder<'a> { 379 | fn from(e: text::LabelKeyEncoder<'a>) -> Self { 380 | Self(LabelKeyEncoderInner::Text(e)) 381 | } 382 | } 383 | 384 | #[cfg(feature = "protobuf")] 385 | impl<'a> From> for LabelKeyEncoder<'a> { 386 | fn from(e: protobuf::LabelKeyEncoder<'a>) -> Self { 387 | Self(LabelKeyEncoderInner::Protobuf(e)) 388 | } 389 | } 390 | 391 | impl std::fmt::Write for LabelKeyEncoder<'_> { 392 | fn write_str(&mut self, s: &str) -> std::fmt::Result { 393 | for_both_mut!(self, LabelKeyEncoderInner, e, e.write_str(s)) 394 | } 395 | } 396 | 397 | impl<'a> LabelKeyEncoder<'a> { 398 | /// Encode a label value. 399 | pub fn encode_label_value(self) -> Result, std::fmt::Error> { 400 | for_both!( 401 | self, 402 | LabelKeyEncoderInner, 403 | e, 404 | e.encode_label_value().map(LabelValueEncoder::from) 405 | ) 406 | } 407 | } 408 | 409 | impl EncodeLabelKey for &str { 410 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 411 | encoder.write_str(self)?; 412 | Ok(()) 413 | } 414 | } 415 | 416 | impl EncodeLabelKey for String { 417 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 418 | EncodeLabelKey::encode(&self.as_str(), encoder) 419 | } 420 | } 421 | 422 | impl EncodeLabelKey for Cow<'_, str> { 423 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 424 | EncodeLabelKey::encode(&self.as_ref(), encoder) 425 | } 426 | } 427 | 428 | impl EncodeLabelKey for Box 429 | where 430 | T: ?Sized, 431 | for<'a> &'a T: EncodeLabelKey, 432 | { 433 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 434 | EncodeLabelKey::encode(&self.as_ref(), encoder) 435 | } 436 | } 437 | 438 | impl EncodeLabelKey for Arc 439 | where 440 | T: ?Sized, 441 | for<'a> &'a T: EncodeLabelKey, 442 | { 443 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 444 | EncodeLabelKey::encode(&self.as_ref(), encoder) 445 | } 446 | } 447 | 448 | impl EncodeLabelKey for Rc 449 | where 450 | T: ?Sized, 451 | for<'a> &'a T: EncodeLabelKey, 452 | { 453 | fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> { 454 | EncodeLabelKey::encode(&self.as_ref(), encoder) 455 | } 456 | } 457 | 458 | /// An encodable label value. 459 | pub trait EncodeLabelValue { 460 | /// Encode oneself into the given encoder. 461 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error>; 462 | } 463 | 464 | /// Encoder for a label value. 465 | #[derive(Debug)] 466 | pub struct LabelValueEncoder<'a>(LabelValueEncoderInner<'a>); 467 | 468 | #[derive(Debug)] 469 | enum LabelValueEncoderInner<'a> { 470 | Text(text::LabelValueEncoder<'a>), 471 | #[cfg(feature = "protobuf")] 472 | Protobuf(protobuf::LabelValueEncoder<'a>), 473 | } 474 | 475 | impl<'a> From> for LabelValueEncoder<'a> { 476 | fn from(e: text::LabelValueEncoder<'a>) -> Self { 477 | LabelValueEncoder(LabelValueEncoderInner::Text(e)) 478 | } 479 | } 480 | 481 | #[cfg(feature = "protobuf")] 482 | impl<'a> From> for LabelValueEncoder<'a> { 483 | fn from(e: protobuf::LabelValueEncoder<'a>) -> Self { 484 | LabelValueEncoder(LabelValueEncoderInner::Protobuf(e)) 485 | } 486 | } 487 | 488 | impl std::fmt::Write for LabelValueEncoder<'_> { 489 | fn write_str(&mut self, s: &str) -> std::fmt::Result { 490 | for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s)) 491 | } 492 | } 493 | 494 | impl LabelValueEncoder<'_> { 495 | /// Finish encoding the label value. 496 | pub fn finish(self) -> Result<(), std::fmt::Error> { 497 | for_both!(self, LabelValueEncoderInner, e, e.finish()) 498 | } 499 | } 500 | 501 | impl EncodeLabelValue for &str { 502 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 503 | encoder.write_str(self)?; 504 | Ok(()) 505 | } 506 | } 507 | 508 | impl EncodeLabelValue for String { 509 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 510 | EncodeLabelValue::encode(&self.as_str(), encoder) 511 | } 512 | } 513 | 514 | impl EncodeLabelValue for &String { 515 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 516 | EncodeLabelValue::encode(&self.as_str(), encoder) 517 | } 518 | } 519 | 520 | impl EncodeLabelValue for Cow<'_, str> { 521 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 522 | EncodeLabelValue::encode(&self.as_ref(), encoder) 523 | } 524 | } 525 | 526 | impl EncodeLabelValue for Box 527 | where 528 | T: ?Sized, 529 | for<'a> &'a T: EncodeLabelValue, 530 | { 531 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 532 | EncodeLabelValue::encode(&self.as_ref(), encoder) 533 | } 534 | } 535 | 536 | impl EncodeLabelValue for Arc 537 | where 538 | T: ?Sized, 539 | for<'a> &'a T: EncodeLabelValue, 540 | { 541 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 542 | EncodeLabelValue::encode(&self.as_ref(), encoder) 543 | } 544 | } 545 | 546 | impl EncodeLabelValue for Rc 547 | where 548 | T: ?Sized, 549 | for<'a> &'a T: EncodeLabelValue, 550 | { 551 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 552 | EncodeLabelValue::encode(&self.as_ref(), encoder) 553 | } 554 | } 555 | 556 | impl EncodeLabelValue for f64 { 557 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 558 | encoder.write_str(dtoa::Buffer::new().format(*self)) 559 | } 560 | } 561 | 562 | impl EncodeLabelValue for Option 563 | where 564 | T: EncodeLabelValue, 565 | { 566 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 567 | match self { 568 | Some(v) => EncodeLabelValue::encode(v, encoder), 569 | None => EncodeLabelValue::encode(&"", encoder), 570 | } 571 | } 572 | } 573 | 574 | impl EncodeLabelValue for bool { 575 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 576 | encoder.write_str(if *self { "true" } else { "false" }) 577 | } 578 | } 579 | 580 | macro_rules! impl_encode_label_value_for_integer { 581 | ($($t:ident),*) => {$( 582 | impl EncodeLabelValue for $t { 583 | fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> { 584 | encoder.write_str(itoa::Buffer::new().format(*self)) 585 | } 586 | } 587 | )*}; 588 | } 589 | 590 | impl_encode_label_value_for_integer!( 591 | u128, i128, u64, i64, u32, i32, u16, i16, u8, i8, usize, isize 592 | ); 593 | 594 | /// An encodable gauge value. 595 | pub trait EncodeGaugeValue { 596 | /// Encode the given instance in the OpenMetrics text encoding. 597 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error>; 598 | } 599 | 600 | impl EncodeGaugeValue for u32 { 601 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 602 | encoder.encode_u32(*self) 603 | } 604 | } 605 | 606 | impl EncodeGaugeValue for i64 { 607 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 608 | encoder.encode_i64(*self) 609 | } 610 | } 611 | 612 | impl EncodeGaugeValue for u64 { 613 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 614 | // Between forcing end users to do endless as i64 for things that are 615 | // clearly valid i64 and having one error case for rarely used protobuf when 616 | // a gauge is set to >i64::MAX, the latter seems like the right choice. 617 | encoder.encode_i64(i64::try_from(*self).map_err(|_err| std::fmt::Error)?) 618 | } 619 | } 620 | 621 | impl EncodeGaugeValue for f64 { 622 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 623 | encoder.encode_f64(*self) 624 | } 625 | } 626 | 627 | impl EncodeGaugeValue for i32 { 628 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 629 | encoder.encode_i64(i64::from(*self)) 630 | } 631 | } 632 | 633 | impl EncodeGaugeValue for f32 { 634 | fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> { 635 | encoder.encode_f64(f64::from(*self)) 636 | } 637 | } 638 | 639 | /// Encoder for a gauge value. 640 | #[derive(Debug)] 641 | pub struct GaugeValueEncoder<'a>(GaugeValueEncoderInner<'a>); 642 | 643 | #[derive(Debug)] 644 | enum GaugeValueEncoderInner<'a> { 645 | Text(text::GaugeValueEncoder<'a>), 646 | #[cfg(feature = "protobuf")] 647 | Protobuf(protobuf::GaugeValueEncoder<'a>), 648 | } 649 | 650 | impl GaugeValueEncoder<'_> { 651 | fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> { 652 | for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_u32(v)) 653 | } 654 | 655 | fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> { 656 | for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_i64(v)) 657 | } 658 | 659 | fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { 660 | for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_f64(v)) 661 | } 662 | } 663 | 664 | impl<'a> From> for GaugeValueEncoder<'a> { 665 | fn from(e: text::GaugeValueEncoder<'a>) -> Self { 666 | GaugeValueEncoder(GaugeValueEncoderInner::Text(e)) 667 | } 668 | } 669 | 670 | #[cfg(feature = "protobuf")] 671 | impl<'a> From> for GaugeValueEncoder<'a> { 672 | fn from(e: protobuf::GaugeValueEncoder<'a>) -> Self { 673 | GaugeValueEncoder(GaugeValueEncoderInner::Protobuf(e)) 674 | } 675 | } 676 | 677 | /// An encodable counter value. 678 | pub trait EncodeCounterValue { 679 | /// Encode the given instance in the OpenMetrics text encoding. 680 | fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error>; 681 | } 682 | 683 | impl EncodeCounterValue for u64 { 684 | fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> { 685 | encoder.encode_u64(*self) 686 | } 687 | } 688 | 689 | impl EncodeCounterValue for f64 { 690 | fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> { 691 | encoder.encode_f64(*self) 692 | } 693 | } 694 | 695 | impl EncodeCounterValue for u32 { 696 | fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> { 697 | encoder.encode_u64(u64::from(*self)) 698 | } 699 | } 700 | 701 | impl EncodeCounterValue for f32 { 702 | fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> { 703 | encoder.encode_f64(f64::from(*self)) 704 | } 705 | } 706 | 707 | /// Encoder for a counter value. 708 | #[derive(Debug)] 709 | pub struct CounterValueEncoder<'a>(CounterValueEncoderInner<'a>); 710 | 711 | #[derive(Debug)] 712 | enum CounterValueEncoderInner<'a> { 713 | Text(text::CounterValueEncoder<'a>), 714 | #[cfg(feature = "protobuf")] 715 | Protobuf(protobuf::CounterValueEncoder<'a>), 716 | } 717 | 718 | impl<'a> From> for CounterValueEncoder<'a> { 719 | fn from(e: text::CounterValueEncoder<'a>) -> Self { 720 | CounterValueEncoder(CounterValueEncoderInner::Text(e)) 721 | } 722 | } 723 | 724 | #[cfg(feature = "protobuf")] 725 | impl<'a> From> for CounterValueEncoder<'a> { 726 | fn from(e: protobuf::CounterValueEncoder<'a>) -> Self { 727 | CounterValueEncoder(CounterValueEncoderInner::Protobuf(e)) 728 | } 729 | } 730 | 731 | impl CounterValueEncoder<'_> { 732 | fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> { 733 | for_both_mut!(self, CounterValueEncoderInner, e, e.encode_f64(v)) 734 | } 735 | 736 | fn encode_u64(&mut self, v: u64) -> Result<(), std::fmt::Error> { 737 | for_both_mut!(self, CounterValueEncoderInner, e, e.encode_u64(v)) 738 | } 739 | } 740 | 741 | /// An encodable exemplar value. 742 | pub trait EncodeExemplarValue { 743 | /// Encode the given instance in the OpenMetrics text encoding. 744 | fn encode(&self, encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error>; 745 | } 746 | 747 | impl EncodeExemplarValue for f64 { 748 | fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> { 749 | encoder.encode(*self) 750 | } 751 | } 752 | 753 | impl EncodeExemplarValue for u64 { 754 | fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> { 755 | encoder.encode(*self as f64) 756 | } 757 | } 758 | 759 | impl EncodeExemplarValue for f32 { 760 | fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> { 761 | encoder.encode(f64::from(*self)) 762 | } 763 | } 764 | 765 | impl EncodeExemplarValue for u32 { 766 | fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> { 767 | encoder.encode(f64::from(*self)) 768 | } 769 | } 770 | 771 | /// An encodable exemplar time. 772 | pub trait EncodeExemplarTime { 773 | /// Encode the time in the OpenMetrics text encoding. 774 | fn encode(&self, encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error>; 775 | } 776 | 777 | impl EncodeExemplarTime for SystemTime { 778 | fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> { 779 | encoder.encode(self.duration_since(UNIX_EPOCH).unwrap().as_secs_f64()) 780 | } 781 | } 782 | 783 | /// Encoder for an exemplar value. 784 | #[derive(Debug)] 785 | pub struct ExemplarValueEncoder<'a>(ExemplarValueEncoderInner<'a>); 786 | 787 | #[derive(Debug)] 788 | enum ExemplarValueEncoderInner<'a> { 789 | Text(text::ExemplarValueEncoder<'a>), 790 | #[cfg(feature = "protobuf")] 791 | Protobuf(protobuf::ExemplarValueEncoder<'a>), 792 | } 793 | 794 | impl<'a> From> for ExemplarValueEncoder<'a> { 795 | fn from(e: text::ExemplarValueEncoder<'a>) -> Self { 796 | ExemplarValueEncoder(ExemplarValueEncoderInner::Text(e)) 797 | } 798 | } 799 | 800 | #[cfg(feature = "protobuf")] 801 | impl<'a> From> for ExemplarValueEncoder<'a> { 802 | fn from(e: protobuf::ExemplarValueEncoder<'a>) -> Self { 803 | ExemplarValueEncoder(ExemplarValueEncoderInner::Protobuf(e)) 804 | } 805 | } 806 | 807 | impl ExemplarValueEncoder<'_> { 808 | fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> { 809 | for_both_mut!(self, ExemplarValueEncoderInner, e, e.encode(v)) 810 | } 811 | } 812 | --------------------------------------------------------------------------------