From de2723402f9f7604e1d4a7419ae11c3f14e9803d Mon Sep 17 00:00:00 2001
From: Qinxuan Chen <koushiro.cqx@gmail.com>
Date: Thu, 7 Mar 2024 17:45:38 +0800
Subject: [PATCH 01/27] feat(metrics): support `Gauge<u32, AtomicU32>` type
 (#191)

Signed-off-by: koushiro <koushiro.cqx@gmail.com>
---
 CHANGELOG.md             |  9 +++++++
 Cargo.toml               |  2 +-
 src/encoding.rs          | 14 ++++++++--
 src/encoding/protobuf.rs |  4 +++
 src/encoding/text.rs     | 13 +++++++--
 src/metrics/gauge.rs     | 58 +++++++++++++++++++++++++++++-----------
 6 files changed, 79 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c83e85b..7b1533d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.22.2]
+
+### Added
+
+- Added `Gauge<u32, AtomicU32>` implementation.
+  See [PR 191].
+
+[PR 191]: https://github.com/prometheus/client_rust/pull/191
+
 ## [0.22.1]
 
 ### Added
diff --git a/Cargo.toml b/Cargo.toml
index ce4d0ce3..7e919c71 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client"
-version = "0.22.1"
+version = "0.22.2"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Open Metrics client library allowing users to natively instrument applications."
diff --git a/src/encoding.rs b/src/encoding.rs
index 24d0d729..23c60328 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -544,6 +544,12 @@ pub trait EncodeGaugeValue {
     fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error>;
 }
 
+impl EncodeGaugeValue for u32 {
+    fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode_u32(*self)
+    }
+}
+
 impl EncodeGaugeValue for i64 {
     fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
         encoder.encode_i64(*self)
@@ -568,13 +574,17 @@ enum GaugeValueEncoderInner<'a> {
 }
 
 impl<'a> GaugeValueEncoder<'a> {
-    fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
-        for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_f64(v))
+    fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
+        for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_u32(v))
     }
 
     fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> {
         for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_i64(v))
     }
+
+    fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
+        for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_f64(v))
+    }
 }
 
 impl<'a> From<text::GaugeValueEncoder<'a>> for GaugeValueEncoder<'a> {
diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs
index 53b30c3a..eef2731a 100644
--- a/src/encoding/protobuf.rs
+++ b/src/encoding/protobuf.rs
@@ -323,6 +323,10 @@ pub(crate) struct GaugeValueEncoder<'a> {
 }
 
 impl<'a> GaugeValueEncoder<'a> {
+    pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
+        self.encode_i64(v as i64)
+    }
+
     pub fn encode_i64(&mut self, v: i64) -> Result<(), std::fmt::Error> {
         *self.value = openmetrics_data_model::gauge_value::Value::IntValue(v);
         Ok(())
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index adf42415..4acf1d51 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -423,9 +423,9 @@ impl<'a> std::fmt::Debug for GaugeValueEncoder<'a> {
 }
 
 impl<'a> GaugeValueEncoder<'a> {
-    pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
+    pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
         self.writer.write_str(" ")?;
-        self.writer.write_str(dtoa::Buffer::new().format(v))?;
+        self.writer.write_str(itoa::Buffer::new().format(v))?;
         Ok(())
     }
 
@@ -434,6 +434,12 @@ impl<'a> GaugeValueEncoder<'a> {
         self.writer.write_str(itoa::Buffer::new().format(v))?;
         Ok(())
     }
+
+    pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
+        self.writer.write_str(" ")?;
+        self.writer.write_str(dtoa::Buffer::new().format(v))?;
+        Ok(())
+    }
 }
 
 pub(crate) struct ExemplarValueEncoder<'a> {
@@ -565,6 +571,7 @@ mod tests {
     use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar};
     use pyo3::{prelude::*, types::PyModule};
     use std::borrow::Cow;
+    use std::sync::atomic::AtomicU32;
 
     #[test]
     fn encode_counter() {
@@ -632,6 +639,8 @@ mod tests {
         let mut registry = Registry::default();
         let gauge: Gauge = Gauge::default();
         registry.register("my_gauge", "My gauge", gauge);
+        let gauge = Gauge::<u32, AtomicU32>::default();
+        registry.register("u32_gauge", "Gauge::<u32, AtomicU32>", gauge);
 
         let mut encoded = String::new();
 
diff --git a/src/metrics/gauge.rs b/src/metrics/gauge.rs
index 98671280..7b268427 100644
--- a/src/metrics/gauge.rs
+++ b/src/metrics/gauge.rs
@@ -6,7 +6,7 @@ use crate::encoding::{EncodeGaugeValue, EncodeMetric, MetricEncoder};
 
 use super::{MetricType, TypedMetric};
 use std::marker::PhantomData;
-use std::sync::atomic::{AtomicI32, Ordering};
+use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
 #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
 use std::sync::atomic::{AtomicI64, AtomicU64};
 use std::sync::Arc;
@@ -134,55 +134,81 @@ pub trait Atomic<N> {
     fn get(&self) -> N;
 }
 
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
-impl Atomic<i64> for AtomicI64 {
-    fn inc(&self) -> i64 {
+impl Atomic<i32> for AtomicI32 {
+    fn inc(&self) -> i32 {
         self.inc_by(1)
     }
 
-    fn inc_by(&self, v: i64) -> i64 {
+    fn inc_by(&self, v: i32) -> i32 {
         self.fetch_add(v, Ordering::Relaxed)
     }
 
-    fn dec(&self) -> i64 {
+    fn dec(&self) -> i32 {
         self.dec_by(1)
     }
 
-    fn dec_by(&self, v: i64) -> i64 {
+    fn dec_by(&self, v: i32) -> i32 {
         self.fetch_sub(v, Ordering::Relaxed)
     }
 
-    fn set(&self, v: i64) -> i64 {
+    fn set(&self, v: i32) -> i32 {
         self.swap(v, Ordering::Relaxed)
     }
 
-    fn get(&self) -> i64 {
+    fn get(&self) -> i32 {
         self.load(Ordering::Relaxed)
     }
 }
 
-impl Atomic<i32> for AtomicI32 {
-    fn inc(&self) -> i32 {
+impl Atomic<u32> for AtomicU32 {
+    fn inc(&self) -> u32 {
         self.inc_by(1)
     }
 
-    fn inc_by(&self, v: i32) -> i32 {
+    fn inc_by(&self, v: u32) -> u32 {
         self.fetch_add(v, Ordering::Relaxed)
     }
 
-    fn dec(&self) -> i32 {
+    fn dec(&self) -> u32 {
         self.dec_by(1)
     }
 
-    fn dec_by(&self, v: i32) -> i32 {
+    fn dec_by(&self, v: u32) -> u32 {
         self.fetch_sub(v, Ordering::Relaxed)
     }
 
-    fn set(&self, v: i32) -> i32 {
+    fn set(&self, v: u32) -> u32 {
         self.swap(v, Ordering::Relaxed)
     }
 
-    fn get(&self) -> i32 {
+    fn get(&self) -> u32 {
+        self.load(Ordering::Relaxed)
+    }
+}
+
+#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+impl Atomic<i64> for AtomicI64 {
+    fn inc(&self) -> i64 {
+        self.inc_by(1)
+    }
+
+    fn inc_by(&self, v: i64) -> i64 {
+        self.fetch_add(v, Ordering::Relaxed)
+    }
+
+    fn dec(&self) -> i64 {
+        self.dec_by(1)
+    }
+
+    fn dec_by(&self, v: i64) -> i64 {
+        self.fetch_sub(v, Ordering::Relaxed)
+    }
+
+    fn set(&self, v: i64) -> i64 {
+        self.swap(v, Ordering::Relaxed)
+    }
+
+    fn get(&self) -> i64 {
         self.load(Ordering::Relaxed)
     }
 }

From b462e610498510cc1a4de42c7e920ba67032a493 Mon Sep 17 00:00:00 2001
From: Jonathan Davies <jd+github@upthedownstair.com>
Date: Sun, 31 Mar 2024 13:41:59 +0000
Subject: [PATCH 02/27] examples/actix-web.rs: default enable compress
 middleware. (#192)

Signed-off-by: Jonathan Davies <jpds@protonmail.com>
---
 examples/actix-web.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/examples/actix-web.rs b/examples/actix-web.rs
index d01a1027..a1ce8e79 100644
--- a/examples/actix-web.rs
+++ b/examples/actix-web.rs
@@ -1,5 +1,6 @@
 use std::sync::Mutex;
 
+use actix_web::middleware::Compress;
 use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result};
 use prometheus_client::encoding::text::encode;
 use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue};
@@ -61,6 +62,7 @@ async fn main() -> std::io::Result<()> {
 
     HttpServer::new(move || {
         App::new()
+            .wrap(Compress::default())
             .app_data(metrics.clone())
             .app_data(state.clone())
             .service(web::resource("/metrics").route(web::get().to(metrics_handler)))

From bf196d77824294cce6bf996f79cef25f7aa82c74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tam=C3=A1s=20L=C3=A9vai?=
 <levaitamas@users.noreply.github.com>
Date: Mon, 13 May 2024 21:17:19 +0200
Subject: [PATCH 03/27] chore(examples/hyper):  port to hyper 1.x (#195)

Based on https://github.com/prometheus/client_rust/pull/184

Co-authored-by: Jun Kurihara <junkurihara@users.noreply.github.com>
Signed-off-by: Tamas Levai <levait@tmit.bme.hu>
---
 Cargo.toml        |  4 ++-
 examples/hyper.rs | 62 ++++++++++++++++++++++++++++++++---------------
 2 files changed, 45 insertions(+), 21 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 7e919c71..06385a2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,7 +35,9 @@ 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"] }
+hyper = { version = "1.3.1", features = ["server", "http1"] }
+hyper-util = { version = "0.1.3", features = ["tokio"] }
+http-body-util = "0.1.1"
 
 [build-dependencies]
 prost-build = { version = "0.11.0", optional = true }
diff --git a/examples/hyper.rs b/examples/hyper.rs
index f5a4009d..82ee121b 100644
--- a/examples/hyper.rs
+++ b/examples/hyper.rs
@@ -1,7 +1,11 @@
+use http_body_util::{combinators, BodyExt, Full};
 use hyper::{
-    service::{make_service_fn, service_fn},
-    Body, Request, Response, Server,
+    body::{Bytes, Incoming},
+    server::conn::http1,
+    service::service_fn,
+    Request, Response,
 };
+use hyper_util::rt::TokioIo;
 use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry};
 use std::{
     future::Future,
@@ -10,7 +14,11 @@ use std::{
     pin::Pin,
     sync::Arc,
 };
-use tokio::signal::unix::{signal, SignalKind};
+use tokio::{
+    net::TcpListener,
+    pin,
+    signal::unix::{signal, SignalKind},
+};
 
 #[tokio::main]
 async fn main() {
@@ -31,39 +39,48 @@ async fn main() {
 
 /// 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))
+
+    let tcp_listener = TcpListener::bind(metrics_addr).await.unwrap();
+    let server = http1::Builder::new();
+    while let Ok((stream, _)) = tcp_listener.accept().await {
+        let mut shutdown_stream = signal(SignalKind::terminate()).unwrap();
+        let io = TokioIo::new(stream);
+        let server_clone = server.clone();
+        let registry_clone = registry.clone();
+        tokio::task::spawn(async move {
+            let conn = server_clone.serve_connection(io, service_fn(make_handler(registry_clone)));
+            pin!(conn);
+            tokio::select! {
+                _ = conn.as_mut() => {}
+                _ = shutdown_stream.recv() => {
+                    conn.as_mut().graceful_shutdown();
+                }
             }
-        }))
-        .with_graceful_shutdown(async move {
-            shutdown_stream.recv().await;
-        })
-        .await
-        .unwrap();
+        });
+    }
 }
 
+/// Boxed HTTP body for responses
+type BoxBody = combinators::BoxBody<Bytes, hyper::Error>;
+
 /// This function returns a HTTP handler (i.e. another function)
 pub fn make_handler(
     registry: Arc<Registry>,
-) -> impl Fn(Request<Body>) -> Pin<Box<dyn Future<Output = io::Result<Response<Body>>> + Send>> {
+) -> impl Fn(Request<Incoming>) -> Pin<Box<dyn Future<Output = io::Result<Response<BoxBody>>> + Send>>
+{
     // This closure accepts a request and responds with the OpenMetrics encoding of our metrics.
-    move |_req: Request<Body>| {
+    move |_req: Request<Incoming>| {
         let reg = registry.clone();
+
         Box::pin(async move {
             let mut buf = String::new();
             encode(&mut buf, &reg.clone())
                 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
                 .map(|_| {
-                    let body = Body::from(buf);
+                    let body = full(Bytes::from(buf));
                     Response::builder()
                         .header(
                             hyper::header::CONTENT_TYPE,
@@ -75,3 +92,8 @@ pub fn make_handler(
         })
     }
 }
+
+/// helper function to build a full boxed body
+pub fn full(body: Bytes) -> BoxBody {
+    Full::new(body).map_err(|never| match never {}).boxed()
+}

From 4b78df1ec4d736d74da6998246f3c37e598b5144 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=A9o=20Gillot-Lamure?= <leo.gillot@navaati.net>
Date: Tue, 2 Jul 2024 10:29:14 +0200
Subject: [PATCH 04/27] chore(portability): support all platforms without 64
 bit atomics (#203)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Notably, RISC-V 32 for ESP32-C3 chips.

Signed-off-by: Léo Gillot-Lamure <leo.gillot@navaati.net>
Signed-off-by: Max Inden <mail@max-inden.de>
Co-authored-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md            |  9 +++++++++
 src/metrics/counter.rs  | 12 ++++++------
 src/metrics/exemplar.rs |  8 ++++----
 src/metrics/gauge.rs    | 10 +++++-----
 4 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b1533d4..a52b060d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.22.3] - unreleased
+
+### Added
+
+- Support all platforms with 32 bit atomics lacking 64 bit atomics.
+  See [PR 203].
+
+[PR 203]: https://github.com/prometheus/client_rust/pull/203
+
 ## [0.22.2]
 
 ### Added
diff --git a/src/metrics/counter.rs b/src/metrics/counter.rs
index c1c5e511..63d84ffc 100644
--- a/src/metrics/counter.rs
+++ b/src/metrics/counter.rs
@@ -6,7 +6,7 @@ use crate::encoding::{EncodeMetric, MetricEncoder};
 
 use super::{MetricType, TypedMetric};
 use std::marker::PhantomData;
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 use std::sync::atomic::AtomicU64;
 use std::sync::atomic::{AtomicU32, Ordering};
 use std::sync::Arc;
@@ -40,7 +40,7 @@ use std::sync::Arc;
 /// counter.inc();
 /// let _value: f64 = counter.get();
 /// ```
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 #[derive(Debug)]
 pub struct Counter<N = u64, A = AtomicU64> {
     value: Arc<A>,
@@ -48,7 +48,7 @@ pub struct Counter<N = u64, A = AtomicU64> {
 }
 
 /// Open Metrics [`Counter`] to measure discrete events.
-#[cfg(any(target_arch = "mips", target_arch = "powerpc"))]
+#[cfg(not(target_has_atomic = "64"))]
 #[derive(Debug)]
 pub struct Counter<N = u32, A = AtomicU32> {
     value: Arc<A>,
@@ -114,7 +114,7 @@ pub trait Atomic<N> {
     fn get(&self) -> N;
 }
 
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 impl Atomic<u64> for AtomicU64 {
     fn inc(&self) -> u64 {
         self.inc_by(1)
@@ -143,7 +143,7 @@ impl Atomic<u32> for AtomicU32 {
     }
 }
 
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 impl Atomic<f64> for AtomicU64 {
     fn inc(&self) -> f64 {
         self.inc_by(1.0)
@@ -231,7 +231,7 @@ mod tests {
         assert_eq!(1, counter.get());
     }
 
-    #[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+    #[cfg(target_has_atomic = "64")]
     #[test]
     fn f64_stored_in_atomic_u64() {
         fn prop(fs: Vec<f64>) {
diff --git a/src/metrics/exemplar.rs b/src/metrics/exemplar.rs
index c8478c6a..e61f4d5a 100644
--- a/src/metrics/exemplar.rs
+++ b/src/metrics/exemplar.rs
@@ -11,9 +11,9 @@ 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"))]
+#[cfg(not(target_has_atomic = "64"))]
 use std::sync::atomic::AtomicU32;
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 use std::sync::atomic::AtomicU64;
 use std::sync::Arc;
 
@@ -65,7 +65,7 @@ pub struct Exemplar<S, V> {
 ///         }),
 ///     );
 /// ```
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 #[derive(Debug)]
 pub struct CounterWithExemplar<S, N = u64, A = AtomicU64> {
     pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
@@ -77,7 +77,7 @@ impl<S> TypedMetric for CounterWithExemplar<S> {
 
 /// 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"))]
+#[cfg(not(target_has_atomic = "64"))]
 #[derive(Debug)]
 pub struct CounterWithExemplar<S, N = u32, A = AtomicU32> {
     pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
diff --git a/src/metrics/gauge.rs b/src/metrics/gauge.rs
index 7b268427..fcf7e6a7 100644
--- a/src/metrics/gauge.rs
+++ b/src/metrics/gauge.rs
@@ -7,7 +7,7 @@ use crate::encoding::{EncodeGaugeValue, EncodeMetric, MetricEncoder};
 use super::{MetricType, TypedMetric};
 use std::marker::PhantomData;
 use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 use std::sync::atomic::{AtomicI64, AtomicU64};
 use std::sync::Arc;
 
@@ -40,7 +40,7 @@ use std::sync::Arc;
 /// gauge.set(42.0);
 /// let _value: f64 = gauge.get();
 /// ```
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 #[derive(Debug)]
 pub struct Gauge<N = i64, A = AtomicI64> {
     value: Arc<A>,
@@ -48,7 +48,7 @@ pub struct Gauge<N = i64, A = AtomicI64> {
 }
 
 /// Open Metrics [`Gauge`] to record current measurements.
-#[cfg(any(target_arch = "mips", target_arch = "powerpc"))]
+#[cfg(not(target_has_atomic = "64"))]
 #[derive(Debug)]
 pub struct Gauge<N = i32, A = AtomicI32> {
     value: Arc<A>,
@@ -186,7 +186,7 @@ impl Atomic<u32> for AtomicU32 {
     }
 }
 
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 impl Atomic<i64> for AtomicI64 {
     fn inc(&self) -> i64 {
         self.inc_by(1)
@@ -213,7 +213,7 @@ impl Atomic<i64> for AtomicI64 {
     }
 }
 
-#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
+#[cfg(target_has_atomic = "64")]
 impl Atomic<f64> for AtomicU64 {
     fn inc(&self) -> f64 {
         self.inc_by(1.0)

From a914fc96308c65e0a87f44d5e30cd7ebc6a77e70 Mon Sep 17 00:00:00 2001
From: Tyrone Wu <wudevelops@gmail.com>
Date: Sun, 7 Jul 2024 09:17:47 -0400
Subject: [PATCH 05/27] feat(encoding): add functions for individually encoding
 registry & EOF (#205)

All credits on the initial idea, implementation, and testing belong to
@amunra, who is the original author of this PR #154.

From the original PR description:

Adds new `encode_registry` and `encode_eof` functions to allow encoding
of parts of the response.

This is useful when there are multiple registries at play, or when
composing metrics for a process that embeds Rust as part of its logic
whilst serving metrics to Prometheus outside of Rust.

Fixes: #153
Refs: #154, #204

Co-authored-by: amunra <cimarosti@gmail.com>
Signed-off-by: tyrone-wu <wudevelops@gmail.com>
Signed-off-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md         |   5 ++
 Cargo.toml           |   2 +-
 src/encoding/text.rs | 204 ++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 200 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a52b060d..d6eed664 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- Added `encode_registry` and `encode_eof` functions to `text` module.
+  See [PR 205].
+  
+  [PR 205]: https://github.com/prometheus/client_rust/pull/205
+
 - Support all platforms with 32 bit atomics lacking 64 bit atomics.
   See [PR 203].
 
diff --git a/Cargo.toml b/Cargo.toml
index 06385a2e..fae43aa5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client"
-version = "0.22.2"
+version = "0.22.3"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Open Metrics client library allowing users to natively instrument applications."
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 4acf1d51..42815269 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -1,7 +1,7 @@
 //! Open Metrics text format implementation.
 //!
 //! ```
-//! # use prometheus_client::encoding::text::encode;
+//! # use prometheus_client::encoding::text::{encode, encode_registry, encode_eof};
 //! # use prometheus_client::metrics::counter::Counter;
 //! # use prometheus_client::registry::Registry;
 //! #
@@ -15,13 +15,26 @@
 //! # );
 //! # counter.inc();
 //! let mut buffer = String::new();
+//!
+//! // Encode the complete OpenMetrics exposition into the message buffer
 //! encode(&mut buffer, &registry).unwrap();
+//! let expected_msg = "# HELP my_counter This is my counter.\n".to_owned() +
+//!                    "# TYPE my_counter counter\n" +
+//!                    "my_counter_total 1\n" +
+//!                    "# EOF\n";
+//! assert_eq!(expected_msg, buffer);
+//! buffer.clear();
+//!
+//! // Encode just the registry into the message buffer
+//! encode_registry(&mut buffer, &registry).unwrap();
+//! let expected_reg = "# HELP my_counter This is my counter.\n".to_owned() +
+//!                    "# TYPE my_counter counter\n" +
+//!                    "my_counter_total 1\n";
+//! assert_eq!(expected_reg, buffer);
 //!
-//! let expected = "# HELP my_counter This is my counter.\n".to_owned() +
-//!                "# TYPE my_counter counter\n" +
-//!                "my_counter_total 1\n" +
-//!                "# EOF\n";
-//! assert_eq!(expected, buffer);
+//! // Encode EOF marker into message buffer to complete the OpenMetrics exposition
+//! encode_eof(&mut buffer).unwrap();
+//! assert_eq!(expected_msg, buffer);
 //! ```
 
 use crate::encoding::{EncodeExemplarValue, EncodeLabelSet};
@@ -33,15 +46,140 @@ use std::borrow::Cow;
 use std::collections::HashMap;
 use std::fmt::Write;
 
+/// Encode both the metrics registered with the provided [`Registry`] and the
+/// EOF marker into the provided [`Write`]r using the OpenMetrics text format.
+///
+/// Note: This function encodes the **complete** OpenMetrics exposition.
+///
+/// Use [`encode_registry`] or [`encode_eof`] if partial encoding is needed.
+///
+/// See [OpenMetrics exposition format](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#text-format)
+/// for additional details.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use prometheus_client::encoding::text::encode;
+/// # use prometheus_client::metrics::counter::Counter;
+/// # use prometheus_client::metrics::gauge::Gauge;
+/// # use prometheus_client::registry::Registry;
+/// #
+/// // Initialize registry with metric families
+/// let mut registry = Registry::default();
+/// let counter: Counter = Counter::default();
+/// registry.register(
+///     "my_counter",
+///     "This is my counter",
+///     counter.clone(),
+/// );
+/// let gauge: Gauge = Gauge::default();
+/// registry.register(
+///     "my_gauge",
+///     "This is my gauge",
+///     gauge.clone(),
+/// );
+///
+/// // Encode the complete OpenMetrics exposition into the buffer
+/// let mut buffer = String::new();
+/// encode(&mut buffer, &registry)?;
+/// # Ok::<(), std::fmt::Error>(())
+/// ```
+pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
+where
+    W: Write,
+{
+    encode_registry(writer, registry)?;
+    encode_eof(writer)
+}
+
 /// Encode the metrics registered with the provided [`Registry`] into the
 /// provided [`Write`]r using the OpenMetrics text format.
-pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
+///
+/// Note: The OpenMetrics exposition requires that a complete message must end
+/// with an EOF marker.
+///
+/// This function may be called repeatedly for the HTTP scrape response until
+/// [`encode_eof`] signals the end of the response.
+///
+/// This may also be used to compose a partial message with metrics assembled
+/// from multiple registries.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use prometheus_client::encoding::text::encode_registry;
+/// # use prometheus_client::metrics::counter::Counter;
+/// # use prometheus_client::metrics::gauge::Gauge;
+/// # use prometheus_client::registry::Registry;
+/// #
+/// // Initialize registry with a counter
+/// let mut reg_counter = Registry::default();
+/// let counter: Counter = Counter::default();
+/// reg_counter.register(
+///     "my_counter",
+///     "This is my counter",
+///     counter.clone(),
+/// );
+///
+/// // Encode the counter registry into the buffer
+/// let mut buffer = String::new();
+/// encode_registry(&mut buffer, &reg_counter)?;
+///
+/// // Initialize another registry but with a gauge
+/// let mut reg_gauge = Registry::default();
+/// let gauge: Gauge = Gauge::default();
+/// reg_gauge.register(
+///   "my_gauge",
+///   "This is my gauge",
+///   gauge.clone(),
+/// );
+///
+/// // Encode the gauge registry into the buffer
+/// encode_registry(&mut buffer, &reg_gauge)?;
+/// # Ok::<(), std::fmt::Error>(())
+/// ```
+pub fn encode_registry<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
 where
     W: Write,
 {
-    registry.encode(&mut DescriptorEncoder::new(writer).into())?;
-    writer.write_str("# EOF\n")?;
-    Ok(())
+    registry.encode(&mut DescriptorEncoder::new(writer).into())
+}
+
+/// Encode the EOF marker into the provided [`Write`]r using the OpenMetrics
+/// text format.
+///
+/// Note: This function is used to mark/signal the end of the exposition.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use prometheus_client::encoding::text::{encode_registry, encode_eof};
+/// # use prometheus_client::metrics::counter::Counter;
+/// # use prometheus_client::metrics::gauge::Gauge;
+/// # use prometheus_client::registry::Registry;
+/// #
+/// // Initialize registry with a counter
+/// let mut registry = Registry::default();
+/// let counter: Counter = Counter::default();
+/// registry.register(
+///     "my_counter",
+///     "This is my counter",
+///     counter.clone(),
+/// );
+///
+/// // Encode registry into the buffer
+/// let mut buffer = String::new();
+/// encode_registry(&mut buffer, &registry)?;
+///
+/// // Encode EOF marker to complete the message
+/// encode_eof(&mut buffer)?;
+/// # Ok::<(), std::fmt::Error>(())
+/// ```
+pub fn encode_eof<W>(writer: &mut W) -> Result<(), std::fmt::Error>
+where
+    W: Write,
+{
+    writer.write_str("# EOF\n")
 }
 
 pub(crate) struct DescriptorEncoder<'a> {
@@ -915,6 +1053,52 @@ mod tests {
         parse_with_python_client(encoded);
     }
 
+    #[test]
+    fn encode_registry_eof() {
+        let mut orders_registry = Registry::default();
+
+        let total_orders: Counter<u64> = Default::default();
+        orders_registry.register("orders", "Total orders received", total_orders.clone());
+        total_orders.inc();
+
+        let processing_times = Histogram::new(exponential_buckets(1.0, 2.0, 10));
+        orders_registry.register_with_unit(
+            "processing_times",
+            "Order times",
+            Unit::Seconds,
+            processing_times.clone(),
+        );
+        processing_times.observe(2.4);
+
+        let mut user_auth_registry = Registry::default();
+
+        let successful_logins: Counter<u64> = Default::default();
+        user_auth_registry.register(
+            "successful_logins",
+            "Total successful logins",
+            successful_logins.clone(),
+        );
+        successful_logins.inc();
+
+        let failed_logins: Counter<u64> = Default::default();
+        user_auth_registry.register(
+            "failed_logins",
+            "Total failed logins",
+            failed_logins.clone(),
+        );
+
+        let mut response = String::new();
+
+        encode_registry(&mut response, &orders_registry).unwrap();
+        assert_eq!(&response[response.len() - 20..], "bucket{le=\"+Inf\"} 1\n");
+
+        encode_registry(&mut response, &user_auth_registry).unwrap();
+        assert_eq!(&response[response.len() - 20..], "iled_logins_total 0\n");
+
+        encode_eof(&mut response).unwrap();
+        assert_eq!(&response[response.len() - 20..], "ogins_total 0\n# EOF\n");
+    }
+
     fn parse_with_python_client(input: String) {
         pyo3::prepare_freethreaded_python();
 

From 0f85e8eb84a9faa3c60585f64eb452c4228d0f55 Mon Sep 17 00:00:00 2001
From: Max Inden <mail@max-inden.de>
Date: Mon, 15 Jul 2024 16:35:40 +0200
Subject: [PATCH 06/27] docs: enable for protobuf feature (#208)

- have docs.rs generate  with `protobuf` feature
- flag that only enabled with `protobuf` feature

Signed-off-by: Max Inden <mail@max-inden.de>
---
 Cargo.toml      | 5 +++++
 src/encoding.rs | 1 +
 src/lib.rs      | 1 +
 3 files changed, 7 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index fae43aa5..4f78e1ef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,3 +61,8 @@ name = "proto"
 path = "benches/encoding/proto.rs"
 harness = false
 required-features = ["protobuf"]
+
+# Passing arguments to the docsrs builder in order to properly document cfg's.
+# More information: https://docs.rs/about/builds#cross-compiling
+[package.metadata.docs.rs]
+all-features = true
diff --git a/src/encoding.rs b/src/encoding.rs
index 23c60328..30993999 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -13,6 +13,7 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 #[cfg(feature = "protobuf")]
+#[cfg_attr(docsrs, doc(cfg(feature = "protobuf")))]
 pub mod protobuf;
 pub mod text;
 
diff --git a/src/lib.rs b/src/lib.rs
index cf39dc1a..cfff6238 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,6 +3,7 @@
 #![deny(unused)]
 #![forbid(unsafe_code)]
 #![warn(missing_debug_implementations)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
 
 //! Client library implementation of the [Open Metrics
 //! specification](https://github.com/OpenObservability/OpenMetrics). Allows

From 7f570f3f6057be5bdab3da4c817ff8b1a962c483 Mon Sep 17 00:00:00 2001
From: Max Inden <mail@max-inden.de>
Date: Wed, 17 Jul 2024 08:45:24 +0200
Subject: [PATCH 07/27] chore: prepare v0.22.3 (#209)

Signed-off-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6eed664..00af9688 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,13 +4,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [0.22.3] - unreleased
+## [0.22.3]
 
 ### Added
 
 - Added `encode_registry` and `encode_eof` functions to `text` module.
   See [PR 205].
-  
+
   [PR 205]: https://github.com/prometheus/client_rust/pull/205
 
 - Support all platforms with 32 bit atomics lacking 64 bit atomics.

From 24a8ada9108ab9d41c725139f6f5b3769bb8a762 Mon Sep 17 00:00:00 2001
From: Max Inden <mail@max-inden.de>
Date: Wed, 17 Jul 2024 08:57:15 +0200
Subject: [PATCH 08/27] chore(deps): update prost dependencies to v0.12 (#210)

Signed-off-by: koushiro <koushiro.cqx@gmail.com>
Signed-off-by: Max Inden <mail@max-inden.de>
Co-authored-by: koushiro <koushiro.cqx@gmail.com>
---
 CHANGELOG.md         |  7 +++++++
 Cargo.toml           | 10 +++++-----
 src/encoding/text.rs |  2 +-
 3 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00af9688..5fa5ea86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.23.0] - unreleased
+
+- Update `prost` dependencies to `v0.12`.
+  See [PR 198].
+
+[PR 198]: https://github.com/prometheus/client_rust/pull/198
+
 ## [0.22.3]
 
 ### Added
diff --git a/Cargo.toml b/Cargo.toml
index 4f78e1ef..2c1f8892 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client"
-version = "0.22.3"
+version = "0.23.0"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Open Metrics client library allowing users to natively instrument applications."
@@ -22,14 +22,14 @@ dtoa = "1.0"
 itoa = "1.0"
 parking_lot = "0.12"
 prometheus-client-derive-encode = { version = "0.4.1", path = "derive-encode" }
-prost = { version = "0.11.0", optional = true }
-prost-types = { version = "0.11.0", optional = true }
+prost = { version = "0.12.0", optional = true }
+prost-types = { version = "0.12.0", optional = true }
 
 [dev-dependencies]
 async-std = { version = "1", features = ["attributes"] }
 criterion = "0.5"
 http-types = "2"
-pyo3 = "0.20"
+pyo3 = "0.21"
 quickcheck = "1"
 rand = "0.8.4"
 tide = "0.16"
@@ -40,7 +40,7 @@ hyper-util = { version = "0.1.3", features = ["tokio"] }
 http-body-util = "0.1.1"
 
 [build-dependencies]
-prost-build = { version = "0.11.0", optional = true }
+prost-build = { version = "0.12.0", optional = true }
 
 [[bench]]
 name = "baseline"
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 42815269..bf65aa06 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -1104,7 +1104,7 @@ mod tests {
 
         println!("{:?}", input);
         Python::with_gil(|py| {
-            let parser = PyModule::from_code(
+            let parser = PyModule::from_code_bound(
                 py,
                 r#"
 from prometheus_client.openmetrics.parser import text_string_to_metric_families

From 87442a202dc73cbe730d8808f4f6682ef6dbc4ae Mon Sep 17 00:00:00 2001
From: Orne Brocaar <info@brocaar.com>
Date: Wed, 17 Jul 2024 08:27:11 +0100
Subject: [PATCH 09/27] feat(encoding): add impls for `Encode*Value` (#173)

Signed-off-by: Orne Brocaar <info@brocaar.com>
Signed-off-by: Max Inden <mail@max-inden.de>
Co-authored-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md         |  5 +++++
 src/collector.rs     |  2 +-
 src/encoding.rs      | 36 ++++++++++++++++++++++++++++++++++++
 src/encoding/text.rs |  2 +-
 src/registry.rs      |  2 +-
 5 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5fa5ea86..448d179f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 [PR 198]: https://github.com/prometheus/client_rust/pull/198
 
+- Add `EncodeGaugeValue` `i32` and `f32`, `EncodeCounterValue` `u32` and `f32` and `EncodeExemplarValue` `f32` and `u32` implementations.
+  See [PR 173].
+
+[PR 173]: https://github.com/prometheus/client_rust/pull/173
+
 ## [0.22.3]
 
 ### Added
diff --git a/src/collector.rs b/src/collector.rs
index eb5bdeb0..66270c41 100644
--- a/src/collector.rs
+++ b/src/collector.rs
@@ -23,7 +23,7 @@ use crate::encoding::DescriptorEncoder;
 ///
 /// impl Collector for MyCollector {
 ///     fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
-///         let counter = ConstCounter::new(42);
+///         let counter = ConstCounter::new(42u64);
 ///         let metric_encoder = encoder.encode_descriptor(
 ///             "my_counter",
 ///             "some help",
diff --git a/src/encoding.rs b/src/encoding.rs
index 30993999..c206a1c9 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -563,6 +563,18 @@ impl EncodeGaugeValue for f64 {
     }
 }
 
+impl EncodeGaugeValue for i32 {
+    fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode_i64(*self as i64)
+    }
+}
+
+impl EncodeGaugeValue for f32 {
+    fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode_f64(*self as f64)
+    }
+}
+
 /// Encoder for a gauge value.
 #[derive(Debug)]
 pub struct GaugeValueEncoder<'a>(GaugeValueEncoderInner<'a>);
@@ -619,6 +631,18 @@ impl EncodeCounterValue for f64 {
     }
 }
 
+impl EncodeCounterValue for u32 {
+    fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode_u64(*self as u64)
+    }
+}
+
+impl EncodeCounterValue for f32 {
+    fn encode(&self, encoder: &mut CounterValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode_f64(*self as f64)
+    }
+}
+
 /// Encoder for a counter value.
 #[derive(Debug)]
 pub struct CounterValueEncoder<'a>(CounterValueEncoderInner<'a>);
@@ -658,6 +682,18 @@ impl EncodeExemplarValue for u64 {
     }
 }
 
+impl EncodeExemplarValue for f32 {
+    fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode(*self as f64)
+    }
+}
+
+impl EncodeExemplarValue for u32 {
+    fn encode(&self, mut encoder: ExemplarValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.encode(*self as f64)
+    }
+}
+
 impl<'a> From<text::CounterValueEncoder<'a>> for CounterValueEncoder<'a> {
     fn from(e: text::CounterValueEncoder<'a>) -> Self {
         CounterValueEncoder(CounterValueEncoderInner::Text(e))
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index bf65aa06..216ddde0 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -1014,7 +1014,7 @@ mod tests {
                 &self,
                 mut encoder: crate::encoding::DescriptorEncoder,
             ) -> Result<(), std::fmt::Error> {
-                let counter = crate::metrics::counter::ConstCounter::new(42);
+                let counter = crate::metrics::counter::ConstCounter::new(42u64);
                 let metric_encoder = encoder.encode_descriptor(
                     &self.name,
                     "some help",
diff --git a/src/registry.rs b/src/registry.rs
index d2bb1972..c7dec3ba 100644
--- a/src/registry.rs
+++ b/src/registry.rs
@@ -185,7 +185,7 @@ impl Registry {
     ///
     /// impl Collector for MyCollector {
     ///     fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
-    ///         let counter = ConstCounter::new(42);
+    ///         let counter = ConstCounter::new(42u64);
     ///         let metric_encoder = encoder.encode_descriptor(
     ///             "my_counter",
     ///             "some help",

From 4216fa6f6f040a6ec73c2e391ea855725ee63854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=A9o=20Gillot-Lamure?= <leo.gillot@navaati.net>
Date: Wed, 17 Jul 2024 20:44:20 +0200
Subject: [PATCH 10/27] chore: add missing tests for u32 i32 that already work
 in metrics (#215)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Léo Gillot-Lamure <leo.gillot@navaati.net>
---
 src/encoding/text.rs | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 216ddde0..7cf22e4f 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -709,7 +709,7 @@ mod tests {
     use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar};
     use pyo3::{prelude::*, types::PyModule};
     use std::borrow::Cow;
-    use std::sync::atomic::AtomicU32;
+    use std::sync::atomic::{AtomicI32, AtomicU32};
 
     #[test]
     fn encode_counter() {
@@ -717,6 +717,9 @@ mod tests {
         let mut registry = Registry::default();
         registry.register("my_counter", "My counter", counter);
 
+        let counter_u32 = Counter::<u32, AtomicU32>::default();
+        registry.register("u32_counter", "Counter::<u32, AtomicU32>", counter_u32);
+
         let mut encoded = String::new();
 
         encode(&mut encoded, &registry).unwrap();
@@ -780,6 +783,9 @@ mod tests {
         let gauge = Gauge::<u32, AtomicU32>::default();
         registry.register("u32_gauge", "Gauge::<u32, AtomicU32>", gauge);
 
+        let gauge_i32 = Gauge::<i32, AtomicI32>::default();
+        registry.register("i32_gauge", "Gauge::<i32, AtomicU32>", gauge_i32);
+
         let mut encoded = String::new();
 
         encode(&mut encoded, &registry).unwrap();

From aeca8d8955e8a6c91e71c46b362b4cfb456cd32e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=A9o=20Gillot-Lamure?= <leo.gillot@navaati.net>
Date: Wed, 17 Jul 2024 20:58:38 +0200
Subject: [PATCH 11/27] feat(metrics): add support of f32 for Gauge and Counter
 (#216)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Léo Gillot-Lamure <leo.gillot@navaati.net>
Signed-off-by: Max Inden <mail@max-inden.de>
Co-authored-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md           | 12 +++++++++--
 src/encoding/text.rs   |  6 +++++-
 src/metrics/counter.rs | 25 ++++++++++++++++++++++
 src/metrics/gauge.rs   | 48 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 448d179f..3930fccb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,15 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [0.23.0] - unreleased
 
+### Changed
+
+- `ConstCounter::new` now requires specifying the type of literal arguments, like this: `ConstCounter::new(42u64);`.
+  See [PR 173].
+
 - Update `prost` dependencies to `v0.12`.
   See [PR 198].
 
 [PR 198]: https://github.com/prometheus/client_rust/pull/198
 
-- Add `EncodeGaugeValue` `i32` and `f32`, `EncodeCounterValue` `u32` and `f32` and `EncodeExemplarValue` `f32` and `u32` implementations.
-  See [PR 173].
+### Added
+
+- Support `i32`/`f32` for `Gauge` and `u32`/`f32` for `Counter`/`CounterWithExemplar`.
+  See [PR 173] and [PR 216].
 
 [PR 173]: https://github.com/prometheus/client_rust/pull/173
+[PR 216]: https://github.com/prometheus/client_rust/pull/216
 
 ## [0.22.3]
 
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 7cf22e4f..955396a6 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -717,9 +717,10 @@ mod tests {
         let mut registry = Registry::default();
         registry.register("my_counter", "My counter", counter);
 
+        let counter_f32 = Counter::<f32, AtomicU32>::default();
+        registry.register("f32_counter", "Counter::<f32, AtomicU32>", counter_f32);
         let counter_u32 = Counter::<u32, AtomicU32>::default();
         registry.register("u32_counter", "Counter::<u32, AtomicU32>", counter_u32);
-
         let mut encoded = String::new();
 
         encode(&mut encoded, &registry).unwrap();
@@ -783,6 +784,9 @@ mod tests {
         let gauge = Gauge::<u32, AtomicU32>::default();
         registry.register("u32_gauge", "Gauge::<u32, AtomicU32>", gauge);
 
+        let gauge_f32 = Gauge::<f32, AtomicU32>::default();
+        registry.register("f32_gauge", "Gauge::<f32, AtomicU32>", gauge_f32);
+
         let gauge_i32 = Gauge::<i32, AtomicI32>::default();
         registry.register("i32_gauge", "Gauge::<i32, AtomicU32>", gauge_i32);
 
diff --git a/src/metrics/counter.rs b/src/metrics/counter.rs
index 63d84ffc..d1fee30f 100644
--- a/src/metrics/counter.rs
+++ b/src/metrics/counter.rs
@@ -169,6 +169,31 @@ impl Atomic<f64> for AtomicU64 {
     }
 }
 
+impl Atomic<f32> for AtomicU32 {
+    fn inc(&self) -> f32 {
+        self.inc_by(1.0)
+    }
+
+    fn inc_by(&self, v: f32) -> f32 {
+        let mut old_u32 = self.load(Ordering::Relaxed);
+        let mut old_f32;
+        loop {
+            old_f32 = f32::from_bits(old_u32);
+            let new = f32::to_bits(old_f32 + v);
+            match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) {
+                Ok(_) => break,
+                Err(x) => old_u32 = x,
+            }
+        }
+
+        old_f32
+    }
+
+    fn get(&self) -> f32 {
+        f32::from_bits(self.load(Ordering::Relaxed))
+    }
+}
+
 impl<N, A> TypedMetric for Counter<N, A> {
     const TYPE: MetricType = MetricType::Counter;
 }
diff --git a/src/metrics/gauge.rs b/src/metrics/gauge.rs
index fcf7e6a7..5c8bef9b 100644
--- a/src/metrics/gauge.rs
+++ b/src/metrics/gauge.rs
@@ -262,6 +262,54 @@ impl Atomic<f64> for AtomicU64 {
     }
 }
 
+impl Atomic<f32> for AtomicU32 {
+    fn inc(&self) -> f32 {
+        self.inc_by(1.0)
+    }
+
+    fn inc_by(&self, v: f32) -> f32 {
+        let mut old_u32 = self.load(Ordering::Relaxed);
+        let mut old_f32;
+        loop {
+            old_f32 = f32::from_bits(old_u32);
+            let new = f32::to_bits(old_f32 + v);
+            match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) {
+                Ok(_) => break,
+                Err(x) => old_u32 = x,
+            }
+        }
+
+        old_f32
+    }
+
+    fn dec(&self) -> f32 {
+        self.dec_by(1.0)
+    }
+
+    fn dec_by(&self, v: f32) -> f32 {
+        let mut old_u32 = self.load(Ordering::Relaxed);
+        let mut old_f32;
+        loop {
+            old_f32 = f32::from_bits(old_u32);
+            let new = f32::to_bits(old_f32 - v);
+            match self.compare_exchange_weak(old_u32, new, Ordering::Relaxed, Ordering::Relaxed) {
+                Ok(_) => break,
+                Err(x) => old_u32 = x,
+            }
+        }
+
+        old_f32
+    }
+
+    fn set(&self, v: f32) -> f32 {
+        f32::from_bits(self.swap(f32::to_bits(v), Ordering::Relaxed))
+    }
+
+    fn get(&self) -> f32 {
+        f32::from_bits(self.load(Ordering::Relaxed))
+    }
+}
+
 impl<N, A> TypedMetric for Gauge<N, A> {
     const TYPE: MetricType = MetricType::Gauge;
 }

From 53f15ec2f9e2d5cf7df548bdae362c7c98aaf024 Mon Sep 17 00:00:00 2001
From: flaneur <me.ssword@gmail.com>
Date: Mon, 29 Jul 2024 15:00:29 +0800
Subject: [PATCH 12/27] feat: allow Arc<String> on EncodeLabelSet (#217)

Signed-off-by: Li Yazhou <me.ssword@gmail.com>
---
 CHANGELOG.md               |  4 ++++
 derive-encode/tests/lib.rs | 32 ++++++++++++++++++++++++++++++++
 src/encoding.rs            |  7 +++++++
 3 files changed, 43 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3930fccb..32de9b95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,8 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Support `i32`/`f32` for `Gauge` and `u32`/`f32` for `Counter`/`CounterWithExemplar`.
   See [PR 173] and [PR 216].
 
+- Supoort `Arc<String>` for `EncodeLabelValue`.
+  See [PR 217].
+
 [PR 173]: https://github.com/prometheus/client_rust/pull/173
 [PR 216]: https://github.com/prometheus/client_rust/pull/216
+[PR 217]: https://github.com/prometheus/client_rust/pull/217
 
 ## [0.22.3]
 
diff --git a/derive-encode/tests/lib.rs b/derive-encode/tests/lib.rs
index fba8412d..5c7eb54d 100644
--- a/derive-encode/tests/lib.rs
+++ b/derive-encode/tests/lib.rs
@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
 use prometheus_client::encoding::text::encode;
 use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue};
 use prometheus_client::metrics::counter::Counter;
@@ -137,6 +139,36 @@ fn remap_keyword_identifiers() {
     assert_eq!(expected, buffer);
 }
 
+#[test]
+fn arc_string() {
+    #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
+    struct Labels {
+        client_id: Arc<String>,
+    }
+
+    let mut registry = Registry::default();
+    let family = Family::<Labels, Counter>::default();
+    registry.register("my_counter", "This is my counter", family.clone());
+
+    // Record a single HTTP GET request.
+    let client_id = Arc::new("client_id".to_string());
+    family
+        .get_or_create(&Labels {
+            client_id: client_id.clone(),
+        })
+        .inc();
+
+    // Encode all metrics in the registry in the text format.
+    let mut buffer = String::new();
+    encode(&mut buffer, &registry).unwrap();
+
+    let expected = "# HELP my_counter This is my counter.\n".to_owned()
+        + "# TYPE my_counter counter\n"
+        + "my_counter_total{client_id=\"client_id\"} 1\n"
+        + "# EOF\n";
+    assert_eq!(expected, buffer);
+}
+
 #[test]
 fn flatten() {
     #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
diff --git a/src/encoding.rs b/src/encoding.rs
index c206a1c9..c9c9a838 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -468,12 +468,19 @@ impl EncodeLabelValue for &str {
         Ok(())
     }
 }
+
 impl EncodeLabelValue for String {
     fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
         EncodeLabelValue::encode(&self.as_str(), encoder)
     }
 }
 
+impl EncodeLabelValue for &String {
+    fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
+        EncodeLabelValue::encode(&self.as_str(), encoder)
+    }
+}
+
 impl<'a> EncodeLabelValue for Cow<'a, str> {
     fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
         EncodeLabelValue::encode(&self.as_ref(), encoder)

From 3e6579ba36c37c108869df27bf6fd29355a7cbef Mon Sep 17 00:00:00 2001
From: Jerry Wang <j3rry.wan9@gmail.com>
Date: Sun, 4 Aug 2024 16:40:23 +0800
Subject: [PATCH 13/27] chore(examples): add `axum` example (#219)

Signed-off-by: Jerry Wang <j3rry.wan9@gmail.com>
---
 Cargo.toml       |  1 +
 examples/axum.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)
 create mode 100644 examples/axum.rs

diff --git a/Cargo.toml b/Cargo.toml
index 2c1f8892..b9e93380 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ prost-types = { version = "0.12.0", optional = true }
 
 [dev-dependencies]
 async-std = { version = "1", features = ["attributes"] }
+axum = "0.7"
 criterion = "0.5"
 http-types = "2"
 pyo3 = "0.21"
diff --git a/examples/axum.rs b/examples/axum.rs
new file mode 100644
index 00000000..bf852559
--- /dev/null
+++ b/examples/axum.rs
@@ -0,0 +1,88 @@
+use axum::body::Body;
+use axum::extract::State;
+use axum::http::header::CONTENT_TYPE;
+use axum::http::StatusCode;
+use axum::response::{IntoResponse, Response};
+use axum::routing::get;
+use axum::Router;
+use prometheus_client::encoding::text::encode;
+use prometheus_client::metrics::counter::Counter;
+use prometheus_client::metrics::family::Family;
+use prometheus_client::registry::Registry;
+use prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue};
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
+pub enum Method {
+    Get,
+    Post,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
+pub struct MethodLabels {
+    pub method: Method,
+}
+
+#[derive(Debug)]
+pub struct Metrics {
+    requests: Family<MethodLabels, Counter>,
+}
+
+impl Metrics {
+    pub fn inc_requests(&self, method: Method) {
+        self.requests.get_or_create(&MethodLabels { method }).inc();
+    }
+}
+
+#[derive(Debug)]
+pub struct AppState {
+    pub registry: Registry,
+}
+
+pub async fn metrics_handler(State(state): State<Arc<Mutex<AppState>>>) -> impl IntoResponse {
+    let state = state.lock().await;
+    let mut buffer = String::new();
+    encode(&mut buffer, &state.registry).unwrap();
+
+    Response::builder()
+        .status(StatusCode::OK)
+        .header(
+            CONTENT_TYPE,
+            "application/openmetrics-text; version=1.0.0; charset=utf-8",
+        )
+        .body(Body::from(buffer))
+        .unwrap()
+}
+
+pub async fn some_handler(State(metrics): State<Arc<Mutex<Metrics>>>) -> impl IntoResponse {
+    metrics.lock().await.inc_requests(Method::Get);
+    "okay".to_string()
+}
+
+#[tokio::main]
+async fn main() {
+    let metrics = Metrics {
+        requests: Family::default(),
+    };
+    let mut state = AppState {
+        registry: Registry::default(),
+    };
+    state
+        .registry
+        .register("requests", "Count of requests", metrics.requests.clone());
+    let metrics = Arc::new(Mutex::new(metrics));
+    let state = Arc::new(Mutex::new(state));
+
+    let router = Router::new()
+        .route("/metrics", get(metrics_handler))
+        .with_state(state)
+        .route("/handler", get(some_handler))
+        .with_state(metrics);
+    let port = 8080;
+    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
+        .await
+        .unwrap();
+
+    axum::serve(listener, router).await.unwrap();
+}

From 0c3e89ea1ea4cf07ca92dc0200721709a194d53d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 6 Aug 2024 16:38:39 +0200
Subject: [PATCH 14/27] build(deps): update pyo3 requirement from 0.21 to 0.22
 (#213)

Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.21.0...v0.22.1)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
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 b9e93380..5514ab68 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,7 @@ async-std = { version = "1", features = ["attributes"] }
 axum = "0.7"
 criterion = "0.5"
 http-types = "2"
-pyo3 = "0.21"
+pyo3 = "0.22"
 quickcheck = "1"
 rand = "0.8.4"
 tide = "0.16"

From ad05f0ff2ea6b2802a306dc9686389f29eb3b25a Mon Sep 17 00:00:00 2001
From: Max Inden <mail@max-inden.de>
Date: Fri, 6 Sep 2024 12:35:57 +0200
Subject: [PATCH 15/27] fix(encoding): correctly handle empty family labels
 (#224)

Signed-off-by: Tyler Levine <tyler.levine@falconx.io>
Signed-off-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md              |  7 ++++
 examples/custom-metric.rs |  4 +--
 src/encoding.rs           |  6 +++-
 src/encoding/text.rs      | 69 ++++++++++++++++++++++++++++++++++-----
 src/metrics/counter.rs    |  6 ++--
 src/metrics/histogram.rs  |  4 +--
 6 files changed, 79 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32de9b95..66eb579d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [PR 216]: https://github.com/prometheus/client_rust/pull/216
 [PR 217]: https://github.com/prometheus/client_rust/pull/217
 
+### Fixed
+
+- Don't prepend `,` when encoding empty family label set.
+  See [PR 175].
+
+[PR 175]: https://github.com/prometheus/client_rust/pull/175
+
 ## [0.22.3]
 
 ### Added
diff --git a/examples/custom-metric.rs b/examples/custom-metric.rs
index 9ad17a5a..a61ff8a7 100644
--- a/examples/custom-metric.rs
+++ b/examples/custom-metric.rs
@@ -1,4 +1,4 @@
-use prometheus_client::encoding::{text::encode, EncodeMetric, MetricEncoder};
+use prometheus_client::encoding::{text::encode, EncodeMetric, MetricEncoder, NoLabelSet};
 use prometheus_client::metrics::MetricType;
 use prometheus_client::registry::Registry;
 
@@ -20,7 +20,7 @@ impl EncodeMetric for MyCustomMetric {
         // E.g. every CPU cycle spend in this method delays the response send to
         // the Prometheus server.
 
-        encoder.encode_counter::<(), _, u64>(&rand::random::<u64>(), None)
+        encoder.encode_counter::<NoLabelSet, _, u64>(&rand::random::<u64>(), None)
     }
 
     fn metric_type(&self) -> prometheus_client::metrics::MetricType {
diff --git a/src/encoding.rs b/src/encoding.rs
index c9c9a838..c644f82b 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -247,6 +247,10 @@ pub trait EncodeLabel {
 #[derive(Debug)]
 pub struct LabelEncoder<'a>(LabelEncoderInner<'a>);
 
+/// Uninhabited type to represent the lack of a label set for a metric
+#[derive(Debug)]
+pub enum NoLabelSet {}
+
 #[derive(Debug)]
 enum LabelEncoderInner<'a> {
     Text(text::LabelEncoder<'a>),
@@ -352,7 +356,7 @@ impl<T: EncodeLabel> EncodeLabelSet for Vec<T> {
     }
 }
 
-impl EncodeLabelSet for () {
+impl EncodeLabelSet for NoLabelSet {
     fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
         Ok(())
     }
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 955396a6..4946fe35 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -37,7 +37,7 @@
 //! assert_eq!(expected_msg, buffer);
 //! ```
 
-use crate::encoding::{EncodeExemplarValue, EncodeLabelSet};
+use crate::encoding::{EncodeExemplarValue, EncodeLabelSet, NoLabelSet};
 use crate::metrics::exemplar::Exemplar;
 use crate::metrics::MetricType;
 use crate::registry::{Prefix, Registry, Unit};
@@ -324,7 +324,7 @@ impl<'a> MetricEncoder<'a> {
 
         self.write_suffix("total")?;
 
-        self.encode_labels::<()>(None)?;
+        self.encode_labels::<NoLabelSet>(None)?;
 
         v.encode(
             &mut CounterValueEncoder {
@@ -348,7 +348,7 @@ impl<'a> MetricEncoder<'a> {
     ) -> Result<(), std::fmt::Error> {
         self.write_prefix_name_unit()?;
 
-        self.encode_labels::<()>(None)?;
+        self.encode_labels::<NoLabelSet>(None)?;
 
         v.encode(
             &mut GaugeValueEncoder {
@@ -404,14 +404,14 @@ impl<'a> MetricEncoder<'a> {
     ) -> Result<(), std::fmt::Error> {
         self.write_prefix_name_unit()?;
         self.write_suffix("sum")?;
-        self.encode_labels::<()>(None)?;
+        self.encode_labels::<NoLabelSet>(None)?;
         self.writer.write_str(" ")?;
         self.writer.write_str(dtoa::Buffer::new().format(sum))?;
         self.newline()?;
 
         self.write_prefix_name_unit()?;
         self.write_suffix("count")?;
-        self.encode_labels::<()>(None)?;
+        self.encode_labels::<NoLabelSet>(None)?;
         self.writer.write_str(" ")?;
         self.writer.write_str(itoa::Buffer::new().format(count))?;
         self.newline()?;
@@ -512,12 +512,37 @@ impl<'a> MetricEncoder<'a> {
             additional_labels.encode(LabelSetEncoder::new(self.writer).into())?;
         }
 
-        if let Some(labels) = &self.family_labels {
-            if !self.const_labels.is_empty() || additional_labels.is_some() {
-                self.writer.write_str(",")?;
+        /// Writer impl which prepends a comma on the first call to write output to the wrapped writer
+        struct CommaPrependingWriter<'a> {
+            writer: &'a mut dyn Write,
+            should_prepend: bool,
+        }
+
+        impl Write for CommaPrependingWriter<'_> {
+            fn write_str(&mut self, s: &str) -> std::fmt::Result {
+                if self.should_prepend {
+                    self.writer.write_char(',')?;
+                    self.should_prepend = false;
+                }
+                self.writer.write_str(s)
             }
+        }
 
-            labels.encode(LabelSetEncoder::new(self.writer).into())?;
+        if let Some(labels) = self.family_labels {
+            // if const labels or additional labels have been written, a comma must be prepended before writing the family labels.
+            // However, it could be the case that the family labels are `Some` and yet empty, so the comma should _only_
+            // be prepended if one of the `Write` methods are actually called when attempting to write the family labels.
+            // Therefore, wrap the writer on `Self` with a CommaPrependingWriter if other labels have been written and
+            // there may be a need to prepend an extra comma before writing additional labels.
+            if !self.const_labels.is_empty() || additional_labels.is_some() {
+                let mut writer = CommaPrependingWriter {
+                    writer: self.writer,
+                    should_prepend: true,
+                };
+                labels.encode(LabelSetEncoder::new(&mut writer).into())?;
+            } else {
+                labels.encode(LabelSetEncoder::new(self.writer).into())?;
+            };
         }
 
         self.writer.write_str("}")?;
@@ -709,6 +734,7 @@ mod tests {
     use crate::metrics::{counter::Counter, exemplar::CounterWithExemplar};
     use pyo3::{prelude::*, types::PyModule};
     use std::borrow::Cow;
+    use std::fmt::Error;
     use std::sync::atomic::{AtomicI32, AtomicU32};
 
     #[test]
@@ -899,6 +925,31 @@ mod tests {
         parse_with_python_client(encoded);
     }
 
+    #[test]
+    fn encode_histogram_family_with_empty_struct_family_labels() {
+        let mut registry = Registry::default();
+        let family =
+            Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
+        registry.register("my_histogram", "My histogram", family.clone());
+
+        #[derive(Eq, PartialEq, Hash, Debug, Clone)]
+        struct EmptyLabels {}
+
+        impl EncodeLabelSet for EmptyLabels {
+            fn encode(&self, _encoder: crate::encoding::LabelSetEncoder) -> Result<(), Error> {
+                Ok(())
+            }
+        }
+
+        family.get_or_create(&EmptyLabels {}).observe(1.0);
+
+        let mut encoded = String::new();
+
+        encode(&mut encoded, &registry).unwrap();
+
+        parse_with_python_client(encoded);
+    }
+
     #[test]
     fn encode_histogram_with_exemplars() {
         let mut registry = Registry::default();
diff --git a/src/metrics/counter.rs b/src/metrics/counter.rs
index d1fee30f..7016c361 100644
--- a/src/metrics/counter.rs
+++ b/src/metrics/counter.rs
@@ -2,7 +2,7 @@
 //!
 //! See [`Counter`] for details.
 
-use crate::encoding::{EncodeMetric, MetricEncoder};
+use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet};
 
 use super::{MetricType, TypedMetric};
 use std::marker::PhantomData;
@@ -204,7 +204,7 @@ where
     A: Atomic<N>,
 {
     fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
-        encoder.encode_counter::<(), _, u64>(&self.get(), None)
+        encoder.encode_counter::<NoLabelSet, _, u64>(&self.get(), None)
     }
 
     fn metric_type(&self) -> MetricType {
@@ -236,7 +236,7 @@ where
     N: crate::encoding::EncodeCounterValue,
 {
     fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
-        encoder.encode_counter::<(), _, u64>(&self.value, None)
+        encoder.encode_counter::<NoLabelSet, _, u64>(&self.value, None)
     }
 
     fn metric_type(&self) -> MetricType {
diff --git a/src/metrics/histogram.rs b/src/metrics/histogram.rs
index 66f4496d..1e418ad9 100644
--- a/src/metrics/histogram.rs
+++ b/src/metrics/histogram.rs
@@ -2,7 +2,7 @@
 //!
 //! See [`Histogram`] for details.
 
-use crate::encoding::{EncodeMetric, MetricEncoder};
+use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet};
 
 use super::{MetricType, TypedMetric};
 use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
@@ -133,7 +133,7 @@ pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item
 impl EncodeMetric for Histogram {
     fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
         let (sum, count, buckets) = self.get();
-        encoder.encode_histogram::<()>(sum, count, &buckets, None)
+        encoder.encode_histogram::<NoLabelSet>(sum, count, &buckets, None)
     }
 
     fn metric_type(&self) -> MetricType {

From 12923ca39c6392dcc7d0e81af1acc3788c4235d4 Mon Sep 17 00:00:00 2001
From: Ivan Babrou <github@ivan.computer>
Date: Wed, 16 Oct 2024 03:40:16 -0700
Subject: [PATCH 16/27] fix(metrics/gauge): implement Atomic<u64> for AtomicU64
 (#226)

Between forcing end users to do endless `as i64` for things that are
clearly `u64` and having one error case for rarely used protobuf when
a gauge is set to `u64::MAX`, the latter seems like the right choice.

Signed-off-by: Ivan Babrou <github@ivan.computer>
---
 CHANGELOG.md             |  5 +++++
 src/encoding.rs          | 13 +++++++++++++
 src/encoding/protobuf.rs | 38 ++++++++++++++++++++++++++++++++++++++
 src/metrics/gauge.rs     | 27 +++++++++++++++++++++++++++
 4 files changed, 83 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66eb579d..c398e073 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [0.23.0] - unreleased
 
+
 ### Changed
 
 - `ConstCounter::new` now requires specifying the type of literal arguments, like this: `ConstCounter::new(42u64);`.
@@ -14,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Update `prost` dependencies to `v0.12`.
   See [PR 198].
 
+- Implement `Atomic<u64>` for `AtomicU64` for gauges.
+  See [PR 226].
+
+[PR 226]: https://github.com/prometheus/client_rust/pull/198
 [PR 198]: https://github.com/prometheus/client_rust/pull/198
 
 ### Added
diff --git a/src/encoding.rs b/src/encoding.rs
index c644f82b..7f77efa0 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -568,6 +568,19 @@ impl EncodeGaugeValue for i64 {
     }
 }
 
+impl EncodeGaugeValue for u64 {
+    fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
+        // Between forcing end users to do endless as i64 for things that are
+        // clearly u64 and having one error case for rarely used protobuf when
+        // a gauge is set to u64::MAX, the latter seems like the right choice.
+        if *self == u64::MAX {
+            return Err(std::fmt::Error);
+        }
+
+        encoder.encode_i64(*self as i64)
+    }
+}
+
 impl EncodeGaugeValue for f64 {
     fn encode(&self, encoder: &mut GaugeValueEncoder) -> Result<(), std::fmt::Error> {
         encoder.encode_f64(*self)
diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs
index eef2731a..095a3dba 100644
--- a/src/encoding/protobuf.rs
+++ b/src/encoding/protobuf.rs
@@ -453,6 +453,7 @@ mod tests {
     use std::borrow::Cow;
     use std::collections::HashSet;
     use std::sync::atomic::AtomicI64;
+    use std::sync::atomic::AtomicU64;
 
     #[test]
     fn encode_counter_int() {
@@ -600,6 +601,43 @@ mod tests {
         }
     }
 
+    #[test]
+    fn encode_gauge_u64_normal() {
+        let mut registry = Registry::default();
+        let gauge = Gauge::<u64, AtomicU64>::default();
+        registry.register("my_gauge", "My gauge", gauge.clone());
+        gauge.set(12345);
+
+        let metric_set = encode(&registry).unwrap();
+        let family = metric_set.metric_families.first().unwrap();
+        assert_eq!("my_gauge", family.name);
+        assert_eq!("My gauge.", family.help);
+
+        assert_eq!(
+            openmetrics_data_model::MetricType::Gauge as i32,
+            extract_metric_type(&metric_set)
+        );
+
+        match extract_metric_point_value(&metric_set) {
+            openmetrics_data_model::metric_point::Value::GaugeValue(value) => {
+                let expected = openmetrics_data_model::gauge_value::Value::IntValue(12345);
+                assert_eq!(Some(expected), value.value);
+            }
+            _ => panic!("wrong value type"),
+        }
+    }
+
+    #[test]
+    fn encode_gauge_u64_max() {
+        let mut registry = Registry::default();
+        let gauge = Gauge::<u64, AtomicU64>::default();
+        registry.register("my_gauge", "My gauge", gauge.clone());
+        gauge.set(u64::MAX);
+
+        // This expected to fail as protobuf uses i64 and u64::MAX does not fit into it.
+        assert!(encode(&registry).is_err());
+    }
+
     #[test]
     fn encode_counter_family() {
         let mut registry = Registry::default();
diff --git a/src/metrics/gauge.rs b/src/metrics/gauge.rs
index 5c8bef9b..abbc8cee 100644
--- a/src/metrics/gauge.rs
+++ b/src/metrics/gauge.rs
@@ -213,6 +213,33 @@ impl Atomic<i64> for AtomicI64 {
     }
 }
 
+#[cfg(target_has_atomic = "64")]
+impl Atomic<u64> for AtomicU64 {
+    fn inc(&self) -> u64 {
+        self.inc_by(1)
+    }
+
+    fn inc_by(&self, v: u64) -> u64 {
+        self.fetch_add(v, Ordering::Relaxed)
+    }
+
+    fn dec(&self) -> u64 {
+        self.dec_by(1)
+    }
+
+    fn dec_by(&self, v: u64) -> u64 {
+        self.fetch_sub(v, Ordering::Relaxed)
+    }
+
+    fn set(&self, v: u64) -> u64 {
+        self.swap(v, Ordering::Relaxed)
+    }
+
+    fn get(&self) -> u64 {
+        self.load(Ordering::Relaxed)
+    }
+}
+
 #[cfg(target_has_atomic = "64")]
 impl Atomic<f64> for AtomicU64 {
     fn inc(&self) -> f64 {

From 3e6b9e2e00c376706e5e4c2ed7cb7bcb5c9fb318 Mon Sep 17 00:00:00 2001
From: katelyn martin <me+cratelyn@katelyn.world>
Date: Thu, 28 Nov 2024 15:13:27 -0500
Subject: [PATCH 17/27] chore: add `CONTRIBUTING.md` instructions (#247)

the test suite in this repository has some implicit dependencies upon
`protoc` and the Python client library.

in order to help newcomers get situated and effectively contributing to
this project, a `CONTRIBUTING.md` file is added to help provide some
guidance on how to install the protocol buffer compiler, and how to
create and activate a virtual environment with the Python library
installed.

Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
---
 CONTRIBUTING.md | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..e20a178b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# Contributing
+
+## Protocol Buffers
+
+The `build.rs` script in this library depends upon the
+[Protocol Buffers compiler][protoc]. Be sure that `protoc` is installed and
+available within your `PATH`.
+
+[protoc]: https://docs.rs/prost-build/latest/prost_build/#sourcing-protoc
+
+## Python Dependencies
+
+This repository uses the [`prometheus-client`][client-python] Python client
+library in its test suite.
+
+You may create and activate a virtual environment with this dependency
+installed by running the following shell commands from the root of this
+repository:
+
+```shell
+python -m venv ./venv
+source venv/bin/activate
+pip install prometheus-client
+```
+
+[client-python]: https://github.com/prometheus/client_python

From d3e363728a40c1f9fefb6ed3003c7cec5786aef4 Mon Sep 17 00:00:00 2001
From: Qinxuan Chen <koushiro.cqx@gmail.com>
Date: Fri, 29 Nov 2024 18:14:51 +0800
Subject: [PATCH 18/27] chore(ci): fix clippy warnings when using rust 1.83
 (#250)

Signed-off-by: koushiro <koushiro.cqx@gmail.com>
---
 Cargo.toml               |  1 +
 examples/hyper.rs        |  6 ++----
 src/encoding.rs          | 20 ++++++++++----------
 src/encoding/protobuf.rs | 16 ++++++++--------
 src/encoding/text.rs     | 34 +++++++++++++++++-----------------
 5 files changed, 38 insertions(+), 39 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 5514ab68..18dd395f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ prost-types = { version = "0.12.0", optional = true }
 async-std = { version = "1", features = ["attributes"] }
 axum = "0.7"
 criterion = "0.5"
+futures = "0.3"
 http-types = "2"
 pyo3 = "0.22"
 quickcheck = "1"
diff --git a/examples/hyper.rs b/examples/hyper.rs
index 82ee121b..bbab0058 100644
--- a/examples/hyper.rs
+++ b/examples/hyper.rs
@@ -1,3 +1,4 @@
+use futures::future::BoxFuture;
 use http_body_util::{combinators, BodyExt, Full};
 use hyper::{
     body::{Bytes, Incoming},
@@ -8,10 +9,8 @@ use hyper::{
 use hyper_util::rt::TokioIo;
 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::{
@@ -69,8 +68,7 @@ type BoxBody = combinators::BoxBody<Bytes, hyper::Error>;
 /// This function returns a HTTP handler (i.e. another function)
 pub fn make_handler(
     registry: Arc<Registry>,
-) -> impl Fn(Request<Incoming>) -> Pin<Box<dyn Future<Output = io::Result<Response<BoxBody>>> + Send>>
-{
+) -> impl Fn(Request<Incoming>) -> BoxFuture<'static, io::Result<Response<BoxBody>>> {
     // This closure accepts a request and responds with the OpenMetrics encoding of our metrics.
     move |_req: Request<Incoming>| {
         let reg = registry.clone();
diff --git a/src/encoding.rs b/src/encoding.rs
index 7f77efa0..9b2e497c 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -230,7 +230,7 @@ impl<'a> From<protobuf::LabelSetEncoder<'a>> for LabelSetEncoder<'a> {
     }
 }
 
-impl<'a> LabelSetEncoder<'a> {
+impl LabelSetEncoder<'_> {
     /// Encode the given label.
     pub fn encode_label(&mut self) -> LabelEncoder {
         for_both_mut!(self, LabelSetEncoderInner, e, e.encode_label().into())
@@ -271,7 +271,7 @@ impl<'a> From<protobuf::LabelEncoder<'a>> for LabelEncoder<'a> {
     }
 }
 
-impl<'a> LabelEncoder<'a> {
+impl LabelEncoder<'_> {
     /// Encode a label.
     pub fn encode_label_key(&mut self) -> Result<LabelKeyEncoder, std::fmt::Error> {
         for_both_mut!(
@@ -313,7 +313,7 @@ impl<'a> From<protobuf::LabelKeyEncoder<'a>> for LabelKeyEncoder<'a> {
     }
 }
 
-impl<'a> std::fmt::Write for LabelKeyEncoder<'a> {
+impl std::fmt::Write for LabelKeyEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         for_both_mut!(self, LabelKeyEncoderInner, e, e.write_str(s))
     }
@@ -390,7 +390,7 @@ impl EncodeLabelKey for String {
     }
 }
 
-impl<'a> EncodeLabelKey for Cow<'a, str> {
+impl EncodeLabelKey for Cow<'_, str> {
     fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> {
         EncodeLabelKey::encode(&self.as_ref(), encoder)
     }
@@ -453,14 +453,14 @@ enum LabelValueEncoderInner<'a> {
     Protobuf(protobuf::LabelValueEncoder<'a>),
 }
 
-impl<'a> LabelValueEncoder<'a> {
+impl LabelValueEncoder<'_> {
     /// Finish encoding the label value.
     pub fn finish(self) -> Result<(), std::fmt::Error> {
         for_both!(self, LabelValueEncoderInner, e, e.finish())
     }
 }
 
-impl<'a> std::fmt::Write for LabelValueEncoder<'a> {
+impl std::fmt::Write for LabelValueEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s))
     }
@@ -485,7 +485,7 @@ impl EncodeLabelValue for &String {
     }
 }
 
-impl<'a> EncodeLabelValue for Cow<'a, str> {
+impl EncodeLabelValue for Cow<'_, str> {
     fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
         EncodeLabelValue::encode(&self.as_ref(), encoder)
     }
@@ -610,7 +610,7 @@ enum GaugeValueEncoderInner<'a> {
     Protobuf(protobuf::GaugeValueEncoder<'a>),
 }
 
-impl<'a> GaugeValueEncoder<'a> {
+impl GaugeValueEncoder<'_> {
     fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
         for_both_mut!(self, GaugeValueEncoderInner, e, e.encode_u32(v))
     }
@@ -678,7 +678,7 @@ enum CounterValueEncoderInner<'a> {
     Protobuf(protobuf::CounterValueEncoder<'a>),
 }
 
-impl<'a> CounterValueEncoder<'a> {
+impl CounterValueEncoder<'_> {
     fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         for_both_mut!(self, CounterValueEncoderInner, e, e.encode_f64(v))
     }
@@ -742,7 +742,7 @@ enum ExemplarValueEncoderInner<'a> {
     Protobuf(protobuf::ExemplarValueEncoder<'a>),
 }
 
-impl<'a> ExemplarValueEncoder<'a> {
+impl ExemplarValueEncoder<'_> {
     fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         for_both_mut!(self, ExemplarValueEncoderInner, e, e.encode(v))
     }
diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs
index 095a3dba..5ec54c91 100644
--- a/src/encoding/protobuf.rs
+++ b/src/encoding/protobuf.rs
@@ -322,7 +322,7 @@ pub(crate) struct GaugeValueEncoder<'a> {
     value: &'a mut openmetrics_data_model::gauge_value::Value,
 }
 
-impl<'a> GaugeValueEncoder<'a> {
+impl GaugeValueEncoder<'_> {
     pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
         self.encode_i64(v as i64)
     }
@@ -343,7 +343,7 @@ pub(crate) struct ExemplarValueEncoder<'a> {
     value: &'a mut f64,
 }
 
-impl<'a> ExemplarValueEncoder<'a> {
+impl ExemplarValueEncoder<'_> {
     pub fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         *self.value = v;
         Ok(())
@@ -364,7 +364,7 @@ pub(crate) struct CounterValueEncoder<'a> {
     value: &'a mut openmetrics_data_model::counter_value::Total,
 }
 
-impl<'a> CounterValueEncoder<'a> {
+impl CounterValueEncoder<'_> {
     pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         *self.value = openmetrics_data_model::counter_value::Total::DoubleValue(v);
         Ok(())
@@ -381,7 +381,7 @@ pub(crate) struct LabelSetEncoder<'a> {
     labels: &'a mut Vec<openmetrics_data_model::Label>,
 }
 
-impl<'a> LabelSetEncoder<'a> {
+impl LabelSetEncoder<'_> {
     pub fn encode_label(&mut self) -> LabelEncoder {
         LabelEncoder {
             labels: self.labels,
@@ -394,7 +394,7 @@ pub(crate) struct LabelEncoder<'a> {
     labels: &'a mut Vec<openmetrics_data_model::Label>,
 }
 
-impl<'a> LabelEncoder<'a> {
+impl LabelEncoder<'_> {
     pub fn encode_label_key(&mut self) -> Result<LabelKeyEncoder, std::fmt::Error> {
         self.labels.push(openmetrics_data_model::Label::default());
 
@@ -409,7 +409,7 @@ pub(crate) struct LabelKeyEncoder<'a> {
     label: &'a mut openmetrics_data_model::Label,
 }
 
-impl<'a> std::fmt::Write for LabelKeyEncoder<'a> {
+impl std::fmt::Write for LabelKeyEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         self.label.name.write_str(s)
     }
@@ -428,13 +428,13 @@ pub(crate) struct LabelValueEncoder<'a> {
     label_value: &'a mut String,
 }
 
-impl<'a> LabelValueEncoder<'a> {
+impl LabelValueEncoder<'_> {
     pub fn finish(self) -> Result<(), std::fmt::Error> {
         Ok(())
     }
 }
 
-impl<'a> std::fmt::Write for LabelValueEncoder<'a> {
+impl std::fmt::Write for LabelValueEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         self.label_value.write_str(s)
     }
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 4946fe35..8f3c63df 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -188,7 +188,7 @@ pub(crate) struct DescriptorEncoder<'a> {
     labels: &'a [(Cow<'static, str>, Cow<'static, str>)],
 }
 
-impl<'a> std::fmt::Debug for DescriptorEncoder<'a> {
+impl std::fmt::Debug for DescriptorEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("DescriptorEncoder").finish()
     }
@@ -293,7 +293,7 @@ pub(crate) struct MetricEncoder<'a> {
     family_labels: Option<&'a dyn super::EncodeLabelSet>,
 }
 
-impl<'a> std::fmt::Debug for MetricEncoder<'a> {
+impl std::fmt::Debug for MetricEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let mut labels = String::new();
         if let Some(l) = self.family_labels {
@@ -310,7 +310,7 @@ impl<'a> std::fmt::Debug for MetricEncoder<'a> {
     }
 }
 
-impl<'a> MetricEncoder<'a> {
+impl MetricEncoder<'_> {
     pub fn encode_counter<
         S: EncodeLabelSet,
         CounterValue: super::EncodeCounterValue,
@@ -555,13 +555,13 @@ pub(crate) struct CounterValueEncoder<'a> {
     writer: &'a mut dyn Write,
 }
 
-impl<'a> std::fmt::Debug for CounterValueEncoder<'a> {
+impl std::fmt::Debug for CounterValueEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("CounterValueEncoder").finish()
     }
 }
 
-impl<'a> CounterValueEncoder<'a> {
+impl CounterValueEncoder<'_> {
     pub fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         self.writer.write_str(" ")?;
         self.writer.write_str(dtoa::Buffer::new().format(v))?;
@@ -579,13 +579,13 @@ pub(crate) struct GaugeValueEncoder<'a> {
     writer: &'a mut dyn Write,
 }
 
-impl<'a> std::fmt::Debug for GaugeValueEncoder<'a> {
+impl std::fmt::Debug for GaugeValueEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("GaugeValueEncoder").finish()
     }
 }
 
-impl<'a> GaugeValueEncoder<'a> {
+impl GaugeValueEncoder<'_> {
     pub fn encode_u32(&mut self, v: u32) -> Result<(), std::fmt::Error> {
         self.writer.write_str(" ")?;
         self.writer.write_str(itoa::Buffer::new().format(v))?;
@@ -609,13 +609,13 @@ pub(crate) struct ExemplarValueEncoder<'a> {
     writer: &'a mut dyn Write,
 }
 
-impl<'a> std::fmt::Debug for ExemplarValueEncoder<'a> {
+impl std::fmt::Debug for ExemplarValueEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("ExemplarValueEncoder").finish()
     }
 }
 
-impl<'a> ExemplarValueEncoder<'a> {
+impl ExemplarValueEncoder<'_> {
     pub fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         self.writer.write_str(dtoa::Buffer::new().format(v))
     }
@@ -626,7 +626,7 @@ pub(crate) struct LabelSetEncoder<'a> {
     first: bool,
 }
 
-impl<'a> std::fmt::Debug for LabelSetEncoder<'a> {
+impl std::fmt::Debug for LabelSetEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("LabelSetEncoder")
             .field("first", &self.first)
@@ -657,7 +657,7 @@ pub(crate) struct LabelEncoder<'a> {
     first: bool,
 }
 
-impl<'a> std::fmt::Debug for LabelEncoder<'a> {
+impl std::fmt::Debug for LabelEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("LabelEncoder")
             .field("first", &self.first)
@@ -665,7 +665,7 @@ impl<'a> std::fmt::Debug for LabelEncoder<'a> {
     }
 }
 
-impl<'a> LabelEncoder<'a> {
+impl LabelEncoder<'_> {
     pub fn encode_label_key(&mut self) -> Result<LabelKeyEncoder, std::fmt::Error> {
         if !self.first {
             self.writer.write_str(",")?;
@@ -680,7 +680,7 @@ pub(crate) struct LabelKeyEncoder<'a> {
     writer: &'a mut dyn Write,
 }
 
-impl<'a> std::fmt::Debug for LabelKeyEncoder<'a> {
+impl std::fmt::Debug for LabelKeyEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("LabelKeyEncoder").finish()
     }
@@ -695,7 +695,7 @@ impl<'a> LabelKeyEncoder<'a> {
     }
 }
 
-impl<'a> std::fmt::Write for LabelKeyEncoder<'a> {
+impl std::fmt::Write for LabelKeyEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         self.writer.write_str(s)
     }
@@ -705,19 +705,19 @@ pub(crate) struct LabelValueEncoder<'a> {
     writer: &'a mut dyn Write,
 }
 
-impl<'a> std::fmt::Debug for LabelValueEncoder<'a> {
+impl std::fmt::Debug for LabelValueEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("LabelValueEncoder").finish()
     }
 }
 
-impl<'a> LabelValueEncoder<'a> {
+impl LabelValueEncoder<'_> {
     pub fn finish(self) -> Result<(), std::fmt::Error> {
         self.writer.write_str("\"")
     }
 }
 
-impl<'a> std::fmt::Write for LabelValueEncoder<'a> {
+impl std::fmt::Write for LabelValueEncoder<'_> {
     fn write_str(&mut self, s: &str) -> std::fmt::Result {
         self.writer.write_str(s)
     }

From 7413b61b12830b4952bcab43d0a2e6a58b1382a6 Mon Sep 17 00:00:00 2001
From: Qinxuan Chen <koushiro.cqx@gmail.com>
Date: Fri, 29 Nov 2024 22:44:48 +0800
Subject: [PATCH 19/27] feat(encoding): impl `EncodeLabelValue` for `bool`
 (#237)

Signed-off-by: koushiro <koushiro.cqx@gmail.com>
---
 CHANGELOG.md    |   7 +-
 src/encoding.rs | 179 +++++++++++++++++++++++++-----------------------
 2 files changed, 99 insertions(+), 87 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c398e073..945d128f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,8 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Implement `Atomic<u64>` for `AtomicU64` for gauges.
   See [PR 226].
 
-[PR 226]: https://github.com/prometheus/client_rust/pull/198
+- Implement `EnableLabelValue` for `bool`.
+  See [PR 237]
+
+[PR 173]: https://github.com/prometheus/client_rust/pull/173
 [PR 198]: https://github.com/prometheus/client_rust/pull/198
+[PR 226]: https://github.com/prometheus/client_rust/pull/226
+[PR 237]: https://github.com/prometheus/client_rust/pull/237
 
 ### Added
 
diff --git a/src/encoding.rs b/src/encoding.rs
index 9b2e497c..9e2acac0 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -206,12 +206,6 @@ pub trait EncodeLabelSet {
     fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error>;
 }
 
-impl<'a> From<text::LabelSetEncoder<'a>> for LabelSetEncoder<'a> {
-    fn from(e: text::LabelSetEncoder<'a>) -> Self {
-        Self(LabelSetEncoderInner::Text(e))
-    }
-}
-
 /// Encoder for a label set.
 #[derive(Debug)]
 pub struct LabelSetEncoder<'a>(LabelSetEncoderInner<'a>);
@@ -223,6 +217,12 @@ enum LabelSetEncoderInner<'a> {
     Protobuf(protobuf::LabelSetEncoder<'a>),
 }
 
+impl<'a> From<text::LabelSetEncoder<'a>> for LabelSetEncoder<'a> {
+    fn from(e: text::LabelSetEncoder<'a>) -> Self {
+        Self(LabelSetEncoderInner::Text(e))
+    }
+}
+
 #[cfg(feature = "protobuf")]
 impl<'a> From<protobuf::LabelSetEncoder<'a>> for LabelSetEncoder<'a> {
     fn from(e: protobuf::LabelSetEncoder<'a>) -> Self {
@@ -237,6 +237,42 @@ impl LabelSetEncoder<'_> {
     }
 }
 
+impl<T: EncodeLabel, const N: usize> EncodeLabelSet for [T; N] {
+    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+        self.as_ref().encode(encoder)
+    }
+}
+
+impl<T: EncodeLabel> EncodeLabelSet for &[T] {
+    fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+        if self.is_empty() {
+            return Ok(());
+        }
+
+        for label in self.iter() {
+            label.encode(encoder.encode_label())?
+        }
+
+        Ok(())
+    }
+}
+
+impl<T: EncodeLabel> EncodeLabelSet for Vec<T> {
+    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+        self.as_slice().encode(encoder)
+    }
+}
+
+/// Uninhabited type to represent the lack of a label set for a metric
+#[derive(Debug)]
+pub enum NoLabelSet {}
+
+impl EncodeLabelSet for NoLabelSet {
+    fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+        Ok(())
+    }
+}
+
 /// An encodable label.
 pub trait EncodeLabel {
     /// Encode oneself into the given encoder.
@@ -247,10 +283,6 @@ pub trait EncodeLabel {
 #[derive(Debug)]
 pub struct LabelEncoder<'a>(LabelEncoderInner<'a>);
 
-/// Uninhabited type to represent the lack of a label set for a metric
-#[derive(Debug)]
-pub enum NoLabelSet {}
-
 #[derive(Debug)]
 enum LabelEncoderInner<'a> {
     Text(text::LabelEncoder<'a>),
@@ -283,6 +315,21 @@ impl LabelEncoder<'_> {
     }
 }
 
+impl<K: EncodeLabelKey, V: EncodeLabelValue> EncodeLabel for (K, V) {
+    fn encode(&self, mut encoder: LabelEncoder) -> Result<(), std::fmt::Error> {
+        let (key, value) = self;
+
+        let mut label_key_encoder = encoder.encode_label_key()?;
+        key.encode(&mut label_key_encoder)?;
+
+        let mut label_value_encoder = label_key_encoder.encode_label_value()?;
+        value.encode(&mut label_value_encoder)?;
+        label_value_encoder.finish()?;
+
+        Ok(())
+    }
+}
+
 /// An encodable label key.
 pub trait EncodeLabelKey {
     /// Encode oneself into the given encoder.
@@ -330,52 +377,6 @@ impl<'a> LabelKeyEncoder<'a> {
         )
     }
 }
-impl<T: EncodeLabel, const N: usize> EncodeLabelSet for [T; N] {
-    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
-        self.as_ref().encode(encoder)
-    }
-}
-
-impl<T: EncodeLabel> EncodeLabelSet for &[T] {
-    fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
-        if self.is_empty() {
-            return Ok(());
-        }
-
-        for label in self.iter() {
-            label.encode(encoder.encode_label())?
-        }
-
-        Ok(())
-    }
-}
-
-impl<T: EncodeLabel> EncodeLabelSet for Vec<T> {
-    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
-        self.as_slice().encode(encoder)
-    }
-}
-
-impl EncodeLabelSet for NoLabelSet {
-    fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
-        Ok(())
-    }
-}
-
-impl<K: EncodeLabelKey, V: EncodeLabelValue> EncodeLabel for (K, V) {
-    fn encode(&self, mut encoder: LabelEncoder) -> Result<(), std::fmt::Error> {
-        let (key, value) = self;
-
-        let mut label_key_encoder = encoder.encode_label_key()?;
-        key.encode(&mut label_key_encoder)?;
-
-        let mut label_value_encoder = label_key_encoder.encode_label_value()?;
-        value.encode(&mut label_value_encoder)?;
-        label_value_encoder.finish()?;
-
-        Ok(())
-    }
-}
 
 impl EncodeLabelKey for &str {
     fn encode(&self, encoder: &mut LabelKeyEncoder) -> Result<(), std::fmt::Error> {
@@ -433,6 +434,13 @@ pub trait EncodeLabelValue {
 #[derive(Debug)]
 pub struct LabelValueEncoder<'a>(LabelValueEncoderInner<'a>);
 
+#[derive(Debug)]
+enum LabelValueEncoderInner<'a> {
+    Text(text::LabelValueEncoder<'a>),
+    #[cfg(feature = "protobuf")]
+    Protobuf(protobuf::LabelValueEncoder<'a>),
+}
+
 impl<'a> From<text::LabelValueEncoder<'a>> for LabelValueEncoder<'a> {
     fn from(e: text::LabelValueEncoder<'a>) -> Self {
         LabelValueEncoder(LabelValueEncoderInner::Text(e))
@@ -446,11 +454,10 @@ impl<'a> From<protobuf::LabelValueEncoder<'a>> for LabelValueEncoder<'a> {
     }
 }
 
-#[derive(Debug)]
-enum LabelValueEncoderInner<'a> {
-    Text(text::LabelValueEncoder<'a>),
-    #[cfg(feature = "protobuf")]
-    Protobuf(protobuf::LabelValueEncoder<'a>),
+impl std::fmt::Write for LabelValueEncoder<'_> {
+    fn write_str(&mut self, s: &str) -> std::fmt::Result {
+        for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s))
+    }
 }
 
 impl LabelValueEncoder<'_> {
@@ -460,12 +467,6 @@ impl LabelValueEncoder<'_> {
     }
 }
 
-impl std::fmt::Write for LabelValueEncoder<'_> {
-    fn write_str(&mut self, s: &str) -> std::fmt::Result {
-        for_both_mut!(self, LabelValueEncoderInner, e, e.write_str(s))
-    }
-}
-
 impl EncodeLabelValue for &str {
     fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
         encoder.write_str(self)?;
@@ -536,6 +537,12 @@ where
     }
 }
 
+impl EncodeLabelValue for bool {
+    fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
+        encoder.write_str(if *self { "true" } else { "false" })
+    }
+}
+
 macro_rules! impl_encode_label_value_for_integer {
     ($($t:ident),*) => {$(
         impl EncodeLabelValue for $t {
@@ -678,6 +685,19 @@ enum CounterValueEncoderInner<'a> {
     Protobuf(protobuf::CounterValueEncoder<'a>),
 }
 
+impl<'a> From<text::CounterValueEncoder<'a>> for CounterValueEncoder<'a> {
+    fn from(e: text::CounterValueEncoder<'a>) -> Self {
+        CounterValueEncoder(CounterValueEncoderInner::Text(e))
+    }
+}
+
+#[cfg(feature = "protobuf")]
+impl<'a> From<protobuf::CounterValueEncoder<'a>> for CounterValueEncoder<'a> {
+    fn from(e: protobuf::CounterValueEncoder<'a>) -> Self {
+        CounterValueEncoder(CounterValueEncoderInner::Protobuf(e))
+    }
+}
+
 impl CounterValueEncoder<'_> {
     fn encode_f64(&mut self, v: f64) -> Result<(), std::fmt::Error> {
         for_both_mut!(self, CounterValueEncoderInner, e, e.encode_f64(v))
@@ -718,19 +738,6 @@ impl EncodeExemplarValue for u32 {
     }
 }
 
-impl<'a> From<text::CounterValueEncoder<'a>> for CounterValueEncoder<'a> {
-    fn from(e: text::CounterValueEncoder<'a>) -> Self {
-        CounterValueEncoder(CounterValueEncoderInner::Text(e))
-    }
-}
-
-#[cfg(feature = "protobuf")]
-impl<'a> From<protobuf::CounterValueEncoder<'a>> for CounterValueEncoder<'a> {
-    fn from(e: protobuf::CounterValueEncoder<'a>) -> Self {
-        CounterValueEncoder(CounterValueEncoderInner::Protobuf(e))
-    }
-}
-
 /// Encoder for an exemplar value.
 #[derive(Debug)]
 pub struct ExemplarValueEncoder<'a>(ExemplarValueEncoderInner<'a>);
@@ -742,12 +749,6 @@ enum ExemplarValueEncoderInner<'a> {
     Protobuf(protobuf::ExemplarValueEncoder<'a>),
 }
 
-impl ExemplarValueEncoder<'_> {
-    fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> {
-        for_both_mut!(self, ExemplarValueEncoderInner, e, e.encode(v))
-    }
-}
-
 impl<'a> From<text::ExemplarValueEncoder<'a>> for ExemplarValueEncoder<'a> {
     fn from(e: text::ExemplarValueEncoder<'a>) -> Self {
         ExemplarValueEncoder(ExemplarValueEncoderInner::Text(e))
@@ -760,3 +761,9 @@ impl<'a> From<protobuf::ExemplarValueEncoder<'a>> for ExemplarValueEncoder<'a> {
         ExemplarValueEncoder(ExemplarValueEncoderInner::Protobuf(e))
     }
 }
+
+impl ExemplarValueEncoder<'_> {
+    fn encode(&mut self, v: f64) -> Result<(), std::fmt::Error> {
+        for_both_mut!(self, ExemplarValueEncoderInner, e, e.encode(v))
+    }
+}

From 93c63dbcdecda2aaca331a1df9746f9774483800 Mon Sep 17 00:00:00 2001
From: Wenbo Zhang <ethercflow@gmail.com>
Date: Tue, 3 Dec 2024 16:49:36 +0800
Subject: [PATCH 20/27] feat(metrics/family): add `get` method to `Family`
 (#234)

Signed-off-by: Wenbo Zhang <wenbo.zhang@iomesh.com>
---
 CHANGELOG.md          |  4 +++
 src/metrics/family.rs | 64 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 945d128f..685c8b66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,9 +34,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Supoort `Arc<String>` for `EncodeLabelValue`.
   See [PR 217].
 
+- Added `get` method to `Family`.
+  See [PR 234].
+
 [PR 173]: https://github.com/prometheus/client_rust/pull/173
 [PR 216]: https://github.com/prometheus/client_rust/pull/216
 [PR 217]: https://github.com/prometheus/client_rust/pull/217
+[PR 234]: https://github.com/prometheus/client_rust/pull/234
 
 ### Fixed
 
diff --git a/src/metrics/family.rs b/src/metrics/family.rs
index 2f23b198..1a76cf8f 100644
--- a/src/metrics/family.rs
+++ b/src/metrics/family.rs
@@ -226,9 +226,7 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
     /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
     /// ```
     pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
-        if let Ok(metric) =
-            RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set))
-        {
+        if let Some(metric) = self.get(label_set) {
             return metric;
         }
 
@@ -247,6 +245,23 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
         })
     }
 
+    /// Access a metric with the given label set, returning None if one
+    /// does not yet exist.
+    ///
+    /// ```
+    /// # use prometheus_client::metrics::counter::{Atomic, Counter};
+    /// # use prometheus_client::metrics::family::Family;
+    /// #
+    /// let family = Family::<Vec<(String, String)>, Counter>::default();
+    ///
+    /// if let Some(metric) = family.get(&vec![("method".to_owned(), "GET".to_owned())]) {
+    ///     metric.inc();
+    /// };
+    /// ```
+    pub fn get(&self, label_set: &S) -> Option<MappedRwLockReadGuard<M>> {
+        RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set)).ok()
+    }
+
     /// Remove a label set from the metric family.
     ///
     /// Returns a bool indicating if a label set was removed or not.
@@ -452,4 +467,47 @@ mod tests {
                 .get()
         );
     }
+
+    #[test]
+    fn test_get() {
+        let family = Family::<Vec<(String, String)>, Counter>::default();
+
+        // Test getting a non-existent metric.
+        let non_existent = family.get(&vec![("method".to_string(), "GET".to_string())]);
+        assert!(non_existent.is_none());
+
+        // Create a metric.
+        family
+            .get_or_create(&vec![("method".to_string(), "GET".to_string())])
+            .inc();
+
+        // Test getting an existing metric.
+        let existing = family.get(&vec![("method".to_string(), "GET".to_string())]);
+        assert!(existing.is_some());
+        assert_eq!(existing.unwrap().get(), 1);
+
+        // Test getting a different non-existent metric.
+        let another_non_existent = family.get(&vec![("method".to_string(), "POST".to_string())]);
+        assert!(another_non_existent.is_none());
+
+        // Test modifying the metric through the returned reference.
+        if let Some(metric) = family.get(&vec![("method".to_string(), "GET".to_string())]) {
+            metric.inc();
+        }
+
+        // Verify the modification.
+        let modified = family.get(&vec![("method".to_string(), "GET".to_string())]);
+        assert_eq!(modified.unwrap().get(), 2);
+
+        // Test with a different label set type.
+        let string_family = Family::<String, Counter>::default();
+        string_family.get_or_create(&"test".to_string()).inc();
+
+        let string_metric = string_family.get(&"test".to_string());
+        assert!(string_metric.is_some());
+        assert_eq!(string_metric.unwrap().get(), 1);
+
+        let non_existent_string = string_family.get(&"non_existent".to_string());
+        assert!(non_existent_string.is_none());
+    }
 }

From 1adc99464d10bfd21e202d50a3fcfadfc3a886cf Mon Sep 17 00:00:00 2001
From: David Ashpole <dashpole@google.com>
Date: Mon, 6 Jan 2025 12:12:15 -0500
Subject: [PATCH 21/27] docs: update openmetrics links to v1.0.0 release (#253)

Signed-off-by: David Ashpole <dashpole@google.com>
---
 src/encoding/protobuf.rs | 2 +-
 src/encoding/text.rs     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs
index 5ec54c91..3c598144 100644
--- a/src/encoding/protobuf.rs
+++ b/src/encoding/protobuf.rs
@@ -14,7 +14,7 @@
 //! #   counter.clone(),
 //! # );
 //! # counter.inc();
-//! // Returns `MetricSet`, the top-level container type. Please refer to [openmetrics_data_model.proto](https://github.com/OpenObservability/OpenMetrics/blob/main/proto/openmetrics_data_model.proto) for details.
+//! // Returns `MetricSet`, the top-level container type. Please refer to [openmetrics_data_model.proto](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/proto/openmetrics_data_model.proto) for details.
 //! let metric_set = encode(&registry).unwrap();
 //!
 //! let family = metric_set.metric_families.first().unwrap();
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index 8f3c63df..f10cf1d3 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -53,7 +53,7 @@ use std::fmt::Write;
 ///
 /// Use [`encode_registry`] or [`encode_eof`] if partial encoding is needed.
 ///
-/// See [OpenMetrics exposition format](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#text-format)
+/// See [OpenMetrics exposition format](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#text-format)
 /// for additional details.
 ///
 /// # Examples

From 8f134607a5204728a1e5b44efe74b3459f18a603 Mon Sep 17 00:00:00 2001
From: Sidhant Kohli <sidhant.kohli@gmail.com>
Date: Tue, 7 Jan 2025 14:08:17 +0530
Subject: [PATCH 22/27] feat: add range based exponential buckets in histogram
 (#233)

Signed-off-by: Sidhant Kohli <sidhant_kohli@intuit.com>
---
 CHANGELOG.md             |  4 ++++
 src/metrics/histogram.rs | 41 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 685c8b66..839d97e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,12 +34,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Supoort `Arc<String>` for `EncodeLabelValue`.
   See [PR 217].
 
+- Add `histogram::exponential_buckets_range`.
+  See [PR 233].
+
 - Added `get` method to `Family`.
   See [PR 234].
 
 [PR 173]: https://github.com/prometheus/client_rust/pull/173
 [PR 216]: https://github.com/prometheus/client_rust/pull/216
 [PR 217]: https://github.com/prometheus/client_rust/pull/217
+[PR 233]: https://github.com/prometheus/client_rust/pull/233
 [PR 234]: https://github.com/prometheus/client_rust/pull/234
 
 ### Fixed
diff --git a/src/metrics/histogram.rs b/src/metrics/histogram.rs
index 1e418ad9..f95ca8d5 100644
--- a/src/metrics/histogram.rs
+++ b/src/metrics/histogram.rs
@@ -122,6 +122,30 @@ pub fn exponential_buckets(start: f64, factor: f64, length: u16) -> impl Iterato
         .take(length.into())
 }
 
+/// Exponential bucket distribution within a range
+///
+/// Creates `length` buckets, where the lowest bucket is `min` and the highest bucket is `max`.
+///
+/// If `length` is less than 1, or `min` is less than or equal to 0, an empty iterator is returned.
+pub fn exponential_buckets_range(min: f64, max: f64, length: u16) -> impl Iterator<Item = f64> {
+    let mut len_observed = length;
+    let mut min_bucket = min;
+    // length needs a positive length and min needs to be greater than 0
+    // set len_observed to 0 and min_bucket to 1.0
+    // this will return an empty iterator in the result
+    if length < 1 || min <= 0.0 {
+        len_observed = 0;
+        min_bucket = 1.0;
+    }
+    // We know max/min and highest bucket. Solve for growth_factor.
+    let growth_factor = (max / min_bucket).powf(1.0 / (len_observed as f64 - 1.0));
+
+    iter::repeat(())
+        .enumerate()
+        .map(move |(i, _)| min_bucket * growth_factor.powf(i as f64))
+        .take(len_observed.into())
+}
+
 /// Linear bucket distribution.
 pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item = f64> {
     iter::repeat(())
@@ -166,4 +190,21 @@ mod tests {
             linear_buckets(0.0, 1.0, 10).collect::<Vec<_>>()
         );
     }
+
+    #[test]
+    fn exponential_range() {
+        assert_eq!(
+            vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0],
+            exponential_buckets_range(1.0, 32.0, 6).collect::<Vec<_>>()
+        );
+    }
+
+    #[test]
+    fn exponential_range_incorrect() {
+        let res = exponential_buckets_range(1.0, 32.0, 0).collect::<Vec<_>>();
+        assert!(res.is_empty());
+
+        let res = exponential_buckets_range(0.0, 32.0, 6).collect::<Vec<_>>();
+        assert!(res.is_empty());
+    }
 }

From cbde2cb6796629d35857761c612fcbe768bfe18b Mon Sep 17 00:00:00 2001
From: Max Inden <mail@max-inden.de>
Date: Tue, 7 Jan 2025 09:49:54 +0100
Subject: [PATCH 23/27] chore: prepare v0.23.0 (#255)

Signed-off-by: Max Inden <mail@max-inden.de>
---
 CHANGELOG.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 839d97e4..db4f6062 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,7 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [0.23.0] - unreleased
-
+## [0.23.0]
 
 ### Changed
 

From 9a74e9900e9a21eb94d34226149343ab7ff1733f Mon Sep 17 00:00:00 2001
From: katelyn martin <me+cratelyn@katelyn.world>
Date: Wed, 15 Jan 2025 13:02:05 -0500
Subject: [PATCH 24/27] refactor(metrics/histogram): constructor accepts
 `IntoIterator` (#243)

this is a very small, **non-breaking**, alteration to the signature of
`Histogram`'s constructor.

rather than accepting an `impl Iterator`, this commit changes the
parameter of `Histogram::new()` to be an `impl IntoIterator` instead.
this does not affect the existing contract because of the blanket
`impl<I: Iterator> IntoIterator for I` implementation provided by the
standard library.

by accepting `IntoIterator` however, callers providing a collection such
as a `[f64; 5]` array or a `Vec<f64>` vector do not need to invoke
`into_iter()` themselves at the call-site.

```rust
// now, constructing a histogram needn't involve `into_iter()`...
use prometheus_client::metrics::histogram::Histogram;
let histogram = Histogram::new([10.0, 100.0, 1_000.0]);
```

this leans on the same sugar used by `for {}` loops, see the relevant
section of the `std::iter` documentation here:
<https://doc.rust-lang.org/stable/std/iter/index.html#for-loops-and-intoiterator>

no changes are needed within `Histogram::new()` because we already call
`into_iter()` on the provider iterator when appending `f64::MAX` and
collecting the buckets into a `Vec<_>`.

Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
---
 CHANGELOG.md             | 9 +++++++++
 Cargo.toml               | 2 +-
 src/metrics/histogram.rs | 9 +++++++--
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index db4f6062..3b9d4f94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.23.1] - unreleased
+
+### Changed
+
+- `Histogram::new` now accepts an `IntoIterator` argument, rather than an `Iterator`.
+  See [PR 243].
+
+[PR 243]: https://github.com/prometheus/client_rust/pull/243
+
 ## [0.23.0]
 
 ### Changed
diff --git a/Cargo.toml b/Cargo.toml
index 18dd395f..d04c8999 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client"
-version = "0.23.0"
+version = "0.23.1"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Open Metrics client library allowing users to natively instrument applications."
diff --git a/src/metrics/histogram.rs b/src/metrics/histogram.rs
index f95ca8d5..5a50278c 100644
--- a/src/metrics/histogram.rs
+++ b/src/metrics/histogram.rs
@@ -28,7 +28,7 @@ use std::sync::Arc;
 /// let custom_buckets = [
 ///    0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
 /// ];
-/// let histogram = Histogram::new(custom_buckets.into_iter());
+/// let histogram = Histogram::new(custom_buckets);
 /// histogram.observe(4.2);
 /// ```
 // TODO: Consider using atomics. See
@@ -57,7 +57,12 @@ pub(crate) struct Inner {
 
 impl Histogram {
     /// Create a new [`Histogram`].
-    pub fn new(buckets: impl Iterator<Item = f64>) -> Self {
+    ///
+    /// ```rust
+    /// # use prometheus_client::metrics::histogram::Histogram;
+    /// let histogram = Histogram::new([10.0, 100.0, 1_000.0]);
+    /// ```
+    pub fn new(buckets: impl IntoIterator<Item = f64>) -> Self {
         Self {
             inner: Arc::new(RwLock::new(Inner {
                 sum: Default::default(),

From 84e2cc6a917341b31cfbf2f809193344fb7fb99d Mon Sep 17 00:00:00 2001
From: katelyn martin <me+cratelyn@katelyn.world>
Date: Sun, 9 Feb 2025 08:48:30 -0500
Subject: [PATCH 25/27] feat(encoding)!: `EncodeLabelSet::encode()` uses
 reference (#257)

this commit alters the signature of the `EncodeLabelSet::encode()` trait
method, such that it now accepts a mutable reference to its encoder.

this is related to #135, and is a second proposal following
previous work in #240.

this change permits distinct label sets to be composed together, now
that the label set encoder is not consumed. a new implementation for
tuples `(A, B)` is provided.

this commit includes a test case showing that a metric family can
compose two label sets together, and that such a family can successfully
be digested by the python client library.

`derive-encode` is altered to generate code matching this new trait
signature, and has been bumped to version 0.5.0 as a result of this
breaking change in the `prometheus-client` library.

Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
---
 CHANGELOG.md             | 12 ++++++-
 Cargo.toml               |  4 +--
 derive-encode/Cargo.toml |  2 +-
 derive-encode/src/lib.rs |  2 +-
 src/encoding.rs          | 28 ++++++++++++----
 src/encoding/protobuf.rs |  8 ++---
 src/encoding/text.rs     | 69 ++++++++++++++++++++++++++++++++++++----
 7 files changed, 103 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b9d4f94..a0e32055 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,17 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [0.23.1] - unreleased
+## [0.24.0] - unreleased
+
+### Added
+
+- `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`.
+
+### Changed
+
+- `EncodeLabelSet::encode()` now accepts a mutable reference to its encoder parameter.
+
+## [0.23.1]
 
 ### Changed
 
diff --git a/Cargo.toml b/Cargo.toml
index d04c8999..a0f5e2a2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client"
-version = "0.23.1"
+version = "0.24.0"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Open Metrics client library allowing users to natively instrument applications."
@@ -21,7 +21,7 @@ members = ["derive-encode"]
 dtoa = "1.0"
 itoa = "1.0"
 parking_lot = "0.12"
-prometheus-client-derive-encode = { version = "0.4.1", path = "derive-encode" }
+prometheus-client-derive-encode = { version = "0.5.0", path = "derive-encode" }
 prost = { version = "0.12.0", optional = true }
 prost-types = { version = "0.12.0", optional = true }
 
diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml
index f365b077..145c0159 100644
--- a/derive-encode/Cargo.toml
+++ b/derive-encode/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "prometheus-client-derive-encode"
-version = "0.4.2"
+version = "0.5.0"
 authors = ["Max Inden <mail@max-inden.de>"]
 edition = "2021"
 description = "Auxiliary crate to derive Encode trait from prometheus-client."
diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs
index 1858cf8d..5b3cbe32 100644
--- a/derive-encode/src/lib.rs
+++ b/derive-encode/src/lib.rs
@@ -72,7 +72,7 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
 
     let gen = quote! {
         impl prometheus_client::encoding::EncodeLabelSet for #name {
-            fn encode(&self, mut encoder: prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> {
+            fn encode(&self, encoder: &mut prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> {
                 use prometheus_client::encoding::EncodeLabel;
                 use prometheus_client::encoding::EncodeLabelKey;
                 use prometheus_client::encoding::EncodeLabelValue;
diff --git a/src/encoding.rs b/src/encoding.rs
index 9e2acac0..ae8420f6 100644
--- a/src/encoding.rs
+++ b/src/encoding.rs
@@ -203,7 +203,7 @@ impl MetricEncoder<'_> {
 /// An encodable label set.
 pub trait EncodeLabelSet {
     /// Encode oneself into the given encoder.
-    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error>;
+    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error>;
 }
 
 /// Encoder for a label set.
@@ -238,19 +238,20 @@ impl LabelSetEncoder<'_> {
 }
 
 impl<T: EncodeLabel, const N: usize> EncodeLabelSet for [T; N] {
-    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
         self.as_ref().encode(encoder)
     }
 }
 
 impl<T: EncodeLabel> EncodeLabelSet for &[T] {
-    fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
         if self.is_empty() {
             return Ok(());
         }
 
         for label in self.iter() {
-            label.encode(encoder.encode_label())?
+            let encoder = encoder.encode_label();
+            label.encode(encoder)?
         }
 
         Ok(())
@@ -258,17 +259,32 @@ impl<T: EncodeLabel> EncodeLabelSet for &[T] {
 }
 
 impl<T: EncodeLabel> EncodeLabelSet for Vec<T> {
-    fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
         self.as_slice().encode(encoder)
     }
 }
 
+impl<A, B> EncodeLabelSet for (A, B)
+where
+    A: EncodeLabelSet,
+    B: EncodeLabelSet,
+{
+    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
+        let (a, b) = self;
+
+        a.encode(encoder)?;
+        b.encode(encoder)?;
+
+        Ok(())
+    }
+}
+
 /// Uninhabited type to represent the lack of a label set for a metric
 #[derive(Debug)]
 pub enum NoLabelSet {}
 
 impl EncodeLabelSet for NoLabelSet {
-    fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
+    fn encode(&self, _encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> {
         Ok(())
     }
 }
diff --git a/src/encoding/protobuf.rs b/src/encoding/protobuf.rs
index 3c598144..b22ea5ae 100644
--- a/src/encoding/protobuf.rs
+++ b/src/encoding/protobuf.rs
@@ -120,7 +120,7 @@ impl DescriptorEncoder<'_> {
         };
         let mut labels = vec![];
         self.labels.encode(
-            LabelSetEncoder {
+            &mut LabelSetEncoder {
                 labels: &mut labels,
             }
             .into(),
@@ -210,7 +210,7 @@ impl MetricEncoder<'_> {
     ) -> Result<(), std::fmt::Error> {
         let mut info_labels = vec![];
         label_set.encode(
-            LabelSetEncoder {
+            &mut LabelSetEncoder {
                 labels: &mut info_labels,
             }
             .into(),
@@ -235,7 +235,7 @@ impl MetricEncoder<'_> {
     ) -> Result<MetricEncoder, std::fmt::Error> {
         let mut labels = self.labels.clone();
         label_set.encode(
-            LabelSetEncoder {
+            &mut LabelSetEncoder {
                 labels: &mut labels,
             }
             .into(),
@@ -303,7 +303,7 @@ impl<S: EncodeLabelSet, V: EncodeExemplarValue> TryFrom<&Exemplar<S, V>>
 
         let mut labels = vec![];
         exemplar.label_set.encode(
-            LabelSetEncoder {
+            &mut LabelSetEncoder {
                 labels: &mut labels,
             }
             .into(),
diff --git a/src/encoding/text.rs b/src/encoding/text.rs
index f10cf1d3..80b91076 100644
--- a/src/encoding/text.rs
+++ b/src/encoding/text.rs
@@ -296,8 +296,9 @@ pub(crate) struct MetricEncoder<'a> {
 impl std::fmt::Debug for MetricEncoder<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let mut labels = String::new();
+        let mut encoder = LabelSetEncoder::new(&mut labels).into();
         if let Some(l) = self.family_labels {
-            l.encode(LabelSetEncoder::new(&mut labels).into())?;
+            l.encode(&mut encoder)?;
         }
 
         f.debug_struct("Encoder")
@@ -451,7 +452,7 @@ impl MetricEncoder<'_> {
         self.writer.write_str(" # {")?;
         exemplar
             .label_set
-            .encode(LabelSetEncoder::new(self.writer).into())?;
+            .encode(&mut LabelSetEncoder::new(self.writer).into())?;
         self.writer.write_str("} ")?;
         exemplar.value.encode(
             ExemplarValueEncoder {
@@ -502,14 +503,14 @@ impl MetricEncoder<'_> {
         self.writer.write_str("{")?;
 
         self.const_labels
-            .encode(LabelSetEncoder::new(self.writer).into())?;
+            .encode(&mut LabelSetEncoder::new(self.writer).into())?;
 
         if let Some(additional_labels) = additional_labels {
             if !self.const_labels.is_empty() {
                 self.writer.write_str(",")?;
             }
 
-            additional_labels.encode(LabelSetEncoder::new(self.writer).into())?;
+            additional_labels.encode(&mut LabelSetEncoder::new(self.writer).into())?;
         }
 
         /// Writer impl which prepends a comma on the first call to write output to the wrapped writer
@@ -539,9 +540,9 @@ impl MetricEncoder<'_> {
                     writer: self.writer,
                     should_prepend: true,
                 };
-                labels.encode(LabelSetEncoder::new(&mut writer).into())?;
+                labels.encode(&mut LabelSetEncoder::new(&mut writer).into())?;
             } else {
-                labels.encode(LabelSetEncoder::new(self.writer).into())?;
+                labels.encode(&mut LabelSetEncoder::new(self.writer).into())?;
             };
         }
 
@@ -936,7 +937,7 @@ mod tests {
         struct EmptyLabels {}
 
         impl EncodeLabelSet for EmptyLabels {
-            fn encode(&self, _encoder: crate::encoding::LabelSetEncoder) -> Result<(), Error> {
+            fn encode(&self, _encoder: &mut crate::encoding::LabelSetEncoder) -> Result<(), Error> {
                 Ok(())
             }
         }
@@ -1114,6 +1115,60 @@ mod tests {
         parse_with_python_client(encoded);
     }
 
+    #[test]
+    fn label_sets_can_be_composed() {
+        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
+        struct Color(&'static str);
+        impl EncodeLabelSet for Color {
+            fn encode(
+                &self,
+                encoder: &mut crate::encoding::LabelSetEncoder,
+            ) -> Result<(), std::fmt::Error> {
+                use crate::encoding::EncodeLabel;
+                let Self(color) = *self;
+                let labels = ("color", color);
+                let encoder = encoder.encode_label();
+                labels.encode(encoder)
+            }
+        }
+
+        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
+        struct Size(&'static str);
+        impl EncodeLabelSet for Size {
+            fn encode(
+                &self,
+                encoder: &mut crate::encoding::LabelSetEncoder,
+            ) -> Result<(), std::fmt::Error> {
+                use crate::encoding::EncodeLabel;
+                let Self(size) = *self;
+                let labels = ("size", size);
+                let encoder = encoder.encode_label();
+                labels.encode(encoder)
+            }
+        }
+
+        type Labels = (Color, Size);
+
+        let mut registry = Registry::default();
+        let family = Family::<Labels, Counter>::default();
+        registry.register("items", "Example metric", family.clone());
+
+        let labels = (Color("red"), Size("large"));
+        let counter = family.get_or_create(&labels);
+        counter.inc();
+
+        let mut encoded = String::new();
+        encode(&mut encoded, &registry).unwrap();
+
+        let expected = "# HELP items Example metric.\n\
+                        # TYPE items counter\n\
+                        items_total{color=\"red\",size=\"large\"} 1\n\
+                        # EOF\n";
+        assert_eq!(expected, encoded);
+
+        parse_with_python_client(encoded);
+    }
+
     #[test]
     fn encode_registry_eof() {
         let mut orders_registry = Registry::default();

From 377ca2d86308d868576e961c778bcbad8b62ec0e Mon Sep 17 00:00:00 2001
From: katelyn martin <me+cratelyn@katelyn.world>
Date: Sun, 9 Feb 2025 15:51:38 -0500
Subject: [PATCH 26/27] =?UTF-8?q?feat(metrics/family):=20=F0=9F=8D=AB=20ad?=
 =?UTF-8?q?d=20`Family::get=5For=5Fcreate=5Fowned()`=20(#244)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

fixes #231.

this introduces a new method to `Family<S, M, C>` to help alleviate the
risk of deadlocks when accessing multiple series within a given metrics
family.

this returns a plain `M`, rather than the `MappedRwLockReadGuard<M>`
RAII guard returned by `get_or_create()`.

a test case is introduced in this commit to demonstrate that structures
accessing multiple series within a single expression will not
accidentally create a deadlock.

Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
---
 CHANGELOG.md          |  8 ++++++
 src/metrics/family.rs | 57 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0e32055..c798c594 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`.
+   See [PR 257].
+
+- `Family::get_or_create_owned` can access a metric in a labeled family. This
+   method avoids the risk of runtime deadlocks at the expense of creating an
+   owned type. See [PR 244].
+ 
+[PR 244]: https://github.com/prometheus/client_rust/pull/244
+[PR 257]: https://github.com/prometheus/client_rust/pull/257
 
 ### Changed
 
diff --git a/src/metrics/family.rs b/src/metrics/family.rs
index 1a76cf8f..d3d55c6f 100644
--- a/src/metrics/family.rs
+++ b/src/metrics/family.rs
@@ -207,6 +207,40 @@ impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
     }
 }
 
+impl<S: Clone + std::hash::Hash + Eq, M: Clone, C: MetricConstructor<M>> Family<S, M, C>
+where
+    S: Clone + std::hash::Hash + Eq,
+    M: Clone,
+    C: MetricConstructor<M>,
+{
+    /// Access a metric with the given label set, creating it if one does not yet exist.
+    ///
+    /// ```
+    /// # use prometheus_client::metrics::counter::{Atomic, Counter};
+    /// # use prometheus_client::metrics::family::Family;
+    /// #
+    /// let family = Family::<Vec<(String, String)>, Counter>::default();
+    ///
+    /// // Will create and return the metric with label `method="GET"` when first called.
+    /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc();
+    ///
+    /// // Will return a clone of the existing metric on all subsequent calls.
+    /// family.get_or_create_owned(&vec![("method".to_owned(), "GET".to_owned())]).inc();
+    /// ```
+    ///
+    /// Callers wishing to avoid a clone of the metric `M` can call [`Family::get_or_create()`] to
+    /// return a reference to the metric instead.
+    pub fn get_or_create_owned(&self, label_set: &S) -> M {
+        use std::ops::Deref;
+
+        let guard = self.get_or_create(label_set);
+        let metric = guard.deref().to_owned();
+        drop(guard);
+
+        metric
+    }
+}
+
 impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
     /// Access a metric with the given label set, creating it if one does not
     /// yet exist.
@@ -225,6 +259,10 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
     /// // calls.
     /// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
     /// ```
+    ///
+    /// NB: This method can cause deadlocks if multiple metrics within this family are read at
+    /// once. Use [`Family::get_or_create_owned()`] if you would like to avoid this by cloning the
+    /// metric `M`.
     pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
         if let Some(metric) = self.get(label_set) {
             return metric;
@@ -510,4 +548,23 @@ mod tests {
         let non_existent_string = string_family.get(&"non_existent".to_string());
         assert!(non_existent_string.is_none());
     }
+
+    /// Tests that [`Family::get_or_create_owned()`] does not cause deadlocks.
+    #[test]
+    fn counter_family_does_not_deadlock() {
+        /// A structure we'll place two counters into, within a single expression.
+        struct S {
+            apples: Counter,
+            oranges: Counter,
+        }
+
+        let family = Family::<(&str, &str), Counter>::default();
+        let s = S {
+            apples: family.get_or_create_owned(&("kind", "apple")),
+            oranges: family.get_or_create_owned(&("kind", "orange")),
+        };
+
+        s.apples.inc();
+        s.oranges.inc_by(2);
+    }
 }

From ab152ee915698f83ae5b7ac5e4bee104678b70b1 Mon Sep 17 00:00:00 2001
From: Edwin Amsler <EdwinGuy@GMail.com>
Date: Thu, 27 Feb 2025 03:57:26 -0500
Subject: [PATCH 27/27] docs: add example using std::io::Write (#261)

Signed-off-by: Edwin Amsler <edwinguy@gmail.com>
---
 examples/io_write.rs | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 examples/io_write.rs

diff --git a/examples/io_write.rs b/examples/io_write.rs
new file mode 100644
index 00000000..47d1056b
--- /dev/null
+++ b/examples/io_write.rs
@@ -0,0 +1,39 @@
+//! Example showing how one could write to a file or socket instead of a string.
+//! For large metrics registries this will be more memory efficient.
+
+use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry};
+use std::io::Write;
+
+fn main() {
+    let mut registry = <Registry>::with_prefix("stream");
+    let request_counter: Counter<u64> = Default::default();
+
+    registry.register(
+        "requests",
+        "How many requests the application has received",
+        request_counter.clone(),
+    );
+
+    let mut buf = String::new();
+    encode(&mut buf, &registry).unwrap();
+
+    let mut file = Vec::new();
+    let mut writer = IoWriterWrapper(&mut file);
+    encode(&mut writer, &registry).unwrap();
+
+    assert!(buf.as_bytes() == file);
+}
+
+pub struct IoWriterWrapper<W>(W);
+
+impl<W> std::fmt::Write for IoWriterWrapper<W>
+where
+    W: Write,
+{
+    fn write_str(&mut self, input: &str) -> std::fmt::Result {
+        self.0
+            .write_all(input.as_bytes())
+            .map(|_| ())
+            .map_err(|_| std::fmt::Error)
+    }
+}