metrics
] + [prometheus
] = ❤️[prometheus
] backend for [metrics
] crate.
[Rust] has at least two ecosystems regarding metrics collection:
- One is based on the [prometheus
] crate, focusing on delivering metrics to [Prometheus] (or its drop-in replacements like [VictoriaMetrics]). It provides a lot of [Prometheus]-specific capabilities and validates metrics strictly to meet the format used by [Prometheus].
- Another one is based on the [metrics
] crate, being more generic and targeting a wider scope, rather than [Prometheus] only. It provides a convenient and ergonomic facade, allowing to work with metrics in the very similar way we do work with logs and traces via [log
]/[tracing
] ecosystems (and even [supports tracing::Span
s for metrics labels][metrics-tracing-context
]).
As the result, some crates use [prometheus
] crate for providing their metrics, and another crates do use [metrics
] crate for that. Furthermore, [prometheus
] and [metrics
] crates are designed quite differently, making their composition a non-trivial task. This crate aims to mitigate this gap, allowing to combine both [prometheus
] and [metrics
] ecosystems in a single project.
If you're not obligated to deal with [prometheus
] crate directly or via third-party crates which do use it, consider the [metrics-exporter-prometheus
] crate, which provides a simple [Prometheus] backend for [metrics
] facade, without bringing in the whole [prometheus
] crate's machinery.
This crate provides a [metrics::Recorder
] implementation, allowing to work with a [prometheus::Registry
] via [metrics
] facade.
It comes in 3 flavours, allowing to choose the smallest performance overhead depending on a use case:
- Regular [Recorder
], allowing to create new metrics via [metrics
] facade anytime, without limits. Provides the same overhead of accessing an already registered metric as a [metrics::Registry
] does: [read
-lock] on a sharded [HashMap
] plus [Arc
] cloning.
- [FrozenRecorder
], unable to create new metrics via [metrics
] facade at all (just no-op in such case). Provides the smallest overhead of accessing an already registered metric: just a regular [HashMap
] lookup plus [Arc
] cloning.
- [FreezableRecorder
], acting the same way as the [Recorder
] at first, but being able to [.freeze()
] and so, becoming a [FrozenRecorder
] at the end. The overhead of accessing an already registered metric is the same as [Recorder
] and [FrozenRecorder
] provide, plus [AtomicBool
] loading to check whether it has been [.freeze()
]d.
Not any [prometheus
] metric is supported, because [metrics
] crate implies only few of them. This is how the [metrics
] crate's metrics are mapped onto [prometheus
] ones:
- [metrics::Counter
]: [prometheus::IntCounter
] + [prometheus::IntCounterVec
]
- [metrics::Gauge
]: [prometheus::Gauge
] + [prometheus::GaugeVec
]
- [metrics::Histogram
]: [prometheus::Histogram
] + [prometheus::HistogramVec
]
[prometheus::MetricVec
] types are used whenever any labels are specified via [metrics
] facade.
To satisfy the [metrics::Recorder
]'s requirement of allowing changing metrics description anytime after its registration ([prometheus
] crate doesn't imply and allow that), the [Describable
] wrapper is used, allowing to [arc-swap
] the description.
``rust
// By default
prometheus::defaultregistry()` is used.
let recorder = metricsprometheus::install();
// Either use metrics
crate interfaces.
metrics::incrementcounter!("count", "whose" => "mine", "kind" => "owned");
metrics::incrementcounter!("count", "whose" => "mine", "kind" => "ref");
metrics::increment_counter!("count", "kind" => "owned", "whose" => "dummy");
// Or construct and provide prometheus
metrics directly.
recorder.register_metric(prometheus::Gauge::new("value", "help")?);
let report = prometheus::TextEncoder::new() .encodetostring(&prometheus::defaultregistry().gather())?; asserteq!( report.trim(), r#"
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
// Metrics can be described anytime after being registered in
// prometheus::Registry
.
metrics::describecounter!("count", "Example of counter.");
metrics::describegauge!("value", "Example of gauge.");
let report = prometheus::TextEncoder::new() .encodetostring(&recorder.registry().gather())?; assert_eq!( report.trim(), r#"
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
// Description can be changed multiple times and anytime. metrics::describe_counter!("count", "Another description.");
// Even before a metric is registered in prometheus::Registry
.
metrics::describecounter!("another", "Yet another counter.");
metrics::incrementcounter!("another");
let report = prometheus::TextEncoder::new() .encodetostring(&recorder.registry().gather())?; assert_eq!( report.trim(), r#"
another 1
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
```
Since [prometheus
] crate validates the metrics format very strictly, not everything, expressed via [metrics
] facade, may be put into a [prometheus::Registry
], ending up with a [prometheus::Error
] being emitted.
Metric names cannot be namespaced with dots (and should follow [Prometheus] format). ```rust,shouldpanic metricsprometheus::install();
// panics: 'queries.count' is not a valid metric name metrics::increment_counter!("queries.count"); ```
The same metric should use always the same set of labels: ```rust,shouldpanic metricsprometheus::install();
metrics::incrementcounter!("count");
// panics: Inconsistent label cardinality, expect 0 label values, but got 1
metrics::incrementcounter!("count", "whose" => "mine");
rust,shouldpanic
metricsprometheus::install();
metrics::incrementcounter!("count", "kind" => "owned");
// panics: label name kind missing in label map
metrics::incrementcounter!("count", "whose" => "mine");
rust,shouldpanic
metricsprometheus::install();
metrics::incrementcounter!("count", "kind" => "owned"); // panics: Inconsistent label cardinality, expect 1 label values, but got 2 metrics::incrementcounter!("count", "kind" => "ref", "whose" => "mine"); ```
The same name cannot be used for different types of metrics: ```rust,shouldpanic metricsprometheus::install();
metrics::incrementcounter!("count"); // panics: Duplicate metrics collector registration attempted metrics::incrementgauge!("count", 1.0); ```
Any metric registered in a [prometheus::Registry
] directly, without using [metrics
] or this crate interfaces, is not usable via [metrics
] facade and will cause a [prometheus::Error
].
```rust,shouldpanic
metricsprometheus::install();
prometheus::default_registry() .register(Box::new(prometheus::Gauge::new("value", "help")?))?;
// panics: Duplicate metrics collector registration attempted metrics::increment_gauge!("value", 4.5);
```
[metrics::Unit
]s are not supported, as [Prometheus] has no notion of ones. Specifying them via [metrics
] macros will be no-op.
prometheus::Error
] handlingSince [metrics::Recorder
] doesn't expose any errors in its API, the emitted [prometheus::Error
]s can be either turned into a panic, or just silently ignored, returning a no-op metric instead (see [metrics::Counter::noop()
] for example).
This can be tuned by providing a [failure::Strategy
] when building a [Recorder
].
```rust use metrics_prometheus::failure::strategy;
metricsprometheus::Recorder::builder() .withfailurestrategy(strategy::NoOp) .buildand_install();
// prometheus::Error
is ignored inside.
metrics::increment_counter!("invalid.name");
let stats = prometheus::defaultregistry().gather(); asserteq!(stats.len(), 0); ```
The default [failure::Strategy
] is [PanicInDebugNoOpInRelease
]. See [failure::strategy
] module for other available [failure::Strategy
]s,
or provide your own one by implementing the [failure::Strategy
] trait.
Copyright © 2022-2023 Instrumentisto Team, https://github.com/instrumentisto
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.