veecle_telemetry/collector/
builder.rs

1use super::global::SetGlobalError;
2use super::{Collector, Export, ProcessId};
3
4use veecle_osal_api::thread::ThreadAbstraction;
5use veecle_osal_api::time::{Instant, SystemTime, SystemTimeError, TimeAbstraction};
6
7/// Returns [`<T as TimeAbstraction>::now`][TimeAbstraction::now] converted into nanoseconds since
8/// [`Instant::MIN`].
9fn timestamp_fn_monotonic<T>() -> u64
10where
11    T: TimeAbstraction,
12{
13    let timestamp_micros: u64 = T::now()
14        .duration_since(Instant::MIN)
15        .expect("now should be later than MIN")
16        .as_micros();
17
18    timestamp_micros * 1000
19}
20
21/// Returns [`<T as SystemTime>::duration_since_epoch`][SystemTime::duration_since_epoch] converted
22/// into nanoseconds, falling back to [`timestamp_fn_monotonic`] if the system time is not
23/// synchronized.
24fn timestamp_fn_system_time<T>() -> u64
25where
26    T: TimeAbstraction + SystemTime,
27{
28    match T::duration_since_epoch() {
29        Ok(duration) => duration.as_micros() * 1000,
30        Err(SystemTimeError::Unsynchronized) => {
31            // Fall back to monotonic time if not synchronized.
32            timestamp_fn_monotonic::<T>()
33        }
34        Err(SystemTimeError::EpochIsLaterThanStartTime) => {
35            panic!(
36                "Failed to get duration since epoch: {:?}",
37                SystemTimeError::EpochIsLaterThanStartTime
38            );
39        }
40    }
41}
42
43/// Type-state markers for builder
44mod state {
45    #[derive(Debug)]
46    pub struct NoProcessId;
47    #[derive(Debug)]
48    pub struct WithProcessId;
49    #[derive(Debug)]
50    pub struct NoExporter;
51    #[derive(Debug)]
52    pub struct WithExporter;
53    #[derive(Debug)]
54    pub struct NoTime;
55    #[derive(Debug)]
56    pub struct WithTime;
57    #[derive(Debug)]
58    pub struct NoThread;
59    #[derive(Debug)]
60    pub struct WithThread;
61}
62
63/// Builder for initializing the telemetry collector.
64///
65/// Uses type-state pattern to ensure all required components are configured at compile time.
66/// Created via [`build()`] and finalized with [`set_global()`](Builder::set_global).
67#[derive(Debug)]
68#[must_use]
69pub struct Builder<PID, EXP, TIME, THREAD> {
70    process_id: Option<ProcessId>,
71    exporter: Option<&'static (dyn Export + Sync)>,
72    timestamp_fn: Option<fn() -> u64>,
73    thread_id_fn: Option<fn() -> core::num::NonZeroU64>,
74    _pid: core::marker::PhantomData<PID>,
75    _exp: core::marker::PhantomData<EXP>,
76    _time: core::marker::PhantomData<TIME>,
77    _thread: core::marker::PhantomData<THREAD>,
78}
79
80/// Creates a new collector builder.
81///
82/// # Example
83///
84/// ```rust,no_run
85/// use veecle_osal_std::{time::Time, thread::Thread};
86/// use veecle_telemetry::collector;
87///
88/// collector::build()
89///     .random_process_id()
90///     .console_json_exporter()
91///     .time::<Time>()
92///     .thread::<Thread>()
93///     .set_global().unwrap();
94/// ```
95pub fn build() -> Builder<state::NoProcessId, state::NoExporter, state::NoTime, state::NoThread> {
96    Builder {
97        process_id: None,
98        exporter: None,
99        timestamp_fn: None,
100        thread_id_fn: None,
101        _pid: core::marker::PhantomData,
102        _exp: core::marker::PhantomData,
103        _time: core::marker::PhantomData,
104        _thread: core::marker::PhantomData,
105    }
106}
107
108impl<PID, EXP, TIME, THREAD> Builder<PID, EXP, TIME, THREAD> {
109    /// Sets the process id for this collector instance.
110    pub fn process_id(
111        self,
112        process_id: ProcessId,
113    ) -> Builder<state::WithProcessId, EXP, TIME, THREAD> {
114        Builder {
115            process_id: Some(process_id),
116            exporter: self.exporter,
117            timestamp_fn: self.timestamp_fn,
118            thread_id_fn: self.thread_id_fn,
119            _pid: core::marker::PhantomData,
120            _exp: core::marker::PhantomData,
121            _time: core::marker::PhantomData,
122            _thread: core::marker::PhantomData,
123        }
124    }
125
126    /// Sets the exporter for telemetry data.
127    pub fn exporter(
128        self,
129        exporter: &'static (dyn Export + Sync),
130    ) -> Builder<PID, state::WithExporter, TIME, THREAD> {
131        Builder {
132            process_id: self.process_id,
133            exporter: Some(exporter),
134            timestamp_fn: self.timestamp_fn,
135            thread_id_fn: self.thread_id_fn,
136            _pid: core::marker::PhantomData,
137            _exp: core::marker::PhantomData,
138            _time: core::marker::PhantomData,
139            _thread: core::marker::PhantomData,
140        }
141    }
142
143    /// Configures the time abstraction to use (monotonic time only).
144    pub fn time<T>(self) -> Builder<PID, EXP, state::WithTime, THREAD>
145    where
146        T: TimeAbstraction,
147    {
148        Builder {
149            process_id: self.process_id,
150            exporter: self.exporter,
151            timestamp_fn: Some(timestamp_fn_monotonic::<T>),
152            thread_id_fn: self.thread_id_fn,
153            _pid: core::marker::PhantomData,
154            _exp: core::marker::PhantomData,
155            _time: core::marker::PhantomData,
156            _thread: core::marker::PhantomData,
157        }
158    }
159
160    /// Configures the time abstraction with system time to use (Unix epoch synchronization).
161    pub fn system_time<T>(self) -> Builder<PID, EXP, state::WithTime, THREAD>
162    where
163        T: TimeAbstraction + SystemTime,
164    {
165        Builder {
166            process_id: self.process_id,
167            exporter: self.exporter,
168            timestamp_fn: Some(timestamp_fn_system_time::<T>),
169            thread_id_fn: self.thread_id_fn,
170            _pid: core::marker::PhantomData,
171            _exp: core::marker::PhantomData,
172            _time: core::marker::PhantomData,
173            _thread: core::marker::PhantomData,
174        }
175    }
176
177    /// Configures the thread abstraction to use.
178    pub fn thread<Th>(self) -> Builder<PID, EXP, TIME, state::WithThread>
179    where
180        Th: ThreadAbstraction,
181    {
182        Builder {
183            process_id: self.process_id,
184            exporter: self.exporter,
185            timestamp_fn: self.timestamp_fn,
186            thread_id_fn: Some(Th::current_thread_id),
187            _pid: core::marker::PhantomData,
188            _exp: core::marker::PhantomData,
189            _time: core::marker::PhantomData,
190            _thread: core::marker::PhantomData,
191        }
192    }
193}
194
195impl<EXP, TIME, THREAD> Builder<state::NoProcessId, EXP, TIME, THREAD> {
196    /// Sets a randomly generated process id.
197    ///
198    /// Equivalent to `.process_id(ProcessId::random(&mut rand::rng()))`.
199    ///
200    /// # Examples
201    ///
202    /// ```rust,no_run
203    /// use veecle_osal_std::{time::Time, thread::Thread};
204    /// use veecle_telemetry::collector;
205    ///
206    /// # let exporter = &veecle_telemetry::collector::ConsoleJsonExporter::DEFAULT;
207    /// collector::build()
208    ///     .random_process_id()
209    ///     .exporter(exporter)
210    ///     .time::<Time>()
211    ///     .thread::<Thread>()
212    ///     .set_global().unwrap();
213    /// ```
214    #[cfg(feature = "std")]
215    pub fn random_process_id(self) -> Builder<state::WithProcessId, EXP, TIME, THREAD> {
216        self.process_id(ProcessId::random(&mut rand::rng()))
217    }
218}
219
220impl<PID, TIME, THREAD> Builder<PID, state::NoExporter, TIME, THREAD> {
221    /// Sets the given exporter by leaking it to obtain a static reference.
222    ///
223    /// This is a convenience method for dynamic exporters that need to be boxed
224    /// and leaked. Equivalent to `.exporter(Box::leak(Box::new(exporter)))`.
225    ///
226    /// # Examples
227    ///
228    /// ```rust,no_run
229    /// use veecle_osal_std::{time::Time, thread::Thread};
230    /// use veecle_telemetry::collector::TestExporter;
231    ///
232    /// let (exporter, _collected) = TestExporter::new();
233    /// veecle_telemetry::collector::build()
234    ///     .random_process_id()
235    ///     .leaked_exporter(exporter)
236    ///     .time::<Time>()
237    ///     .thread::<Thread>()
238    ///     .set_global().unwrap();
239    /// ```
240    #[cfg(feature = "alloc")]
241    pub fn leaked_exporter(
242        self,
243        exporter: impl Export + Sync + 'static,
244    ) -> Builder<PID, state::WithExporter, TIME, THREAD> {
245        self.exporter(alloc::boxed::Box::leak(alloc::boxed::Box::new(exporter)))
246    }
247
248    /// Sets the exporter to be the [`ConsoleJsonExporter`][super::ConsoleJsonExporter].
249    ///
250    /// Equivalent to `.exporter(&ConsoleJsonExporter::DEFAULT)`.
251    ///
252    /// # Examples
253    ///
254    /// ```rust,no_run
255    /// use veecle_osal_std::{time::Time, thread::Thread};
256    /// use veecle_telemetry::collector;
257    ///
258    /// collector::build()
259    ///     .random_process_id()
260    ///     .console_json_exporter()
261    ///     .time::<Time>()
262    ///     .thread::<Thread>()
263    ///     .set_global().unwrap();
264    /// ```
265    #[cfg(feature = "std")]
266    pub fn console_json_exporter(self) -> Builder<PID, state::WithExporter, TIME, THREAD> {
267        self.exporter(&super::ConsoleJsonExporter::DEFAULT)
268    }
269}
270
271impl Builder<state::WithProcessId, state::WithExporter, state::WithTime, state::WithThread> {
272    /// Builds this configuration into a [`Collector`] instance.
273    pub fn build(self) -> Collector {
274        Collector::new(
275            self.process_id.unwrap(),
276            self.exporter.unwrap(),
277            self.timestamp_fn.unwrap(),
278            self.thread_id_fn.unwrap(),
279        )
280    }
281
282    /// Sets this collector as the global collector instance.
283    ///
284    /// This can only be called once per process.
285    pub fn set_global(self) -> Result<(), SetGlobalError> {
286        super::global::set_collector(self.build())
287    }
288}