1use std::cell::Cell;
14use std::sync::Arc;
15use std::sync::atomic::AtomicU64;
16use std::sync::atomic::Ordering;
17use std::sync::mpsc;
18use std::thread::JoinHandle;
19use std::time::Duration;
20use std::time::SystemTime;
21
22use smallvec::SmallVec;
23use tracing::Id;
24use tracing::Subscriber;
25use tracing::level_filters::LevelFilter;
26use tracing_subscriber::filter::Targets;
27use tracing_subscriber::layer::Context;
28use tracing_subscriber::layer::Layer;
29use tracing_subscriber::registry::LookupSpan;
30
31use crate::EntityEvent;
32
33const QUEUE_CAPACITY: usize = 100_000;
34
35pub(crate) type TraceFields = SmallVec<[(&'static str, FieldValue); 4]>;
38
39#[inline]
40pub(crate) fn get_field<'a>(fields: &'a TraceFields, key: &str) -> Option<&'a FieldValue> {
41 fields.iter().find(|(k, _)| *k == key).map(|(_, v)| v)
42}
43
44#[derive(Debug, Clone)]
48pub enum TraceEvent {
49 NewSpan {
51 id: u64,
52 name: &'static str,
53 target: &'static str,
54 level: tracing::Level,
55 fields: TraceFields,
56 timestamp: SystemTime,
57 parent_id: Option<u64>,
58 thread_name: &'static str,
59 file: Option<&'static str>,
60 line: Option<u32>,
61 },
62 SpanEnter {
64 id: u64,
65 timestamp: SystemTime,
66 thread_name: &'static str,
67 },
68 SpanExit {
70 id: u64,
71 timestamp: SystemTime,
72 thread_name: &'static str,
73 },
74 SpanClose { id: u64, timestamp: SystemTime },
76 Event {
78 name: &'static str,
79 target: &'static str,
80 level: tracing::Level,
81 fields: TraceFields,
82 timestamp: SystemTime,
83 parent_span: Option<u64>,
84 thread_id: &'static str,
85 thread_name: &'static str,
86 module_path: Option<&'static str>,
87 file: Option<&'static str>,
88 line: Option<u32>,
89 },
90 Entity(EntityEvent),
92}
93
94#[derive(Debug, Clone)]
96pub enum FieldValue {
97 Bool(bool),
98 I64(i64),
99 U64(u64),
100 F64(f64),
101 Str(String),
102 Debug(String),
103}
104
105pub trait TraceEventSink: Send + 'static {
109 fn consume(&mut self, event: &TraceEvent) -> Result<(), anyhow::Error>;
111
112 fn target_filter(&self) -> Option<&Targets> {
132 None
133 }
134
135 fn flush(&mut self) -> Result<(), anyhow::Error>;
138
139 fn name(&self) -> &str {
141 std::any::type_name::<Self>()
142 }
143}
144
145thread_local! {
146 static CACHED_THREAD_INFO: Cell<Option<(&'static str, &'static str)>> = const { Cell::new(None) };
150}
151
152#[inline(always)]
153fn get_thread_info() -> (&'static str, &'static str) {
154 CACHED_THREAD_INFO.with(|cache| {
155 if let Some(info) = cache.get() {
156 return info;
157 }
158
159 let thread_name: &'static str = Box::leak(
160 std::thread::current()
161 .name()
162 .unwrap_or("")
163 .to_string()
164 .into_boxed_str(),
165 );
166
167 #[cfg(target_os = "linux")]
168 let thread_id: &'static str = {
169 let tid = unsafe { libc::syscall(libc::SYS_gettid) as u64 };
173 Box::leak(tid.to_string().into_boxed_str())
174 };
175 #[cfg(not(target_os = "linux"))]
176 let thread_id: &'static str = {
177 let tid_num = std::thread::current().id().as_u64().get();
178 Box::leak(tid_num.to_string().into_boxed_str())
179 };
180
181 cache.set(Some((thread_name, thread_id)));
182 (thread_name, thread_id)
183 })
184}
185
186pub enum DispatcherControl {
188 AddSink(Box<dyn TraceEventSink>),
190}
191
192pub struct TraceEventDispatcher {
195 sender: Option<mpsc::SyncSender<TraceEvent>>,
196 dropped_sender: Option<mpsc::Sender<TraceEvent>>,
198 _worker_handle: WorkerHandle,
199 max_level: Option<LevelFilter>,
200 dropped_events: Arc<AtomicU64>,
201}
202
203struct WorkerHandle {
204 join_handle: Option<JoinHandle<()>>,
205}
206
207thread_local! {
208 static IN_SEND: Cell<bool> = const { Cell::new(false) };
209}
210
211struct InSendGuard;
212
213impl Drop for InSendGuard {
214 fn drop(&mut self) {
215 IN_SEND.with(|f| f.set(false));
216 }
217}
218
219impl TraceEventDispatcher {
220 pub(crate) fn new(sinks: Vec<Box<dyn TraceEventSink>>) -> Self {
232 let max_level = Self::derive_max_level(&sinks);
233
234 let (sender, receiver) = mpsc::sync_channel(QUEUE_CAPACITY);
235 let (dropped_sender, dropped_receiver) = mpsc::channel();
236 let control_receiver = crate::take_sink_control_receiver();
238 let dropped_events = Arc::new(AtomicU64::new(0));
239 let dropped_events_worker = Arc::clone(&dropped_events);
240
241 let worker_handle = std::thread::Builder::new()
242 .name("telemetry-worker".into())
243 .spawn(move || {
244 worker_loop(
245 receiver,
246 dropped_receiver,
247 control_receiver,
248 sinks,
249 dropped_events_worker,
250 );
251 })
252 .expect("failed to spawn telemetry worker thread");
253
254 Self {
255 sender: Some(sender),
256 dropped_sender: Some(dropped_sender),
257 _worker_handle: WorkerHandle {
258 join_handle: Some(worker_handle),
259 },
260 max_level,
261 dropped_events,
262 }
263 }
264
265 fn derive_max_level(sinks: &[Box<dyn TraceEventSink>]) -> Option<LevelFilter> {
266 let mut max_level: Option<LevelFilter> = None;
267
268 for sink in sinks {
269 let sink_max = match sink.target_filter() {
270 None => LevelFilter::TRACE,
271 Some(targets) => {
272 let levels = [
273 (tracing::Level::TRACE, LevelFilter::TRACE),
274 (tracing::Level::DEBUG, LevelFilter::DEBUG),
275 (tracing::Level::INFO, LevelFilter::INFO),
276 (tracing::Level::WARN, LevelFilter::WARN),
277 (tracing::Level::ERROR, LevelFilter::ERROR),
278 ];
279 let mut result = LevelFilter::OFF;
280 for (level, filter) in levels {
281 if targets.would_enable("", &level) {
282 result = filter;
283 break;
284 }
285 }
286 result
287 }
288 };
289
290 max_level = Some(match max_level {
291 None => sink_max,
292 Some(current) => std::cmp::max(current, sink_max),
293 });
294 }
295
296 max_level
297 }
298
299 fn send_event(&self, event: TraceEvent) {
300 if IN_SEND.with(|f| f.replace(true)) {
305 return;
306 }
307 let _reset = InSendGuard;
308
309 if let Some(sender) = &self.sender
310 && let Err(mpsc::TrySendError::Full(_)) = sender.try_send(event)
311 {
312 let dropped = self.dropped_events.fetch_add(1, Ordering::Relaxed) + 1;
313
314 if dropped == 1 || dropped.is_multiple_of(1000) {
315 eprintln!(
316 "[telemetry]: {} events and log lines dropped que to full queue (capacity: {})",
317 dropped, QUEUE_CAPACITY
318 );
319 self.send_drop_event(dropped);
320 }
321 }
322 }
323
324 pub(crate) fn sender(&self) -> mpsc::SyncSender<TraceEvent> {
325 self.sender
326 .as_ref()
327 .expect("trace event dispatcher sender should exist during initialization")
328 .clone()
329 }
330
331 fn send_drop_event(&self, total_dropped: u64) {
332 if let Some(dropped_sender) = &self.dropped_sender {
333 let (thread_name, thread_id) = get_thread_info();
334
335 let mut fields = TraceFields::new();
336 fields.push((
337 "message",
338 FieldValue::Str(format!(
339 "Telemetry events and log lines dropped due to full queue (capacity: {}). Worker may be falling behind.",
340 QUEUE_CAPACITY
341 )),
342 ));
343 fields.push(("dropped_count", FieldValue::U64(total_dropped)));
344
345 let drop_event = TraceEvent::Event {
348 name: "dropped events",
349 target: module_path!(),
350 level: tracing::Level::ERROR,
351 fields,
352 timestamp: SystemTime::now(),
353 parent_span: None,
354 thread_id,
355 thread_name,
356 module_path: Some(module_path!()),
357 file: Some(file!()),
358 line: Some(line!()),
359 };
360
361 if dropped_sender.send(drop_event).is_err() {
362 eprintln!(
364 "[telemetry] CRITICAL: {} events and log lines dropped and unable to log to telemetry \
365 (worker thread may have died). Telemetry system offline.",
366 total_dropped
367 );
368 }
369 }
370 }
371}
372
373impl Drop for TraceEventDispatcher {
374 fn drop(&mut self) {
375 drop(self.sender.take());
379 drop(self.dropped_sender.take());
380 }
381}
382
383impl<S> Layer<S> for TraceEventDispatcher
384where
385 S: Subscriber + for<'a> LookupSpan<'a>,
386{
387 fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
388 let metadata = attrs.metadata();
389 let mut fields = TraceFields::new();
390
391 let mut visitor = FieldVisitor(&mut fields);
392 attrs.record(&mut visitor);
393
394 let parent_id = if let Some(parent) = attrs.parent() {
395 Some(parent.into_u64())
396 } else {
397 ctx.current_span().id().map(|id| id.into_u64())
398 };
399
400 let (thread_name, _) = get_thread_info();
401
402 let event = TraceEvent::NewSpan {
403 id: id.into_u64(),
404 name: metadata.name(),
405 target: metadata.target(),
406 level: *metadata.level(),
407 fields,
408 timestamp: SystemTime::now(),
409 parent_id,
410 thread_name,
411 file: metadata.file(),
412 line: metadata.line(),
413 };
414
415 self.send_event(event);
416 }
417
418 fn on_enter(&self, id: &Id, _ctx: Context<'_, S>) {
419 let (thread_name, _) = get_thread_info();
420 let event = TraceEvent::SpanEnter {
421 id: id.into_u64(),
422 timestamp: SystemTime::now(),
423 thread_name,
424 };
425
426 self.send_event(event);
427 }
428
429 fn on_exit(&self, id: &Id, _ctx: Context<'_, S>) {
430 let (thread_name, _) = get_thread_info();
431 let event = TraceEvent::SpanExit {
432 id: id.into_u64(),
433 timestamp: SystemTime::now(),
434 thread_name,
435 };
436
437 self.send_event(event);
438 }
439
440 fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
441 let metadata = event.metadata();
442 let mut fields = TraceFields::new();
443 let mut visitor = FieldVisitor(&mut fields);
444 event.record(&mut visitor);
445
446 let parent_span = ctx.event_span(event).map(|span| span.id().into_u64());
447
448 let (thread_name, thread_id) = get_thread_info();
449
450 let trace_event = TraceEvent::Event {
451 name: metadata.name(),
452 target: metadata.target(),
453 level: *metadata.level(),
454 fields,
455 timestamp: SystemTime::now(),
456 parent_span,
457 thread_id,
458 thread_name,
459 module_path: metadata.module_path(),
460 file: metadata.file(),
461 line: metadata.line(),
462 };
463
464 self.send_event(trace_event);
465 }
466
467 fn on_close(&self, id: Id, _ctx: Context<'_, S>) {
468 let event = TraceEvent::SpanClose {
469 id: id.into_u64(),
470 timestamp: SystemTime::now(),
471 };
472
473 self.send_event(event);
474 }
475
476 fn max_level_hint(&self) -> Option<LevelFilter> {
477 self.max_level
478 }
479}
480
481struct FieldVisitor<'a>(&'a mut TraceFields);
482
483impl tracing::field::Visit for FieldVisitor<'_> {
484 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
485 self.0.push((field.name(), FieldValue::Bool(value)));
486 }
487
488 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
489 self.0.push((field.name(), FieldValue::I64(value)));
490 }
491
492 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
493 self.0.push((field.name(), FieldValue::U64(value)));
494 }
495
496 fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
497 self.0.push((field.name(), FieldValue::F64(value)));
498 }
499
500 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
501 self.0
502 .push((field.name(), FieldValue::Str(value.to_string())));
503 }
504
505 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
506 self.0
507 .push((field.name(), FieldValue::Debug(format!("{:?}", value))));
508 }
509}
510
511fn worker_loop(
515 receiver: mpsc::Receiver<TraceEvent>,
516 dropped_receiver: mpsc::Receiver<TraceEvent>,
517 control_receiver: Option<mpsc::Receiver<DispatcherControl>>,
518 mut sinks: Vec<Box<dyn TraceEventSink>>,
519 dropped_events: Arc<AtomicU64>,
520) {
521 const FLUSH_INTERVAL: Duration = Duration::from_millis(100);
522 const FLUSH_EVENT_COUNT: usize = 1000;
523 let mut last_flush = std::time::Instant::now();
524 let mut events_since_flush = 0;
525
526 fn flush_sinks(sinks: &mut [Box<dyn TraceEventSink>]) {
527 for sink in sinks {
528 if let Err(e) = sink.flush() {
529 eprintln!("[telemetry] sink {} failed to flush: {}", sink.name(), e);
530 }
531 }
532 }
533
534 fn process_control_messages(
535 control_receiver: Option<&mpsc::Receiver<DispatcherControl>>,
536 sinks: &mut Vec<Box<dyn TraceEventSink>>,
537 ) {
538 if let Some(ctrl_rx) = control_receiver {
539 while let Ok(control) = ctrl_rx.try_recv() {
540 match control {
541 DispatcherControl::AddSink(sink) => {
542 sinks.push(sink);
543 }
544 }
545 }
546 }
547 }
548
549 fn dispatch_to_sinks(sinks: &mut [Box<dyn TraceEventSink>], event: TraceEvent) {
550 for sink in sinks {
551 if match &event {
552 TraceEvent::NewSpan { target, level, .. }
553 | TraceEvent::Event { target, level, .. } => match sink.target_filter() {
554 Some(targets) => targets.would_enable(target, level),
555 None => true,
556 },
557 _ => true,
562 } && let Err(e) = sink.consume(&event)
563 {
564 eprintln!(
565 "[telemetry] sink {} failed to consume event: {}",
566 sink.name(),
567 e
568 );
569 }
570 }
571 }
572
573 loop {
574 while let Ok(event) = dropped_receiver.try_recv() {
575 dispatch_to_sinks(&mut sinks, event);
576 events_since_flush += 1;
577 }
578
579 match receiver.recv_timeout(FLUSH_INTERVAL) {
580 Ok(event) => {
581 process_control_messages(control_receiver.as_ref(), &mut sinks);
586 dispatch_to_sinks(&mut sinks, event);
587 events_since_flush += 1;
588
589 if events_since_flush >= FLUSH_EVENT_COUNT || last_flush.elapsed() >= FLUSH_INTERVAL
590 {
591 flush_sinks(&mut sinks);
592 last_flush = std::time::Instant::now();
593 events_since_flush = 0;
594 }
595 }
596 Err(mpsc::RecvTimeoutError::Timeout) => {
597 flush_sinks(&mut sinks);
598 last_flush = std::time::Instant::now();
599 events_since_flush = 0;
600 }
601 Err(mpsc::RecvTimeoutError::Disconnected) => {
602 break;
603 }
604 }
605 }
606
607 process_control_messages(control_receiver.as_ref(), &mut sinks);
611
612 while let Ok(event) = dropped_receiver.try_recv() {
613 dispatch_to_sinks(&mut sinks, event);
614 }
615 while let Ok(event) = receiver.try_recv() {
616 dispatch_to_sinks(&mut sinks, event);
617 }
618
619 flush_sinks(&mut sinks);
620
621 let total_dropped = dropped_events.load(Ordering::Relaxed);
622 if total_dropped > 0 {
623 eprintln!(
624 "[telemetry] Telemetry worker shutting down. Total events dropped during session: {}",
625 total_dropped
626 );
627 }
628}
629
630impl Drop for WorkerHandle {
631 fn drop(&mut self) {
632 if let Some(handle) = self.join_handle.take()
633 && let Err(e) = handle.join()
634 {
635 eprintln!("[telemetry] worker thread panicked: {:?}", e);
636 }
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use std::io::Read;
643 use std::os::unix::net::UnixListener;
644 use std::sync::Arc;
645 use std::sync::Mutex;
646 use std::sync::atomic::AtomicU64;
647 use std::sync::atomic::Ordering;
648 use std::sync::mpsc;
649
650 use super::*;
651
652 static TEST_SEQ: AtomicU64 = AtomicU64::new(0);
653
654 #[derive(Default)]
655 struct RecordingSink {
656 events: Arc<Mutex<Vec<TraceEvent>>>,
657 }
658
659 impl TraceEventSink for RecordingSink {
660 fn consume(&mut self, event: &TraceEvent) -> Result<(), anyhow::Error> {
661 self.events.lock().unwrap().push(event.clone());
662 Ok(())
663 }
664
665 fn flush(&mut self) -> Result<(), anyhow::Error> {
666 Ok(())
667 }
668 }
669
670 struct CountingSink {
671 entity_events: Arc<AtomicU64>,
672 target_filter: Option<Targets>,
673 }
674
675 impl TraceEventSink for CountingSink {
676 fn consume(&mut self, event: &TraceEvent) -> Result<(), anyhow::Error> {
677 if matches!(event, TraceEvent::Entity(_)) {
678 self.entity_events.fetch_add(1, Ordering::Relaxed);
679 }
680 Ok(())
681 }
682
683 fn target_filter(&self) -> Option<&Targets> {
684 self.target_filter.as_ref()
685 }
686
687 fn flush(&mut self) -> Result<(), anyhow::Error> {
688 Ok(())
689 }
690 }
691
692 fn span_close(id: u64) -> TraceEvent {
693 TraceEvent::SpanClose {
694 id,
695 timestamp: SystemTime::now(),
696 }
697 }
698
699 fn event() -> TraceEvent {
700 TraceEvent::Event {
701 name: "test_event",
702 target: "test",
703 level: tracing::Level::INFO,
704 fields: TraceFields::new(),
705 timestamp: SystemTime::now(),
706 parent_span: None,
707 thread_id: "1",
708 thread_name: "test",
709 module_path: Some("test"),
710 file: Some("test.rs"),
711 line: Some(1),
712 }
713 }
714
715 fn entity_event() -> TraceEvent {
716 TraceEvent::Entity(EntityEvent::Mesh(crate::MeshEvent {
717 id: 11,
718 timestamp: SystemTime::now(),
719 class: "Host".to_string(),
720 given_name: "test_mesh".to_string(),
721 full_name: "test_mesh".to_string(),
722 shape_json: "{}".to_string(),
723 parent_mesh_id: None,
724 parent_view_json: None,
725 }))
726 }
727
728 fn socket_path(name: &str) -> std::path::PathBuf {
729 let seq = TEST_SEQ.fetch_add(1, Ordering::Relaxed);
730 let dir = std::env::temp_dir().join(format!(
731 "monarch_trace_dispatcher_{}_{}",
732 std::process::id(),
733 seq
734 ));
735 std::fs::create_dir_all(&dir).unwrap();
736 dir.join(name)
737 }
738
739 fn read_frame_table(listener: UnixListener) -> String {
740 let (mut stream, _addr) = listener.accept().unwrap();
741 let mut name_len_bytes = [0; 2];
742 stream.read_exact(&mut name_len_bytes).unwrap();
743 let name_len = u16::from_be_bytes(name_len_bytes) as usize;
744 let mut name_bytes = vec![0; name_len];
745 stream.read_exact(&mut name_bytes).unwrap();
746 String::from_utf8(name_bytes).unwrap()
747 }
748
749 #[test]
750 fn send_event_delivers_repeatedly() {
751 let sink = RecordingSink::default();
752 let recorded = Arc::clone(&sink.events);
753 let dispatcher = TraceEventDispatcher::new(vec![Box::new(sink)]);
754
755 dispatcher.send_event(span_close(1));
756 dispatcher.send_event(span_close(2));
757 dispatcher.send_event(span_close(3));
758
759 drop(dispatcher);
760 assert_eq!(recorded.lock().unwrap().len(), 3);
761 }
762
763 #[test]
764 fn send_event_drops_on_reentrance() {
765 let sink = RecordingSink::default();
766 let recorded = Arc::clone(&sink.events);
767 let dispatcher = TraceEventDispatcher::new(vec![Box::new(sink)]);
768
769 IN_SEND.with(|f| f.set(true));
773 dispatcher.send_event(span_close(1));
774 IN_SEND.with(|f| f.set(false));
775
776 drop(dispatcher);
777 assert!(recorded.lock().unwrap().is_empty());
778 }
779
780 #[test]
781 fn entity_event_bypasses_target_filter_and_reaches_sink() {
782 let entity_events = Arc::new(AtomicU64::new(0));
783 let sink = CountingSink {
784 entity_events: Arc::clone(&entity_events),
785 target_filter: Some(Targets::new().with_default(LevelFilter::OFF)),
786 };
787 let dispatcher = TraceEventDispatcher::new(vec![Box::new(sink)]);
788
789 dispatcher.send_event(entity_event());
790
791 drop(dispatcher);
792 assert_eq!(entity_events.load(Ordering::Relaxed), 1);
793 }
794
795 #[test]
796 fn shutdown_drain_processes_pending_sink_registration() {
797 let (sender, receiver) = mpsc::sync_channel(1);
798 let (dropped_sender, dropped_receiver) = mpsc::channel();
799 let (control_sender, control_receiver) = mpsc::channel();
800 let recorded = Arc::new(Mutex::new(Vec::new()));
801 let dropped_events = Arc::new(AtomicU64::new(0));
802
803 let worker = std::thread::spawn(move || {
804 worker_loop(
805 receiver,
806 dropped_receiver,
807 Some(control_receiver),
808 Vec::new(),
809 dropped_events,
810 );
811 });
812
813 std::thread::sleep(Duration::from_millis(200));
818
819 control_sender
820 .send(DispatcherControl::AddSink(Box::new(RecordingSink {
821 events: Arc::clone(&recorded),
822 })))
823 .unwrap();
824 dropped_sender.send(span_close(7)).unwrap();
825
826 drop(sender);
827 drop(dropped_sender);
828 drop(control_sender);
829 worker.join().unwrap();
830
831 assert_eq!(recorded.lock().unwrap().len(), 1);
832 }
833
834 #[test]
835 fn unix_socket_sink_receives_dispatched_event() {
836 let path = socket_path("telemetry.sock");
837 let listener = UnixListener::bind(&path).unwrap();
838 let (sender, receiver) = mpsc::channel();
839 let read_handle = std::thread::spawn(move || {
840 sender.send(read_frame_table(listener)).unwrap();
841 });
842
843 let sink = Arc::new(crate::unix_sink::UnixSocketSink::new());
844 sink.set_path(path).unwrap();
845 let dispatcher =
846 TraceEventDispatcher::new(vec![crate::unix_sink::adapter_for_test(Arc::clone(&sink))]);
847
848 dispatcher.send_event(event());
849
850 drop(dispatcher);
851 let table_name = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
852 read_handle.join().unwrap();
853 assert_eq!(table_name, monarch_telemetry_schema::trace_tables::EVENTS);
854 }
855}