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;
93mod token_bucket;
94
95#[cfg(feature = "tracing")]
97pub mod tracing_ext;
98
99#[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#[cfg(feature = "gauge")]
127pub use gauge::specialized as gauge_specialized;
128#[cfg(feature = "meter")]
129pub use rate_meter::specialized as rate_meter_specialized;
130
131pub static METRICS: OnceLock<MetricsCore> = OnceLock::new();
133
134pub fn init() -> &'static MetricsCore {
138 METRICS.get_or_init(MetricsCore::new)
139}
140
141pub fn metrics() -> &'static MetricsCore {
149 METRICS
150 .get()
151 .expect("Metrics not initialized - call metrics_lib::init() first")
152}
153
154#[repr(align(64))] pub struct MetricsCore {
157 registry: Registry,
158 system: SystemHealth,
159}
160
161impl MetricsCore {
162 pub fn new() -> Self {
164 Self {
165 registry: Registry::new(),
166 system: SystemHealth::new(),
167 }
168 }
169
170 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline(always)]
329 pub fn system(&self) -> &SystemHealth {
330 &self.system
331 }
332
333 #[inline(always)]
335 pub fn registry(&self) -> &Registry {
336 &self.registry
337 }
338
339 #[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
365pub type Result<T> = std::result::Result<T, MetricsError>;
367
368#[derive(Debug, Clone, PartialEq)]
370pub enum MetricsError {
371 CircuitOpen,
373 Overloaded,
375 InvalidName,
377 InvalidValue {
379 reason: &'static str,
381 },
382 Overflow,
384 Underflow,
386 OverLimit,
388 WouldBlock,
390 NotInitialized,
392 CardinalityExceeded,
397 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
423pub 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 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 #[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 m.counter_with("c", &labels).inc();
546 assert!(m.try_counter_with("c", &labels).is_ok());
547 m.gauge_with("g", &labels).set(2.5);
549 assert!(m.try_gauge_with("g", &labels).is_ok());
550 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}