From ae25cdf2dcfb7919f10e484d5915eeae72b4b039 Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Fri, 9 Jul 2021 14:29:35 +0100 Subject: [PATCH] Add 'PrometheusMetrics::with_default_registry' associated function Closes #12. --- CHANGELOG.md | 1 + src/lib.rs | 8 +++ tests/default_registry.rs | 121 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 tests/default_registry.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4509fba..6ff416f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use `Duration::as_secs_f64` instead of manually calculating nanoseconds when calculating request durations. This bumps the minimum supported Rust version to 1.38.0, which is unlikely to be a problem in practice, since Rocket still requires a nightly version of Rust. - Impl `From for Vec` instead of `Into> for PrometheusMetrics`, since the former gives us the latter for free. - `PrometheusMetrics::registry` is now a `const fn`. +- Add `PrometheusMetrics::with_default_registry` associated function, which creates a new `PrometheusMetrics` using the default global `prometheus::Registry` and will therefore expose metrics created by the various macros in the `prometheus` crate. ## [0.7.0] - 2020-06-19 ### Changed diff --git a/src/lib.rs b/src/lib.rs index 0d4031d..28e4d88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,6 +226,14 @@ impl PrometheusMetrics { Self::with_registry(Registry::new()) } + /// Create a new `PrometheusMetrics` using the default Prometheus `Registry`. + /// + /// This will cause the fairing to include metrics created by the various + /// `prometheus` macros, e.g. `register_int_counter`. + pub fn with_default_registry() -> Self { + Self::with_registry(prometheus::default_registry().clone()) + } + /// Create a new `PrometheusMetrics` with a custom `Registry`. pub fn with_registry(registry: Registry) -> Self { let namespace = env::var(NAMESPACE_ENV_VAR).unwrap_or_else(|_| "rocket".into()); diff --git a/tests/default_registry.rs b/tests/default_registry.rs new file mode 100644 index 0000000..ebfec00 --- /dev/null +++ b/tests/default_registry.rs @@ -0,0 +1,121 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] +extern crate rocket; + +use once_cell::sync::Lazy; +use prometheus::{register_int_counter_vec, IntCounterVec}; +use rocket::{http::ContentType, local::Client}; +use rocket_prometheus::PrometheusMetrics; +use serde_json::json; + +static NAME_COUNTER: Lazy = Lazy::new(|| { + register_int_counter_vec!("name_counter", "Count of names", &["name"]) + .expect("Could not create name_counter") +}); + +mod routes { + use rocket::http::RawStr; + use rocket_contrib::json::Json; + use serde::Deserialize; + + use super::NAME_COUNTER; + + #[get("/hello/?")] + pub fn hello(name: &RawStr, caps: Option) -> String { + NAME_COUNTER.with_label_values(&[name]).inc(); + let name = caps + .unwrap_or_default() + .then(|| name.to_uppercase()) + .unwrap_or_else(|| name.to_string()); + format!("Hello, {}!", name) + } + + #[derive(Deserialize)] + pub struct Person { + age: u8, + } + + #[post("/hello/?", format = "json", data = "")] + pub fn hello_post(name: String, person: Json, caps: Option) -> String { + let name = caps + .unwrap_or_default() + .then(|| name.to_uppercase()) + .unwrap_or_else(|| name.to_string()); + format!("Hello, {} year old named {}!", person.age, name) + } +} + +#[test] +fn main() { + let prometheus = PrometheusMetrics::new(); + prometheus + .registry() + .register(Box::new(NAME_COUNTER.clone())) + .unwrap(); + let rocket = rocket::ignite() + .attach(prometheus.clone()) + .mount("/", routes![routes::hello, routes::hello_post]) + .mount("/metrics", prometheus); + let client = Client::new(rocket).expect("valid rocket instance"); + client.get("/hello/foo").dispatch(); + client.get("/hello/foo").dispatch(); + client.get("/hello/bar").dispatch(); + client + .post("/hello/bar") + .header(ContentType::JSON) + .body(serde_json::to_string(&json!({"age": 50})).unwrap()) + .dispatch(); + let mut metrics = client.get("/metrics").dispatch(); + let response = metrics.body_string().unwrap(); + assert_eq!( + response + .lines() + .enumerate() + .filter_map(|(i, line)| + // Skip out the 'sum' lines since they depend on request duration. + if i != 18 && i != 32 { + Some(line) + } else { + None + }) + .collect::>() + .join("\n"), + r#"# HELP name_counter Count of names +# TYPE name_counter counter +name_counter{name="bar"} 1 +name_counter{name="foo"} 2 +# HELP rocket_http_requests_duration_seconds HTTP request duration in seconds for all requests +# TYPE rocket_http_requests_duration_seconds histogram +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.005"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.01"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.025"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.05"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.1"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.25"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="0.5"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="1"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="2.5"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="5"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="10"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="GET",status="200",le="+Inf"} 3 +rocket_http_requests_duration_seconds_count{endpoint="/hello/?",method="GET",status="200"} 3 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.005"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.01"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.025"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.05"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.1"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.25"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="0.5"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="1"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="2.5"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="5"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="10"} 1 +rocket_http_requests_duration_seconds_bucket{endpoint="/hello/?",method="POST",status="200",le="+Inf"} 1 +rocket_http_requests_duration_seconds_count{endpoint="/hello/?",method="POST",status="200"} 1 +# HELP rocket_http_requests_total Total number of HTTP requests +# TYPE rocket_http_requests_total counter +rocket_http_requests_total{endpoint="/hello/?",method="GET",status="200"} 3 +rocket_http_requests_total{endpoint="/hello/?",method="POST",status="200"} 1"# + ); +}