Skip to main content

metrics_lib/
lib.rs

1//! # Ultimate Metrics Library
2//!
3//! The most powerful, lightweight, and efficient metrics library ever built.
4//!
5//! ## Features
6//!
7//! - **Sub-nanosecond operations** - Counter increments in ~2-3ns
8//! - **Lock-free everything** - No locks anywhere in hot paths
9//! - **System health monitoring** - Built-in CPU/memory tracking
10//! - **Dynamic configuration** - Runtime tuning without restarts
11//! - **Circuit breakers** - Fault tolerance with auto-recovery
12//! - **Dead simple API** - `METRICS.counter("requests").inc()`
13//!
14//! ## Quick Start
15//!
16//! ```no_run
17//! use metrics_lib::{init, metrics};
18//!
19//! // Initialize metrics (do this once at startup)
20//! init();
21//!
22//! // Counters (sub-nanosecond)
23//! #[cfg(feature = "count")]
24//! {
25//! metrics().counter("requests").inc();
26//! metrics().counter("errors").add(5);
27//! }
28//!
29//! // Gauges (atomic)  
30//! #[cfg(feature = "gauge")]
31//! {
32//! metrics().gauge("cpu_usage").set(87.3);
33//! metrics().gauge("memory_mb").set(1024.5);
34//! }
35//!
36//! // Timers (high precision)
37//! #[cfg(feature = "timer")]
38//! {
39//! let timer_metric = metrics().timer("api_call");
40//! let timer = timer_metric.start();
41//! // ... do work ...
42//! timer.stop(); // Auto-records
43//!
44//! // Or even simpler
45//! let result = metrics().time("db_query", || {
46//!     // Simulated database query
47//!     "user data"
48//! });
49//! let _ = result;
50//! }
51//!
52//! // System health
53//! let cpu_pct = metrics().system().cpu_used();
54//! let mem_mb = metrics().system().mem_used_mb();
55//! let _ = (cpu_pct, mem_mb);
56//!
57//! // Rate limiting
58//! #[cfg(feature = "meter")]
59//! {
60//! metrics().rate("api_calls").tick();
61//! let rate_per_sec = metrics().rate("api_calls").rate();
62//! let _ = rate_per_sec;
63//! }
64//! ```
65
66#![warn(missing_docs)]
67#![allow(unsafe_code)] // For pin-projection in async support
68
69use std::sync::OnceLock;
70
71// Core metric-type modules — each gated on its own Cargo feature.
72#[cfg(feature = "sample")]
73mod adaptive;
74#[cfg(feature = "async")]
75mod async_support;
76#[cfg(feature = "count")]
77mod counter;
78#[cfg(feature = "gauge")]
79mod gauge;
80#[cfg(feature = "histogram")]
81mod histogram;
82#[cfg(feature = "meter")]
83mod rate_meter;
84#[cfg(feature = "timer")]
85mod timer;
86
87// Always-compiled infrastructure modules.
88pub mod exporters;
89mod labels;
90mod metadata;
91mod registry;
92mod system_health;
93
94// Public re-exports — gated to match their feature.
95#[cfg(feature = "sample")]
96pub use adaptive::{
97    AdaptiveSampler, BackpressureController, MetricCircuitBreaker, SamplingStrategy,
98};
99#[cfg(feature = "async")]
100pub use async_support::{AsyncMetricBatch, AsyncMetricsBatcher, AsyncTimerExt, AsyncTimerGuard};
101#[cfg(feature = "count")]
102pub use counter::*;
103#[cfg(feature = "gauge")]
104pub use gauge::{Gauge, GaugeStats};
105#[cfg(feature = "histogram")]
106pub use histogram::{Histogram, HistogramBucket, HistogramSnapshot, DEFAULT_SECONDS_BUCKETS};
107#[cfg(feature = "meter")]
108pub use rate_meter::{RateMeter, RateStats};
109#[cfg(feature = "timer")]
110pub use timer::*;
111
112pub use labels::{Label, LabelSet};
113pub use metadata::{MetricKind, MetricMetadata, Unit};
114pub use registry::*;
115pub use system_health::*;
116
117// Specialised sub-module re-exports.
118#[cfg(feature = "gauge")]
119pub use gauge::specialized as gauge_specialized;
120#[cfg(feature = "meter")]
121pub use rate_meter::specialized as rate_meter_specialized;
122
123/// Global metrics instance - initialize once, use everywhere
124pub static METRICS: OnceLock<MetricsCore> = OnceLock::new();
125
126/// Initialize the global metrics instance
127///
128/// Call this once at the start of your application
129pub fn init() -> &'static MetricsCore {
130    METRICS.get_or_init(MetricsCore::new)
131}
132
133/// Get the global metrics instance
134///
135/// Panics if not initialized - call `init()` first.
136///
137/// Panic conditions:
138/// - If [`init()`] has not been called yet, this function will panic with a clear message.
139///   Prefer passing `&MetricsCore` explicitly in library code to avoid relying on globals.
140pub fn metrics() -> &'static MetricsCore {
141    METRICS
142        .get()
143        .expect("Metrics not initialized - call metrics_lib::init() first")
144}
145
146/// Main metrics interface - the core of everything
147#[repr(align(64))] // Cache line aligned
148pub struct MetricsCore {
149    registry: Registry,
150    system: SystemHealth,
151}
152
153impl MetricsCore {
154    /// Create new metrics core
155    pub fn new() -> Self {
156        Self {
157            registry: Registry::new(),
158            system: SystemHealth::new(),
159        }
160    }
161
162    /// Get or create a counter by name. Requires the `count` feature.
163    ///
164    /// `name` is accepted as `&str` — string literals (`"counter"`) and
165    /// owned/borrowed runtime names both work. The first lookup for a given
166    /// name allocates a `String` key inside the registry; subsequent lookups
167    /// of the same name reuse the cached `Arc` and perform no allocations.
168    #[cfg(feature = "count")]
169    #[inline(always)]
170    pub fn counter(&self, name: &str) -> std::sync::Arc<Counter> {
171        self.registry.get_or_create_counter(name)
172    }
173
174    /// Get or create a labeled counter. Requires the `count` feature.
175    ///
176    /// Routes to the cardinality overflow sink when the cap is full; use
177    /// [`Self::try_counter_with`] to receive an explicit error instead.
178    #[cfg(feature = "count")]
179    #[inline]
180    pub fn counter_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Counter> {
181        self.registry.get_or_create_counter_with(name, labels)
182    }
183
184    /// Labeled counter returning `Err(CardinalityExceeded)` when the cap is
185    /// full. Requires the `count` feature.
186    #[cfg(feature = "count")]
187    #[inline]
188    pub fn try_counter_with(
189        &self,
190        name: &str,
191        labels: &LabelSet,
192    ) -> Result<std::sync::Arc<Counter>> {
193        self.registry.try_get_or_create_counter_with(name, labels)
194    }
195
196    /// Get or create a gauge by name. Requires the `gauge` feature.
197    ///
198    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
199    /// semantics.
200    #[cfg(feature = "gauge")]
201    #[inline(always)]
202    pub fn gauge(&self, name: &str) -> std::sync::Arc<Gauge> {
203        self.registry.get_or_create_gauge(name)
204    }
205
206    /// Get or create a labeled gauge. Requires the `gauge` feature.
207    #[cfg(feature = "gauge")]
208    #[inline]
209    pub fn gauge_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Gauge> {
210        self.registry.get_or_create_gauge_with(name, labels)
211    }
212
213    /// Labeled gauge returning `Err(CardinalityExceeded)` when the cap is
214    /// full. Requires the `gauge` feature.
215    #[cfg(feature = "gauge")]
216    #[inline]
217    pub fn try_gauge_with(&self, name: &str, labels: &LabelSet) -> Result<std::sync::Arc<Gauge>> {
218        self.registry.try_get_or_create_gauge_with(name, labels)
219    }
220
221    /// Get or create a timer by name. Requires the `timer` feature.
222    ///
223    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
224    /// semantics.
225    #[cfg(feature = "timer")]
226    #[inline(always)]
227    pub fn timer(&self, name: &str) -> std::sync::Arc<Timer> {
228        self.registry.get_or_create_timer(name)
229    }
230
231    /// Get or create a labeled timer. Requires the `timer` feature.
232    #[cfg(feature = "timer")]
233    #[inline]
234    pub fn timer_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Timer> {
235        self.registry.get_or_create_timer_with(name, labels)
236    }
237
238    /// Labeled timer returning `Err(CardinalityExceeded)` when the cap is
239    /// full. Requires the `timer` feature.
240    #[cfg(feature = "timer")]
241    #[inline]
242    pub fn try_timer_with(&self, name: &str, labels: &LabelSet) -> Result<std::sync::Arc<Timer>> {
243        self.registry.try_get_or_create_timer_with(name, labels)
244    }
245
246    /// Get or create a rate meter by name. Requires the `meter` feature.
247    ///
248    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
249    /// semantics.
250    #[cfg(feature = "meter")]
251    #[inline(always)]
252    pub fn rate(&self, name: &str) -> std::sync::Arc<RateMeter> {
253        self.registry.get_or_create_rate_meter(name)
254    }
255
256    /// Get or create a labeled rate meter. Requires the `meter` feature.
257    #[cfg(feature = "meter")]
258    #[inline]
259    pub fn rate_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<RateMeter> {
260        self.registry.get_or_create_rate_meter_with(name, labels)
261    }
262
263    /// Labeled rate meter returning `Err(CardinalityExceeded)` when the cap
264    /// is full. Requires the `meter` feature.
265    #[cfg(feature = "meter")]
266    #[inline]
267    pub fn try_rate_with(
268        &self,
269        name: &str,
270        labels: &LabelSet,
271    ) -> Result<std::sync::Arc<RateMeter>> {
272        self.registry
273            .try_get_or_create_rate_meter_with(name, labels)
274    }
275
276    /// Get or create an unlabeled histogram. Requires the `histogram`
277    /// feature.
278    ///
279    /// Uses buckets pre-configured via [`Registry::configure_histogram`] for
280    /// the same name, or the standard Prometheus latency-seconds buckets
281    /// ([`crate::DEFAULT_SECONDS_BUCKETS`]) when none configured.
282    #[cfg(feature = "histogram")]
283    #[inline]
284    pub fn histogram(&self, name: &str) -> std::sync::Arc<Histogram> {
285        self.registry.get_or_create_histogram(name)
286    }
287
288    /// Get or create a labeled histogram. Requires the `histogram` feature.
289    #[cfg(feature = "histogram")]
290    #[inline]
291    pub fn histogram_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Histogram> {
292        self.registry.get_or_create_histogram_with(name, labels)
293    }
294
295    /// Labeled histogram returning `Err(CardinalityExceeded)` when the cap
296    /// is full. Requires the `histogram` feature.
297    #[cfg(feature = "histogram")]
298    #[inline]
299    pub fn try_histogram_with(
300        &self,
301        name: &str,
302        labels: &LabelSet,
303    ) -> Result<std::sync::Arc<Histogram>> {
304        self.registry.try_get_or_create_histogram_with(name, labels)
305    }
306
307    /// Time a synchronous closure and record the elapsed duration.
308    /// Requires the `timer` feature.
309    #[cfg(feature = "timer")]
310    #[inline]
311    pub fn time<T>(&self, name: &str, f: impl FnOnce() -> T) -> T {
312        let binding = self.timer(name);
313        let timer = binding.start();
314        let result = f();
315        timer.stop();
316        result
317    }
318
319    /// Get system health interface
320    #[inline(always)]
321    pub fn system(&self) -> &SystemHealth {
322        &self.system
323    }
324
325    /// Get registry for advanced operations
326    #[inline(always)]
327    pub fn registry(&self) -> &Registry {
328        &self.registry
329    }
330}
331
332impl Default for MetricsCore {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338/// Common result type for metrics operations
339pub type Result<T> = std::result::Result<T, MetricsError>;
340
341/// Metrics errors
342#[derive(Debug, Clone, PartialEq)]
343pub enum MetricsError {
344    /// Circuit breaker is open and the operation is not allowed.
345    CircuitOpen,
346    /// System is overloaded (e.g., adaptive sampler reduced acceptance) and rejected the operation.
347    Overloaded,
348    /// Invalid metric name (empty, overly long, or otherwise rejected by a policy).
349    InvalidName,
350    /// Invalid value supplied (NaN, non-finite, out-of-range, or otherwise invalid).
351    InvalidValue {
352        /// Short, static explanation of why the value was invalid (e.g., "value is not finite").
353        reason: &'static str,
354    },
355    /// Arithmetic would overflow the counter or index (checked variants only).
356    Overflow,
357    /// Arithmetic would underflow (checked variants only).
358    Underflow,
359    /// Operation would exceed a configured limit (rate limiting, quotas, etc.).
360    OverLimit,
361    /// Operation would block and a non-blocking/try path was requested.
362    WouldBlock,
363    /// Global metrics were not initialized and the operation requires initialization.
364    NotInitialized,
365    /// Registering this `(name, labels)` combination would exceed the
366    /// configured cardinality cap. The `try_*_with` lookup variants return
367    /// this error; the non-`try` variants route to a per-type sink instead
368    /// (see [`Registry::set_cardinality_cap`]).
369    CardinalityExceeded,
370    /// Configuration error with details.
371    Config(String),
372}
373
374impl std::fmt::Display for MetricsError {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        match self {
377            MetricsError::CircuitOpen => write!(f, "Circuit breaker is open"),
378            MetricsError::Overloaded => write!(f, "System is overloaded"),
379            MetricsError::InvalidName => write!(f, "Invalid metric name"),
380            MetricsError::InvalidValue { reason } => write!(f, "Invalid value: {reason}"),
381            MetricsError::Overflow => write!(f, "Operation would overflow"),
382            MetricsError::Underflow => write!(f, "Operation would underflow"),
383            MetricsError::OverLimit => write!(f, "Operation would exceed limit"),
384            MetricsError::WouldBlock => write!(f, "Operation would block"),
385            MetricsError::NotInitialized => write!(f, "Global metrics not initialized"),
386            MetricsError::CardinalityExceeded => {
387                write!(f, "Cardinality cap exceeded for labeled metric")
388            }
389            MetricsError::Config(msg) => write!(f, "Configuration error: {msg}"),
390        }
391    }
392}
393
394impl std::error::Error for MetricsError {}
395
396/// Prelude for convenient glob imports.
397///
398/// Items that require a Cargo feature are only re-exported when that feature is
399/// enabled — they will be absent from the prelude on minimal builds.
400pub mod prelude {
401    #[cfg(feature = "count")]
402    pub use crate::Counter;
403    #[cfg(feature = "gauge")]
404    pub use crate::Gauge;
405    #[cfg(feature = "meter")]
406    pub use crate::RateMeter;
407    #[cfg(feature = "timer")]
408    pub use crate::Timer;
409    pub use crate::{init, metrics, MetricsCore, MetricsError, Result, METRICS};
410    pub use crate::{Registry, SystemHealth};
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416
417    #[test]
418    fn test_metrics_initialization() {
419        let metrics = MetricsCore::new();
420        // SystemHealth is always available regardless of active features.
421        let _cpu = metrics.system().cpu_used();
422        let _mem = metrics.system().mem_used_mb();
423        #[cfg(feature = "count")]
424        {
425            metrics.counter("test").inc();
426            assert_eq!(metrics.counter("test").get(), 1);
427        }
428        #[cfg(feature = "gauge")]
429        {
430            metrics.gauge("test").set(42.5);
431            assert_eq!(metrics.gauge("test").get(), 42.5);
432        }
433    }
434
435    #[cfg(feature = "count")]
436    #[test]
437    fn test_global_metrics() {
438        let _metrics = init();
439        metrics().counter("global_test").inc();
440        assert_eq!(metrics().counter("global_test").get(), 1);
441    }
442
443    #[cfg(feature = "timer")]
444    #[test]
445    fn test_time_function_records_and_returns() {
446        let metrics = MetricsCore::new();
447        let result = metrics.time("timed_op", || 123usize);
448        assert_eq!(result, 123);
449        assert_eq!(metrics.timer("timed_op").count(), 1);
450    }
451
452    #[cfg(feature = "count")]
453    #[test]
454    fn test_accessors_system_and_registry() {
455        let metrics = MetricsCore::new();
456        let _ = metrics.system().cpu_used();
457        let reg = metrics.registry();
458        let c = reg.get_or_create_counter("from_registry");
459        c.add(2);
460        assert_eq!(metrics.counter("from_registry").get(), 2);
461    }
462
463    #[cfg(feature = "count")]
464    #[test]
465    fn test_default_impl() {
466        let metrics: MetricsCore = Default::default();
467        metrics.counter("default_impl").inc();
468        assert_eq!(metrics.counter("default_impl").get(), 1);
469    }
470
471    #[test]
472    fn test_metrics_error_display() {
473        let e1 = MetricsError::CircuitOpen;
474        let e2 = MetricsError::Overloaded;
475        let e3 = MetricsError::InvalidName;
476        let e4 = MetricsError::Config("bad cfg".to_string());
477        let e5 = MetricsError::CardinalityExceeded;
478        let e6 = MetricsError::Overflow;
479        let e7 = MetricsError::Underflow;
480        let e8 = MetricsError::OverLimit;
481        let e9 = MetricsError::WouldBlock;
482        let e10 = MetricsError::NotInitialized;
483        let e11 = MetricsError::InvalidValue { reason: "x" };
484
485        for (err, needle) in [
486            (e1, "Circuit breaker is open"),
487            (e2, "System is overloaded"),
488            (e3, "Invalid metric name"),
489            (e5, "Cardinality"),
490            (e6, "overflow"),
491            (e7, "underflow"),
492            (e8, "exceed"),
493            (e9, "block"),
494            (e10, "not initialized"),
495            (e11, "Invalid value"),
496        ] {
497            assert!(
498                format!("{err}")
499                    .to_lowercase()
500                    .contains(&needle.to_lowercase()),
501                "err {err:?} should contain {needle}"
502            );
503        }
504        let s4 = format!("{e4}");
505        assert!(s4.contains("Configuration error"));
506        assert!(s4.contains("bad cfg"));
507    }
508
509    // ---------- v0.9.3 MetricsCore labeled-method coverage ----------
510
511    #[test]
512    #[cfg(all(feature = "count", feature = "gauge", feature = "timer"))]
513    fn metricscore_labeled_methods_exercise_all_paths() {
514        let m = MetricsCore::new();
515        let labels = LabelSet::from([("k", "v")]);
516
517        // counter_with + try_counter_with happy paths
518        m.counter_with("c", &labels).inc();
519        assert!(m.try_counter_with("c", &labels).is_ok());
520        // gauge_with + try_gauge_with
521        m.gauge_with("g", &labels).set(2.5);
522        assert!(m.try_gauge_with("g", &labels).is_ok());
523        // timer_with + try_timer_with
524        m.timer_with("t", &labels)
525            .record(std::time::Duration::from_micros(1));
526        assert!(m.try_timer_with("t", &labels).is_ok());
527
528        assert_eq!(m.registry().cardinality_count(), 3);
529    }
530
531    #[test]
532    #[cfg(feature = "meter")]
533    fn metricscore_rate_with_paths() {
534        let m = MetricsCore::new();
535        let labels = LabelSet::from([("tier", "1")]);
536        m.rate_with("qps", &labels).tick();
537        assert!(m.try_rate_with("qps", &labels).is_ok());
538        assert_eq!(m.registry().cardinality_count(), 1);
539    }
540
541    #[test]
542    #[cfg(feature = "histogram")]
543    fn metricscore_histogram_paths() {
544        let m = MetricsCore::new();
545        m.histogram("default_buckets").observe(0.5);
546        let labels = LabelSet::from([("op", "x")]);
547        m.histogram_with("custom", &labels).observe(0.1);
548        assert!(m.try_histogram_with("custom", &labels).is_ok());
549    }
550}