Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/prometheus/client_rust in…
Browse files Browse the repository at this point in the history
…to dyn-disp-metric
  • Loading branch information
mxinden committed Oct 2, 2022
2 parents c1bb0e2 + 682b24e commit ef379f2
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 5 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Added support for the OpenMetrics protobuf format. See [PR 83].
- Added a `remove` method to `Family` to allow the removal of a specified label
set from a family. See [PR 85].
- Added a `clear` method to `Family` to allow the removal of all label sets
from a family. See [PR 85].
- Impl `TypedMetric` for `CounterWithExemplar` and `HistogramWithExemplar`, so that they can be used with `Family`. See [PR 96].

### Changed

Expand All @@ -16,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Move`Encode` trait from `prometheus_client::encoding::text` to `prometheus_client::encoding`. See [PR 83].

[PR 83]: https://github.com/prometheus/client_rust/pull/83
[PR 85]: https://github.com/prometheus/client_rust/pull/85
[PR 96]: https://github.com/prometheus/client_rust/pull/96

## [0.18.0]

Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -27,13 +27,15 @@ void = { version = "1.0", optional = true }

[dev-dependencies]
async-std = { version = "1", features = ["attributes"] }
criterion = "0.3"
criterion = "0.4"
http-types = "2"
pyo3 = "0.17"
quickcheck = "1"
rand = "0.8.4"
tide = "0.16"
actix-web = "4"
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal"] }
hyper = { version = "0.14.16", features = ["server", "http1", "tcp"] }

[build-dependencies]
prost-build = { version = "0.9.0", optional = true }
Expand Down
3 changes: 0 additions & 3 deletions README.md
Expand Up @@ -44,9 +44,6 @@ be fixed in the future. Contributions in all forms are most welcome.
- Enforce "Exposers SHOULD avoid names that could be confused with the suffixes
that text format sample metric names use".

- Protobuf wire format. (Follow [spec
issue](https://github.com/OpenObservability/OpenMetrics/issues/183).)

- Gauge histogram metric.

- Allow "A MetricPoint in a Metric with the type [Counter, Histogram] SHOULD have a Timestamp
Expand Down
2 changes: 1 addition & 1 deletion derive-encode/src/lib.rs
Expand Up @@ -104,7 +104,7 @@ fn derive_protobuf_encode(ast: DeriveInput) -> TokenStream2 {
quote! {
let mut label = {
let mut labels = vec![];
self.#ident.encode(&mut labels);
prometheus_client::encoding::proto::EncodeLabels::encode(&self.#ident, &mut labels);
debug_assert_eq!(1, labels.len(), "Labels encoded from {} should have only one label.", #ident_string);
labels.pop().expect("should have an element")
};
Expand Down
75 changes: 75 additions & 0 deletions examples/hyper.rs
@@ -0,0 +1,75 @@
use hyper::{
service::{make_service_fn, service_fn},
Body, Request, Response, Server,
};
use prometheus_client::{encoding::text::encode, metrics::counter::Counter, registry::Registry};
use std::{
future::Future,
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
pin::Pin,
sync::Arc,
};
use tokio::signal::unix::{signal, SignalKind};

#[tokio::main]
async fn main() {
let request_counter: Counter<u64> = Default::default();

let mut registry = <Registry>::with_prefix("tokio_hyper_example");

registry.register(
"requests",
"How many requests the application has received",
request_counter.clone(),
);

// Spawn a server to serve the OpenMetrics endpoint.
let metrics_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8001);
start_metrics_server(metrics_addr, registry).await
}

/// Start a HTTP server to report metrics.
pub async fn start_metrics_server(metrics_addr: SocketAddr, registry: Registry) {
let mut shutdown_stream = signal(SignalKind::terminate()).unwrap();

eprintln!("Starting metrics server on {metrics_addr}");

let registry = Arc::new(registry);
Server::bind(&metrics_addr)
.serve(make_service_fn(move |_conn| {
let registry = registry.clone();
async move {
let handler = make_handler(registry);
Ok::<_, io::Error>(service_fn(handler))
}
}))
.with_graceful_shutdown(async move {
shutdown_stream.recv().await;
})
.await
.unwrap();
}

/// This function returns a HTTP handler (i.e. another function)
pub fn make_handler(
registry: Arc<Registry>,
) -> impl Fn(Request<Body>) -> Pin<Box<dyn Future<Output = io::Result<Response<Body>>> + Send>> {
// This closure accepts a request and responds with the OpenMetrics encoding of our metrics.
move |_req: Request<Body>| {
let reg = registry.clone();
Box::pin(async move {
let mut buf = Vec::new();
encode(&mut buf, &reg.clone()).map(|_| {
let body = Body::from(buf);
Response::builder()
.header(
hyper::header::CONTENT_TYPE,
"application/openmetrics-text; version=1.0.0; charset=utf-8",
)
.body(body)
.unwrap()
})
})
}
}
70 changes: 70 additions & 0 deletions src/metrics/exemplar.rs
Expand Up @@ -4,6 +4,7 @@

use super::counter::{self, Counter};
use super::histogram::Histogram;
use super::{MetricType, TypedMetric};
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use std::collections::HashMap;
#[cfg(any(target_arch = "mips", target_arch = "powerpc"))]
Expand Down Expand Up @@ -31,12 +32,45 @@ pub struct Exemplar<S, V> {
/// counter_with_exemplar.inc_by(1, Some(vec![("user_id".to_string(), "42".to_string())]));
/// let _value: (u64, _) = counter_with_exemplar.get();
/// ```
/// You can also use exemplars with families. Just wrap the exemplar in a Family.
/// ```
/// # use prometheus_client::metrics::exemplar::CounterWithExemplar;
/// # use prometheus_client::metrics::histogram::exponential_buckets;
/// # use prometheus_client::metrics::family::Family;
/// # use prometheus_client_derive_encode::Encode;
/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
/// pub struct ResultLabel {
/// pub result: String,
/// }
///
/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
/// pub struct TraceLabel {
/// pub trace_id: String,
/// }
///
/// let latency: Family<ResultLabel, CounterWithExemplar<TraceLabel>> = Family::default();
///
/// latency
/// .get_or_create(&ResultLabel {
/// result: "success".to_owned(),
/// })
/// .inc_by(
/// 1,
/// Some(TraceLabel {
/// trace_id: "3a2f90c9f80b894f".to_owned(),
/// }),
/// );
/// ```
#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
#[derive(Debug)]
pub struct CounterWithExemplar<S, N = u64, A = AtomicU64> {
pub(crate) inner: Arc<RwLock<CounterWithExemplarInner<S, N, A>>>,
}

impl<S> TypedMetric for CounterWithExemplar<S> {
const TYPE: MetricType = MetricType::Counter;
}

/// Open Metrics [`Counter`] with an [`Exemplar`] to both measure discrete
/// events and track references to data outside of the metric set.
#[cfg(any(target_arch = "mips", target_arch = "powerpc"))]
Expand Down Expand Up @@ -122,12 +156,48 @@ impl<S, N: Clone, A: counter::Atomic<N>> CounterWithExemplar<S, N, A> {
/// let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
/// histogram.observe(4.2, Some(vec![("user_id".to_string(), "42".to_string())]));
/// ```
/// You can also use exemplars with families. Just wrap the exemplar in a Family.
/// ```
/// # use prometheus_client::metrics::exemplar::HistogramWithExemplars;
/// # use prometheus_client::metrics::histogram::exponential_buckets;
/// # use prometheus_client::metrics::family::Family;
/// # use prometheus_client_derive_encode::Encode;
/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
/// pub struct ResultLabel {
/// pub result: String,
/// }
///
/// #[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
/// pub struct TraceLabel {
/// pub trace_id: String,
/// }
///
/// let latency: Family<ResultLabel, HistogramWithExemplars<TraceLabel>> =
/// Family::new_with_constructor(|| {
/// HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10))
/// });
///
/// latency
/// .get_or_create(&ResultLabel {
/// result: "success".to_owned(),
/// })
/// .observe(
/// 0.001345422,
/// Some(TraceLabel {
/// trace_id: "3a2f90c9f80b894f".to_owned(),
/// }),
/// );
/// ```
#[derive(Debug)]
pub struct HistogramWithExemplars<S> {
// TODO: Not ideal, as Histogram has a Mutex as well.
pub(crate) inner: Arc<RwLock<HistogramWithExemplarsInner<S>>>,
}

impl<S> TypedMetric for HistogramWithExemplars<S> {
const TYPE: MetricType = MetricType::Histogram;
}

impl<S> Clone for HistogramWithExemplars<S> {
fn clone(&self) -> Self {
Self {
Expand Down
124 changes: 124 additions & 0 deletions src/metrics/family.rs
Expand Up @@ -241,6 +241,47 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
})
}

/// Remove a label set from the metric family.
///
/// Returns a bool indicating if a label set was removed or not.
///
/// ```
/// # use prometheus_client::metrics::counter::{Atomic, Counter};
/// # use prometheus_client::metrics::family::Family;
/// #
/// let family = Family::<Vec<(String, String)>, Counter>::default();
///
/// // Will create the metric with label `method="GET"` on first call and
/// // return a reference.
/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
///
/// // Will return `true`, indicating that the `method="GET"` label set was
/// // removed.
/// assert!(family.remove(&vec![("method".to_owned(), "GET".to_owned())]));
/// ```
pub fn remove(&self, label_set: &S) -> bool {
self.metrics.write().remove(label_set).is_some()
}

/// Clear all label sets from the metric family.
///
/// ```
/// # use prometheus_client::metrics::counter::{Atomic, Counter};
/// # use prometheus_client::metrics::family::Family;
/// #
/// let family = Family::<Vec<(String, String)>, Counter>::default();
///
/// // Will create the metric with label `method="GET"` on first call and
/// // return a reference.
/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc();
///
/// // Clear the family of all label sets.
/// family.clear();
/// ```
pub fn clear(&self) {
self.metrics.write().clear()
}

pub(crate) fn read(&self) -> RwLockReadGuard<HashMap<S, M>> {
self.metrics.read()
}
Expand Down Expand Up @@ -302,4 +343,87 @@ mod tests {
let custom_builder = CustomBuilder { custom_start: 1.0 };
Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
}

#[test]
fn counter_family_remove() {
let family = Family::<Vec<(String, String)>, Counter>::default();

family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();

assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);

family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.inc_by(2);

assert_eq!(
2,
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.get()
);

// Attempt to remove it twice, showing it really was removed on the
// first attempt.
assert!(family.remove(&vec![("method".to_string(), "POST".to_string())]));
assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())]));

// This should make a new POST label.
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.inc();

assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.get()
);

// GET label should have be untouched.
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
}

#[test]
fn counter_family_clear() {
let family = Family::<Vec<(String, String)>, Counter>::default();

// Create a label and check it.
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();

assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);

// Clear it, then try recreating and checking it again.
family.clear();

family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();

assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
}
}

0 comments on commit ef379f2

Please sign in to comment.