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;
93mod token_bucket;
94
95/// Optional `tracing` integration helpers (behind the `tracing` Cargo feature).
96#[cfg(feature = "tracing")]
97pub mod tracing_ext;
98
99// Public re-exports — gated to match their feature.
100#[cfg(feature = "sample")]
101pub use adaptive::{
102    AdaptiveSampler, BackpressureController, MetricCircuitBreaker, SamplingStrategy,
103};
104#[cfg(feature = "async")]
105pub use async_support::{AsyncMetricBatch, AsyncMetricsBatcher, AsyncTimerExt, AsyncTimerGuard};
106#[cfg(feature = "count")]
107pub use counter::*;
108#[cfg(feature = "gauge")]
109pub use gauge::{Gauge, GaugeStats};
110#[cfg(feature = "histogram")]
111pub use histogram::{Histogram, HistogramBucket, HistogramSnapshot, DEFAULT_SECONDS_BUCKETS};
112#[cfg(feature = "meter")]
113pub use rate_meter::{RateMeter, RateStats};
114#[cfg(feature = "timer")]
115pub use timer::*;
116
117pub use labels::{Label, LabelSet};
118pub use metadata::{MetricKind, MetricMetadata, Unit};
119pub use registry::*;
120pub use system_health::{
121    HealthConfig, HealthStatus, ProcessStats, Step, SystemHealth, SystemSnapshot,
122};
123pub use token_bucket::TokenBucket;
124
125// Specialised sub-module re-exports.
126#[cfg(feature = "gauge")]
127pub use gauge::specialized as gauge_specialized;
128#[cfg(feature = "meter")]
129pub use rate_meter::specialized as rate_meter_specialized;
130
131/// Global metrics instance - initialize once, use everywhere
132pub static METRICS: OnceLock<MetricsCore> = OnceLock::new();
133
134/// Initialize the global metrics instance
135///
136/// Call this once at the start of your application
137pub fn init() -> &'static MetricsCore {
138    METRICS.get_or_init(MetricsCore::new)
139}
140
141/// Get the global metrics instance
142///
143/// Panics if not initialized - call `init()` first.
144///
145/// Panic conditions:
146/// - If [`init()`] has not been called yet, this function will panic with a clear message.
147///   Prefer passing `&MetricsCore` explicitly in library code to avoid relying on globals.
148pub fn metrics() -> &'static MetricsCore {
149    METRICS
150        .get()
151        .expect("Metrics not initialized - call metrics_lib::init() first")
152}
153
154/// Main metrics interface - the core of everything
155#[repr(align(64))] // Cache line aligned
156pub struct MetricsCore {
157    registry: Registry,
158    system: SystemHealth,
159}
160
161impl MetricsCore {
162    /// Create new metrics core
163    pub fn new() -> Self {
164        Self {
165            registry: Registry::new(),
166            system: SystemHealth::new(),
167        }
168    }
169
170    /// Get or create a counter by name. Requires the `count` feature.
171    ///
172    /// `name` is accepted as `&str` — string literals (`"counter"`) and
173    /// owned/borrowed runtime names both work. The first lookup for a given
174    /// name allocates a `String` key inside the registry; subsequent lookups
175    /// of the same name reuse the cached `Arc` and perform no allocations.
176    #[cfg(feature = "count")]
177    #[inline(always)]
178    pub fn counter(&self, name: &str) -> std::sync::Arc<Counter> {
179        self.registry.get_or_create_counter(name)
180    }
181
182    /// Get or create a labeled counter. Requires the `count` feature.
183    ///
184    /// Routes to the cardinality overflow sink when the cap is full; use
185    /// [`Self::try_counter_with`] to receive an explicit error instead.
186    #[cfg(feature = "count")]
187    #[inline]
188    pub fn counter_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Counter> {
189        self.registry.get_or_create_counter_with(name, labels)
190    }
191
192    /// Labeled counter returning `Err(CardinalityExceeded)` when the cap is
193    /// full. Requires the `count` feature.
194    #[cfg(feature = "count")]
195    #[inline]
196    pub fn try_counter_with(
197        &self,
198        name: &str,
199        labels: &LabelSet,
200    ) -> Result<std::sync::Arc<Counter>> {
201        self.registry.try_get_or_create_counter_with(name, labels)
202    }
203
204    /// Get or create a gauge by name. Requires the `gauge` feature.
205    ///
206    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
207    /// semantics.
208    #[cfg(feature = "gauge")]
209    #[inline(always)]
210    pub fn gauge(&self, name: &str) -> std::sync::Arc<Gauge> {
211        self.registry.get_or_create_gauge(name)
212    }
213
214    /// Get or create a labeled gauge. Requires the `gauge` feature.
215    #[cfg(feature = "gauge")]
216    #[inline]
217    pub fn gauge_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Gauge> {
218        self.registry.get_or_create_gauge_with(name, labels)
219    }
220
221    /// Labeled gauge returning `Err(CardinalityExceeded)` when the cap is
222    /// full. Requires the `gauge` feature.
223    #[cfg(feature = "gauge")]
224    #[inline]
225    pub fn try_gauge_with(&self, name: &str, labels: &LabelSet) -> Result<std::sync::Arc<Gauge>> {
226        self.registry.try_get_or_create_gauge_with(name, labels)
227    }
228
229    /// Get or create a timer by name. Requires the `timer` feature.
230    ///
231    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
232    /// semantics.
233    #[cfg(feature = "timer")]
234    #[inline(always)]
235    pub fn timer(&self, name: &str) -> std::sync::Arc<Timer> {
236        self.registry.get_or_create_timer(name)
237    }
238
239    /// Get or create a labeled timer. Requires the `timer` feature.
240    #[cfg(feature = "timer")]
241    #[inline]
242    pub fn timer_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Timer> {
243        self.registry.get_or_create_timer_with(name, labels)
244    }
245
246    /// Labeled timer returning `Err(CardinalityExceeded)` when the cap is
247    /// full. Requires the `timer` feature.
248    #[cfg(feature = "timer")]
249    #[inline]
250    pub fn try_timer_with(&self, name: &str, labels: &LabelSet) -> Result<std::sync::Arc<Timer>> {
251        self.registry.try_get_or_create_timer_with(name, labels)
252    }
253
254    /// Get or create a rate meter by name. Requires the `meter` feature.
255    ///
256    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
257    /// semantics.
258    #[cfg(feature = "meter")]
259    #[inline(always)]
260    pub fn rate(&self, name: &str) -> std::sync::Arc<RateMeter> {
261        self.registry.get_or_create_rate_meter(name)
262    }
263
264    /// Get or create a labeled rate meter. Requires the `meter` feature.
265    #[cfg(feature = "meter")]
266    #[inline]
267    pub fn rate_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<RateMeter> {
268        self.registry.get_or_create_rate_meter_with(name, labels)
269    }
270
271    /// Labeled rate meter returning `Err(CardinalityExceeded)` when the cap
272    /// is full. Requires the `meter` feature.
273    #[cfg(feature = "meter")]
274    #[inline]
275    pub fn try_rate_with(
276        &self,
277        name: &str,
278        labels: &LabelSet,
279    ) -> Result<std::sync::Arc<RateMeter>> {
280        self.registry
281            .try_get_or_create_rate_meter_with(name, labels)
282    }
283
284    /// Get or create an unlabeled histogram. Requires the `histogram`
285    /// feature.
286    ///
287    /// Uses buckets pre-configured via [`Registry::configure_histogram`] for
288    /// the same name, or the standard Prometheus latency-seconds buckets
289    /// ([`crate::DEFAULT_SECONDS_BUCKETS`]) when none configured.
290    #[cfg(feature = "histogram")]
291    #[inline]
292    pub fn histogram(&self, name: &str) -> std::sync::Arc<Histogram> {
293        self.registry.get_or_create_histogram(name)
294    }
295
296    /// Get or create a labeled histogram. Requires the `histogram` feature.
297    #[cfg(feature = "histogram")]
298    #[inline]
299    pub fn histogram_with(&self, name: &str, labels: &LabelSet) -> std::sync::Arc<Histogram> {
300        self.registry.get_or_create_histogram_with(name, labels)
301    }
302
303    /// Labeled histogram returning `Err(CardinalityExceeded)` when the cap
304    /// is full. Requires the `histogram` feature.
305    #[cfg(feature = "histogram")]
306    #[inline]
307    pub fn try_histogram_with(
308        &self,
309        name: &str,
310        labels: &LabelSet,
311    ) -> Result<std::sync::Arc<Histogram>> {
312        self.registry.try_get_or_create_histogram_with(name, labels)
313    }
314
315    /// Time a synchronous closure and record the elapsed duration.
316    /// Requires the `timer` feature.
317    #[cfg(feature = "timer")]
318    #[inline]
319    pub fn time<T>(&self, name: &str, f: impl FnOnce() -> T) -> T {
320        let binding = self.timer(name);
321        let timer = binding.start();
322        let result = f();
323        timer.stop();
324        result
325    }
326
327    /// Get system health interface
328    #[inline(always)]
329    pub fn system(&self) -> &SystemHealth {
330        &self.system
331    }
332
333    /// Get registry for advanced operations
334    #[inline(always)]
335    pub fn registry(&self) -> &Registry {
336        &self.registry
337    }
338
339    /// Create a [`ScopedRegistry`] over this `MetricsCore`'s [`Registry`].
340    ///
341    /// Shorthand for `self.registry().scoped(prefix)`. Useful for tying a
342    /// metrics namespace to a subsystem. Available since v0.9.5.
343    ///
344    /// # Example
345    ///
346    /// ```
347    /// use metrics_lib::{init, metrics};
348    /// init();
349    /// let http = metrics().scoped("http.");
350    /// # #[cfg(feature = "count")]
351    /// http.counter("requests").inc();
352    /// ```
353    #[inline]
354    pub fn scoped(&self, prefix: impl Into<String>) -> ScopedRegistry<'_> {
355        self.registry.scoped(prefix)
356    }
357}
358
359impl Default for MetricsCore {
360    fn default() -> Self {
361        Self::new()
362    }
363}
364
365/// Common result type for metrics operations
366pub type Result<T> = std::result::Result<T, MetricsError>;
367
368/// Metrics errors
369#[derive(Debug, Clone, PartialEq)]
370pub enum MetricsError {
371    /// Circuit breaker is open and the operation is not allowed.
372    CircuitOpen,
373    /// System is overloaded (e.g., adaptive sampler reduced acceptance) and rejected the operation.
374    Overloaded,
375    /// Invalid metric name (empty, overly long, or otherwise rejected by a policy).
376    InvalidName,
377    /// Invalid value supplied (NaN, non-finite, out-of-range, or otherwise invalid).
378    InvalidValue {
379        /// Short, static explanation of why the value was invalid (e.g., "value is not finite").
380        reason: &'static str,
381    },
382    /// Arithmetic would overflow the counter or index (checked variants only).
383    Overflow,
384    /// Arithmetic would underflow (checked variants only).
385    Underflow,
386    /// Operation would exceed a configured limit (rate limiting, quotas, etc.).
387    OverLimit,
388    /// Operation would block and a non-blocking/try path was requested.
389    WouldBlock,
390    /// Global metrics were not initialized and the operation requires initialization.
391    NotInitialized,
392    /// Registering this `(name, labels)` combination would exceed the
393    /// configured cardinality cap. The `try_*_with` lookup variants return
394    /// this error; the non-`try` variants route to a per-type sink instead
395    /// (see [`Registry::set_cardinality_cap`]).
396    CardinalityExceeded,
397    /// Configuration error with details.
398    Config(String),
399}
400
401impl std::fmt::Display for MetricsError {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        match self {
404            MetricsError::CircuitOpen => write!(f, "Circuit breaker is open"),
405            MetricsError::Overloaded => write!(f, "System is overloaded"),
406            MetricsError::InvalidName => write!(f, "Invalid metric name"),
407            MetricsError::InvalidValue { reason } => write!(f, "Invalid value: {reason}"),
408            MetricsError::Overflow => write!(f, "Operation would overflow"),
409            MetricsError::Underflow => write!(f, "Operation would underflow"),
410            MetricsError::OverLimit => write!(f, "Operation would exceed limit"),
411            MetricsError::WouldBlock => write!(f, "Operation would block"),
412            MetricsError::NotInitialized => write!(f, "Global metrics not initialized"),
413            MetricsError::CardinalityExceeded => {
414                write!(f, "Cardinality cap exceeded for labeled metric")
415            }
416            MetricsError::Config(msg) => write!(f, "Configuration error: {msg}"),
417        }
418    }
419}
420
421impl std::error::Error for MetricsError {}
422
423/// Prelude for convenient glob imports.
424///
425/// Items that require a Cargo feature are only re-exported when that feature is
426/// enabled — they will be absent from the prelude on minimal builds.
427pub mod prelude {
428    #[cfg(feature = "count")]
429    pub use crate::Counter;
430    #[cfg(feature = "gauge")]
431    pub use crate::Gauge;
432    #[cfg(feature = "meter")]
433    pub use crate::RateMeter;
434    #[cfg(feature = "timer")]
435    pub use crate::Timer;
436    pub use crate::{init, metrics, MetricsCore, MetricsError, Result, METRICS};
437    pub use crate::{Registry, SystemHealth};
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_metrics_initialization() {
446        let metrics = MetricsCore::new();
447        // SystemHealth is always available regardless of active features.
448        let _cpu = metrics.system().cpu_used();
449        let _mem = metrics.system().mem_used_mb();
450        #[cfg(feature = "count")]
451        {
452            metrics.counter("test").inc();
453            assert_eq!(metrics.counter("test").get(), 1);
454        }
455        #[cfg(feature = "gauge")]
456        {
457            metrics.gauge("test").set(42.5);
458            assert_eq!(metrics.gauge("test").get(), 42.5);
459        }
460    }
461
462    #[cfg(feature = "count")]
463    #[test]
464    fn test_global_metrics() {
465        let _metrics = init();
466        metrics().counter("global_test").inc();
467        assert_eq!(metrics().counter("global_test").get(), 1);
468    }
469
470    #[cfg(feature = "timer")]
471    #[test]
472    fn test_time_function_records_and_returns() {
473        let metrics = MetricsCore::new();
474        let result = metrics.time("timed_op", || 123usize);
475        assert_eq!(result, 123);
476        assert_eq!(metrics.timer("timed_op").count(), 1);
477    }
478
479    #[cfg(feature = "count")]
480    #[test]
481    fn test_accessors_system_and_registry() {
482        let metrics = MetricsCore::new();
483        let _ = metrics.system().cpu_used();
484        let reg = metrics.registry();
485        let c = reg.get_or_create_counter("from_registry");
486        c.add(2);
487        assert_eq!(metrics.counter("from_registry").get(), 2);
488    }
489
490    #[cfg(feature = "count")]
491    #[test]
492    fn test_default_impl() {
493        let metrics: MetricsCore = Default::default();
494        metrics.counter("default_impl").inc();
495        assert_eq!(metrics.counter("default_impl").get(), 1);
496    }
497
498    #[test]
499    fn test_metrics_error_display() {
500        let e1 = MetricsError::CircuitOpen;
501        let e2 = MetricsError::Overloaded;
502        let e3 = MetricsError::InvalidName;
503        let e4 = MetricsError::Config("bad cfg".to_string());
504        let e5 = MetricsError::CardinalityExceeded;
505        let e6 = MetricsError::Overflow;
506        let e7 = MetricsError::Underflow;
507        let e8 = MetricsError::OverLimit;
508        let e9 = MetricsError::WouldBlock;
509        let e10 = MetricsError::NotInitialized;
510        let e11 = MetricsError::InvalidValue { reason: "x" };
511
512        for (err, needle) in [
513            (e1, "Circuit breaker is open"),
514            (e2, "System is overloaded"),
515            (e3, "Invalid metric name"),
516            (e5, "Cardinality"),
517            (e6, "overflow"),
518            (e7, "underflow"),
519            (e8, "exceed"),
520            (e9, "block"),
521            (e10, "not initialized"),
522            (e11, "Invalid value"),
523        ] {
524            assert!(
525                format!("{err}")
526                    .to_lowercase()
527                    .contains(&needle.to_lowercase()),
528                "err {err:?} should contain {needle}"
529            );
530        }
531        let s4 = format!("{e4}");
532        assert!(s4.contains("Configuration error"));
533        assert!(s4.contains("bad cfg"));
534    }
535
536    // ---------- v0.9.3 MetricsCore labeled-method coverage ----------
537
538    #[test]
539    #[cfg(all(feature = "count", feature = "gauge", feature = "timer"))]
540    fn metricscore_labeled_methods_exercise_all_paths() {
541        let m = MetricsCore::new();
542        let labels = LabelSet::from([("k", "v")]);
543
544        // counter_with + try_counter_with happy paths
545        m.counter_with("c", &labels).inc();
546        assert!(m.try_counter_with("c", &labels).is_ok());
547        // gauge_with + try_gauge_with
548        m.gauge_with("g", &labels).set(2.5);
549        assert!(m.try_gauge_with("g", &labels).is_ok());
550        // timer_with + try_timer_with
551        m.timer_with("t", &labels)
552            .record(std::time::Duration::from_micros(1));
553        assert!(m.try_timer_with("t", &labels).is_ok());
554
555        assert_eq!(m.registry().cardinality_count(), 3);
556    }
557
558    #[test]
559    #[cfg(feature = "meter")]
560    fn metricscore_rate_with_paths() {
561        let m = MetricsCore::new();
562        let labels = LabelSet::from([("tier", "1")]);
563        m.rate_with("qps", &labels).tick();
564        assert!(m.try_rate_with("qps", &labels).is_ok());
565        assert_eq!(m.registry().cardinality_count(), 1);
566    }
567
568    #[test]
569    #[cfg(feature = "histogram")]
570    fn metricscore_histogram_paths() {
571        let m = MetricsCore::new();
572        m.histogram("default_buckets").observe(0.5);
573        let labels = LabelSet::from([("op", "x")]);
574        m.histogram_with("custom", &labels).observe(0.1);
575        assert!(m.try_histogram_with("custom", &labels).is_ok());
576    }
577}