use std::{cell::Cell, marker::PhantomData, ptr::NonNull};
mod cell;
use self::cell::RecorderOnceCell;
mod errors;
pub use self::errors::SetRecorderError;
mod noop;
pub use self::noop::NoopRecorder;
use crate::{Counter, Gauge, Histogram, Key, KeyName, Metadata, SharedString, Unit};
static NOOP_RECORDER: NoopRecorder = NoopRecorder;
static GLOBAL_RECORDER: RecorderOnceCell = RecorderOnceCell::new();
thread_local! {
static LOCAL_RECORDER: Cell<Option<NonNull<dyn Recorder>>> = Cell::new(None);
}
pub trait Recorder {
fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter;
fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge;
fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram;
}
macro_rules! impl_recorder {
($inner_ty:ident, $ptr_ty:ty) => {
impl<$inner_ty> $crate::Recorder for $ptr_ty
where
$inner_ty: $crate::Recorder + ?Sized,
{
fn describe_counter(
&self,
key: $crate::KeyName,
unit: Option<$crate::Unit>,
description: $crate::SharedString,
) {
std::ops::Deref::deref(self).describe_counter(key, unit, description)
}
fn describe_gauge(
&self,
key: $crate::KeyName,
unit: Option<$crate::Unit>,
description: $crate::SharedString,
) {
std::ops::Deref::deref(self).describe_gauge(key, unit, description)
}
fn describe_histogram(
&self,
key: $crate::KeyName,
unit: Option<$crate::Unit>,
description: $crate::SharedString,
) {
std::ops::Deref::deref(self).describe_histogram(key, unit, description)
}
fn register_counter(
&self,
key: &$crate::Key,
metadata: &$crate::Metadata<'_>,
) -> $crate::Counter {
std::ops::Deref::deref(self).register_counter(key, metadata)
}
fn register_gauge(
&self,
key: &$crate::Key,
metadata: &$crate::Metadata<'_>,
) -> $crate::Gauge {
std::ops::Deref::deref(self).register_gauge(key, metadata)
}
fn register_histogram(
&self,
key: &$crate::Key,
metadata: &$crate::Metadata<'_>,
) -> $crate::Histogram {
std::ops::Deref::deref(self).register_histogram(key, metadata)
}
}
};
}
impl_recorder!(T, &T);
impl_recorder!(T, &mut T);
impl_recorder!(T, std::boxed::Box<T>);
impl_recorder!(T, std::sync::Arc<T>);
pub struct LocalRecorderGuard<'a> {
prev_recorder: Option<NonNull<dyn Recorder>>,
phantom: PhantomData<&'a dyn Recorder>,
}
impl<'a> LocalRecorderGuard<'a> {
fn new(recorder: &'a (dyn Recorder + 'a)) -> Self {
let recorder_ptr = unsafe {
std::mem::transmute::<*const (dyn Recorder + 'a), *mut (dyn Recorder + 'static)>(
recorder as &'a (dyn Recorder + 'a),
)
};
let recorder_ptr = unsafe { NonNull::new_unchecked(recorder_ptr) };
let prev_recorder =
LOCAL_RECORDER.with(|local_recorder| local_recorder.replace(Some(recorder_ptr)));
Self { prev_recorder, phantom: PhantomData }
}
}
impl<'a> Drop for LocalRecorderGuard<'a> {
fn drop(&mut self) {
LOCAL_RECORDER.with(|local_recorder| local_recorder.replace(self.prev_recorder.take()));
}
}
pub fn set_global_recorder<R>(recorder: R) -> Result<(), SetRecorderError<R>>
where
R: Recorder + Sync + 'static,
{
GLOBAL_RECORDER.set(recorder)
}
#[must_use]
pub fn set_default_local_recorder(recorder: &dyn Recorder) -> LocalRecorderGuard<'_> {
LocalRecorderGuard::new(recorder)
}
pub fn with_local_recorder<T>(recorder: &dyn Recorder, f: impl FnOnce() -> T) -> T {
let _local = LocalRecorderGuard::new(recorder);
f()
}
pub fn with_recorder<T>(f: impl FnOnce(&dyn Recorder) -> T) -> T {
LOCAL_RECORDER.with(|local_recorder| {
if let Some(recorder) = local_recorder.get() {
unsafe { f(recorder.as_ref()) }
} else if let Some(global_recorder) = GLOBAL_RECORDER.try_load() {
f(global_recorder)
} else {
f(&NOOP_RECORDER)
}
})
}
#[cfg(test)]
mod tests {
use std::sync::{atomic::Ordering, Arc};
use crate::{with_local_recorder, NoopRecorder};
use super::{Recorder, RecorderOnceCell};
#[test]
fn boxed_recorder_dropped_on_existing_set() {
let recorder_cell = RecorderOnceCell::new();
let (first_recorder, _) = test_recorders::TrackOnDropRecorder::new();
let first_set_result = recorder_cell.set(first_recorder);
assert!(first_set_result.is_ok());
let (second_recorder, was_dropped) = test_recorders::TrackOnDropRecorder::new();
assert!(!was_dropped.load(Ordering::SeqCst));
let second_set_result = recorder_cell.set(second_recorder);
assert!(second_set_result.is_err());
assert!(!was_dropped.load(Ordering::SeqCst));
drop(second_set_result);
assert!(was_dropped.load(Ordering::SeqCst));
}
#[test]
fn blanket_implementations() {
fn is_recorder<T: Recorder>(_recorder: T) {}
let mut local = NoopRecorder;
is_recorder(NoopRecorder);
is_recorder(Arc::new(NoopRecorder));
is_recorder(Box::new(NoopRecorder));
is_recorder(&local);
is_recorder(&mut local);
}
#[test]
fn thread_scoped_recorder_guards() {
let t1_recorder = test_recorders::SimpleCounterRecorder::new();
let t2_recorder = test_recorders::SimpleCounterRecorder::new();
let t3_recorder = test_recorders::SimpleCounterRecorder::new();
std::thread::scope(|s| {
s.spawn(|| {
let _guard = crate::set_default_local_recorder(&t1_recorder);
crate::counter!("t1_counter").increment(1);
});
s.spawn(|| {
with_local_recorder(&t2_recorder, || {
crate::counter!("t2_counter").increment(2);
})
});
s.spawn(|| {
let _guard = crate::set_default_local_recorder(&t3_recorder);
crate::counter!("t3_counter").increment(3);
});
});
assert!(t1_recorder.get_value() == 1);
assert!(t2_recorder.get_value() == 2);
assert!(t3_recorder.get_value() == 3);
}
#[test]
fn local_recorder_restored_when_dropped() {
let root_recorder = test_recorders::SimpleCounterRecorder::new();
let _guard = crate::set_default_local_recorder(&root_recorder);
crate::counter!("test_counter").increment(1);
let next_recorder = test_recorders::SimpleCounterRecorder::new();
let next_guard = crate::set_default_local_recorder(&next_recorder);
crate::counter!("test_counter").increment(1);
let final_recorder = test_recorders::SimpleCounterRecorder::new();
crate::with_local_recorder(&final_recorder, || {
crate::counter!("test_counter").increment(10);
});
crate::counter!("test_counter").increment(1);
assert!(next_recorder.get_value() == 2);
drop(next_guard);
crate::counter!("test_counter").increment(20);
assert!(root_recorder.get_value() == 21);
}
mod test_recorders {
use std::sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
};
use crate::Recorder;
#[derive(Debug)]
pub struct TrackOnDropRecorder(Arc<AtomicBool>);
impl TrackOnDropRecorder {
pub fn new() -> (Self, Arc<AtomicBool>) {
let arc = Arc::new(AtomicBool::new(false));
(Self(arc.clone()), arc)
}
}
impl Recorder for TrackOnDropRecorder {
fn describe_counter(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn describe_gauge(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn describe_histogram(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn register_counter(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Counter {
crate::Counter::noop()
}
fn register_gauge(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Gauge {
crate::Gauge::noop()
}
fn register_histogram(
&self,
_: &crate::Key,
_: &crate::Metadata<'_>,
) -> crate::Histogram {
crate::Histogram::noop()
}
}
impl Drop for TrackOnDropRecorder {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
#[derive(Debug)]
pub struct SimpleCounterRecorder {
state: Arc<AtomicU64>,
}
impl SimpleCounterRecorder {
pub fn new() -> Self {
Self { state: Arc::new(AtomicU64::default()) }
}
pub fn get_value(&self) -> u64 {
self.state.load(Ordering::Acquire)
}
}
struct SimpleCounterHandle {
state: Arc<AtomicU64>,
}
impl crate::CounterFn for SimpleCounterHandle {
fn increment(&self, value: u64) {
self.state.fetch_add(value, Ordering::Acquire);
}
fn absolute(&self, _value: u64) {
unimplemented!()
}
}
impl Recorder for SimpleCounterRecorder {
fn describe_counter(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn describe_gauge(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn describe_histogram(
&self,
_: crate::KeyName,
_: Option<crate::Unit>,
_: crate::SharedString,
) {
}
fn register_counter(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Counter {
crate::Counter::from_arc(Arc::new(SimpleCounterHandle {
state: self.state.clone(),
}))
}
fn register_gauge(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Gauge {
crate::Gauge::noop()
}
fn register_histogram(
&self,
_: &crate::Key,
_: &crate::Metadata<'_>,
) -> crate::Histogram {
crate::Histogram::noop()
}
}
}
}