veecle_telemetry/collector/
global.rs

1//! Global collector state and initialization.
2
3use core::sync::atomic::{AtomicUsize, Ordering};
4
5use core::{error, fmt};
6
7use super::{Collector, Export, InstanceMessage, ProcessId};
8
9/// No-op exporter used when telemetry is disabled or not initialized.
10#[derive(Debug)]
11struct NopExporter;
12
13impl Export for NopExporter {
14    fn export(&self, _: InstanceMessage) {}
15}
16
17static NO_EXPORTER: NopExporter = NopExporter;
18
19static NO_COLLECTOR: Collector = Collector::new(
20    ProcessId::from_raw(0),
21    &NO_EXPORTER,
22    nop_timestamp,
23    nop_thread_id,
24);
25
26/// The `GLOBAL_COLLECTOR` static holds the global collector instance. It is protected by
27/// the `GLOBAL_INIT` static which determines whether `GLOBAL_COLLECTOR` has been initialized.
28static mut GLOBAL_COLLECTOR: Collector = Collector::new(
29    ProcessId::from_raw(0),
30    &NO_EXPORTER,
31    nop_timestamp,
32    nop_thread_id,
33);
34
35fn nop_timestamp() -> u64 {
36    0
37}
38
39fn nop_thread_id() -> core::num::NonZeroU64 {
40    core::num::NonZeroU64::new(1).unwrap()
41}
42
43static GLOBAL_INIT: AtomicUsize = AtomicUsize::new(0);
44
45// There are three different states that we care about:
46// - the collector is uninitialized
47// - the collector is initializing (`set_global` has been called but `GLOBAL_COLLECTOR` hasn't been set yet)
48// - the collector is active
49const UNINITIALIZED: usize = 0;
50const INITIALIZING: usize = 1;
51const INITIALIZED: usize = 2;
52
53/// Set the global collector instance.
54pub(super) fn set_collector(collector: Collector) -> Result<(), SetGlobalError> {
55    if GLOBAL_INIT
56        .compare_exchange(
57            UNINITIALIZED,
58            INITIALIZING,
59            Ordering::Acquire,
60            Ordering::Relaxed,
61        )
62        .is_ok()
63    {
64        // SAFETY: this is guarded by the atomic
65        unsafe { GLOBAL_COLLECTOR = collector }
66        GLOBAL_INIT.store(INITIALIZED, Ordering::Release);
67        Ok(())
68    } else {
69        Err(SetGlobalError(()))
70    }
71}
72
73/// Returns a reference to the collector.
74///
75/// If an exporter has not been set, a no-op implementation is returned.
76pub fn get_collector() -> &'static Collector {
77    #[cfg(not(feature = "enable"))]
78    {
79        &NO_COLLECTOR
80    }
81
82    #[cfg(feature = "enable")]
83    // Acquire memory ordering guarantees that current thread would see any
84    // memory writes that happened before store of the value
85    // into `GLOBAL_INIT` with memory ordering `Release` or stronger.
86    //
87    // Since the value `INITIALIZED` is written only after `GLOBAL_COLLECTOR` was
88    // initialized, observing it after `Acquire` load here makes both
89    // write to the `GLOBAL_COLLECTOR` static and initialization of the exporter
90    // internal state synchronized with current thread.
91    if GLOBAL_INIT.load(Ordering::Acquire) != INITIALIZED {
92        &NO_COLLECTOR
93    } else {
94        // SAFETY: this is guarded by the atomic
95        unsafe {
96            #[expect(clippy::deref_addrof, reason = "false positive")]
97            &*&raw const GLOBAL_COLLECTOR
98        }
99    }
100}
101
102/// The type returned by [`set_global`][super::Builder::set_global] if the collector has already been initialized.
103#[derive(Debug)]
104pub struct SetGlobalError(());
105
106impl SetGlobalError {
107    const MESSAGE: &'static str = "a global exporter has already been set";
108}
109
110impl fmt::Display for SetGlobalError {
111    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
112        fmt.write_str(Self::MESSAGE)
113    }
114}
115
116impl error::Error for SetGlobalError {}