metriken-core 0.3.0

A fast and lightweight metrics library
Documentation
//! Easily registered distributed metrics.
//!
//! You should usually be using the [`metriken`] crate instead. This crate
//! contains the core distributed slice used by [`metriken`] so that multiple
//! major versions of [`metriken`] can coexist.
//!
//! [`metriken`]: https://docs.rs/metriken

use std::any::Any;
use std::borrow::Cow;

/// A helper macro for marking imports as being used.
///
/// This is meant to be used for when a reference is made to an item from a doc
/// comment but that item isn't actually used for code anywhere.
macro_rules! used_in_docs {
    ($($item:ident),* $(,)?) => {
        const _: () = {
            #[allow(unused_imports)]
            mod _docs {
                $( use super::$item; )*
            }
        };
    };
}

pub mod dynmetrics;
mod formatter;
mod metadata;
mod metrics;
mod null;
mod provide;
mod traits;
mod window;
mod wrapper;

pub use crate::formatter::{default_formatter, Format};
pub use crate::metadata::{Metadata, MetadataIter};
pub use crate::metrics::{metrics, DynMetricsIter, Metrics, MetricsIter};
pub use crate::provide::{request_ref, request_value, Request};
pub use crate::traits::{
    CounterGroupMetric, GaugeGroupMetric, HistogramGroupMetric, HistogramMetric,
};
pub use crate::window::Window;

/// Global interface to a metric.
///
/// Most use of metrics should use the directly declared constants.
pub trait Metric: Send + Sync + 'static {
    /// Indicate whether this metric has been set up.
    ///
    /// Generally, if this returns `false` then the other methods on this
    /// trait should return `None`.
    fn is_enabled(&self) -> bool {
        self.as_any().is_some()
    }

    /// Get the current metric as an [`Any`] instance. This is meant to allow
    /// custom processing for known metric types.
    ///
    /// [`Any`]: std::any::Any
    fn as_any(&self) -> Option<&dyn Any>;

    /// Get the value of the current metric, should it be enabled.
    ///
    /// # Note to Implementors
    /// If your metric's value does not correspond to one of the variants of
    /// [`Value`] then return [`Value::Other`] and metric consumers can use
    /// [`as_any`](crate::Metric::as_any) to specifically handle your metric.
    fn value(&self) -> Option<Value<'_>>;

    /// Get this metric's acquisition window, if one has been recorded.
    ///
    /// The acquisition window is the interval over which the metric's value
    /// was read. Default: `None` — most metrics do not record a window. The
    /// windowed scalar wrappers (`WindowedLazyCounter`, `WindowedLazyGauge`)
    /// and the base `RwLockHistogram` override this to return the window
    /// recorded by `set_with_window`.
    fn load_window(&self) -> Option<Window> {
        None
    }

    /// Get this metric's value and its acquisition window as a torn-safe pair.
    ///
    /// Consumers that need a self-consistent `(value, window)` pair (such as
    /// exposition) must call this instead of pairing separate `value()` and
    /// `load_window()` reads, which can tear under a concurrent
    /// `set_with_window`. Default: `(self.value(), None)`. The windowed scalar
    /// wrappers (`WindowedLazyCounter`, `WindowedLazyGauge`) and the base
    /// `RwLockHistogram` override this to read both the value and the window
    /// under a single acquisition of their window lock, so the pair is never
    /// torn.
    fn value_with_window(&self) -> (Option<Value<'_>>, Option<Window>) {
        (self.value(), None)
    }

    /// Provides type based access to context.
    ///
    /// This can be used in conjunction with [`Request::provide_value`] and
    /// [`Request::provide_ref`] to extract references to member variables from
    /// `dyn Metric` trait objects.
    ///
    /// If you want to read provided types from a metric see
    /// [`MetricEntry::request_value`] and [`MetricEntry::request_ref`].
    fn provide<'a>(&'a self, request: &mut Request<'a>) {
        // Silence the unused variable warning.
        let _ = request;
    }
}

/// The value of a metric.
///
/// See [`Metric::value`].
#[non_exhaustive]
pub enum Value<'a> {
    /// A counter value.
    Counter(u64),

    /// A gauge value.
    Gauge(i64),

    /// A histogram metric that can produce snapshots.
    Histogram(&'a dyn HistogramMetric),

    /// A group of counter metrics with per-entry metadata.
    CounterGroup(&'a dyn CounterGroupMetric),

    /// A group of gauge metrics with per-entry metadata.
    GaugeGroup(&'a dyn GaugeGroupMetric),

    /// A group of histogram metrics with per-entry metadata.
    HistogramGroup(&'a dyn HistogramGroupMetric),

    /// The value of the metric could not be represented using the other `Value`
    /// variants.
    ///
    /// Use [`Metric::as_any`] to specifically handle the type of this metric.
    Other(&'a dyn Any),
}

/// A statically declared metric entry.
pub struct MetricEntry {
    metric: *const dyn Metric,
    name: Cow<'static, str>,
    description: Option<Cow<'static, str>>,
    module: Cow<'static, str>,
}

impl MetricEntry {
    /// Get a reference to the metric that this entry corresponds to.
    pub fn metric(&self) -> &dyn Metric {
        unsafe { &*self.metric }
    }

    /// Get the name of this metric.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the module path where this metric was defined (`module_path!()` at
    /// the `#[metric]` definition site).
    pub fn module(&self) -> &str {
        &self.module
    }

    /// Get the description of this metric.
    pub fn description(&self) -> Option<&str> {
        self.description.as_deref()
    }

    /// Access the [`Metadata`] associated with this metrics entry.
    pub fn metadata(&self) -> &Metadata {
        static EMPTY: Metadata = Metadata::default_const();
        self.request_ref::<Metadata>().unwrap_or(&EMPTY)
    }

    /// Format the metric into a string with the given format.
    pub fn formatted(&self, format: Format) -> String {
        let formatter = self
            .request_value::<crate::wrapper::FormattingFn>()
            .map(|func| func.0)
            .unwrap_or(crate::default_formatter);

        formatter(self, format)
    }

    /// Checks whether `metric` is the metric for this entry.
    ///
    /// This checks both the type id and the address. Note that it may have
    /// false positives if `metric` is a ZST since multiple ZSTs may share
    /// the same address.
    pub fn is(&self, metric: &dyn Metric) -> bool {
        if self.metric().type_id() != metric.type_id() {
            return false;
        }

        let a = self.metric() as *const _ as *const ();
        let b = metric as *const _ as *const ();
        a == b
    }

    /// Request a value of type `T` from the metric.
    ///
    /// This will succeed if the metric's [`provide`] implementation called
    /// [`Request::provide_value`] with a value of type `T`.
    ///
    /// [`provide`]: Metric::provide
    pub fn request_value<T>(&self) -> Option<T>
    where
        T: 'static,
    {
        crate::request_value(self.metric())
    }

    /// Request a reference of type `T` from the metric.
    ///
    /// This will succeed if the metric's [`provide`] implementation called
    /// [`Request::provide_ref`] with a value of type `T`.
    ///
    /// [`provide`]: Metric::provide
    pub fn request_ref<T>(&self) -> Option<&T>
    where
        T: ?Sized + 'static,
    {
        crate::request_ref(self.metric())
    }
}

unsafe impl Send for MetricEntry {}
unsafe impl Sync for MetricEntry {}

impl std::ops::Deref for MetricEntry {
    type Target = dyn Metric;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.metric()
    }
}

impl std::fmt::Debug for MetricEntry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MetricEntry")
            .field("name", &self.name())
            .field("metric", &"<dyn Metric>")
            .finish()
    }
}

/// Implementation detail exports for use by the `#[metric]`
#[doc(hidden)]
pub mod export {
    use crate::{Metadata, Metric};

    pub extern crate linkme;
    pub extern crate phf;

    pub use crate::wrapper::*;

    #[linkme::distributed_slice]
    pub static METRICS: [crate::MetricEntry] = [..];

    pub const fn entry_v1(
        metric: &'static dyn Metric,
        name: &'static str,
        description: Option<&'static str>,
        module: &'static str,
    ) -> crate::MetricEntry {
        use std::borrow::Cow;

        crate::MetricEntry {
            metric,
            name: Cow::Borrowed(name),
            description: match description {
                Some(desc) => Some(Cow::Borrowed(desc)),
                None => None,
            },
            module: Cow::Borrowed(module),
        }
    }

    pub const fn metadata(metadata: &'static phf::Map<&'static str, &'static str>) -> Metadata {
        Metadata::new_static(metadata)
    }
}

/// Declare a new metric.
#[macro_export]
macro_rules! declare_metric_v1 {
    {
        metric: $metric:expr,
        name: $name:expr,
        description: $description:expr,
        module: $module:expr,
        metadata: { $( $key:expr => $value:expr ),* $(,)? },
        formatter: $formatter:expr $(,)?
    } => {
        const _: () = {
            use $crate::export::phf;

            static __METADATA_MAP: $crate::export::phf::Map<&'static str, &'static str> =
                $crate::export::phf::phf_map! { $( $key => $value, )* };
            static __METADATA: $crate::Metadata = $crate::export::metadata(&__METADATA_MAP);

            // We use this to inject some provided values into metric itself
            // without having to use up extra memory storing anything.
            struct MetricProvider;

            impl $crate::export::InjectedProvider for MetricProvider {
                fn provide(request: &mut $crate::Request<'_>) {
                    request
                        .provide_ref(&__METADATA)
                        .provide_value($crate::export::FormattingFn($formatter));
                }
            }

            #[$crate::export::linkme::distributed_slice($crate::export::METRICS)]
            #[linkme(crate = $crate::export::linkme)]
            static __ENTRY: $crate::MetricEntry = $crate::export::entry_v1(
                $crate::export::MetricWrapper::<_, MetricProvider>::from_ref(&$metric),
                $name,
                $description,
                $module,
            );
        };
    }
}