use crate::{GaugeValue, Key, Unit};
use core::fmt;
use core::sync::atomic::{AtomicUsize, Ordering};
static mut RECORDER: &'static dyn Recorder = &NoopRecorder;
static STATE: AtomicUsize = AtomicUsize::new(0);
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
static SET_RECORDER_ERROR: &str =
"attempted to set a recorder after the metrics system was already initialized";
pub trait Recorder {
fn register_counter(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>);
fn register_gauge(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>);
fn register_histogram(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>);
fn increment_counter(&self, key: &Key, value: u64);
fn update_gauge(&self, key: &Key, value: GaugeValue);
fn record_histogram(&self, key: &Key, value: f64);
}
pub struct NoopRecorder;
impl Recorder for NoopRecorder {
fn register_counter(
&self,
_key: &Key,
_unit: Option<Unit>,
_description: Option<&'static str>,
) {
}
fn register_gauge(&self, _key: &Key, _unit: Option<Unit>, _description: Option<&'static str>) {}
fn register_histogram(
&self,
_key: &Key,
_unit: Option<Unit>,
_description: Option<&'static str>,
) {
}
fn increment_counter(&self, _key: &Key, _value: u64) {}
fn update_gauge(&self, _key: &Key, _value: GaugeValue) {}
fn record_histogram(&self, _key: &Key, _value: f64) {}
}
#[cfg(atomic_cas)]
pub fn set_recorder(recorder: &'static dyn Recorder) -> Result<(), SetRecorderError> {
set_recorder_inner(|| recorder)
}
#[cfg(all(feature = "std", atomic_cas))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn set_boxed_recorder(recorder: Box<dyn Recorder>) -> Result<(), SetRecorderError> {
set_recorder_inner(|| unsafe { &*Box::into_raw(recorder) })
}
#[cfg(atomic_cas)]
fn set_recorder_inner<F>(make_recorder: F) -> Result<(), SetRecorderError>
where
F: FnOnce() -> &'static dyn Recorder,
{
unsafe {
match STATE.compare_exchange(
UNINITIALIZED,
INITIALIZING,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(UNINITIALIZED) => {
RECORDER = make_recorder();
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
Err(INITIALIZING) => {
while STATE.load(Ordering::SeqCst) == INITIALIZING {}
Err(SetRecorderError(()))
}
_ => Err(SetRecorderError(())),
}
}
}
pub unsafe fn set_recorder_racy(recorder: &'static dyn Recorder) -> Result<(), SetRecorderError> {
match STATE.load(Ordering::SeqCst) {
UNINITIALIZED => {
RECORDER = recorder;
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
unreachable!("set_recorder_racy must not be used with other initialization functions")
}
_ => Err(SetRecorderError(())),
}
}
#[doc(hidden)]
pub fn clear_recorder() {
STATE.store(UNINITIALIZED, Ordering::SeqCst);
}
#[derive(Debug)]
pub struct SetRecorderError(());
impl fmt::Display for SetRecorderError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(SET_RECORDER_ERROR)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SetRecorderError {
fn description(&self) -> &str {
SET_RECORDER_ERROR
}
}
pub fn recorder() -> &'static dyn Recorder {
static NOOP: NoopRecorder = NoopRecorder;
try_recorder().unwrap_or(&NOOP)
}
pub fn try_recorder() -> Option<&'static dyn Recorder> {
unsafe {
if STATE.load(Ordering::Relaxed) != INITIALIZED {
None
} else {
Some(RECORDER)
}
}
}