use std::{collections::HashMap, fmt, sync::Mutex};
use once_cell::sync::Lazy;
use prometheus_client::{
encoding::{text, DescriptorEncoder},
registry::{Registry as RegistryInner, Unit},
};
use crate::{
collector::{Collector, LazyGlobalCollector},
descriptors::{FullMetricDescriptor, MetricGroupDescriptor},
encoding::GroupedMetric,
format::{EscapeWrapper, Format, PrometheusWrapper},
Metrics,
};
impl FullMetricDescriptor {
fn format_for_panic(&self) -> String {
format!(
"{module}::{group_name}.{field_name} (line {line}) in crate {crate_name} {crate_version}",
module = self.group.module_path,
group_name = self.group.name,
field_name = self.metric.field_name,
line = self.group.line,
crate_name = self.group.crate_name,
crate_version = self.group.crate_version
)
}
}
#[derive(Debug, Default)]
pub struct RegisteredDescriptors {
groups: Vec<&'static MetricGroupDescriptor>,
metrics_by_name: HashMap<String, FullMetricDescriptor>,
}
impl RegisteredDescriptors {
pub fn groups(&self) -> impl ExactSizeIterator<Item = &MetricGroupDescriptor> + '_ {
self.groups.iter().copied()
}
pub fn metric(&self, full_name: &str) -> Option<FullMetricDescriptor> {
self.metrics_by_name.get(full_name).copied()
}
pub fn metric_count(&self) -> usize {
self.groups.iter().map(|group| group.metrics.len()).sum()
}
fn push(&mut self, group: &'static MetricGroupDescriptor) {
for field in group.metrics {
let descriptor = FullMetricDescriptor::new(group, field);
let metric_name = field.full_name();
if let Some(prev_descriptor) =
self.metrics_by_name.insert(metric_name.clone(), descriptor)
{
panic!(
"Metric `{metric_name}` is redefined. New definition is at {descriptor}, \
previous definition was at {prev_descriptor}",
descriptor = descriptor.format_for_panic(),
prev_descriptor = prev_descriptor.format_for_panic()
);
}
}
self.groups.push(group);
}
}
#[derive(Debug)]
pub struct MetricsCollection<F = fn(&MetricGroupDescriptor) -> bool> {
is_lazy: bool,
filter_fn: F,
}
impl Default for MetricsCollection {
fn default() -> Self {
Self {
is_lazy: false,
filter_fn: |_| true,
}
}
}
impl MetricsCollection {
pub fn lazy() -> Self {
Self {
is_lazy: true,
..Self::default()
}
}
pub fn filter<F>(self, filter_fn: F) -> MetricsCollection<F>
where
F: FnMut(&MetricGroupDescriptor) -> bool,
{
MetricsCollection {
is_lazy: self.is_lazy,
filter_fn,
}
}
}
impl<F: FnMut(&MetricGroupDescriptor) -> bool> MetricsCollection<F> {
#[allow(clippy::missing_panics_doc)]
pub fn collect(mut self) -> Registry {
let mut registry = Registry::empty();
registry.is_lazy = self.is_lazy;
for metric in METRICS_REGISTRATIONS.get() {
if (self.filter_fn)(metric.descriptor()) {
metric.collect_to_registry(&mut registry);
}
}
registry
}
}
#[derive(Debug)]
pub struct Registry {
descriptors: RegisteredDescriptors,
inner: RegistryInner,
is_lazy: bool,
}
impl Registry {
pub fn empty() -> Self {
Self {
descriptors: RegisteredDescriptors::default(),
inner: RegistryInner::default(),
is_lazy: false,
}
}
pub fn descriptors(&self) -> &RegisteredDescriptors {
&self.descriptors
}
pub fn register_metrics<M: Metrics>(&mut self, metrics: &M) {
self.descriptors.push(&M::DESCRIPTOR);
metrics.visit_metrics(self);
}
pub(crate) fn register_global_metrics<M: Metrics>(
&mut self,
metrics: &'static Lazy<M>,
force_lazy: bool,
) {
if force_lazy || self.is_lazy {
self.descriptors.push(&M::DESCRIPTOR);
let collector = LazyGlobalCollector::new(metrics);
self.inner.register_collector(Box::new(collector));
} else {
self.register_metrics::<M>(metrics);
}
}
pub fn register_collector<M: Metrics>(&mut self, collector: &'static Collector<M>) {
self.descriptors.push(&M::DESCRIPTOR);
self.inner.register_collector(Box::new(collector));
}
pub fn encode<W: fmt::Write>(&self, writer: &mut W, format: Format) -> fmt::Result {
match format {
Format::Prometheus | Format::OpenMetricsForPrometheus => {
let mut wrapper = PrometheusWrapper::new(writer);
if matches!(format, Format::Prometheus) {
wrapper.remove_eof_terminator();
wrapper.translate_info_metrics_type();
}
text::encode(&mut EscapeWrapper::new(&mut wrapper), &self.inner)?;
wrapper.flush()
}
Format::OpenMetrics => text::encode(&mut EscapeWrapper::new(writer), &self.inner),
}
}
}
pub trait MetricsVisitor {
#[doc(hidden)] fn visit_metric(
&mut self,
name: &'static str,
help: &'static str,
unit: Option<Unit>,
metric: Box<dyn GroupedMetric>,
);
}
impl MetricsVisitor for Registry {
fn visit_metric(
&mut self,
name: &'static str,
help: &'static str,
unit: Option<Unit>,
metric: Box<dyn GroupedMetric>,
) {
if let Some(unit) = unit {
self.inner.register_with_unit(name, help, unit, metric);
} else {
self.inner.register(name, help, metric);
}
}
}
#[derive(Debug)]
pub(crate) struct MetricsEncoder<'a> {
inner: Result<DescriptorEncoder<'a>, fmt::Error>,
}
impl MetricsEncoder<'_> {
pub(crate) fn check(self) -> fmt::Result {
self.inner.map(drop)
}
}
impl<'a> From<DescriptorEncoder<'a>> for MetricsEncoder<'a> {
fn from(inner: DescriptorEncoder<'a>) -> Self {
Self { inner: Ok(inner) }
}
}
impl MetricsVisitor for MetricsEncoder<'_> {
fn visit_metric(
&mut self,
name: &'static str,
help: &'static str,
unit: Option<Unit>,
metric: Box<dyn GroupedMetric>,
) {
if let Ok(encoder) = &mut self.inner {
let mut help = String::from(help);
help.push('.');
let new_result = encoder
.encode_descriptor(name, &help, unit.as_ref(), metric.metric_type())
.and_then(|encoder| metric.encode(encoder));
if let Err(err) = new_result {
self.inner = Err(err);
}
}
}
}
pub trait CollectToRegistry: 'static + Send + Sync {
#[doc(hidden)] fn descriptor(&self) -> &'static MetricGroupDescriptor;
#[doc(hidden)] fn collect_to_registry(&'static self, registry: &mut Registry);
}
pub struct MetricsRegistrations {
inner: Mutex<Vec<&'static dyn CollectToRegistry>>,
}
impl fmt::Debug for MetricsRegistrations {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Ok(metrics) = self.inner.lock() {
let descriptors = metrics.iter().map(|metrics| metrics.descriptor());
formatter.debug_list().entries(descriptors).finish()
} else {
formatter
.debug_tuple("MetricsRegistrations")
.field(&"poisoned")
.finish()
}
}
}
impl MetricsRegistrations {
const fn new() -> Self {
Self {
inner: Mutex::new(Vec::new()),
}
}
pub fn push(&self, metrics: &'static dyn CollectToRegistry) {
self.inner.lock().unwrap().push(metrics);
}
fn get(&self) -> Vec<&'static dyn CollectToRegistry> {
self.inner.lock().unwrap().clone()
}
}
#[doc(hidden)] pub static METRICS_REGISTRATIONS: MetricsRegistrations = MetricsRegistrations::new();