use std::sync::Arc;
use folk_api::{
Counter as ApiCounter, CounterVec as ApiCounterVec, Gauge as ApiGauge, GaugeVec as ApiGaugeVec,
Histogram as ApiHistogram, HistogramVec as ApiHistogramVec, MetricsRegistry,
};
use prometheus::{
CounterVec as PromCounterVec, Encoder, GaugeVec as PromGaugeVec, HistogramOpts,
HistogramVec as PromHistogramVec, Opts, Registry, TextEncoder,
};
pub struct MetricsRegistryImpl {
registry: Registry,
}
impl MetricsRegistryImpl {
pub fn new() -> Arc<Self> {
Arc::new(Self {
registry: Registry::new(),
})
}
}
impl MetricsRegistry for MetricsRegistryImpl {
fn counter_vec(&self, name: &str, help: &str, label_keys: &[&str]) -> Arc<dyn ApiCounterVec> {
let opts = Opts::new(name, help);
let cv = PromCounterVec::new(opts, label_keys).expect("valid counter spec");
self.registry
.register(Box::new(cv.clone()))
.expect("register counter");
Arc::new(CounterVecAdapter { inner: cv })
}
fn gauge_vec(&self, name: &str, help: &str, label_keys: &[&str]) -> Arc<dyn ApiGaugeVec> {
let opts = Opts::new(name, help);
let gv = PromGaugeVec::new(opts, label_keys).expect("valid gauge spec");
self.registry
.register(Box::new(gv.clone()))
.expect("register gauge");
Arc::new(GaugeVecAdapter { inner: gv })
}
fn histogram_vec(
&self,
name: &str,
help: &str,
label_keys: &[&str],
) -> Arc<dyn ApiHistogramVec> {
let opts = HistogramOpts::new(name, help);
let hv = PromHistogramVec::new(opts, label_keys).expect("valid histogram spec");
self.registry
.register(Box::new(hv.clone()))
.expect("register histogram");
Arc::new(HistogramVecAdapter { inner: hv })
}
fn render(&self) -> String {
let encoder = TextEncoder::new();
let metric_families = self.registry.gather();
let mut buf = Vec::new();
encoder
.encode(&metric_families, &mut buf)
.expect("encode metrics");
String::from_utf8(buf).expect("UTF-8 from text encoder")
}
}
struct CounterVecAdapter {
inner: PromCounterVec,
}
struct GaugeVecAdapter {
inner: PromGaugeVec,
}
struct HistogramVecAdapter {
inner: PromHistogramVec,
}
impl ApiCounterVec for CounterVecAdapter {
fn with_labels(&self, labels: &[&str]) -> Arc<dyn ApiCounter> {
Arc::new(CounterAdapter {
inner: self.inner.with_label_values(labels),
})
}
}
impl ApiGaugeVec for GaugeVecAdapter {
fn with_labels(&self, labels: &[&str]) -> Arc<dyn ApiGauge> {
Arc::new(GaugeAdapter {
inner: self.inner.with_label_values(labels),
})
}
}
impl ApiHistogramVec for HistogramVecAdapter {
fn with_labels(&self, labels: &[&str]) -> Arc<dyn ApiHistogram> {
Arc::new(HistogramAdapter {
inner: self.inner.with_label_values(labels),
})
}
}
struct CounterAdapter {
inner: prometheus::Counter,
}
struct GaugeAdapter {
inner: prometheus::Gauge,
}
struct HistogramAdapter {
inner: prometheus::Histogram,
}
impl ApiCounter for CounterAdapter {
fn inc(&self) {
self.inner.inc();
}
#[allow(clippy::cast_precision_loss)]
fn inc_by(&self, v: u64) {
self.inner.inc_by(v as f64);
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn get(&self) -> u64 {
self.inner.get() as u64
}
}
impl ApiGauge for GaugeAdapter {
#[allow(clippy::cast_precision_loss)]
fn set(&self, v: i64) {
self.inner.set(v as f64);
}
fn inc(&self) {
self.inner.inc();
}
fn dec(&self) {
self.inner.dec();
}
#[allow(clippy::cast_possible_truncation)]
fn get(&self) -> i64 {
self.inner.get() as i64
}
}
impl ApiHistogram for HistogramAdapter {
fn observe(&self, v: f64) {
self.inner.observe(v);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registers_and_renders_counter() {
let reg = MetricsRegistryImpl::new();
let cv = reg.counter_vec("requests_total", "Number of requests", &["method"]);
cv.with_labels(&["GET"]).inc();
cv.with_labels(&["GET"]).inc_by(4);
cv.with_labels(&["POST"]).inc();
let output = reg.render();
assert!(output.contains("requests_total"));
assert!(output.contains("method=\"GET\""));
assert!(output.contains("method=\"POST\""));
}
#[test]
fn registers_gauge_and_histogram() {
let reg = MetricsRegistryImpl::new();
reg.gauge_vec("connections", "active connections", &[])
.with_labels(&[])
.set(7);
reg.histogram_vec("request_seconds", "request duration", &[])
.with_labels(&[])
.observe(0.123);
let output = reg.render();
assert!(output.contains("connections 7"));
assert!(output.contains("request_seconds"));
}
}