Skip to main content

micromegas_tracing/
dispatch.rs

1//! Where events are recorded and eventually sent to a sink
2use crate::dyn_deps::merge_properties;
3pub use crate::errors::{Error, Result};
4use crate::images::{ImageBlock, ImageEvent, ImageStream};
5use crate::logs::{TaggedLogInteropEvent, TaggedLogStaticStrEvent, TaggedLogString};
6use crate::metrics::{TaggedFloatMetricEvent, TaggedIntegerMetricEvent};
7use crate::prelude::*;
8use crate::property_set::{DynProperty, PropertySet, PropertySetId};
9use crate::{
10    event::{EventBlock, EventSink, EventStream, ExtractDeps, NullEventSink, TracingBlock},
11    info,
12    logs::{
13        LogMetadata, LogStaticStrEvent, LogStaticStrInteropEventV2, LogStream, LogStringEvent,
14        LogStringInteropEvent,
15    },
16    metrics::{FloatMetricEvent, IntegerMetricEvent, MetricsStream, StaticMetricMetadata},
17    spans::{
18        BeginAsyncDynNamedSpanEvent, BeginAsyncNamedSpanEvent, BeginAsyncSpanEvent,
19        BeginThreadDynNamedSpanEvent, BeginThreadNamedSpanEvent, BeginThreadSpanEvent,
20        EndAsyncDynNamedSpanEvent, EndAsyncNamedSpanEvent, EndAsyncSpanEvent,
21        EndThreadDynNamedSpanEvent, EndThreadNamedSpanEvent, EndThreadSpanEvent, SpanLocation,
22        SpanMetadata, ThreadBlock, ThreadEventQueueTypeIndex, ThreadStream,
23    },
24    string_id::DynStringId,
25    warn,
26};
27
28const IMAGE_BUFFER_SIZE: usize = 1024 * 1024;
29use chrono::Utc;
30use std::cell::OnceCell;
31use std::cell::UnsafeCell;
32use std::collections::HashMap;
33use std::fmt;
34use std::sync::RwLock;
35use std::{
36    cell::Cell,
37    sync::{Arc, Mutex, MutexGuard},
38};
39
40pub fn init_event_dispatch(
41    logs_buffer_size: usize,
42    metrics_buffer_size: usize,
43    threads_buffer_size: usize,
44    sink: Arc<dyn EventSink>,
45    process_properties: HashMap<String, String>,
46    cpu_tracing_enabled: bool,
47) -> Result<()> {
48    lazy_static::lazy_static! {
49        static ref INIT_MUTEX: Mutex<()> = Mutex::new(());
50    }
51    let _guard = INIT_MUTEX.lock().unwrap();
52    let dispatch_ref = &G_DISPATCH.inner;
53    unsafe {
54        if (*dispatch_ref.get()).get().is_none() {
55            (*dispatch_ref.get())
56                .set(Dispatch::new(
57                    logs_buffer_size,
58                    metrics_buffer_size,
59                    threads_buffer_size,
60                    sink,
61                    process_properties,
62                    cpu_tracing_enabled,
63                ))
64                .map_err(|_| Error::AlreadyInitialized())
65        } else {
66            info!("event dispatch already initialized");
67            Err(Error::AlreadyInitialized())
68        }
69    }
70}
71
72#[inline]
73pub fn process_id() -> Option<uuid::Uuid> {
74    G_DISPATCH.get().map(Dispatch::get_process_id)
75}
76
77#[inline]
78pub fn cpu_tracing_enabled() -> Option<bool> {
79    G_DISPATCH.get().map(Dispatch::get_cpu_tracing_enabled)
80}
81
82pub fn get_sink() -> Option<Arc<dyn EventSink>> {
83    G_DISPATCH.get().map(Dispatch::get_sink)
84}
85
86pub fn shutdown_dispatch() {
87    G_DISPATCH.get().map(Dispatch::shutdown);
88}
89
90#[inline(always)]
91pub fn int_metric(metric_desc: &'static StaticMetricMetadata, value: u64) {
92    if let Some(d) = G_DISPATCH.get() {
93        d.int_metric(metric_desc, value);
94    }
95}
96
97#[inline(always)]
98pub fn float_metric(metric_desc: &'static StaticMetricMetadata, value: f64) {
99    if let Some(d) = G_DISPATCH.get() {
100        d.float_metric(metric_desc, value);
101    }
102}
103
104#[inline(always)]
105pub fn tagged_float_metric(
106    desc: &'static StaticMetricMetadata,
107    properties: &'static PropertySet,
108    value: f64,
109) {
110    if let Some(d) = G_DISPATCH.get() {
111        d.tagged_float_metric(desc, properties, value);
112    }
113}
114
115#[inline(always)]
116pub fn tagged_integer_metric(
117    desc: &'static StaticMetricMetadata,
118    properties: &'static PropertySet,
119    value: u64,
120) {
121    if let Some(d) = G_DISPATCH.get() {
122        d.tagged_integer_metric(desc, properties, value);
123    }
124}
125
126#[inline(always)]
127pub fn tagged_float_metric_dyn(
128    desc: &'static StaticMetricMetadata,
129    properties: &'static PropertySet,
130    dyn_properties: &[DynProperty<'_>],
131    value: f64,
132) {
133    if let Some(d) = G_DISPATCH.get() {
134        d.tagged_float_metric_dyn(desc, properties, dyn_properties, value);
135    }
136}
137
138#[inline(always)]
139pub fn tagged_integer_metric_dyn(
140    desc: &'static StaticMetricMetadata,
141    properties: &'static PropertySet,
142    dyn_properties: &[DynProperty<'_>],
143    value: u64,
144) {
145    if let Some(d) = G_DISPATCH.get() {
146        d.tagged_integer_metric_dyn(desc, properties, dyn_properties, value);
147    }
148}
149
150/// Logs with an interned property set plus dynamic values.
151#[inline(always)]
152pub fn log_dyn_tagged(
153    desc: &'static LogMetadata,
154    properties: &'static PropertySet,
155    dyn_properties: &[DynProperty<'_>],
156    args: fmt::Arguments<'_>,
157) {
158    if let Some(d) = G_DISPATCH.get() {
159        d.log_dyn_tagged(desc, properties, dyn_properties, args);
160    }
161}
162
163/// A sink that has accepted a log callsite; obtained from `enabled_log_sink`.
164#[doc(hidden)]
165pub struct EnabledLogSink {
166    dispatch: &'static Dispatch,
167    sink: Arc<dyn EventSink>,
168}
169
170/// Fetches the sink once and checks the callsite against it, so that the gated macro arm
171/// neither re-checks nor re-fetches.
172#[doc(hidden)]
173#[inline(always)]
174pub fn enabled_log_sink(desc: &LogMetadata) -> Option<EnabledLogSink> {
175    let dispatch = G_DISPATCH.get()?;
176    let sink = dispatch.get_sink();
177    if sink.on_log_enabled(desc) {
178        Some(EnabledLogSink { dispatch, sink })
179    } else {
180        None
181    }
182}
183
184#[doc(hidden)]
185#[inline(always)]
186pub fn log_dyn_tagged_enabled(
187    sink: EnabledLogSink,
188    desc: &'static LogMetadata,
189    properties: &'static PropertySet,
190    dyn_properties: &[DynProperty<'_>],
191    args: fmt::Arguments<'_>,
192) {
193    sink.dispatch
194        .log_dyn_tagged_to_sink(&*sink.sink, desc, properties, dyn_properties, args);
195}
196
197#[inline(always)]
198pub fn log_interop_dyn_tagged(
199    metadata: &LogMetadata,
200    dyn_properties: &[DynProperty<'_>],
201    args: fmt::Arguments<'_>,
202) {
203    if let Some(d) = G_DISPATCH.get() {
204        d.log_interop_dyn_tagged(metadata, dyn_properties, args);
205    }
206}
207
208#[inline(always)]
209pub fn log(desc: &'static LogMetadata, args: fmt::Arguments<'_>) {
210    if let Some(d) = G_DISPATCH.get() {
211        d.log(desc, args);
212    }
213}
214
215/// Logs with an interned property set only.
216#[inline(always)]
217pub fn log_tagged(
218    desc: &'static LogMetadata,
219    properties: &'static PropertySet,
220    args: fmt::Arguments<'_>,
221) {
222    if let Some(d) = G_DISPATCH.get() {
223        d.log_tagged(desc, properties, args);
224    }
225}
226
227#[inline(always)]
228pub fn log_interop(metadata: &LogMetadata, args: fmt::Arguments<'_>) {
229    if let Some(d) = G_DISPATCH.get() {
230        d.log_interop(metadata, args);
231    }
232}
233
234#[inline(always)]
235pub fn log_enabled(metadata: &LogMetadata) -> bool {
236    if let Some(d) = G_DISPATCH.get() {
237        d.log_enabled(metadata)
238    } else {
239        false
240    }
241}
242
243#[inline(always)]
244pub fn flush_log_buffer() {
245    if let Some(d) = G_DISPATCH.get() {
246        d.flush_log_buffer();
247    }
248}
249
250pub fn send_image(name: &str, format: &str, data: Vec<u8>) {
251    if let Some(d) = G_DISPATCH.get() {
252        d.send_image(name, format, data);
253    }
254}
255
256#[inline(always)]
257pub fn flush_image_buffer() {
258    if let Some(d) = G_DISPATCH.get() {
259        d.flush_image_buffer();
260    }
261}
262
263#[inline(always)]
264pub fn flush_metrics_buffer() {
265    if let Some(d) = G_DISPATCH.get() {
266        d.flush_metrics_buffer();
267    }
268}
269
270//todo: should be implicit by default but limit the maximum number of tracked
271// threads
272#[inline(always)]
273pub fn init_thread_stream() {
274    LOCAL_THREAD_STREAM.with(|cell| unsafe {
275        if (*cell.as_ptr()).is_some() {
276            return;
277        }
278        #[allow(static_mut_refs)]
279        if let Some(d) = G_DISPATCH.get() {
280            // Check if CPU tracing is enabled before creating thread stream
281            if !d.cpu_tracing_enabled {
282                return;
283            }
284            d.init_thread_stream(cell);
285        } else {
286            warn!("dispatch not initialized, cannot init thread stream, events will be lost for this thread");
287        }
288    });
289}
290
291pub fn for_each_thread_stream(fun: &mut dyn FnMut(*mut ThreadStream)) {
292    if let Some(d) = G_DISPATCH.get() {
293        d.for_each_thread_stream(fun);
294    }
295}
296
297#[inline(always)]
298pub fn flush_thread_buffer() {
299    LOCAL_THREAD_STREAM.with(|cell| unsafe {
300        let opt_stream = &mut *cell.as_ptr();
301        if let Some(stream) = opt_stream {
302            #[allow(static_mut_refs)]
303            match G_DISPATCH.get() {
304                Some(d) => {
305                    d.flush_thread_buffer(stream);
306                }
307                None => {
308                    panic!("threads are recording but there is no event dispatch");
309                }
310            }
311        }
312    });
313}
314
315/// Unregisters the current thread's stream from the global dispatch to prevent
316/// dangling pointers when the thread is destroyed.
317#[inline(always)]
318pub fn unregister_thread_stream() {
319    LOCAL_THREAD_STREAM.with(|cell| unsafe {
320        let opt_stream = &mut *cell.as_ptr();
321        if let Some(stream) = opt_stream {
322            #[allow(static_mut_refs)]
323            match G_DISPATCH.get() {
324                Some(d) => {
325                    // Flush any remaining events before unregistering
326                    d.flush_thread_buffer(stream);
327                    // Unregister the thread stream
328                    d.unregister_thread_stream(stream);
329                    // Clear the local thread stream
330                    *opt_stream = None;
331                }
332                None => {
333                    // If dispatch is already shut down, just clear the local stream
334                    *opt_stream = None;
335                }
336            }
337        }
338    });
339}
340
341#[inline(always)]
342pub fn on_begin_scope(scope: &'static SpanMetadata) {
343    on_thread_event(BeginThreadSpanEvent {
344        time: now(),
345        thread_span_desc: scope,
346    });
347}
348
349#[inline(always)]
350pub fn on_end_scope(scope: &'static SpanMetadata) {
351    on_thread_event(EndThreadSpanEvent {
352        time: now(),
353        thread_span_desc: scope,
354    });
355}
356
357#[inline(always)]
358pub fn on_begin_named_scope(thread_span_location: &'static SpanLocation, name: &'static str) {
359    on_thread_event(BeginThreadNamedSpanEvent {
360        thread_span_location,
361        name: name.into(),
362        time: now(),
363    });
364}
365
366#[inline(always)]
367pub fn on_end_named_scope(thread_span_location: &'static SpanLocation, name: &'static str) {
368    on_thread_event(EndThreadNamedSpanEvent {
369        thread_span_location,
370        name: name.into(),
371        time: now(),
372    });
373}
374
375#[inline(always)]
376pub fn on_begin_async_scope(scope: &'static SpanMetadata, parent_span_id: u64, depth: u32) -> u64 {
377    let id = G_ASYNC_SPAN_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
378    on_thread_event(BeginAsyncSpanEvent {
379        span_desc: scope,
380        span_id: id as u64,
381        parent_span_id,
382        depth,
383        time: now(),
384    });
385    id as u64
386}
387
388#[inline(always)]
389pub fn on_end_async_scope(
390    span_id: u64,
391    parent_span_id: u64,
392    scope: &'static SpanMetadata,
393    depth: u32,
394) {
395    on_thread_event(EndAsyncSpanEvent {
396        span_desc: scope,
397        span_id,
398        parent_span_id,
399        depth,
400        time: now(),
401    });
402}
403
404#[inline(always)]
405pub fn on_begin_async_named_scope(
406    span_location: &'static SpanLocation,
407    name: &'static str,
408    parent_span_id: u64,
409    depth: u32,
410) -> u64 {
411    let id = G_ASYNC_SPAN_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
412    on_thread_event(BeginAsyncNamedSpanEvent {
413        span_location,
414        name: name.into(),
415        span_id: id as u64,
416        parent_span_id,
417        depth,
418        time: now(),
419    });
420    id as u64
421}
422
423#[inline(always)]
424pub fn on_end_async_named_scope(
425    span_id: u64,
426    parent_span_id: u64,
427    span_location: &'static SpanLocation,
428    name: &'static str,
429    depth: u32,
430) {
431    on_thread_event(EndAsyncNamedSpanEvent {
432        span_location,
433        name: name.into(),
434        span_id,
435        parent_span_id,
436        depth,
437        time: now(),
438    });
439}
440
441/// Like `on_begin_named_scope`, for a name built at runtime. The name is copied into the
442/// current block, so it needs to outlive only this call.
443#[inline(always)]
444pub fn on_begin_named_scope_dyn(thread_span_location: &'static SpanLocation, name: &str) {
445    on_thread_dyn_named_event(name, |name| BeginThreadDynNamedSpanEvent {
446        thread_span_location,
447        name,
448        time: now(),
449    });
450}
451
452/// Like `on_end_named_scope`, for a name built at runtime. The name is written again to the
453/// block that is current now, which may differ from the block of the begin event.
454#[inline(always)]
455pub fn on_end_named_scope_dyn(thread_span_location: &'static SpanLocation, name: &str) {
456    let time = now();
457    on_thread_dyn_named_event(name, |name| EndThreadDynNamedSpanEvent {
458        thread_span_location,
459        name,
460        time,
461    });
462}
463
464/// Like `on_begin_async_named_scope`, for a name built at runtime. The name is copied into the
465/// current block, so it needs to outlive only this call.
466#[inline(always)]
467pub fn on_begin_async_named_scope_dyn(
468    span_location: &'static SpanLocation,
469    name: &str,
470    parent_span_id: u64,
471    depth: u32,
472) -> u64 {
473    let id = G_ASYNC_SPAN_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
474    on_thread_dyn_named_event(name, |name| BeginAsyncDynNamedSpanEvent {
475        span_location,
476        name,
477        span_id: id as u64,
478        parent_span_id,
479        depth,
480        time: now(),
481    });
482    id as u64
483}
484
485/// Like `on_end_async_named_scope`, for a name built at runtime. The name is written again to
486/// the block that is current now, which may differ from the block of the begin event and from
487/// the thread it ran on.
488#[inline(always)]
489pub fn on_end_async_named_scope_dyn(
490    span_id: u64,
491    parent_span_id: u64,
492    span_location: &'static SpanLocation,
493    name: &str,
494    depth: u32,
495) {
496    let time = now();
497    on_thread_dyn_named_event(name, |name| EndAsyncDynNamedSpanEvent {
498        span_location,
499        name,
500        span_id,
501        parent_span_id,
502        depth,
503        time,
504    });
505}
506
507pub struct DispatchCell {
508    // unsafecell is only necessary for force_uninit()
509    inner: UnsafeCell<OnceCell<Dispatch>>,
510}
511
512impl DispatchCell {
513    const fn new() -> Self {
514        Self {
515            inner: UnsafeCell::new(OnceCell::new()),
516        }
517    }
518
519    fn get(&self) -> Option<&Dispatch> {
520        unsafe { (*self.inner.get()).get() }
521    }
522
523    unsafe fn take(&self) -> Option<Dispatch> {
524        unsafe { (*self.inner.get()).take() }
525    }
526}
527
528// very unsafe indeed - we don't want to pay for locking every time we need to record an event
529unsafe impl Sync for DispatchCell {}
530
531static G_DISPATCH: DispatchCell = DispatchCell::new();
532static G_ASYNC_SPAN_COUNTER: std::sync::atomic::AtomicUsize =
533    std::sync::atomic::AtomicUsize::new(1);
534
535/// # Safety
536/// very unsafe! make sure there is no thread running that could be sending events
537/// normally that global state should not be destroyed
538pub unsafe fn force_uninit() {
539    unsafe {
540        G_DISPATCH.take();
541    }
542}
543
544thread_local! {
545    static LOCAL_THREAD_STREAM: Cell<Option<ThreadStream>> = const { Cell::new(None) };
546}
547
548#[inline(always)]
549fn on_thread_event<T>(event: T)
550where
551    T: micromegas_transit::InProcSerialize + ThreadEventQueueTypeIndex,
552{
553    LOCAL_THREAD_STREAM.with(|cell| unsafe {
554        let opt_stream = &mut *cell.as_ptr();
555        if let Some(stream) = opt_stream {
556            stream.get_events_mut().push(event);
557            if stream.is_full() {
558                flush_thread_buffer();
559            }
560        }
561    });
562}
563
564#[inline(always)]
565fn on_thread_dyn_named_event<T>(name: &str, make: impl FnOnce(DynStringId) -> T)
566where
567    T: micromegas_transit::InProcSerialize + ThreadEventQueueTypeIndex,
568{
569    LOCAL_THREAD_STREAM.with(|cell| unsafe {
570        let opt_stream = &mut *cell.as_ptr();
571        if let Some(stream) = opt_stream {
572            stream.push_dyn_named(name, make);
573            if stream.is_full() {
574                flush_thread_buffer();
575            }
576        }
577    });
578}
579
580struct Dispatch {
581    process_id: uuid::Uuid,
582    logs_buffer_size: usize,
583    metrics_buffer_size: usize,
584    threads_buffer_size: usize,
585    cpu_tracing_enabled: bool,
586    image_stream: Mutex<ImageStream>,
587    log_stream: Mutex<LogStream>,
588    metrics_stream: Mutex<MetricsStream>,
589    thread_streams: Mutex<Vec<*mut ThreadStream>>, // very very unsafe - threads would need to be unregistered before they are destroyed
590    sink: RwLock<Arc<dyn EventSink>>,
591}
592
593/// The message is `fmt_str` verbatim, so analytics can read it from the metadata.
594#[inline(always)]
595fn is_static_msg(desc: &LogMetadata, args: &fmt::Arguments<'_>) -> bool {
596    args.as_str() == Some(desc.fmt_str)
597}
598
599impl Dispatch {
600    pub fn new(
601        logs_buffer_size: usize,
602        metrics_buffer_size: usize,
603        threads_buffer_size: usize,
604        sink: Arc<dyn EventSink>,
605        process_properties: HashMap<String, String>,
606        cpu_tracing_enabled: bool,
607    ) -> Self {
608        let process_id = uuid::Uuid::new_v4();
609        let obj = Self {
610            process_id,
611            logs_buffer_size,
612            metrics_buffer_size,
613            threads_buffer_size,
614            cpu_tracing_enabled,
615            image_stream: Mutex::new(ImageStream::new(
616                IMAGE_BUFFER_SIZE,
617                process_id,
618                &[String::from("image")],
619                HashMap::new(),
620            )),
621            log_stream: Mutex::new(LogStream::new(
622                logs_buffer_size,
623                process_id,
624                &[String::from("log")],
625                HashMap::new(),
626            )),
627            metrics_stream: Mutex::new(MetricsStream::new(
628                metrics_buffer_size,
629                process_id,
630                &[String::from("metrics")],
631                HashMap::new(),
632            )),
633            thread_streams: Mutex::new(vec![]),
634            sink: RwLock::new(sink),
635        };
636        obj.startup(process_properties);
637        obj.init_image_stream();
638        obj.init_log_stream();
639        obj.init_metrics_stream();
640        obj
641    }
642
643    pub fn get_process_id(&self) -> uuid::Uuid {
644        self.process_id
645    }
646
647    pub fn get_cpu_tracing_enabled(&self) -> bool {
648        self.cpu_tracing_enabled
649    }
650
651    pub fn get_sink(&self) -> Arc<dyn EventSink> {
652        if let Ok(guard) = self.sink.try_read() {
653            (*guard).clone()
654        } else {
655            Arc::new(NullEventSink {})
656        }
657    }
658
659    fn shutdown(&self) {
660        let old_sink = self.get_sink();
661        let null_sink = Arc::new(NullEventSink {});
662        if let Ok(mut guard) = self.sink.write() {
663            *guard = null_sink;
664            drop(guard)
665        }
666        old_sink.on_shutdown();
667    }
668
669    fn startup(&self, process_properties: HashMap<String, String>) {
670        let mut parent_process = None;
671
672        if let Ok(parent_process_guid) = std::env::var("MICROMEGAS_TELEMETRY_PARENT_PROCESS")
673            && let Ok(parent_process_id) = uuid::Uuid::try_parse(&parent_process_guid)
674        {
675            parent_process = Some(parent_process_id);
676        }
677
678        unsafe {
679            std::env::set_var(
680                "MICROMEGAS_TELEMETRY_PARENT_PROCESS",
681                self.process_id.to_string(),
682            );
683        }
684
685        let process_info = Arc::new(make_process_info(
686            self.process_id,
687            parent_process,
688            process_properties,
689        ));
690
691        self.get_sink().on_startup(process_info);
692    }
693
694    fn init_log_stream(&self) {
695        let log_stream = self.log_stream.lock().unwrap();
696        self.get_sink().on_init_log_stream(&log_stream);
697    }
698
699    fn init_image_stream(&self) {
700        let image_stream = self.image_stream.lock().expect("image_stream lock");
701        self.get_sink().on_init_image_stream(&image_stream);
702    }
703
704    fn init_metrics_stream(&self) {
705        let metrics_stream = self.metrics_stream.lock().unwrap();
706        self.get_sink().on_init_metrics_stream(&metrics_stream);
707    }
708
709    pub fn send_image(&self, name: &str, format: &str, data: Vec<u8>) {
710        let time = now();
711        let mut image_stream = self.image_stream.lock().expect("image_stream lock");
712        image_stream.get_events_mut().push(ImageEvent {
713            time,
714            name: micromegas_transit::DynString(name.to_owned()),
715            format: micromegas_transit::DynString(format.to_owned()),
716            data: micromegas_transit::DynBlob(data),
717        });
718        if image_stream.is_full() {
719            drop(image_stream);
720            self.flush_image_buffer();
721        }
722    }
723
724    fn flush_image_buffer(&self) {
725        let mut image_stream = self.image_stream.lock().expect("image_stream lock");
726        if image_stream.is_empty() {
727            return;
728        }
729        let stream_id = image_stream.stream_id();
730        let next_offset = image_stream.get_block_ref().object_offset()
731            + image_stream.get_block_ref().nb_objects();
732        let new_block = Arc::new(ImageBlock::new(
733            IMAGE_BUFFER_SIZE,
734            self.process_id,
735            stream_id,
736            next_offset,
737        ));
738        let begin = new_block.begin;
739        let mut old_event_block = image_stream.replace_block(new_block);
740        assert!(!image_stream.is_full());
741        Arc::get_mut(&mut old_event_block)
742            .expect("image block exclusive ref")
743            .close_at(begin);
744        drop(image_stream);
745        self.get_sink().on_process_image_block(old_event_block);
746    }
747
748    fn init_thread_stream(&self, cell: &Cell<Option<ThreadStream>>) {
749        // Early return if CPU tracing is disabled
750        if !self.cpu_tracing_enabled {
751            return;
752        }
753
754        let mut properties = HashMap::new();
755        properties.insert(String::from("thread-id"), thread_id::get().to_string());
756        if let Some(name) = std::thread::current().name() {
757            properties.insert("thread-name".to_owned(), name.to_owned());
758        }
759        let thread_stream = ThreadStream::new(
760            self.threads_buffer_size,
761            self.process_id,
762            &["cpu".to_owned()],
763            properties,
764        );
765        unsafe {
766            let opt_ref = &mut *cell.as_ptr();
767            self.get_sink().on_init_thread_stream(&thread_stream);
768            *opt_ref = Some(thread_stream);
769            let mut vec_guard = self.thread_streams.lock().unwrap();
770            vec_guard.push(opt_ref.as_mut().unwrap());
771        }
772    }
773
774    fn for_each_thread_stream(&self, fun: &mut dyn FnMut(*mut ThreadStream)) {
775        let mut vec_guard = self.thread_streams.lock().unwrap();
776        for stream in &mut *vec_guard {
777            fun(*stream);
778        }
779    }
780
781    fn unregister_thread_stream(&self, stream_to_remove: &mut ThreadStream) {
782        let mut vec_guard = self.thread_streams.lock().unwrap();
783        let stream_ptr = stream_to_remove as *mut ThreadStream;
784
785        // Find and remove the thread stream pointer from the vector
786        if let Some(pos) = vec_guard.iter().position(|&ptr| ptr == stream_ptr) {
787            vec_guard.remove(pos);
788        }
789    }
790
791    #[inline]
792    fn int_metric(&self, desc: &'static StaticMetricMetadata, value: u64) {
793        let time = now();
794        let mut metrics_stream = self.metrics_stream.lock().unwrap();
795        metrics_stream
796            .get_events_mut()
797            .push(IntegerMetricEvent { desc, value, time });
798        self.on_metrics_pushed(metrics_stream);
799    }
800
801    #[inline]
802    fn float_metric(&self, desc: &'static StaticMetricMetadata, value: f64) {
803        let time = now();
804        let mut metrics_stream = self.metrics_stream.lock().unwrap();
805        metrics_stream
806            .get_events_mut()
807            .push(FloatMetricEvent { desc, value, time });
808        self.on_metrics_pushed(metrics_stream);
809    }
810
811    #[inline]
812    fn tagged_float_metric(
813        &self,
814        desc: &'static StaticMetricMetadata,
815        properties: &'static PropertySet,
816        value: f64,
817    ) {
818        let time = now();
819        let mut metrics_stream = self.metrics_stream.lock().unwrap();
820        metrics_stream
821            .get_events_mut()
822            .push(TaggedFloatMetricEvent {
823                desc,
824                properties: PropertySetId::interned(properties),
825                value,
826                time,
827            });
828        self.on_metrics_pushed(metrics_stream);
829    }
830
831    #[inline]
832    fn tagged_integer_metric(
833        &self,
834        desc: &'static StaticMetricMetadata,
835        properties: &'static PropertySet,
836        value: u64,
837    ) {
838        let time = now();
839        let mut metrics_stream = self.metrics_stream.lock().unwrap();
840        metrics_stream
841            .get_events_mut()
842            .push(TaggedIntegerMetricEvent {
843                desc,
844                properties: PropertySetId::interned(properties),
845                value,
846                time,
847            });
848        self.on_metrics_pushed(metrics_stream);
849    }
850
851    #[inline]
852    fn tagged_float_metric_dyn(
853        &self,
854        desc: &'static StaticMetricMetadata,
855        properties: &'static PropertySet,
856        dyn_properties: &[DynProperty<'_>],
857        value: f64,
858    ) {
859        if dyn_properties.is_empty() {
860            return self.tagged_float_metric(desc, properties, value);
861        }
862        let time = now();
863        let merged = merge_properties(properties, dyn_properties);
864        let mut metrics_stream = self.metrics_stream.lock().unwrap();
865        let (events, dyn_deps) = metrics_stream.get_block_mut_parts();
866        let properties = dyn_deps.intern_set(&merged);
867        events.push(TaggedFloatMetricEvent {
868            desc,
869            properties,
870            value,
871            time,
872        });
873        self.on_metrics_pushed(metrics_stream);
874    }
875
876    #[inline]
877    fn tagged_integer_metric_dyn(
878        &self,
879        desc: &'static StaticMetricMetadata,
880        properties: &'static PropertySet,
881        dyn_properties: &[DynProperty<'_>],
882        value: u64,
883    ) {
884        if dyn_properties.is_empty() {
885            return self.tagged_integer_metric(desc, properties, value);
886        }
887        let time = now();
888        let merged = merge_properties(properties, dyn_properties);
889        let mut metrics_stream = self.metrics_stream.lock().unwrap();
890        let (events, dyn_deps) = metrics_stream.get_block_mut_parts();
891        let properties = dyn_deps.intern_set(&merged);
892        events.push(TaggedIntegerMetricEvent {
893            desc,
894            properties,
895            value,
896            time,
897        });
898        self.on_metrics_pushed(metrics_stream);
899    }
900
901    #[inline]
902    fn on_metrics_pushed(&self, mut metrics_stream: MutexGuard<'_, MetricsStream>) {
903        if !metrics_stream.is_full() {
904            return;
905        }
906        // Swapped in the critical section that filled the block, so no other thread can push
907        // into it past the size margin or flush it a second time.
908        let old_block = swap_block(
909            &mut metrics_stream,
910            self.metrics_buffer_size,
911            self.process_id,
912        );
913        drop(metrics_stream);
914        self.get_sink().on_process_metrics_block(old_block);
915    }
916
917    #[inline]
918    fn flush_metrics_buffer(&self) {
919        let mut metrics_stream = self.metrics_stream.lock().unwrap();
920        if metrics_stream.is_empty() {
921            return;
922        }
923        let old_block = swap_block(
924            &mut metrics_stream,
925            self.metrics_buffer_size,
926            self.process_id,
927        );
928        drop(metrics_stream);
929        self.get_sink().on_process_metrics_block(old_block);
930    }
931
932    fn log_enabled(&self, metadata: &LogMetadata) -> bool {
933        self.get_sink().on_log_enabled(metadata)
934    }
935
936    #[inline]
937    fn log(&self, metadata: &'static LogMetadata, args: fmt::Arguments<'_>) {
938        let sink = self.get_sink();
939        if !sink.on_log_enabled(metadata) {
940            return;
941        }
942        let time = now();
943        sink.on_log(metadata, &[], time, args);
944        let msg = if is_static_msg(metadata, &args) {
945            None
946        } else {
947            Some(micromegas_transit::DynString(args.to_string()))
948        };
949        let mut log_stream = self.log_stream.lock().unwrap();
950        match msg {
951            None => log_stream.get_events_mut().push(LogStaticStrEvent {
952                desc: metadata,
953                time,
954            }),
955            Some(msg) => log_stream.get_events_mut().push(LogStringEvent {
956                desc: metadata,
957                time,
958                msg,
959            }),
960        }
961        self.on_log_pushed(log_stream);
962    }
963
964    #[inline]
965    fn log_tagged(
966        &self,
967        desc: &'static LogMetadata,
968        properties: &'static PropertySet,
969        args: fmt::Arguments<'_>,
970    ) {
971        let sink = self.get_sink();
972        if !sink.on_log_enabled(desc) {
973            return;
974        }
975        self.log_tagged_to_sink(&*sink, desc, properties, args);
976    }
977
978    #[inline]
979    fn log_tagged_to_sink(
980        &self,
981        sink: &dyn EventSink,
982        desc: &'static LogMetadata,
983        properties: &'static PropertySet,
984        args: fmt::Arguments<'_>,
985    ) {
986        let time = now();
987        sink.on_log(desc, properties.dyn_properties(), time, args);
988        let properties = PropertySetId::interned(properties);
989        let msg = if is_static_msg(desc, &args) {
990            None
991        } else {
992            Some(micromegas_transit::DynString(args.to_string()))
993        };
994        let mut log_stream = self.log_stream.lock().unwrap();
995        match msg {
996            None => log_stream.get_events_mut().push(TaggedLogStaticStrEvent {
997                desc,
998                properties,
999                time,
1000            }),
1001            Some(msg) => log_stream.get_events_mut().push(TaggedLogString {
1002                desc,
1003                properties,
1004                time,
1005                msg,
1006            }),
1007        }
1008        self.on_log_pushed(log_stream);
1009    }
1010
1011    #[inline]
1012    fn log_interop(&self, desc: &LogMetadata, args: fmt::Arguments<'_>) {
1013        let time = now();
1014        self.get_sink().on_log(desc, &[], time, args);
1015        let msg = args
1016            .as_str()
1017            .ok_or_else(|| micromegas_transit::DynString(args.to_string()));
1018        let mut log_stream = self.log_stream.lock().unwrap();
1019        let (events, dyn_deps) = log_stream.get_block_mut_parts();
1020        match msg {
1021            Ok(static_msg) => {
1022                let id = dyn_deps.intern_str(desc.target);
1023                events.push(LogStaticStrInteropEventV2 {
1024                    time,
1025                    level: desc.level as u32,
1026                    target: DynStringId { id },
1027                    msg: static_msg.into(),
1028                });
1029            }
1030            Err(msg) => {
1031                let target = dyn_deps.intern_str_ref(desc.target);
1032                events.push(LogStringInteropEvent {
1033                    time,
1034                    level: desc.level as u8,
1035                    target,
1036                    msg,
1037                });
1038            }
1039        }
1040        self.on_log_pushed(log_stream);
1041    }
1042
1043    #[inline]
1044    fn log_dyn_tagged(
1045        &self,
1046        desc: &'static LogMetadata,
1047        properties: &'static PropertySet,
1048        dyn_properties: &[DynProperty<'_>],
1049        args: fmt::Arguments<'_>,
1050    ) {
1051        let sink = self.get_sink();
1052        if !sink.on_log_enabled(desc) {
1053            return;
1054        }
1055        self.log_dyn_tagged_to_sink(&*sink, desc, properties, dyn_properties, args);
1056    }
1057
1058    #[inline]
1059    fn log_dyn_tagged_to_sink(
1060        &self,
1061        sink: &dyn EventSink,
1062        desc: &'static LogMetadata,
1063        properties: &'static PropertySet,
1064        dyn_properties: &[DynProperty<'_>],
1065        args: fmt::Arguments<'_>,
1066    ) {
1067        if dyn_properties.is_empty() {
1068            return self.log_tagged_to_sink(sink, desc, properties, args);
1069        }
1070        let time = now();
1071        let merged = merge_properties(properties, dyn_properties);
1072        // Before taking the lock, so that local sinks can log without deadlocking.
1073        sink.on_log(desc, &merged.properties, time, args);
1074        let msg = if is_static_msg(desc, &args) {
1075            None
1076        } else {
1077            Some(micromegas_transit::DynString(args.to_string()))
1078        };
1079        let mut log_stream = self.log_stream.lock().unwrap();
1080        let (events, dyn_deps) = log_stream.get_block_mut_parts();
1081        let properties = dyn_deps.intern_set(&merged);
1082        match msg {
1083            None => events.push(TaggedLogStaticStrEvent {
1084                desc,
1085                properties,
1086                time,
1087            }),
1088            Some(msg) => events.push(TaggedLogString {
1089                desc,
1090                properties,
1091                time,
1092                msg,
1093            }),
1094        }
1095        self.on_log_pushed(log_stream);
1096    }
1097
1098    #[inline]
1099    fn log_interop_dyn_tagged(
1100        &self,
1101        desc: &LogMetadata,
1102        dyn_properties: &[DynProperty<'_>],
1103        args: fmt::Arguments<'_>,
1104    ) {
1105        let time = now();
1106        let merged = merge_properties(PropertySet::empty(), dyn_properties);
1107        // Sinks treat `target`/`level` properties as overrides; interop logs must keep their own.
1108        let is_override = |p: &DynProperty<'_>| {
1109            p.name().eq_ignore_ascii_case("target") || p.name().eq_ignore_ascii_case("level")
1110        };
1111        if merged.properties.iter().any(is_override) {
1112            let filtered: Vec<DynProperty<'_>> = merged
1113                .properties
1114                .iter()
1115                .copied()
1116                .filter(|p| !is_override(p))
1117                .collect();
1118            self.get_sink().on_log(desc, &filtered, time, args);
1119        } else {
1120            self.get_sink().on_log(desc, &merged.properties, time, args);
1121        }
1122        let msg = micromegas_transit::DynString(args.to_string());
1123        let mut log_stream = self.log_stream.lock().unwrap();
1124        let (events, dyn_deps) = log_stream.get_block_mut_parts();
1125        let target = dyn_deps.intern_str_ref(desc.target);
1126        let properties = dyn_deps.intern_set(&merged);
1127        events.push(TaggedLogInteropEvent {
1128            time,
1129            level: desc.level as u8,
1130            target,
1131            properties,
1132            msg,
1133        });
1134        self.on_log_pushed(log_stream);
1135    }
1136
1137    #[inline]
1138    fn on_log_pushed(&self, mut log_stream: MutexGuard<'_, LogStream>) {
1139        if !log_stream.is_full() {
1140            return;
1141        }
1142        let old_block = swap_block(&mut log_stream, self.logs_buffer_size, self.process_id);
1143        drop(log_stream);
1144        self.get_sink().on_process_log_block(old_block);
1145    }
1146
1147    #[inline]
1148    fn flush_log_buffer(&self) {
1149        let mut log_stream = self.log_stream.lock().unwrap();
1150        if log_stream.is_empty() {
1151            return;
1152        }
1153        let old_block = swap_block(&mut log_stream, self.logs_buffer_size, self.process_id);
1154        drop(log_stream);
1155        self.get_sink().on_process_log_block(old_block);
1156    }
1157
1158    #[inline]
1159    fn flush_thread_buffer(&self, stream: &mut ThreadStream) {
1160        if stream.is_empty() {
1161            return;
1162        }
1163        let next_offset =
1164            stream.get_block_ref().object_offset() + stream.get_block_ref().nb_objects();
1165        let new_block = Arc::new(ThreadBlock::new(
1166            self.threads_buffer_size,
1167            self.process_id,
1168            stream.stream_id(),
1169            next_offset,
1170        ));
1171        let begin = new_block.begin;
1172        let mut old_block = stream.replace_block(new_block);
1173        assert!(!stream.is_full());
1174        Arc::get_mut(&mut old_block).unwrap().close_at(begin);
1175        self.get_sink().on_process_thread_block(old_block);
1176    }
1177}
1178
1179/// Replaces the stream's current block with an empty one and returns the closed old block.
1180///
1181/// Must be called with the stream locked: `begin` is stamped here so that consecutive blocks
1182/// stay contiguous.
1183fn swap_block<Q>(
1184    stream: &mut EventStream<EventBlock<Q>>,
1185    buffer_size: usize,
1186    process_id: uuid::Uuid,
1187) -> Arc<EventBlock<Q>>
1188where
1189    Q: micromegas_transit::HeterogeneousQueue + ExtractDeps,
1190    EventBlock<Q>: TracingBlock,
1191{
1192    let stream_id = stream.stream_id();
1193    let next_offset = stream.get_block_ref().object_offset() + stream.get_block_ref().nb_objects();
1194    let new_block = Arc::new(EventBlock::<Q>::new(
1195        buffer_size,
1196        process_id,
1197        stream_id,
1198        next_offset,
1199    ));
1200    let begin = new_block.begin;
1201    let mut old_block = stream.replace_block(new_block);
1202    assert!(!stream.is_full());
1203    Arc::get_mut(&mut old_block).unwrap().close_at(begin);
1204    old_block
1205}
1206
1207fn get_cpu_brand() -> String {
1208    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1209    return raw_cpuid::CpuId::new()
1210        .get_processor_brand_string()
1211        .map_or_else(|| "unknown".to_owned(), |b| b.as_str().to_owned());
1212    #[cfg(target_arch = "aarch64")]
1213    return String::from("aarch64");
1214}
1215
1216pub fn make_process_info(
1217    process_id: uuid::Uuid,
1218    parent_process_id: Option<uuid::Uuid>,
1219    properties: HashMap<String, String>,
1220) -> ProcessInfo {
1221    let start_ticks = now();
1222    let start_time = Utc::now();
1223    let cpu_brand = get_cpu_brand();
1224    ProcessInfo {
1225        process_id,
1226        username: whoami::username(),
1227        realname: whoami::realname(),
1228        exe: std::env::current_exe()
1229            .unwrap_or_default()
1230            .to_string_lossy()
1231            .into_owned(),
1232        computer: whoami::devicename(),
1233        distro: whoami::distro(),
1234        cpu_brand,
1235        tsc_frequency: frequency(),
1236        start_time,
1237        start_ticks,
1238        parent_process_id,
1239        properties,
1240    }
1241}