use std::{iter, sync::Arc};
use arc_swap::ArcSwap;
use sealed::sealed;
use smallvec::SmallVec;
#[doc(inline)]
pub use self::bundle::Bundle;
use self::bundle::Either;
#[derive(Clone, Copy, Debug)]
pub struct Metric<M>(M);
impl<M> Metric<M> {
#[must_use]
pub const fn wrap(metric: M) -> Self {
Self(metric)
}
#[must_use]
pub fn into_inner(self) -> M {
self.0
}
}
impl<M> AsRef<M> for Metric<M> {
fn as_ref(&self) -> &M {
&self.0
}
}
impl<M> AsMut<M> for Metric<M> {
fn as_mut(&mut self) -> &mut M {
&mut self.0
}
}
#[warn(clippy::missing_trait_methods)]
impl metrics::CounterFn for Metric<prometheus::IntCounter> {
fn increment(&self, value: u64) {
self.0.inc_by(value);
}
fn absolute(&self, value: u64) {
self.0.reset();
self.0.inc_by(value);
}
}
#[warn(clippy::missing_trait_methods)]
impl metrics::GaugeFn for Metric<prometheus::Gauge> {
fn increment(&self, value: f64) {
self.0.add(value);
}
fn decrement(&self, value: f64) {
self.0.sub(value);
}
fn set(&self, value: f64) {
self.0.set(value);
}
}
#[warn(clippy::missing_trait_methods)]
impl metrics::HistogramFn for Metric<prometheus::Histogram> {
fn record(&self, value: f64) {
self.0.observe(value);
}
fn record_many(&self, value: f64, count: usize) {
for _ in 0..count {
self.record(value);
}
}
}
#[derive(Debug)]
pub struct Fallible<M>(pub Arc<prometheus::Result<Arc<Metric<M>>>>);
impl<M> Clone for Fallible<M> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<M> From<prometheus::Result<Arc<Metric<M>>>> for Fallible<M> {
fn from(res: prometheus::Result<Arc<Metric<M>>>) -> Self {
Self(Arc::new(res))
}
}
impl<M> Fallible<M> {
pub fn as_ref(&self) -> Result<&Arc<Metric<M>>, &prometheus::Error> {
(*self.0).as_ref()
}
}
#[warn(clippy::missing_trait_methods)]
impl<M> metrics::CounterFn for Fallible<M>
where
Metric<M>: metrics::CounterFn,
{
fn increment(&self, value: u64) {
if let Ok(m) = &*self.0 {
m.increment(value);
}
}
fn absolute(&self, value: u64) {
if let Ok(m) = &*self.0 {
m.absolute(value);
}
}
}
#[warn(clippy::missing_trait_methods)]
impl<M> metrics::GaugeFn for Fallible<M>
where
Metric<M>: metrics::GaugeFn,
{
fn increment(&self, value: f64) {
if let Ok(m) = &*self.0 {
m.increment(value);
}
}
fn decrement(&self, value: f64) {
if let Ok(m) = &*self.0 {
m.decrement(value);
}
}
fn set(&self, value: f64) {
if let Ok(m) = &*self.0 {
m.set(value);
}
}
}
#[warn(clippy::missing_trait_methods)]
impl<M> metrics::HistogramFn for Fallible<M>
where
Metric<M>: metrics::HistogramFn,
{
fn record(&self, value: f64) {
if let Ok(m) = &*self.0 {
m.record(value);
}
}
fn record_many(&self, value: f64, count: usize) {
if let Ok(m) = &*self.0 {
for _ in 0..count {
m.record(value);
}
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Describable<Metric> {
pub(crate) description: Arc<ArcSwap<String>>,
pub(crate) metric: Metric,
}
impl<M> Describable<M> {
#[must_use]
pub fn wrap(metric: M) -> Self {
Self { description: Arc::default(), metric }
}
#[must_use]
pub fn only_description(help: impl Into<String>) -> Self
where
M: Default,
{
Self {
description: Arc::new(ArcSwap::new(Arc::new(help.into()))),
metric: M::default(),
}
}
#[must_use]
pub fn map<Into>(self, into: impl FnOnce(M) -> Into) -> Describable<Into> {
Describable { description: self.description, metric: into(self.metric) }
}
}
impl<M> Describable<Option<M>> {
#[must_use]
pub fn transpose(self) -> Option<Describable<M>> {
self.metric
.map(|metric| Describable { description: self.description, metric })
}
}
#[warn(clippy::missing_trait_methods)]
impl<M> prometheus::core::Collector for Describable<M>
where
M: prometheus::core::Collector,
{
fn desc(&self) -> Vec<&prometheus::core::Desc> {
self.metric.desc()
}
fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
let mut out = self.metric.collect();
let new_help = self.description.load_full();
if !new_help.is_empty() {
for mf in &mut out {
mf.set_help((*new_help).clone());
}
}
out
}
}
trait To<T> {
fn to(&self) -> T;
}
impl To<prometheus::Opts> for metrics::Key {
fn to(&self) -> prometheus::Opts {
prometheus::Opts::new(self.name(), self.name())
}
}
impl To<prometheus::HistogramOpts> for metrics::Key {
fn to(&self) -> prometheus::HistogramOpts {
prometheus::HistogramOpts::new(self.name(), self.name())
}
}
#[sealed]
pub trait Bundled {
type Bundle: Bundle;
fn into_bundle(self) -> Self::Bundle;
}
#[sealed]
impl Bundled for prometheus::IntCounter {
type Bundle = PrometheusIntCounter;
fn into_bundle(self) -> Self::Bundle {
PrometheusIntCounter::Single(self)
}
}
#[sealed]
impl Bundled for prometheus::IntCounterVec {
type Bundle = PrometheusIntCounter;
fn into_bundle(self) -> Self::Bundle {
PrometheusIntCounter::Vec(self)
}
}
#[sealed]
impl Bundled for prometheus::Gauge {
type Bundle = PrometheusGauge;
fn into_bundle(self) -> Self::Bundle {
PrometheusGauge::Single(self)
}
}
#[sealed]
impl Bundled for prometheus::GaugeVec {
type Bundle = PrometheusGauge;
fn into_bundle(self) -> Self::Bundle {
PrometheusGauge::Vec(self)
}
}
#[sealed]
impl Bundled for prometheus::Histogram {
type Bundle = PrometheusHistogram;
fn into_bundle(self) -> Self::Bundle {
PrometheusHistogram::Single(self)
}
}
#[sealed]
impl Bundled for prometheus::HistogramVec {
type Bundle = PrometheusHistogram;
fn into_bundle(self) -> Self::Bundle {
PrometheusHistogram::Vec(self)
}
}
pub type PrometheusIntCounter =
Either<prometheus::IntCounter, prometheus::IntCounterVec>;
impl TryFrom<&metrics::Key> for PrometheusIntCounter {
type Error = prometheus::Error;
fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
let mut labels_iter = key.labels();
Ok(if let Some(first_label) = labels_iter.next() {
let label_names = iter::once(first_label)
.chain(labels_iter)
.map(metrics::Label::key)
.collect::<SmallVec<[_; 10]>>();
Self::Vec(prometheus::IntCounterVec::new(key.to(), &label_names)?)
} else {
Self::Single(prometheus::IntCounter::with_opts(key.to())?)
})
}
}
pub type PrometheusGauge = Either<prometheus::Gauge, prometheus::GaugeVec>;
impl TryFrom<&metrics::Key> for PrometheusGauge {
type Error = prometheus::Error;
fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
let mut labels_iter = key.labels();
Ok(if let Some(first_label) = labels_iter.next() {
let label_names = iter::once(first_label)
.chain(labels_iter)
.map(metrics::Label::key)
.collect::<SmallVec<[_; 10]>>();
Self::Vec(prometheus::GaugeVec::new(key.to(), &label_names)?)
} else {
Self::Single(prometheus::Gauge::with_opts(key.to())?)
})
}
}
pub type PrometheusHistogram =
Either<prometheus::Histogram, prometheus::HistogramVec>;
impl TryFrom<&metrics::Key> for PrometheusHistogram {
type Error = prometheus::Error;
fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
let mut labels_iter = key.labels();
Ok(if let Some(first_label) = labels_iter.next() {
let label_names = iter::once(first_label)
.chain(labels_iter)
.map(metrics::Label::key)
.collect::<SmallVec<[_; 10]>>();
Self::Vec(prometheus::HistogramVec::new(key.to(), &label_names)?)
} else {
Self::Single(prometheus::Histogram::with_opts(key.to())?)
})
}
}
pub mod bundle {
use std::{collections::HashMap, hash::BuildHasher};
use sealed::sealed;
#[derive(Clone, Copy, Debug)]
pub enum Either<Single, Vec> {
Single(Single),
Vec(Vec),
}
#[warn(clippy::missing_trait_methods)]
impl<S, V> prometheus::core::Collector for Either<S, V>
where
S: prometheus::core::Collector,
V: prometheus::core::Collector,
{
fn desc(&self) -> Vec<&prometheus::core::Desc> {
match self {
Self::Single(m) => m.desc(),
Self::Vec(v) => v.desc(),
}
}
fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
match self {
Self::Single(m) => m.collect(),
Self::Vec(v) => v.collect(),
}
}
}
#[sealed]
pub trait MetricVec {
type Metric: prometheus::core::Metric;
fn get_metric_with<S: BuildHasher>(
&self,
labels: &HashMap<&str, &str, S>,
) -> prometheus::Result<Self::Metric>;
}
#[sealed]
impl<M, B> MetricVec for prometheus::core::MetricVec<B>
where
M: prometheus::core::Metric,
B: prometheus::core::MetricVecBuilder<M = M>,
{
type Metric = M;
fn get_metric_with<S: BuildHasher>(
&self,
labels: &HashMap<&str, &str, S>,
) -> prometheus::Result<M> {
self.get_metric_with(labels)
}
}
#[sealed]
pub trait Bundle {
type Single: prometheus::core::Metric;
type Vec: MetricVec<Metric = Self::Single>;
fn get_single_metric(
&self,
key: &metrics::Key,
) -> prometheus::Result<Self::Single>;
}
#[sealed]
impl<M, B> Bundle for Either<M, prometheus::core::MetricVec<B>>
where
M: prometheus::core::Metric + Clone,
B: prometheus::core::MetricVecBuilder<M = M>,
{
type Single = M;
type Vec = prometheus::core::MetricVec<B>;
fn get_single_metric(
&self,
key: &metrics::Key,
) -> prometheus::Result<M> {
match self {
Self::Single(c) => {
if key.labels().next().is_some() {
return Err(
prometheus::Error::InconsistentCardinality {
expect: 0,
got: key.labels().count(),
},
);
}
Ok(c.clone())
}
Self::Vec(v) => {
let labels = key
.labels()
.map(|l| (l.key(), l.value()))
.collect::<HashMap<_, _>>();
v.get_metric_with(&labels)
}
}
}
}
}