Skip to main content

micromegas_tracing/
dispatch.rs

1//! Where events are recorded and eventually sent to a sink
2pub use crate::errors::{Error, Result};
3use crate::images::{ImageBlock, ImageEvent, ImageStream};
4use crate::intern_string::intern_string;
5use crate::logs::TaggedLogString;
6use crate::metrics::{TaggedFloatMetricEvent, TaggedIntegerMetricEvent};
7use crate::prelude::*;
8use crate::property_set::PropertySet;
9use crate::{
10    event::{EventSink, NullEventSink, TracingBlock},
11    info,
12    logs::{
13        LogBlock, LogMetadata, LogStaticStrEvent, LogStaticStrInteropEvent, LogStream,
14        LogStringEvent, LogStringInteropEvent,
15    },
16    metrics::{
17        FloatMetricEvent, IntegerMetricEvent, MetricsBlock, MetricsStream, StaticMetricMetadata,
18    },
19    spans::{
20        BeginAsyncNamedSpanEvent, BeginAsyncSpanEvent, BeginThreadNamedSpanEvent,
21        BeginThreadSpanEvent, EndAsyncNamedSpanEvent, EndAsyncSpanEvent, EndThreadNamedSpanEvent,
22        EndThreadSpanEvent, SpanLocation, SpanMetadata, ThreadBlock, ThreadEventQueueTypeIndex,
23        ThreadStream,
24    },
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},
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 log(desc: &'static LogMetadata, args: fmt::Arguments<'_>) {
128    if let Some(d) = G_DISPATCH.get() {
129        d.log(desc, args);
130    }
131}
132
133#[inline(always)]
134pub fn log_tagged(
135    desc: &'static LogMetadata,
136    properties: &'static PropertySet,
137    args: fmt::Arguments<'_>,
138) {
139    if let Some(d) = G_DISPATCH.get() {
140        d.log_tagged(desc, properties, args);
141    }
142}
143
144#[inline(always)]
145pub fn log_interop(metadata: &LogMetadata, args: fmt::Arguments<'_>) {
146    if let Some(d) = G_DISPATCH.get() {
147        d.log_interop(metadata, args);
148    }
149}
150
151#[inline(always)]
152pub fn log_enabled(metadata: &LogMetadata) -> bool {
153    if let Some(d) = G_DISPATCH.get() {
154        d.log_enabled(metadata)
155    } else {
156        false
157    }
158}
159
160#[inline(always)]
161pub fn flush_log_buffer() {
162    if let Some(d) = G_DISPATCH.get() {
163        d.flush_log_buffer();
164    }
165}
166
167pub fn send_image(name: &str, format: &str, data: Vec<u8>) {
168    if let Some(d) = G_DISPATCH.get() {
169        d.send_image(name, format, data);
170    }
171}
172
173#[inline(always)]
174pub fn flush_image_buffer() {
175    if let Some(d) = G_DISPATCH.get() {
176        d.flush_image_buffer();
177    }
178}
179
180#[inline(always)]
181pub fn flush_metrics_buffer() {
182    if let Some(d) = G_DISPATCH.get() {
183        d.flush_metrics_buffer();
184    }
185}
186
187//todo: should be implicit by default but limit the maximum number of tracked
188// threads
189#[inline(always)]
190pub fn init_thread_stream() {
191    LOCAL_THREAD_STREAM.with(|cell| unsafe {
192        if (*cell.as_ptr()).is_some() {
193            return;
194        }
195        #[allow(static_mut_refs)]
196        if let Some(d) = G_DISPATCH.get() {
197            // Check if CPU tracing is enabled before creating thread stream
198            if !d.cpu_tracing_enabled {
199                return;
200            }
201            d.init_thread_stream(cell);
202        } else {
203            warn!("dispatch not initialized, cannot init thread stream, events will be lost for this thread");
204        }
205    });
206}
207
208pub fn for_each_thread_stream(fun: &mut dyn FnMut(*mut ThreadStream)) {
209    if let Some(d) = G_DISPATCH.get() {
210        d.for_each_thread_stream(fun);
211    }
212}
213
214#[inline(always)]
215pub fn flush_thread_buffer() {
216    LOCAL_THREAD_STREAM.with(|cell| unsafe {
217        let opt_stream = &mut *cell.as_ptr();
218        if let Some(stream) = opt_stream {
219            #[allow(static_mut_refs)]
220            match G_DISPATCH.get() {
221                Some(d) => {
222                    d.flush_thread_buffer(stream);
223                }
224                None => {
225                    panic!("threads are recording but there is no event dispatch");
226                }
227            }
228        }
229    });
230}
231
232/// Unregisters the current thread's stream from the global dispatch to prevent
233/// dangling pointers when the thread is destroyed.
234#[inline(always)]
235pub fn unregister_thread_stream() {
236    LOCAL_THREAD_STREAM.with(|cell| unsafe {
237        let opt_stream = &mut *cell.as_ptr();
238        if let Some(stream) = opt_stream {
239            #[allow(static_mut_refs)]
240            match G_DISPATCH.get() {
241                Some(d) => {
242                    // Flush any remaining events before unregistering
243                    d.flush_thread_buffer(stream);
244                    // Unregister the thread stream
245                    d.unregister_thread_stream(stream);
246                    // Clear the local thread stream
247                    *opt_stream = None;
248                }
249                None => {
250                    // If dispatch is already shut down, just clear the local stream
251                    *opt_stream = None;
252                }
253            }
254        }
255    });
256}
257
258#[inline(always)]
259pub fn on_begin_scope(scope: &'static SpanMetadata) {
260    on_thread_event(BeginThreadSpanEvent {
261        time: now(),
262        thread_span_desc: scope,
263    });
264}
265
266#[inline(always)]
267pub fn on_end_scope(scope: &'static SpanMetadata) {
268    on_thread_event(EndThreadSpanEvent {
269        time: now(),
270        thread_span_desc: scope,
271    });
272}
273
274#[inline(always)]
275pub fn on_begin_named_scope(thread_span_location: &'static SpanLocation, name: &'static str) {
276    on_thread_event(BeginThreadNamedSpanEvent {
277        thread_span_location,
278        name: name.into(),
279        time: now(),
280    });
281}
282
283#[inline(always)]
284pub fn on_end_named_scope(thread_span_location: &'static SpanLocation, name: &'static str) {
285    on_thread_event(EndThreadNamedSpanEvent {
286        thread_span_location,
287        name: name.into(),
288        time: now(),
289    });
290}
291
292#[inline(always)]
293pub fn on_begin_async_scope(scope: &'static SpanMetadata, parent_span_id: u64, depth: u32) -> u64 {
294    let id = G_ASYNC_SPAN_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
295    on_thread_event(BeginAsyncSpanEvent {
296        span_desc: scope,
297        span_id: id as u64,
298        parent_span_id,
299        depth,
300        time: now(),
301    });
302    id as u64
303}
304
305#[inline(always)]
306pub fn on_end_async_scope(
307    span_id: u64,
308    parent_span_id: u64,
309    scope: &'static SpanMetadata,
310    depth: u32,
311) {
312    on_thread_event(EndAsyncSpanEvent {
313        span_desc: scope,
314        span_id,
315        parent_span_id,
316        depth,
317        time: now(),
318    });
319}
320
321#[inline(always)]
322pub fn on_begin_async_named_scope(
323    span_location: &'static SpanLocation,
324    name: &'static str,
325    parent_span_id: u64,
326    depth: u32,
327) -> u64 {
328    let id = G_ASYNC_SPAN_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
329    on_thread_event(BeginAsyncNamedSpanEvent {
330        span_location,
331        name: name.into(),
332        span_id: id as u64,
333        parent_span_id,
334        depth,
335        time: now(),
336    });
337    id as u64
338}
339
340#[inline(always)]
341pub fn on_end_async_named_scope(
342    span_id: u64,
343    parent_span_id: u64,
344    span_location: &'static SpanLocation,
345    name: &'static str,
346    depth: u32,
347) {
348    on_thread_event(EndAsyncNamedSpanEvent {
349        span_location,
350        name: name.into(),
351        span_id,
352        parent_span_id,
353        depth,
354        time: now(),
355    });
356}
357
358pub struct DispatchCell {
359    // unsafecell is only necessary for force_uninit()
360    inner: UnsafeCell<OnceCell<Dispatch>>,
361}
362
363impl DispatchCell {
364    const fn new() -> Self {
365        Self {
366            inner: UnsafeCell::new(OnceCell::new()),
367        }
368    }
369
370    fn get(&self) -> Option<&Dispatch> {
371        unsafe { (*self.inner.get()).get() }
372    }
373
374    unsafe fn take(&self) -> Option<Dispatch> {
375        unsafe { (*self.inner.get()).take() }
376    }
377}
378
379// very unsafe indeed - we don't want to pay for locking every time we need to record an event
380unsafe impl Sync for DispatchCell {}
381
382static G_DISPATCH: DispatchCell = DispatchCell::new();
383static G_ASYNC_SPAN_COUNTER: std::sync::atomic::AtomicUsize =
384    std::sync::atomic::AtomicUsize::new(1);
385
386/// # Safety
387/// very unsafe! make sure there is no thread running that could be sending events
388/// normally that global state should not be destroyed
389pub unsafe fn force_uninit() {
390    unsafe {
391        G_DISPATCH.take();
392    }
393}
394
395thread_local! {
396    static LOCAL_THREAD_STREAM: Cell<Option<ThreadStream>> = const { Cell::new(None) };
397}
398
399#[inline(always)]
400fn on_thread_event<T>(event: T)
401where
402    T: micromegas_transit::InProcSerialize + ThreadEventQueueTypeIndex,
403{
404    LOCAL_THREAD_STREAM.with(|cell| unsafe {
405        let opt_stream = &mut *cell.as_ptr();
406        if let Some(stream) = opt_stream {
407            stream.get_events_mut().push(event);
408            if stream.is_full() {
409                flush_thread_buffer();
410            }
411        }
412    });
413}
414
415struct Dispatch {
416    process_id: uuid::Uuid,
417    logs_buffer_size: usize,
418    metrics_buffer_size: usize,
419    threads_buffer_size: usize,
420    cpu_tracing_enabled: bool,
421    image_stream: Mutex<ImageStream>,
422    log_stream: Mutex<LogStream>,
423    metrics_stream: Mutex<MetricsStream>,
424    thread_streams: Mutex<Vec<*mut ThreadStream>>, // very very unsafe - threads would need to be unregistered before they are destroyed
425    sink: RwLock<Arc<dyn EventSink>>,
426}
427
428impl Dispatch {
429    pub fn new(
430        logs_buffer_size: usize,
431        metrics_buffer_size: usize,
432        threads_buffer_size: usize,
433        sink: Arc<dyn EventSink>,
434        process_properties: HashMap<String, String>,
435        cpu_tracing_enabled: bool,
436    ) -> Self {
437        let process_id = uuid::Uuid::new_v4();
438        let obj = Self {
439            process_id,
440            logs_buffer_size,
441            metrics_buffer_size,
442            threads_buffer_size,
443            cpu_tracing_enabled,
444            image_stream: Mutex::new(ImageStream::new(
445                IMAGE_BUFFER_SIZE,
446                process_id,
447                &[String::from("image")],
448                HashMap::new(),
449            )),
450            log_stream: Mutex::new(LogStream::new(
451                logs_buffer_size,
452                process_id,
453                &[String::from("log")],
454                HashMap::new(),
455            )),
456            metrics_stream: Mutex::new(MetricsStream::new(
457                metrics_buffer_size,
458                process_id,
459                &[String::from("metrics")],
460                HashMap::new(),
461            )),
462            thread_streams: Mutex::new(vec![]),
463            sink: RwLock::new(sink),
464        };
465        obj.startup(process_properties);
466        obj.init_image_stream();
467        obj.init_log_stream();
468        obj.init_metrics_stream();
469        obj
470    }
471
472    pub fn get_process_id(&self) -> uuid::Uuid {
473        self.process_id
474    }
475
476    pub fn get_cpu_tracing_enabled(&self) -> bool {
477        self.cpu_tracing_enabled
478    }
479
480    pub fn get_sink(&self) -> Arc<dyn EventSink> {
481        if let Ok(guard) = self.sink.try_read() {
482            (*guard).clone()
483        } else {
484            Arc::new(NullEventSink {})
485        }
486    }
487
488    fn shutdown(&self) {
489        let old_sink = self.get_sink();
490        let null_sink = Arc::new(NullEventSink {});
491        if let Ok(mut guard) = self.sink.write() {
492            *guard = null_sink;
493            drop(guard)
494        }
495        old_sink.on_shutdown();
496    }
497
498    fn startup(&self, process_properties: HashMap<String, String>) {
499        let mut parent_process = None;
500
501        if let Ok(parent_process_guid) = std::env::var("MICROMEGAS_TELEMETRY_PARENT_PROCESS")
502            && let Ok(parent_process_id) = uuid::Uuid::try_parse(&parent_process_guid)
503        {
504            parent_process = Some(parent_process_id);
505        }
506
507        unsafe {
508            std::env::set_var(
509                "MICROMEGAS_TELEMETRY_PARENT_PROCESS",
510                self.process_id.to_string(),
511            );
512        }
513
514        let process_info = Arc::new(make_process_info(
515            self.process_id,
516            parent_process,
517            process_properties,
518        ));
519
520        self.get_sink().on_startup(process_info);
521    }
522
523    fn init_log_stream(&self) {
524        let log_stream = self.log_stream.lock().unwrap();
525        self.get_sink().on_init_log_stream(&log_stream);
526    }
527
528    fn init_image_stream(&self) {
529        let image_stream = self.image_stream.lock().expect("image_stream lock");
530        self.get_sink().on_init_image_stream(&image_stream);
531    }
532
533    fn init_metrics_stream(&self) {
534        let metrics_stream = self.metrics_stream.lock().unwrap();
535        self.get_sink().on_init_metrics_stream(&metrics_stream);
536    }
537
538    pub fn send_image(&self, name: &str, format: &str, data: Vec<u8>) {
539        let time = now();
540        let mut image_stream = self.image_stream.lock().expect("image_stream lock");
541        image_stream.get_events_mut().push(ImageEvent {
542            time,
543            name: micromegas_transit::DynString(name.to_owned()),
544            format: micromegas_transit::DynString(format.to_owned()),
545            data: micromegas_transit::DynBlob(data),
546        });
547        if image_stream.is_full() {
548            drop(image_stream);
549            self.flush_image_buffer();
550        }
551    }
552
553    fn flush_image_buffer(&self) {
554        let mut image_stream = self.image_stream.lock().expect("image_stream lock");
555        if image_stream.is_empty() {
556            return;
557        }
558        let stream_id = image_stream.stream_id();
559        let next_offset = image_stream.get_block_ref().object_offset()
560            + image_stream.get_block_ref().nb_objects();
561        let mut old_event_block = image_stream.replace_block(Arc::new(ImageBlock::new(
562            IMAGE_BUFFER_SIZE,
563            self.process_id,
564            stream_id,
565            next_offset,
566        )));
567        assert!(!image_stream.is_full());
568        Arc::get_mut(&mut old_event_block)
569            .expect("image block exclusive ref")
570            .close();
571        drop(image_stream);
572        self.get_sink().on_process_image_block(old_event_block);
573    }
574
575    fn init_thread_stream(&self, cell: &Cell<Option<ThreadStream>>) {
576        // Early return if CPU tracing is disabled
577        if !self.cpu_tracing_enabled {
578            return;
579        }
580
581        let mut properties = HashMap::new();
582        properties.insert(String::from("thread-id"), thread_id::get().to_string());
583        if let Some(name) = std::thread::current().name() {
584            properties.insert("thread-name".to_owned(), name.to_owned());
585        }
586        let thread_stream = ThreadStream::new(
587            self.threads_buffer_size,
588            self.process_id,
589            &["cpu".to_owned()],
590            properties,
591        );
592        unsafe {
593            let opt_ref = &mut *cell.as_ptr();
594            self.get_sink().on_init_thread_stream(&thread_stream);
595            *opt_ref = Some(thread_stream);
596            let mut vec_guard = self.thread_streams.lock().unwrap();
597            vec_guard.push(opt_ref.as_mut().unwrap());
598        }
599    }
600
601    fn for_each_thread_stream(&self, fun: &mut dyn FnMut(*mut ThreadStream)) {
602        let mut vec_guard = self.thread_streams.lock().unwrap();
603        for stream in &mut *vec_guard {
604            fun(*stream);
605        }
606    }
607
608    fn unregister_thread_stream(&self, stream_to_remove: &mut ThreadStream) {
609        let mut vec_guard = self.thread_streams.lock().unwrap();
610        let stream_ptr = stream_to_remove as *mut ThreadStream;
611
612        // Find and remove the thread stream pointer from the vector
613        if let Some(pos) = vec_guard.iter().position(|&ptr| ptr == stream_ptr) {
614            vec_guard.remove(pos);
615        }
616    }
617
618    #[inline]
619    fn int_metric(&self, desc: &'static StaticMetricMetadata, value: u64) {
620        let time = now();
621        let mut metrics_stream = self.metrics_stream.lock().unwrap();
622        metrics_stream
623            .get_events_mut()
624            .push(IntegerMetricEvent { desc, value, time });
625        if metrics_stream.is_full() {
626            // Release the lock before calling flush_metrics_buffer
627            drop(metrics_stream);
628            self.flush_metrics_buffer();
629        }
630    }
631
632    #[inline]
633    fn float_metric(&self, desc: &'static StaticMetricMetadata, value: f64) {
634        let time = now();
635        let mut metrics_stream = self.metrics_stream.lock().unwrap();
636        metrics_stream
637            .get_events_mut()
638            .push(FloatMetricEvent { desc, value, time });
639        if metrics_stream.is_full() {
640            drop(metrics_stream);
641            // Release the lock before calling flush_metrics_buffer
642            self.flush_metrics_buffer();
643        }
644    }
645
646    #[inline]
647    fn tagged_float_metric(
648        &self,
649        desc: &'static StaticMetricMetadata,
650        properties: &'static PropertySet,
651        value: f64,
652    ) {
653        let time = now();
654        let mut metrics_stream = self.metrics_stream.lock().unwrap();
655        metrics_stream
656            .get_events_mut()
657            .push(TaggedFloatMetricEvent {
658                desc,
659                properties,
660                value,
661                time,
662            });
663        if metrics_stream.is_full() {
664            drop(metrics_stream);
665            // Release the lock before calling flush_metrics_buffer
666            self.flush_metrics_buffer();
667        }
668    }
669
670    #[inline]
671    fn tagged_integer_metric(
672        &self,
673        desc: &'static StaticMetricMetadata,
674        properties: &'static PropertySet,
675        value: u64,
676    ) {
677        let time = now();
678        let mut metrics_stream = self.metrics_stream.lock().unwrap();
679        metrics_stream
680            .get_events_mut()
681            .push(TaggedIntegerMetricEvent {
682                desc,
683                properties,
684                value,
685                time,
686            });
687        if metrics_stream.is_full() {
688            drop(metrics_stream);
689            // Release the lock before calling flush_metrics_buffer
690            self.flush_metrics_buffer();
691        }
692    }
693
694    #[inline]
695    fn flush_metrics_buffer(&self) {
696        let mut metrics_stream = self.metrics_stream.lock().unwrap();
697        if metrics_stream.is_empty() {
698            return;
699        }
700        let stream_id = metrics_stream.stream_id();
701        let next_offset = metrics_stream.get_block_ref().object_offset()
702            + metrics_stream.get_block_ref().nb_objects();
703        let mut old_event_block = metrics_stream.replace_block(Arc::new(MetricsBlock::new(
704            self.metrics_buffer_size,
705            self.process_id,
706            stream_id,
707            next_offset,
708        )));
709        assert!(!metrics_stream.is_full());
710        Arc::get_mut(&mut old_event_block).unwrap().close();
711        self.get_sink().on_process_metrics_block(old_event_block);
712    }
713
714    fn log_enabled(&self, metadata: &LogMetadata) -> bool {
715        self.get_sink().on_log_enabled(metadata)
716    }
717
718    #[inline]
719    fn log(&self, metadata: &'static LogMetadata, args: fmt::Arguments<'_>) {
720        if !self.log_enabled(metadata) {
721            return;
722        }
723        let time = now();
724        self.get_sink().on_log(metadata, &[], time, args);
725        let mut log_stream = self.log_stream.lock().unwrap();
726        if args.as_str().is_some() {
727            log_stream.get_events_mut().push(LogStaticStrEvent {
728                desc: metadata,
729                time,
730            });
731        } else {
732            log_stream.get_events_mut().push(LogStringEvent {
733                desc: metadata,
734                time,
735                msg: micromegas_transit::DynString(args.to_string()),
736            });
737        }
738        if log_stream.is_full() {
739            // Release the lock before calling flush_log_buffer
740            drop(log_stream);
741            self.flush_log_buffer();
742        }
743    }
744
745    #[inline]
746    fn log_tagged(
747        &self,
748        desc: &'static LogMetadata,
749        properties: &'static PropertySet,
750        args: fmt::Arguments<'_>,
751    ) {
752        if !self.log_enabled(desc) {
753            return;
754        }
755        let time = now();
756        self.get_sink()
757            .on_log(desc, properties.get_properties(), time, args);
758        let mut log_stream = self.log_stream.lock().unwrap();
759        log_stream.get_events_mut().push(TaggedLogString {
760            desc,
761            properties,
762            time,
763            msg: micromegas_transit::DynString(args.to_string()),
764        });
765        if log_stream.is_full() {
766            // Release the lock before calling flush_log_buffer
767            drop(log_stream);
768            self.flush_log_buffer();
769        }
770    }
771
772    #[inline]
773    fn log_interop(&self, desc: &LogMetadata, args: fmt::Arguments<'_>) {
774        let time = now();
775        self.get_sink().on_log(desc, &[], time, args);
776        let mut log_stream = self.log_stream.lock().unwrap();
777        if let Some(msg) = args.as_str() {
778            log_stream.get_events_mut().push(LogStaticStrInteropEvent {
779                time,
780                level: desc.level as u32,
781                target: intern_string(desc.target).into(),
782                msg: msg.into(),
783            });
784        } else {
785            log_stream.get_events_mut().push(LogStringInteropEvent {
786                time,
787                level: desc.level as u8,
788                target: intern_string(desc.target).into(),
789                msg: micromegas_transit::DynString(args.to_string()),
790            });
791        }
792        if log_stream.is_full() {
793            // Release the lock before calling flush_log_buffer
794            drop(log_stream);
795            self.flush_log_buffer();
796        }
797    }
798
799    #[inline]
800    fn flush_log_buffer(&self) {
801        let mut log_stream = self.log_stream.lock().unwrap();
802        if log_stream.is_empty() {
803            return;
804        }
805        let stream_id = log_stream.stream_id();
806        let next_offset =
807            log_stream.get_block_ref().object_offset() + log_stream.get_block_ref().nb_objects();
808        let mut old_event_block = log_stream.replace_block(Arc::new(LogBlock::new(
809            self.logs_buffer_size,
810            self.process_id,
811            stream_id,
812            next_offset,
813        )));
814        assert!(!log_stream.is_full());
815        Arc::get_mut(&mut old_event_block).unwrap().close();
816        self.get_sink().on_process_log_block(old_event_block);
817    }
818
819    #[inline]
820    fn flush_thread_buffer(&self, stream: &mut ThreadStream) {
821        if stream.is_empty() {
822            return;
823        }
824        let next_offset =
825            stream.get_block_ref().object_offset() + stream.get_block_ref().nb_objects();
826        let mut old_block = stream.replace_block(Arc::new(ThreadBlock::new(
827            self.threads_buffer_size,
828            self.process_id,
829            stream.stream_id(),
830            next_offset,
831        )));
832        assert!(!stream.is_full());
833        Arc::get_mut(&mut old_block).unwrap().close();
834        self.get_sink().on_process_thread_block(old_block);
835    }
836}
837
838fn get_cpu_brand() -> String {
839    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
840    return raw_cpuid::CpuId::new()
841        .get_processor_brand_string()
842        .map_or_else(|| "unknown".to_owned(), |b| b.as_str().to_owned());
843    #[cfg(target_arch = "aarch64")]
844    return String::from("aarch64");
845}
846
847pub fn make_process_info(
848    process_id: uuid::Uuid,
849    parent_process_id: Option<uuid::Uuid>,
850    properties: HashMap<String, String>,
851) -> ProcessInfo {
852    let start_ticks = now();
853    let start_time = Utc::now();
854    let cpu_brand = get_cpu_brand();
855    ProcessInfo {
856        process_id,
857        username: whoami::username(),
858        realname: whoami::realname(),
859        exe: std::env::current_exe()
860            .unwrap_or_default()
861            .to_string_lossy()
862            .into_owned(),
863        computer: whoami::devicename(),
864        distro: whoami::distro(),
865        cpu_brand,
866        tsc_frequency: frequency(),
867        start_time,
868        start_ticks,
869        parent_process_id,
870        properties,
871    }
872}