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 = "meter")]
81mod rate_meter;
82#[cfg(feature = "timer")]
83mod timer;
84
85// Always-compiled infrastructure modules.
86mod registry;
87mod system_health;
88
89// Public re-exports — gated to match their feature.
90#[cfg(feature = "sample")]
91pub use adaptive::{
92    AdaptiveSampler, BackpressureController, MetricCircuitBreaker, SamplingStrategy,
93};
94#[cfg(feature = "async")]
95pub use async_support::{AsyncMetricBatch, AsyncMetricsBatcher, AsyncTimerExt, AsyncTimerGuard};
96#[cfg(feature = "count")]
97pub use counter::*;
98#[cfg(feature = "gauge")]
99pub use gauge::{Gauge, GaugeStats};
100#[cfg(feature = "meter")]
101pub use rate_meter::{RateMeter, RateStats};
102#[cfg(feature = "timer")]
103pub use timer::*;
104
105pub use registry::*;
106pub use system_health::*;
107
108// Specialised sub-module re-exports.
109#[cfg(feature = "gauge")]
110pub use gauge::specialized as gauge_specialized;
111#[cfg(feature = "meter")]
112pub use rate_meter::specialized as rate_meter_specialized;
113
114/// Global metrics instance - initialize once, use everywhere
115pub static METRICS: OnceLock<MetricsCore> = OnceLock::new();
116
117/// Initialize the global metrics instance
118///
119/// Call this once at the start of your application
120pub fn init() -> &'static MetricsCore {
121    METRICS.get_or_init(MetricsCore::new)
122}
123
124/// Get the global metrics instance
125///
126/// Panics if not initialized - call `init()` first.
127///
128/// Panic conditions:
129/// - If [`init()`] has not been called yet, this function will panic with a clear message.
130///   Prefer passing `&MetricsCore` explicitly in library code to avoid relying on globals.
131pub fn metrics() -> &'static MetricsCore {
132    METRICS
133        .get()
134        .expect("Metrics not initialized - call metrics_lib::init() first")
135}
136
137/// Main metrics interface - the core of everything
138#[repr(align(64))] // Cache line aligned
139pub struct MetricsCore {
140    registry: Registry,
141    system: SystemHealth,
142}
143
144impl MetricsCore {
145    /// Create new metrics core
146    pub fn new() -> Self {
147        Self {
148            registry: Registry::new(),
149            system: SystemHealth::new(),
150        }
151    }
152
153    /// Get or create a counter by name. Requires the `count` feature.
154    ///
155    /// `name` is accepted as `&str` — string literals (`"counter"`) and
156    /// owned/borrowed runtime names both work. The first lookup for a given
157    /// name allocates a `String` key inside the registry; subsequent lookups
158    /// of the same name reuse the cached `Arc` and perform no allocations.
159    #[cfg(feature = "count")]
160    #[inline(always)]
161    pub fn counter(&self, name: &str) -> std::sync::Arc<Counter> {
162        self.registry.get_or_create_counter(name)
163    }
164
165    /// Get or create a gauge by name. Requires the `gauge` feature.
166    ///
167    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
168    /// semantics.
169    #[cfg(feature = "gauge")]
170    #[inline(always)]
171    pub fn gauge(&self, name: &str) -> std::sync::Arc<Gauge> {
172        self.registry.get_or_create_gauge(name)
173    }
174
175    /// Get or create a timer by name. Requires the `timer` feature.
176    ///
177    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
178    /// semantics.
179    #[cfg(feature = "timer")]
180    #[inline(always)]
181    pub fn timer(&self, name: &str) -> std::sync::Arc<Timer> {
182        self.registry.get_or_create_timer(name)
183    }
184
185    /// Get or create a rate meter by name. Requires the `meter` feature.
186    ///
187    /// `name` is accepted as `&str` — see [`Self::counter`] for allocation
188    /// semantics.
189    #[cfg(feature = "meter")]
190    #[inline(always)]
191    pub fn rate(&self, name: &str) -> std::sync::Arc<RateMeter> {
192        self.registry.get_or_create_rate_meter(name)
193    }
194
195    /// Time a synchronous closure and record the elapsed duration.
196    /// Requires the `timer` feature.
197    #[cfg(feature = "timer")]
198    #[inline]
199    pub fn time<T>(&self, name: &str, f: impl FnOnce() -> T) -> T {
200        let binding = self.timer(name);
201        let timer = binding.start();
202        let result = f();
203        timer.stop();
204        result
205    }
206
207    /// Get system health interface
208    #[inline(always)]
209    pub fn system(&self) -> &SystemHealth {
210        &self.system
211    }
212
213    /// Get registry for advanced operations
214    #[inline(always)]
215    pub fn registry(&self) -> &Registry {
216        &self.registry
217    }
218}
219
220impl Default for MetricsCore {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226/// Common result type for metrics operations
227pub type Result<T> = std::result::Result<T, MetricsError>;
228
229/// Metrics errors
230#[derive(Debug, Clone, PartialEq)]
231pub enum MetricsError {
232    /// Circuit breaker is open and the operation is not allowed.
233    CircuitOpen,
234    /// System is overloaded (e.g., adaptive sampler reduced acceptance) and rejected the operation.
235    Overloaded,
236    /// Invalid metric name (empty, overly long, or otherwise rejected by a policy).
237    InvalidName,
238    /// Invalid value supplied (NaN, non-finite, out-of-range, or otherwise invalid).
239    InvalidValue {
240        /// Short, static explanation of why the value was invalid (e.g., "value is not finite").
241        reason: &'static str,
242    },
243    /// Arithmetic would overflow the counter or index (checked variants only).
244    Overflow,
245    /// Arithmetic would underflow (checked variants only).
246    Underflow,
247    /// Operation would exceed a configured limit (rate limiting, quotas, etc.).
248    OverLimit,
249    /// Operation would block and a non-blocking/try path was requested.
250    WouldBlock,
251    /// Global metrics were not initialized and the operation requires initialization.
252    NotInitialized,
253    /// Configuration error with details.
254    Config(String),
255}
256
257impl std::fmt::Display for MetricsError {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        match self {
260            MetricsError::CircuitOpen => write!(f, "Circuit breaker is open"),
261            MetricsError::Overloaded => write!(f, "System is overloaded"),
262            MetricsError::InvalidName => write!(f, "Invalid metric name"),
263            MetricsError::InvalidValue { reason } => write!(f, "Invalid value: {reason}"),
264            MetricsError::Overflow => write!(f, "Operation would overflow"),
265            MetricsError::Underflow => write!(f, "Operation would underflow"),
266            MetricsError::OverLimit => write!(f, "Operation would exceed limit"),
267            MetricsError::WouldBlock => write!(f, "Operation would block"),
268            MetricsError::NotInitialized => write!(f, "Global metrics not initialized"),
269            MetricsError::Config(msg) => write!(f, "Configuration error: {msg}"),
270        }
271    }
272}
273
274impl std::error::Error for MetricsError {}
275
276/// Prelude for convenient glob imports.
277///
278/// Items that require a Cargo feature are only re-exported when that feature is
279/// enabled — they will be absent from the prelude on minimal builds.
280pub mod prelude {
281    #[cfg(feature = "count")]
282    pub use crate::Counter;
283    #[cfg(feature = "gauge")]
284    pub use crate::Gauge;
285    #[cfg(feature = "meter")]
286    pub use crate::RateMeter;
287    #[cfg(feature = "timer")]
288    pub use crate::Timer;
289    pub use crate::{init, metrics, MetricsCore, MetricsError, Result, METRICS};
290    pub use crate::{Registry, SystemHealth};
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_metrics_initialization() {
299        let metrics = MetricsCore::new();
300        // SystemHealth is always available regardless of active features.
301        let _cpu = metrics.system().cpu_used();
302        let _mem = metrics.system().mem_used_mb();
303        #[cfg(feature = "count")]
304        {
305            metrics.counter("test").inc();
306            assert_eq!(metrics.counter("test").get(), 1);
307        }
308        #[cfg(feature = "gauge")]
309        {
310            metrics.gauge("test").set(42.5);
311            assert_eq!(metrics.gauge("test").get(), 42.5);
312        }
313    }
314
315    #[cfg(feature = "count")]
316    #[test]
317    fn test_global_metrics() {
318        let _metrics = init();
319        metrics().counter("global_test").inc();
320        assert_eq!(metrics().counter("global_test").get(), 1);
321    }
322
323    #[cfg(feature = "timer")]
324    #[test]
325    fn test_time_function_records_and_returns() {
326        let metrics = MetricsCore::new();
327        let result = metrics.time("timed_op", || 123usize);
328        assert_eq!(result, 123);
329        assert_eq!(metrics.timer("timed_op").count(), 1);
330    }
331
332    #[cfg(feature = "count")]
333    #[test]
334    fn test_accessors_system_and_registry() {
335        let metrics = MetricsCore::new();
336        let _ = metrics.system().cpu_used();
337        let reg = metrics.registry();
338        let c = reg.get_or_create_counter("from_registry");
339        c.add(2);
340        assert_eq!(metrics.counter("from_registry").get(), 2);
341    }
342
343    #[cfg(feature = "count")]
344    #[test]
345    fn test_default_impl() {
346        let metrics: MetricsCore = Default::default();
347        metrics.counter("default_impl").inc();
348        assert_eq!(metrics.counter("default_impl").get(), 1);
349    }
350
351    #[test]
352    fn test_metrics_error_display() {
353        let e1 = MetricsError::CircuitOpen;
354        let e2 = MetricsError::Overloaded;
355        let e3 = MetricsError::InvalidName;
356        let e4 = MetricsError::Config("bad cfg".to_string());
357
358        let s1 = format!("{e1}");
359        let s2 = format!("{e2}");
360        let s3 = format!("{e3}");
361        let s4 = format!("{e4}");
362
363        assert!(s1.contains("Circuit breaker is open"));
364        assert!(s2.contains("System is overloaded"));
365        assert!(s3.contains("Invalid metric name"));
366        assert!(s4.contains("Configuration error"));
367        assert!(s4.contains("bad cfg"));
368    }
369}