Skip to content

Commit e60486e

Browse files
committed
Fix #342: Async support for push_metrics behind feature push-async
1 parent b7be575 commit e60486e

4 files changed

Lines changed: 146 additions & 9 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ gen = ["protobuf-codegen-pure"]
2323
nightly = ["libc"]
2424
process = ["libc", "procfs"]
2525
push = ["reqwest", "libc", "protobuf"]
26+
push-async = ["reqwest", "libc", "protobuf"]
2627

2728
[dependencies]
2829
cfg-if = "^0.1"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ This crate provides several optional components which can be enabled via [Cargo
2525

2626
- `process`: Enable [process metrics](https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics) support.
2727

28-
- `push`: Enable [push metrics](https://prometheus.io/docs/instrumenting/pushing/) support.
28+
- `push`: Enable [push metrics](https://prometheus.io/docs/instrumenting/pushing/) support with blocking network calls. Incompatible with `push-async`.
29+
30+
- `push-async`: Enable [push metrics](https://prometheus.io/docs/instrumenting/pushing/) support with async/await. Incompatible with `push`.
2931

3032
### Static Metric
3133

src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ This library supports four features:
108108
using the pre-generated client.
109109
* `nightly`: Enable nightly only features.
110110
* `process`: For collecting process info.
111-
* `push`: Enable push support.
111+
* `push`: Enable push support using blocking network calls. Incompatible with `push`.
112+
* `push-async`: Enable push support using blocking network calls. Incompatible with `push-async`.
112113
113114
*/
114115

@@ -163,6 +164,8 @@ mod histogram;
163164
mod metrics;
164165
#[cfg(feature = "push")]
165166
mod push;
167+
#[cfg(feature = "push-async")]
168+
mod push;
166169
mod registry;
167170
mod value;
168171
mod vec;
@@ -226,5 +229,10 @@ pub use self::push::{
226229
hostname_grouping_key, push_add_collector, push_add_metrics, push_collector, push_metrics,
227230
BasicAuthentication,
228231
};
232+
#[cfg(feature = "push-async")]
233+
pub use self::push::{
234+
hostname_grouping_key, push_add_collector_async, push_add_metrics_async, push_collector_async,
235+
push_metrics_async, BasicAuthentication,
236+
};
229237
pub use self::registry::Registry;
230238
pub use self::registry::{default_registry, gather, register, unregister};

src/push.rs

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ lazy_static! {
2424
.build()
2525
.unwrap();
2626
}
27+
#[cfg(feature = "push-async")]
28+
lazy_static! {
29+
static ref ASYNC_HTTP_CLIENT: reqwest::Client = reqwest::Client::builder()
30+
.timeout(REQWEST_TIMEOUT_SEC)
31+
.build()
32+
.unwrap();
33+
}
2734

2835
/// `BasicAuthentication` holder for supporting `push` to Pushgateway endpoints
2936
/// using Basic access authentication.
@@ -50,6 +57,7 @@ pub struct BasicAuthentication {
5057
/// Note that all previously pushed metrics with the same job and other grouping
5158
/// labels will be replaced with the metrics pushed by this call. (It uses HTTP
5259
/// method 'PUT' to push to the Pushgateway.)
60+
#[cfg(feature = "push")]
5361
pub fn push_metrics<S: BuildHasher>(
5462
job: &str,
5563
grouping: HashMap<String, String, S>,
@@ -60,9 +68,24 @@ pub fn push_metrics<S: BuildHasher>(
6068
push(job, grouping, url, mfs, "PUT", basic_auth)
6169
}
6270

71+
/// Functions just like `push_metrics`, except the metrics are pushed
72+
/// asynchronously.
73+
/// Requires the feature `push-async`.
74+
#[cfg(feature = "push-async")]
75+
pub async fn push_metrics_async<S: BuildHasher>(
76+
job: &str,
77+
grouping: HashMap<String, String, S>,
78+
url: &str,
79+
mfs: Vec<proto::MetricFamily>,
80+
basic_auth: Option<BasicAuthentication>,
81+
) -> Result<()> {
82+
push_async(job, grouping, url, mfs, "PUT", basic_auth).await
83+
}
84+
6385
/// `push_add_metrics` works like `push_metrics`, but only previously pushed
6486
/// metrics with the same name (and the same job and other grouping labels) will
6587
/// be replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
88+
#[cfg(feature = "push")]
6689
pub fn push_add_metrics<S: BuildHasher>(
6790
job: &str,
6891
grouping: HashMap<String, String, S>,
@@ -73,16 +96,26 @@ pub fn push_add_metrics<S: BuildHasher>(
7396
push(job, grouping, url, mfs, "POST", basic_auth)
7497
}
7598

99+
/// `push_add_metrics_async` works like `push_metrics`, but async.
100+
#[cfg(feature = "push-async")]
101+
pub async fn push_add_metrics_async<'a, S: BuildHasher>(
102+
job: &'a str,
103+
grouping: HashMap<String, String, S>,
104+
url: &'a str,
105+
mfs: Vec<proto::MetricFamily>,
106+
basic_auth: Option<BasicAuthentication>,
107+
) -> Result<()> {
108+
push(job, grouping, url, mfs, "POST", basic_auth)
109+
}
110+
76111
const LABEL_NAME_JOB: &str = "job";
77112

78-
fn push<S: BuildHasher>(
113+
fn configure_push<S: BuildHasher>(
79114
job: &str,
80115
grouping: HashMap<String, String, S>,
81116
url: &str,
82117
mfs: Vec<proto::MetricFamily>,
83-
method: &str,
84-
basic_auth: Option<BasicAuthentication>,
85-
) -> Result<()> {
118+
) -> Result<(String, impl Encoder, Vec<u8>)> {
86119
// Suppress clippy warning needless_pass_by_value.
87120
let grouping = grouping;
88121

@@ -145,7 +178,18 @@ fn push<S: BuildHasher>(
145178
// Ignore error, `no metrics` and `no name`.
146179
let _ = encoder.encode(&[mf], &mut buf);
147180
}
181+
Ok((push_url, encoder, buf))
182+
}
148183

184+
fn push<S: BuildHasher>(
185+
job: &str,
186+
grouping: HashMap<String, String, S>,
187+
url: &str,
188+
mfs: Vec<proto::MetricFamily>,
189+
method: &str,
190+
basic_auth: Option<BasicAuthentication>,
191+
) -> Result<()> {
192+
let (push_url, encoder, buf) = configure_push(job, grouping, url, mfs)?;
149193
let mut builder = HTTP_CLIENT
150194
.request(
151195
Method::from_str(method).unwrap(),
@@ -159,18 +203,49 @@ fn push<S: BuildHasher>(
159203
}
160204

161205
let response = builder.send().map_err(|e| Error::Msg(format!("{}", e)))?;
206+
handle_push_response(response.status(), push_url)
207+
}
162208

163-
match response.status() {
209+
/// Requires the feature `push-async`.
210+
#[cfg(feature = "push-async")]
211+
async fn push_async<S: BuildHasher>(
212+
job: &str,
213+
grouping: HashMap<String, String, S>,
214+
url: &str,
215+
mfs: Vec<proto::MetricFamily>,
216+
method: &str,
217+
basic_auth: Option<BasicAuthentication>,
218+
) -> Result<()> {
219+
let (push_url, encoder, buf) = configure_push(job, grouping, url, mfs)?;
220+
let mut builder = ASYNC_HTTP_CLIENT
221+
.request(
222+
Method::from_str(method).unwrap(),
223+
Url::from_str(&push_url).unwrap(),
224+
)
225+
.header(CONTENT_TYPE, encoder.format_type())
226+
.body(buf);
227+
228+
if let Some(BasicAuthentication { username, password }) = basic_auth {
229+
builder = builder.basic_auth(username, Some(password));
230+
}
231+
232+
let response = builder.send().await.map_err(|e| Error::Msg(format!("{}", e)))?;
233+
handle_push_response(response.status(), push_url)
234+
}
235+
236+
fn handle_push_response(status: StatusCode, push_url: String) -> Result<()> {
237+
match status {
164238
StatusCode::ACCEPTED => Ok(()),
165239
StatusCode::OK => Ok(()),
166240
_ => Err(Error::Msg(format!(
167241
"unexpected status code {} while pushing to {}",
168-
response.status(),
242+
status,
169243
push_url
170244
))),
171245
}
172246
}
173247

248+
#[cfg(feature = "push")]
174249
fn push_from_collector<S: BuildHasher>(
175250
job: &str,
176251
grouping: HashMap<String, String, S>,
@@ -188,8 +263,28 @@ fn push_from_collector<S: BuildHasher>(
188263
push(job, grouping, url, mfs, method, basic_auth)
189264
}
190265

266+
/// Requires the feature `push-async`.
267+
#[cfg(feature = "push-async")]
268+
async fn push_from_collector_async<'a, S: BuildHasher>(
269+
job: &'a str,
270+
grouping: HashMap<String, String, S>,
271+
url: &'a str,
272+
collectors: Vec<Box<dyn Collector>>,
273+
method: &'a str,
274+
basic_auth: Option<BasicAuthentication>,
275+
) -> Result<()> {
276+
let registry = Registry::new();
277+
for bc in collectors {
278+
registry.register(bc)?;
279+
}
280+
281+
let mfs = registry.gather();
282+
push_async(job, grouping, url, mfs, method, basic_auth).await
283+
}
284+
191285
/// `push_collector` push metrics collected from the provided collectors. It is
192286
/// a convenient way to push only a few metrics.
287+
#[cfg(feature = "push")]
193288
pub fn push_collector<S: BuildHasher>(
194289
job: &str,
195290
grouping: HashMap<String, String, S>,
@@ -200,8 +295,24 @@ pub fn push_collector<S: BuildHasher>(
200295
push_from_collector(job, grouping, url, collectors, "PUT", basic_auth)
201296
}
202297

203-
/// `push_add_collector` works like `push_add_metrics`, it collects from the
298+
/// `push_collector_async` is just an async version of `push_collector`.
299+
/// Pushes metrics collected from the provided collectors. It is
300+
/// a convenient way to push only a few metrics.
301+
/// Requires the feature `push-async`.
302+
#[cfg(feature = "push-async")]
303+
pub async fn push_collector_async<'a, S: BuildHasher>(
304+
job: &'a str,
305+
grouping: HashMap<String, String, S>,
306+
url: &'a str,
307+
collectors: Vec<Box<dyn Collector>>,
308+
basic_auth: Option<BasicAuthentication>,
309+
) -> Result<()> {
310+
push_from_collector_async(job, grouping, url, collectors, "PUT", basic_auth).await
311+
}
312+
313+
/// `push_add_collector` works like `push_add_collector`, it collects from the
204314
/// provided collectors. It is a convenient way to push only a few metrics.
315+
#[cfg(feature = "push")]
205316
pub fn push_add_collector<S: BuildHasher>(
206317
job: &str,
207318
grouping: HashMap<String, String, S>,
@@ -212,6 +323,21 @@ pub fn push_add_collector<S: BuildHasher>(
212323
push_from_collector(job, grouping, url, collectors, "POST", basic_auth)
213324
}
214325

326+
/// `push_add_collector_async` works like `push_add_collector`, but async.
327+
/// It collects from the provided collectors. It is a convenient way to push
328+
/// only a few metrics.
329+
/// Requires the feature `push-async`.
330+
#[cfg(feature = "push-async")]
331+
pub async fn push_add_collector_async<'a, S: BuildHasher>(
332+
job: &'a str,
333+
grouping: HashMap<String, String, S>,
334+
url: &'a str,
335+
collectors: Vec<Box<dyn Collector>>,
336+
basic_auth: Option<BasicAuthentication>,
337+
) -> Result<()> {
338+
push_from_collector_async(job, grouping, url, collectors, "POST", basic_auth).await
339+
}
340+
215341
const DEFAULT_GROUP_LABEL_PAIR: (&str, &str) = ("instance", "unknown");
216342

217343
/// `hostname_grouping_key` returns a label map with the only entry

0 commit comments

Comments
 (0)