1#![warn(missing_docs)]
67#![allow(unsafe_code)] use std::sync::OnceLock;
70
71#[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
87pub mod exporters;
89mod labels;
90mod metadata;
91mod registry;
92mod system_health;
93
94#[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#[cfg(feature = "gauge")]
119pub use gauge::specialized as gauge_specialized;
120#[cfg(feature = "meter")]
121pub use rate_meter::specialized as rate_meter_specialized;
122
123pub static METRICS: OnceLock<MetricsCore> = OnceLock::new();
125
126pub fn init() -> &'static MetricsCore {
130 METRICS.get_or_init(MetricsCore::new)
131}
132
133pub fn metrics() -> &'static MetricsCore {
141 METRICS
142 .get()
143 .expect("Metrics not initialized - call metrics_lib::init() first")
144}
145
146#[repr(align(64))] pub struct MetricsCore {
149 registry: Registry,
150 system: SystemHealth,
151}
152
153impl MetricsCore {
154 pub fn new() -> Self {
156 Self {
157 registry: Registry::new(),
158 system: SystemHealth::new(),
159 }
160 }
161
162 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline(always)]
321 pub fn system(&self) -> &SystemHealth {
322 &self.system
323 }
324
325 #[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
338pub type Result<T> = std::result::Result<T, MetricsError>;
340
341#[derive(Debug, Clone, PartialEq)]
343pub enum MetricsError {
344 CircuitOpen,
346 Overloaded,
348 InvalidName,
350 InvalidValue {
352 reason: &'static str,
354 },
355 Overflow,
357 Underflow,
359 OverLimit,
361 WouldBlock,
363 NotInitialized,
365 CardinalityExceeded,
370 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
396pub 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 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 #[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 m.counter_with("c", &labels).inc();
519 assert!(m.try_counter_with("c", &labels).is_ok());
520 m.gauge_with("g", &labels).set(2.5);
522 assert!(m.try_gauge_with("g", &labels).is_ok());
523 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}