use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use crate::backpressure::{release, try_acquire};
use crate::consent::should_allow;
use crate::health::increment_emitted;
use crate::runtime::get_runtime_config;
use crate::sampling::{should_sample, Signal};
static METRICS_INITIALIZED: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "otel")]
fn maybe_record_counter_add(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
if !crate::otel::metrics::meter_provider_installed() {
return;
}
crate::otel::metrics::record_counter_add(name, value, attributes);
}
#[cfg(feature = "otel")]
fn maybe_record_gauge_set(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
if !crate::otel::metrics::meter_provider_installed() {
return;
}
crate::otel::metrics::record_gauge_set(name, value, attributes);
}
#[cfg(feature = "otel")]
fn maybe_record_histogram(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
if !crate::otel::metrics::meter_provider_installed() {
return;
}
crate::otel::metrics::record_histogram(name, value, attributes);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Meter {
name: String,
}
impl Meter {
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug, Default)]
struct CounterState {
value: f64,
}
#[derive(Clone, Debug)]
pub struct Counter {
name: String,
#[allow(dead_code)]
description: Option<String>,
#[allow(dead_code)]
unit: Option<String>,
state: Arc<Mutex<CounterState>>,
}
impl Counter {
pub fn add(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
if !metrics_enabled() {
return;
}
if !should_allow("metrics", None) {
return;
}
if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
return;
}
let acquired = try_acquire(Signal::Metrics);
if acquired.is_none() {
return;
}
let ticket = acquired.expect("metrics ticket must exist after none guard");
crate::_lock::lock(&self.state).value += value;
#[cfg(feature = "otel")]
{
maybe_record_counter_add(&self.name, value, attributes.as_ref());
}
#[cfg(not(feature = "otel"))]
let _ = &attributes;
increment_emitted(Signal::Metrics, 1);
release(ticket);
}
pub fn value(&self) -> f64 {
crate::_lock::lock(&self.state).value
}
}
#[derive(Clone, Debug, Default)]
struct GaugeState {
last_value: f64,
}
#[derive(Clone, Debug)]
pub struct Gauge {
name: String,
#[allow(dead_code)]
description: Option<String>,
#[allow(dead_code)]
unit: Option<String>,
state: Arc<Mutex<GaugeState>>,
}
impl Gauge {
pub fn add(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
if !metrics_enabled() {
return;
}
if !should_allow("metrics", None) {
return;
}
if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
return;
}
let acquired = try_acquire(Signal::Metrics);
if acquired.is_none() {
return;
}
let ticket = acquired.expect("metrics ticket must exist after none guard");
#[cfg_attr(not(feature = "otel"), allow(unused_variables))]
let new_absolute = {
let mut state = crate::_lock::lock(&self.state);
state.last_value += value;
state.last_value
};
#[cfg(feature = "otel")]
{
maybe_record_gauge_set(&self.name, new_absolute, attributes.as_ref());
}
#[cfg(not(feature = "otel"))]
let _ = &attributes;
increment_emitted(Signal::Metrics, 1);
release(ticket);
}
pub fn set(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
if !metrics_enabled() {
return;
}
if !should_allow("metrics", None) {
return;
}
if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
return;
}
let acquired = try_acquire(Signal::Metrics);
if acquired.is_none() {
return;
}
let ticket = acquired.expect("metrics ticket must exist after none guard");
crate::_lock::lock(&self.state).last_value = value;
#[cfg(feature = "otel")]
{
maybe_record_gauge_set(&self.name, value, attributes.as_ref());
}
#[cfg(not(feature = "otel"))]
let _ = &attributes;
increment_emitted(Signal::Metrics, 1);
release(ticket);
}
pub fn value(&self) -> f64 {
crate::_lock::lock(&self.state).last_value
}
}
#[derive(Clone, Debug, Default)]
struct HistogramState {
count: usize,
total: f64,
}
#[derive(Clone, Debug)]
pub struct Histogram {
name: String,
#[allow(dead_code)]
description: Option<String>,
#[allow(dead_code)]
unit: Option<String>,
state: Arc<Mutex<HistogramState>>,
}
impl Histogram {
pub fn record(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
if !metrics_enabled() {
return;
}
if !should_allow("metrics", None) {
return;
}
if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
return;
}
let acquired = try_acquire(Signal::Metrics);
if acquired.is_none() {
return;
}
let ticket = acquired.expect("metrics ticket must exist after none guard");
let mut state = crate::_lock::lock(&self.state);
state.count += 1;
state.total += value;
drop(state);
#[cfg(feature = "otel")]
{
maybe_record_histogram(&self.name, value, attributes.as_ref());
}
#[cfg(not(feature = "otel"))]
let _ = &attributes;
increment_emitted(Signal::Metrics, 1);
release(ticket);
}
pub fn count(&self) -> usize {
crate::_lock::lock(&self.state).count
}
pub fn total(&self) -> f64 {
crate::_lock::lock(&self.state).total
}
}
fn metrics_enabled() -> bool {
get_runtime_config()
.map(|config| config.metrics.enabled)
.unwrap_or(true)
}
pub fn get_meter(name: Option<&str>) -> Meter {
Meter {
name: name.unwrap_or("provide.telemetry").to_string(),
}
}
pub fn counter(name: &str, description: Option<&str>, unit: Option<&str>) -> Counter {
METRICS_INITIALIZED.store(true, Ordering::SeqCst);
Counter {
name: name.to_string(),
description: description.map(str::to_string),
unit: unit.map(str::to_string),
state: Arc::new(Mutex::new(CounterState::default())),
}
}
pub fn gauge(name: &str, description: Option<&str>, unit: Option<&str>) -> Gauge {
METRICS_INITIALIZED.store(true, Ordering::SeqCst);
Gauge {
name: name.to_string(),
description: description.map(str::to_string),
unit: unit.map(str::to_string),
state: Arc::new(Mutex::new(GaugeState::default())),
}
}
pub fn histogram(name: &str, description: Option<&str>, unit: Option<&str>) -> Histogram {
METRICS_INITIALIZED.store(true, Ordering::SeqCst);
Histogram {
name: name.to_string(),
description: description.map(str::to_string),
unit: unit.map(str::to_string),
state: Arc::new(Mutex::new(HistogramState::default())),
}
}
pub fn metrics_initialized_for_tests() -> bool {
METRICS_INITIALIZED.load(Ordering::SeqCst)
}
pub fn reset_metrics_for_tests() {
METRICS_INITIALIZED.store(false, Ordering::SeqCst);
}
#[cfg(test)]
#[path = "metrics_tests.rs"]
mod tests;