Skip to content

Commit 86d81e1

Browse files
tyrone-wuamunra
andcommitted
feat(encoding): add functions for individually encoding registry & EOF
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 <[email protected]> Signed-off-by: tyrone-wu <[email protected]>
1 parent bf196d7 commit 86d81e1

File tree

1 file changed

+194
-10
lines changed

1 file changed

+194
-10
lines changed

src/encoding/text.rs

+194-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Open Metrics text format implementation.
22
//!
33
//! ```
4-
//! # use prometheus_client::encoding::text::encode;
4+
//! # use prometheus_client::encoding::text::{encode, encode_registry, encode_eof};
55
//! # use prometheus_client::metrics::counter::Counter;
66
//! # use prometheus_client::registry::Registry;
77
//! #
@@ -15,13 +15,26 @@
1515
//! # );
1616
//! # counter.inc();
1717
//! let mut buffer = String::new();
18+
//!
19+
//! // Encode the complete OpenMetrics exposition into the message buffer
1820
//! encode(&mut buffer, &registry).unwrap();
21+
//! let expected_msg = "# HELP my_counter This is my counter.\n".to_owned() +
22+
//! "# TYPE my_counter counter\n" +
23+
//! "my_counter_total 1\n" +
24+
//! "# EOF\n";
25+
//! assert_eq!(expected_msg, buffer);
26+
//! buffer.clear();
27+
//!
28+
//! // Encode just the registry into the message buffer
29+
//! encode_registry(&mut buffer, &registry).unwrap();
30+
//! let expected_reg = "# HELP my_counter This is my counter.\n".to_owned() +
31+
//! "# TYPE my_counter counter\n" +
32+
//! "my_counter_total 1\n";
33+
//! assert_eq!(expected_reg, buffer);
1934
//!
20-
//! let expected = "# HELP my_counter This is my counter.\n".to_owned() +
21-
//! "# TYPE my_counter counter\n" +
22-
//! "my_counter_total 1\n" +
23-
//! "# EOF\n";
24-
//! assert_eq!(expected, buffer);
35+
//! // Encode EOF marker into message buffer to complete the OpenMetrics exposition
36+
//! encode_eof(&mut buffer).unwrap();
37+
//! assert_eq!(expected_msg, buffer);
2538
//! ```
2639
2740
use crate::encoding::{EncodeExemplarValue, EncodeLabelSet};
@@ -33,15 +46,140 @@ use std::borrow::Cow;
3346
use std::collections::HashMap;
3447
use std::fmt::Write;
3548

49+
/// Encode both the metrics registered with the provided [`Registry`] and the
50+
/// EOF marker into the provided [`Write`]r using the OpenMetrics text format.
51+
///
52+
/// Note: This function encodes the **complete** OpenMetrics exposition.
53+
///
54+
/// Use [`encode_registry`] or [`encode_eof`] if partial encoding is needed.
55+
///
56+
/// See [OpenMetrics exposition format](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#text-format)
57+
/// for additional details.
58+
///
59+
/// # Examples
60+
///
61+
/// ```no_run
62+
/// # use prometheus_client::encoding::text::encode;
63+
/// # use prometheus_client::metrics::counter::Counter;
64+
/// # use prometheus_client::metrics::gauge::Gauge;
65+
/// # use prometheus_client::registry::Registry;
66+
/// #
67+
/// // Initialize registry with metric families
68+
/// let mut registry = Registry::default();
69+
/// let counter: Counter = Counter::default();
70+
/// registry.register(
71+
/// "my_counter",
72+
/// "This is my counter",
73+
/// counter.clone(),
74+
/// );
75+
/// let gauge: Gauge = Gauge::default();
76+
/// registry.register(
77+
/// "my_gauge",
78+
/// "This is my gauge",
79+
/// gauge.clone(),
80+
/// );
81+
///
82+
/// // Encode the complete OpenMetrics exposition into the buffer
83+
/// let mut buffer = String::new();
84+
/// encode(&mut buffer, &registry)?;
85+
/// # Ok::<(), std::fmt::Error>(())
86+
/// ```
87+
pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
88+
where
89+
W: Write,
90+
{
91+
encode_registry(writer, registry)?;
92+
encode_eof(writer)
93+
}
94+
3695
/// Encode the metrics registered with the provided [`Registry`] into the
3796
/// provided [`Write`]r using the OpenMetrics text format.
38-
pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
97+
///
98+
/// Note: The OpenMetrics exposition requires that a complete message must end
99+
/// with an EOF marker.
100+
///
101+
/// This function may be called repeatedly for the HTTP scrape response until
102+
/// [`encode_eof`] signals the end of the response.
103+
///
104+
/// This may also be used to compose a partial message with metrics assembled
105+
/// from multiple registries.
106+
///
107+
/// # Examples
108+
///
109+
/// ```no_run
110+
/// # use prometheus_client::encoding::text::encode_registry;
111+
/// # use prometheus_client::metrics::counter::Counter;
112+
/// # use prometheus_client::metrics::gauge::Gauge;
113+
/// # use prometheus_client::registry::Registry;
114+
/// #
115+
/// // Initialize registry with a counter
116+
/// let mut reg_counter = Registry::default();
117+
/// let counter: Counter = Counter::default();
118+
/// reg_counter.register(
119+
/// "my_counter",
120+
/// "This is my counter",
121+
/// counter.clone(),
122+
/// );
123+
///
124+
/// // Encode the counter registry into the buffer
125+
/// let mut buffer = String::new();
126+
/// encode_registry(&mut buffer, &reg_counter)?;
127+
///
128+
/// // Initialize another registry but with a gauge
129+
/// let mut reg_gauge = Registry::default();
130+
/// let gauge: Gauge = Gauge::default();
131+
/// reg_gauge.register(
132+
/// "my_gauge",
133+
/// "This is my gauge",
134+
/// gauge.clone(),
135+
/// );
136+
///
137+
/// // Encode the gauge registry into the buffer
138+
/// encode_registry(&mut buffer, &reg_gauge)?;
139+
/// # Ok::<(), std::fmt::Error>(())
140+
/// ```
141+
pub fn encode_registry<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error>
39142
where
40143
W: Write,
41144
{
42-
registry.encode(&mut DescriptorEncoder::new(writer).into())?;
43-
writer.write_str("# EOF\n")?;
44-
Ok(())
145+
registry.encode(&mut DescriptorEncoder::new(writer).into())
146+
}
147+
148+
/// Encode the EOF marker into the provided [`Write`]r using the OpenMetrics
149+
/// text format.
150+
///
151+
/// Note: This function is used to mark/signal the end of the exposition.
152+
///
153+
/// # Examples
154+
///
155+
/// ```no_run
156+
/// # use prometheus_client::encoding::text::{encode_registry, encode_eof};
157+
/// # use prometheus_client::metrics::counter::Counter;
158+
/// # use prometheus_client::metrics::gauge::Gauge;
159+
/// # use prometheus_client::registry::Registry;
160+
/// #
161+
/// // Initialize registry with a counter
162+
/// let mut registry = Registry::default();
163+
/// let counter: Counter = Counter::default();
164+
/// registry.register(
165+
/// "my_counter",
166+
/// "This is my counter",
167+
/// counter.clone(),
168+
/// );
169+
///
170+
/// // Encode registry into the buffer
171+
/// let mut buffer = String::new();
172+
/// encode_registry(&mut buffer, &registry)?;
173+
///
174+
/// // Encode EOF marker to complete the message
175+
/// encode_eof(&mut buffer)?;
176+
/// # Ok::<(), std::fmt::Error>(())
177+
/// ```
178+
pub fn encode_eof<W>(writer: &mut W) -> Result<(), std::fmt::Error>
179+
where
180+
W: Write,
181+
{
182+
writer.write_str("# EOF\n")
45183
}
46184

47185
pub(crate) struct DescriptorEncoder<'a> {
@@ -915,6 +1053,52 @@ mod tests {
9151053
parse_with_python_client(encoded);
9161054
}
9171055

1056+
#[test]
1057+
fn encode_registry_eof() {
1058+
let mut orders_registry = Registry::default();
1059+
1060+
let total_orders: Counter<u64> = Default::default();
1061+
orders_registry.register("orders", "Total orders received", total_orders.clone());
1062+
total_orders.inc();
1063+
1064+
let processing_times = Histogram::new(exponential_buckets(1.0, 2.0, 10));
1065+
orders_registry.register_with_unit(
1066+
"processing_times",
1067+
"Order times",
1068+
Unit::Seconds,
1069+
processing_times.clone(),
1070+
);
1071+
processing_times.observe(2.4);
1072+
1073+
let mut user_auth_registry = Registry::default();
1074+
1075+
let successful_logins: Counter<u64> = Default::default();
1076+
user_auth_registry.register(
1077+
"successful_logins",
1078+
"Total successful logins",
1079+
successful_logins.clone(),
1080+
);
1081+
successful_logins.inc();
1082+
1083+
let failed_logins: Counter<u64> = Default::default();
1084+
user_auth_registry.register(
1085+
"failed_logins",
1086+
"Total failed logins",
1087+
failed_logins.clone(),
1088+
);
1089+
1090+
let mut response = String::new();
1091+
1092+
encode_registry(&mut response, &orders_registry).unwrap();
1093+
assert_eq!(&response[response.len() - 20..], "bucket{le=\"+Inf\"} 1\n");
1094+
1095+
encode_registry(&mut response, &user_auth_registry).unwrap();
1096+
assert_eq!(&response[response.len() - 20..], "iled_logins_total 0\n");
1097+
1098+
encode_eof(&mut response).unwrap();
1099+
assert_eq!(&response[response.len() - 20..], "ogins_total 0\n# EOF\n");
1100+
}
1101+
9181102
fn parse_with_python_client(input: String) {
9191103
pyo3::prepare_freethreaded_python();
9201104

0 commit comments

Comments
 (0)