From 1d40f46424d07bba44e63ca04a1c666a3895b6bd Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 11 Sep 2022 09:43:45 +0200 Subject: [PATCH 1/6] README.md: Remove mention of missing Protobuf (#88) Protobuf support was added in ffc2ab6. Signed-off-by: Max Inden --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index fb599048..d046c1ca 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,6 @@ be fixed in the future. Contributions in all forms are most welcome. - Enforce "Exposers SHOULD avoid names that could be confused with the suffixes that text format sample metric names use". -- Protobuf wire format. (Follow [spec - issue](https://github.com/OpenObservability/OpenMetrics/issues/183).) - - Gauge histogram metric. - Allow "A MetricPoint in a Metric with the type [Counter, Histogram] SHOULD have a Timestamp From 1742d51a155c321306a39ce50c7005ad34a568f7 Mon Sep 17 00:00:00 2001 From: David O'Rourke Date: Thu, 15 Sep 2022 15:08:26 +0100 Subject: [PATCH 2/6] src/metrics/family.rs: Add remove and clear methods (#85) Signed-off-by: David O'Rourke --- CHANGELOG.md | 5 ++ src/metrics/family.rs | 124 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901b2c91..a554904b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for the OpenMetrics protobuf format. See [PR 83]. +- Added a `remove` method to `Family` to allow the removal of a specified label + set from a family. See [PR 85]. +- Added a `clear` method to `Family` to allow the removal of all label sets + from a family. See [PR 85]. ### Changed - Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83]. [PR 83]: https://github.com/prometheus/client_rust/pull/83 +[PR 85]: https://github.com/prometheus/client_rust/pull/85 ## [0.18.0] diff --git a/src/metrics/family.rs b/src/metrics/family.rs index 304b0e43..c3cf6b9b 100644 --- a/src/metrics/family.rs +++ b/src/metrics/family.rs @@ -234,6 +234,47 @@ impl> Family, Counter>::default(); + /// + /// // Will create the metric with label `method="GET"` on first call and + /// // return a reference. + /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); + /// + /// // Will return `true`, indicating that the `method="GET"` label set was + /// // removed. + /// assert!(family.remove(&vec![("method".to_owned(), "GET".to_owned())])); + /// ``` + pub fn remove(&self, label_set: &S) -> bool { + self.metrics.write().remove(label_set).is_some() + } + + /// Clear all label sets from the metric family. + /// + /// ``` + /// # use prometheus_client::metrics::counter::{Atomic, Counter}; + /// # use prometheus_client::metrics::family::Family; + /// # + /// let family = Family::, Counter>::default(); + /// + /// // Will create the metric with label `method="GET"` on first call and + /// // return a reference. + /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc(); + /// + /// // Clear the family of all label sets. + /// family.clear(); + /// ``` + pub fn clear(&self) { + self.metrics.write().clear() + } + pub(crate) fn read(&self) -> RwLockReadGuard> { self.metrics.read() } @@ -295,4 +336,87 @@ mod tests { let custom_builder = CustomBuilder { custom_start: 1.0 }; Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder); } + + #[test] + fn counter_family_remove() { + let family = Family::, Counter>::default(); + + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .inc(); + + assert_eq!( + 1, + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .get() + ); + + family + .get_or_create(&vec![("method".to_string(), "POST".to_string())]) + .inc_by(2); + + assert_eq!( + 2, + family + .get_or_create(&vec![("method".to_string(), "POST".to_string())]) + .get() + ); + + // Attempt to remove it twice, showing it really was removed on the + // first attempt. + assert!(family.remove(&vec![("method".to_string(), "POST".to_string())])); + assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())])); + + // This should make a new POST label. + family + .get_or_create(&vec![("method".to_string(), "POST".to_string())]) + .inc(); + + assert_eq!( + 1, + family + .get_or_create(&vec![("method".to_string(), "POST".to_string())]) + .get() + ); + + // GET label should have be untouched. + assert_eq!( + 1, + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .get() + ); + } + + #[test] + fn counter_family_clear() { + let family = Family::, Counter>::default(); + + // Create a label and check it. + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .inc(); + + assert_eq!( + 1, + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .get() + ); + + // Clear it, then try recreating and checking it again. + family.clear(); + + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .inc(); + + assert_eq!( + 1, + family + .get_or_create(&vec![("method".to_string(), "GET".to_string())]) + .get() + ); + } } From 5b3aa2c2bc68eb367a54645a46f237e7df259246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Sep 2022 23:33:50 +0200 Subject: [PATCH 3/6] build(deps): update criterion requirement from 0.3 to 0.4 (#91) Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version. - [Release notes](https://github.com/bheisler/criterion.rs/releases) - [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/bheisler/criterion.rs/compare/0.3.0...0.4.0) --- updated-dependencies: - dependency-name: criterion dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eef8be6e..74fe6257 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ void = { version = "1.0", optional = true } [dev-dependencies] async-std = { version = "1", features = ["attributes"] } -criterion = "0.3" +criterion = "0.4" http-types = "2" pyo3 = "0.17" quickcheck = "1" From 12c1de56116d803c709ed6584c263353727221fa Mon Sep 17 00:00:00 2001 From: Akihito Nakano Date: Wed, 21 Sep 2022 19:21:06 +0900 Subject: [PATCH 4/6] derive-encode: Fix `multiple applicable items in scope` error (#93) Signed-off-by: ackintosh --- derive-encode/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index 4612ecc9..d6f6eba1 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -104,7 +104,7 @@ fn derive_protobuf_encode(ast: DeriveInput) -> TokenStream2 { quote! { let mut label = { let mut labels = vec![]; - self.#ident.encode(&mut labels); + prometheus_client::encoding::proto::EncodeLabels::encode(&self.#ident, &mut labels); debug_assert_eq!(1, labels.len(), "Labels encoded from {} should have only one label.", #ident_string); labels.pop().expect("should have an element") }; From 9e1e3441cf6f77c687d4665d4f55c63398dd7192 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 27 Sep 2022 11:48:19 -0500 Subject: [PATCH 5/6] metrics/exemplar: Implement TypedMetric for Counter/HistogramWithExemplars (#96) Signed-off-by: Adam Chalmers --- CHANGELOG.md | 2 ++ src/metrics/exemplar.rs | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a554904b..0532a102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 set from a family. See [PR 85]. - Added a `clear` method to `Family` to allow the removal of all label sets from a family. See [PR 85]. +- Impl `TypedMetric` for `CounterWithExemplar` and `HistogramWithExemplar`, so that they can be used with `Family`. See [PR 96]. ### Changed @@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [PR 83]: https://github.com/prometheus/client_rust/pull/83 [PR 85]: https://github.com/prometheus/client_rust/pull/85 +[PR 96]: https://github.com/prometheus/client_rust/pull/96 ## [0.18.0] diff --git a/src/metrics/exemplar.rs b/src/metrics/exemplar.rs index 84462003..a2cffc03 100644 --- a/src/metrics/exemplar.rs +++ b/src/metrics/exemplar.rs @@ -4,6 +4,7 @@ use super::counter::{self, Counter}; use super::histogram::Histogram; +use super::{MetricType, TypedMetric}; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use std::collections::HashMap; #[cfg(any(target_arch = "mips", target_arch = "powerpc"))] @@ -31,12 +32,45 @@ pub struct Exemplar { /// counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), "42".to_string())])); /// let _value: (u64, _) = counter_with_exemplar.get(); /// ``` +/// You can also use exemplars with families. Just wrap the exemplar in a Family. +/// ``` +/// # use prometheus_client::metrics::exemplar::CounterWithExemplar; +/// # use prometheus_client::metrics::histogram::exponential_buckets; +/// # use prometheus_client::metrics::family::Family; +/// # use prometheus_client_derive_encode::Encode; +/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// pub struct ResultLabel { +/// pub result: String, +/// } +/// +/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// pub struct TraceLabel { +/// pub trace_id: String, +/// } +/// +/// let latency: Family> = Family::default(); +/// +/// latency +/// .get_or_create(&ResultLabel { +/// result: "success".to_owned(), +/// }) +/// .inc_by( +/// 1, +/// Some(TraceLabel { +/// trace_id: "3a2f90c9f80b894f".to_owned(), +/// }), +/// ); +/// ``` #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))] #[derive(Debug)] pub struct CounterWithExemplar { pub(crate) inner: Arc>>, } +impl TypedMetric for CounterWithExemplar { + const TYPE: MetricType = MetricType::Counter; +} + /// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete /// events and track references to data outside of the metric set. #[cfg(any(target_arch = "mips", target_arch = "powerpc"))] @@ -122,12 +156,48 @@ impl> CounterWithExemplar { /// let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)); /// histogram.observe(4.2, Some(vec![("user_id".to_string(), "42".to_string())])); /// ``` +/// You can also use exemplars with families. Just wrap the exemplar in a Family. +/// ``` +/// # use prometheus_client::metrics::exemplar::HistogramWithExemplars; +/// # use prometheus_client::metrics::histogram::exponential_buckets; +/// # use prometheus_client::metrics::family::Family; +/// # use prometheus_client_derive_encode::Encode; +/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// pub struct ResultLabel { +/// pub result: String, +/// } +/// +/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)] +/// pub struct TraceLabel { +/// pub trace_id: String, +/// } +/// +/// let latency: Family> = +/// Family::new_with_constructor(|| { +/// HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10)) +/// }); +/// +/// latency +/// .get_or_create(&ResultLabel { +/// result: "success".to_owned(), +/// }) +/// .observe( +/// 0.001345422, +/// Some(TraceLabel { +/// trace_id: "3a2f90c9f80b894f".to_owned(), +/// }), +/// ); +/// ``` #[derive(Debug)] pub struct HistogramWithExemplars { // TODO: Not ideal, as Histogram has a Mutex as well. pub(crate) inner: Arc>>, } +impl TypedMetric for HistogramWithExemplars { + const TYPE: MetricType = MetricType::Histogram; +} + impl Clone for HistogramWithExemplars { fn clone(&self) -> Self { Self { From 682b24ee8c6c857b76c0683b1dd7df5a97b75c27 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 27 Sep 2022 12:13:10 -0500 Subject: [PATCH 6/6] examples/: Add example metrics server using Hyper (#94) Signed-off-by: Adam Chalmers --- Cargo.toml | 2 ++ examples/hyper.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 examples/hyper.rs diff --git a/Cargo.toml b/Cargo.toml index 74fe6257..88bc0569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ quickcheck = "1" rand = "0.8.4" tide = "0.16" actix-web = "4" +tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal"] } +hyper = { version = "0.14.16", features = ["server", "http1", "tcp"] } [build-dependencies] prost-build = { version = "0.9.0", optional = true } diff --git a/examples/hyper.rs b/examples/hyper.rs new file mode 100644 index 00000000..404b6c17 --- /dev/null +++ b/examples/hyper.rs @@ -0,0 +1,75 @@ +use hyper::{ + service::{make_service_fn, service_fn}, + Body, Request, Response, Server, +}; +use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry}; +use std::{ + future::Future, + io, + net::{IpAddr, Ipv4Addr, SocketAddr}, + pin::Pin, + sync::Arc, +}; +use tokio::signal::unix::{signal, SignalKind}; + +#[tokio::main] +async fn main() { + let request_counter: Counter = Default::default(); + + let mut registry = ::with_prefix("tokio_hyper_example"); + + registry.register( + "requests", + "How many requests the application has received", + Box::new(request_counter.clone()), + ); + + // Spawn a server to serve the OpenMetrics endpoint. + let metrics_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8001); + start_metrics_server(metrics_addr, registry).await +} + +/// Start a HTTP server to report metrics. +pub async fn start_metrics_server(metrics_addr: SocketAddr, registry: Registry) { + let mut shutdown_stream = signal(SignalKind::terminate()).unwrap(); + + eprintln!("Starting metrics server on {metrics_addr}"); + + let registry = Arc::new(registry); + Server::bind(&metrics_addr) + .serve(make_service_fn(move |_conn| { + let registry = registry.clone(); + async move { + let handler = make_handler(registry); + Ok::<_, io::Error>(service_fn(handler)) + } + })) + .with_graceful_shutdown(async move { + shutdown_stream.recv().await; + }) + .await + .unwrap(); +} + +/// This function returns a HTTP handler (i.e. another function) +pub fn make_handler( + registry: Arc, +) -> impl Fn(Request) -> Pin>> + Send>> { + // This closure accepts a request and responds with the OpenMetrics encoding of our metrics. + move |_req: Request| { + let reg = registry.clone(); + Box::pin(async move { + let mut buf = Vec::new(); + encode(&mut buf, ®.clone()).map(|_| { + let body = Body::from(buf); + Response::builder() + .header( + hyper::header::CONTENT_TYPE, + "application/openmetrics-text; version=1.0.0; charset=utf-8", + ) + .body(body) + .unwrap() + }) + }) + } +}