1use std::io::Write;
27use std::os::unix::net::UnixStream;
28use std::path::PathBuf;
29use std::sync::Arc;
30use std::sync::LazyLock;
31use std::sync::Mutex;
32use std::sync::OnceLock;
33use std::sync::atomic::AtomicU64;
34use std::sync::atomic::Ordering;
35use std::sync::mpsc;
36use std::time::SystemTime;
37
38use monarch_record_batch::RecordBatchBuffer;
39use monarch_telemetry_schema::MAX_FRAME_LEN;
40use monarch_telemetry_schema::entity_tables::ACTOR_STATUS_EVENTS;
41use monarch_telemetry_schema::entity_tables::ACTORS;
42use monarch_telemetry_schema::entity_tables::Actor;
43use monarch_telemetry_schema::entity_tables::ActorBuffer;
44use monarch_telemetry_schema::entity_tables::ActorStatusEvent as ActorStatusEventRow;
45use monarch_telemetry_schema::entity_tables::ActorStatusEventBuffer;
46use monarch_telemetry_schema::entity_tables::MESHES;
47use monarch_telemetry_schema::entity_tables::MESSAGE_STATUS_EVENTS;
48use monarch_telemetry_schema::entity_tables::MESSAGES;
49use monarch_telemetry_schema::entity_tables::Mesh;
50use monarch_telemetry_schema::entity_tables::MeshBuffer;
51use monarch_telemetry_schema::entity_tables::Message;
52use monarch_telemetry_schema::entity_tables::MessageBuffer;
53use monarch_telemetry_schema::entity_tables::MessageStatusEvent as MessageStatusEventRow;
54use monarch_telemetry_schema::entity_tables::MessageStatusEventBuffer;
55use monarch_telemetry_schema::entity_tables::SENT_MESSAGES;
56use monarch_telemetry_schema::entity_tables::SentMessage;
57use monarch_telemetry_schema::entity_tables::SentMessageBuffer;
58use monarch_telemetry_schema::metric_tables::METRIC_GAUGES;
59use monarch_telemetry_schema::metric_tables::METRIC_HISTOGRAMS;
60use monarch_telemetry_schema::metric_tables::METRIC_SUMS;
61use monarch_telemetry_schema::metric_tables::MetricGaugeBuffer;
62use monarch_telemetry_schema::metric_tables::MetricHistogramBuffer;
63use monarch_telemetry_schema::metric_tables::MetricSumBuffer;
64use monarch_telemetry_schema::trace_tables::EVENTS;
65use monarch_telemetry_schema::trace_tables::Event;
66use monarch_telemetry_schema::trace_tables::EventBuffer;
67use monarch_telemetry_schema::trace_tables::SPAN_EVENTS;
68use monarch_telemetry_schema::trace_tables::SPANS;
69use monarch_telemetry_schema::trace_tables::Span;
70use monarch_telemetry_schema::trace_tables::SpanBuffer;
71use monarch_telemetry_schema::trace_tables::SpanEvent;
72use monarch_telemetry_schema::trace_tables::SpanEventBuffer;
73use monarch_telemetry_schema::write_frame_header;
74use tracing_subscriber::filter::Targets;
75
76use crate::EntityEvent;
77use crate::FieldValue;
78use crate::TraceEvent;
79use crate::TraceEventSink;
80use crate::config::get_tracing_targets;
81use crate::generate_sent_message_id;
82
83const WORKER_QUEUE_CAPACITY: usize = 10_000;
85const HYPERACTOR_PROCESS_NAME_ENV: &str = "HYPERACTOR_PROCESS_NAME";
86const HOSTNAME_ENV: &str = "HOSTNAME";
87
88fn fallback_process_id() -> String {
89 let pid = std::process::id();
90 let hostname = std::env::var(HOSTNAME_ENV)
91 .ok()
92 .filter(|hostname| !hostname.is_empty())
93 .unwrap_or_else(|| format!("{:032x}", rand::random::<u128>()));
94
95 format!("unnamed@{hostname}:{pid}")
96}
97
98static PROCESS_ID: LazyLock<String> = LazyLock::new(|| {
100 std::env::var(HYPERACTOR_PROCESS_NAME_ENV)
101 .ok()
102 .filter(|process_id| !process_id.is_empty())
103 .unwrap_or_else(fallback_process_id)
104});
105
106static UNIX_SOCKET_SINK: OnceLock<Arc<UnixSocketSink>> = OnceLock::new();
108
109fn unix_socket_sink() -> &'static Arc<UnixSocketSink> {
110 UNIX_SOCKET_SINK.get_or_init(|| Arc::new(UnixSocketSink::new()))
111}
112
113pub struct UnixSocketSink {
115 inner: Mutex<UnixSocketSinkInner>,
118 dropped: Arc<AtomicU64>,
121}
122
123struct UnixSocketSinkInner {
124 spans_buffer: SpanBuffer,
125 span_events_buffer: SpanEventBuffer,
126 events_buffer: EventBuffer,
127 actors_buffer: ActorBuffer,
128 meshes_buffer: MeshBuffer,
129 actor_status_events_buffer: ActorStatusEventBuffer,
130 sent_messages_buffer: SentMessageBuffer,
131 messages_buffer: MessageBuffer,
132 message_status_events_buffer: MessageStatusEventBuffer,
133 path: Option<PathBuf>,
134 worker: Option<WorkerHandle>,
135}
136
137struct WorkerHandle {
138 sender: mpsc::SyncSender<TelemetryTableBuffer>,
139 _join_handle: std::thread::JoinHandle<()>,
140}
141
142enum TelemetryTableBuffer {
143 Spans(SpanBuffer),
144 SpanEvents(SpanEventBuffer),
145 Events(EventBuffer),
146 Actors(ActorBuffer),
147 Meshes(MeshBuffer),
148 ActorStatusEvents(ActorStatusEventBuffer),
149 SentMessages(SentMessageBuffer),
150 Messages(MessageBuffer),
151 MessageStatusEvents(MessageStatusEventBuffer),
152 MetricGauges(MetricGaugeBuffer),
153 MetricSums(MetricSumBuffer),
154 MetricHistograms(MetricHistogramBuffer),
155}
156
157struct UnixSocketSinkAdapter {
158 sink: Arc<UnixSocketSink>,
161 target_filter: Targets,
162}
163
164impl UnixSocketSink {
165 pub fn new() -> Self {
167 Self {
168 inner: Mutex::new(UnixSocketSinkInner {
169 spans_buffer: SpanBuffer::default(),
170 span_events_buffer: SpanEventBuffer::default(),
171 events_buffer: EventBuffer::default(),
172 actors_buffer: ActorBuffer::default(),
173 meshes_buffer: MeshBuffer::default(),
174 actor_status_events_buffer: ActorStatusEventBuffer::default(),
175 sent_messages_buffer: SentMessageBuffer::default(),
176 messages_buffer: MessageBuffer::default(),
177 message_status_events_buffer: MessageStatusEventBuffer::default(),
178 path: None,
179 worker: None,
180 }),
181 dropped: Arc::new(AtomicU64::new(0)),
182 }
183 }
184
185 pub fn set_path(&self, path: PathBuf) -> anyhow::Result<()> {
190 let mut inner = self
191 .inner
192 .lock()
193 .map_err(|_| anyhow::anyhow!("lock poisoned"))?;
194
195 if inner.path.as_ref() == Some(&path) {
196 return Ok(());
197 }
198 if let Some(existing) = &inner.path {
199 anyhow::bail!(
200 "unix socket sink already activated with {}",
201 existing.display()
202 );
203 }
204
205 let (sender, receiver) = mpsc::sync_channel(WORKER_QUEUE_CAPACITY);
209 let dropped = Arc::clone(&self.dropped);
210 let writer_path = path.clone();
211 let join_handle = std::thread::Builder::new()
212 .name("monarch-telemetry-unix-sink".into())
213 .spawn(move || writer_loop(writer_path, receiver, dropped))
214 .map_err(|error| anyhow::anyhow!("failed to spawn unix socket sink: {error}"))?;
215
216 inner.path = Some(path);
217 inner.worker = Some(WorkerHandle {
218 sender,
219 _join_handle: join_handle,
220 });
221 Ok(())
222 }
223
224 pub fn is_active(&self) -> bool {
226 self.inner
227 .lock()
228 .map(|inner| inner.worker.is_some())
229 .unwrap_or(false)
230 }
231
232 pub fn dropped_frames(&self) -> u64 {
234 self.dropped.load(Ordering::Relaxed)
235 }
236
237 fn send_metric_buffers(
238 &self,
239 gauges: MetricGaugeBuffer,
240 sums: MetricSumBuffer,
241 histograms: MetricHistogramBuffer,
242 ) {
243 let sender = match self.inner.lock() {
244 Ok(inner) => inner.worker.as_ref().map(|worker| worker.sender.clone()),
245 Err(_) => {
246 let dropped = [gauges.is_empty(), sums.is_empty(), histograms.is_empty()]
247 .into_iter()
248 .filter(|empty| !empty)
249 .count() as u64;
250 self.dropped.fetch_add(dropped, Ordering::Relaxed);
251 return;
252 }
253 };
254
255 let Some(sender) = sender else {
256 return;
257 };
258
259 if !gauges.is_empty() {
260 try_send_table_buffer(
261 TelemetryTableBuffer::MetricGauges(gauges),
262 &sender,
263 &self.dropped,
264 );
265 }
266 if !sums.is_empty() {
267 try_send_table_buffer(
268 TelemetryTableBuffer::MetricSums(sums),
269 &sender,
270 &self.dropped,
271 );
272 }
273 if !histograms.is_empty() {
274 try_send_table_buffer(
275 TelemetryTableBuffer::MetricHistograms(histograms),
276 &sender,
277 &self.dropped,
278 );
279 }
280 }
281
282 fn consume_shared(&self, event: &TraceEvent) -> anyhow::Result<()> {
284 let mut inner = self
285 .inner
286 .lock()
287 .map_err(|_| anyhow::anyhow!("lock poisoned"))?;
288
289 match event {
290 TraceEvent::NewSpan {
291 id,
292 name,
293 target,
294 level,
295 fields,
296 timestamp,
297 parent_id,
298 thread_name,
299 file,
300 line,
301 } => {
302 inner.spans_buffer.insert(Span {
303 process_id: PROCESS_ID.as_str(),
304 id: *id,
305 name: name.to_string(),
306 target: target.to_string(),
307 level: level.to_string(),
308 fields_json: fields_to_json(fields),
309 timestamp_us: timestamp_to_micros(timestamp),
310 parent_id: *parent_id,
311 thread_name: thread_name.to_string(),
312 file: file.map(|s| s.to_string()),
313 line: *line,
314 });
315 }
316 TraceEvent::SpanEnter { id, timestamp, .. } => {
317 inner.span_events_buffer.insert(SpanEvent {
318 process_id: PROCESS_ID.as_str(),
319 id: *id,
320 timestamp_us: timestamp_to_micros(timestamp),
321 event_type: "enter".to_string(),
322 });
323 }
324 TraceEvent::SpanExit { id, timestamp, .. } => {
325 inner.span_events_buffer.insert(SpanEvent {
326 process_id: PROCESS_ID.as_str(),
327 id: *id,
328 timestamp_us: timestamp_to_micros(timestamp),
329 event_type: "exit".to_string(),
330 });
331 }
332 TraceEvent::SpanClose { id, timestamp } => {
333 inner.span_events_buffer.insert(SpanEvent {
334 process_id: PROCESS_ID.as_str(),
335 id: *id,
336 timestamp_us: timestamp_to_micros(timestamp),
337 event_type: "close".to_string(),
338 });
339 }
340 TraceEvent::Event {
341 name,
342 target,
343 level,
344 fields,
345 timestamp,
346 parent_span,
347 thread_id,
348 thread_name,
349 module_path,
350 file,
351 line,
352 } => {
353 inner.events_buffer.insert(Event {
354 name: name.to_string(),
355 target: target.to_string(),
356 level: level.to_string(),
357 fields_json: fields_to_json(fields),
358 timestamp_us: timestamp_to_micros(timestamp),
359 parent_span: *parent_span,
360 thread_id: thread_id.to_string(),
361 thread_name: thread_name.to_string(),
362 module_path: module_path.map(|s| s.to_string()),
363 file: file.map(|s| s.to_string()),
364 line: *line,
365 });
366 }
367 TraceEvent::Entity(event) => buffer_entity_event(&mut inner, event),
368 }
369 Ok(())
370 }
371
372 fn flush_shared(&self) -> anyhow::Result<()> {
374 let mut inner = self
375 .inner
376 .lock()
377 .map_err(|_| anyhow::anyhow!("lock poisoned"))?;
378
379 if inner.worker.is_none() {
380 inner.spans_buffer = SpanBuffer::default();
385 inner.span_events_buffer = SpanEventBuffer::default();
386 inner.events_buffer = EventBuffer::default();
387 inner.actors_buffer = ActorBuffer::default();
388 inner.meshes_buffer = MeshBuffer::default();
389 inner.actor_status_events_buffer = ActorStatusEventBuffer::default();
390 inner.sent_messages_buffer = SentMessageBuffer::default();
391 inner.messages_buffer = MessageBuffer::default();
392 inner.message_status_events_buffer = MessageStatusEventBuffer::default();
393 return Ok(());
394 }
395
396 let sender = inner
397 .worker
398 .as_ref()
399 .expect("worker should exist")
400 .sender
401 .clone();
402
403 flush_buffer(
404 &mut inner.spans_buffer,
405 TelemetryTableBuffer::Spans,
406 &sender,
407 &self.dropped,
408 );
409 flush_buffer(
410 &mut inner.span_events_buffer,
411 TelemetryTableBuffer::SpanEvents,
412 &sender,
413 &self.dropped,
414 );
415 flush_buffer(
416 &mut inner.events_buffer,
417 TelemetryTableBuffer::Events,
418 &sender,
419 &self.dropped,
420 );
421 flush_buffer(
422 &mut inner.actors_buffer,
423 TelemetryTableBuffer::Actors,
424 &sender,
425 &self.dropped,
426 );
427 flush_buffer(
428 &mut inner.meshes_buffer,
429 TelemetryTableBuffer::Meshes,
430 &sender,
431 &self.dropped,
432 );
433 flush_buffer(
434 &mut inner.actor_status_events_buffer,
435 TelemetryTableBuffer::ActorStatusEvents,
436 &sender,
437 &self.dropped,
438 );
439 flush_buffer(
440 &mut inner.sent_messages_buffer,
441 TelemetryTableBuffer::SentMessages,
442 &sender,
443 &self.dropped,
444 );
445 flush_buffer(
446 &mut inner.messages_buffer,
447 TelemetryTableBuffer::Messages,
448 &sender,
449 &self.dropped,
450 );
451 flush_buffer(
452 &mut inner.message_status_events_buffer,
453 TelemetryTableBuffer::MessageStatusEvents,
454 &sender,
455 &self.dropped,
456 );
457 Ok(())
458 }
459}
460
461impl TraceEventSink for UnixSocketSinkAdapter {
462 fn consume(&mut self, event: &TraceEvent) -> Result<(), anyhow::Error> {
463 self.sink.consume_shared(event)
464 }
465
466 fn target_filter(&self) -> Option<&Targets> {
467 Some(&self.target_filter)
468 }
469
470 fn flush(&mut self) -> Result<(), anyhow::Error> {
471 self.sink.flush_shared()
472 }
473
474 fn name(&self) -> &str {
475 "UnixSocketSink"
476 }
477}
478
479pub(crate) fn install_unix_socket_sink_inactive() -> Box<dyn TraceEventSink> {
481 let sink = Arc::clone(unix_socket_sink());
482 Box::new(UnixSocketSinkAdapter {
483 sink,
484 target_filter: get_tracing_targets(),
485 })
486}
487
488pub fn set_unix_socket_sink_path(path: impl Into<PathBuf>) -> anyhow::Result<()> {
490 unix_socket_sink().set_path(path.into())
491}
492
493pub fn unix_socket_sink_is_active() -> bool {
495 UNIX_SOCKET_SINK
496 .get()
497 .map(|sink| sink.is_active())
498 .unwrap_or(false)
499}
500
501pub fn unix_socket_sink_dropped_frames() -> Option<u64> {
503 UNIX_SOCKET_SINK.get().map(|sink| sink.dropped_frames())
504}
505
506pub(crate) fn send_metric_buffers(
508 gauges: MetricGaugeBuffer,
509 sums: MetricSumBuffer,
510 histograms: MetricHistogramBuffer,
511) {
512 let Some(sink) = UNIX_SOCKET_SINK.get() else {
513 return;
514 };
515 sink.send_metric_buffers(gauges, sums, histograms);
516}
517
518fn flush_buffer<B>(
519 buffer: &mut B,
520 table_buffer: impl FnOnce(B) -> TelemetryTableBuffer,
521 sender: &mpsc::SyncSender<TelemetryTableBuffer>,
522 dropped: &AtomicU64,
523) where
524 B: RecordBatchBuffer + Default,
525{
526 if buffer.is_empty() {
527 return;
528 }
529
530 let batch = table_buffer(std::mem::take(buffer));
531 try_send_table_buffer(batch, sender, dropped);
532}
533
534fn try_send_table_buffer(
535 buffer: TelemetryTableBuffer,
536 sender: &mpsc::SyncSender<TelemetryTableBuffer>,
537 dropped: &AtomicU64,
538) {
539 match sender.try_send(buffer) {
540 Ok(()) => {}
541 Err(mpsc::TrySendError::Full(_)) | Err(mpsc::TrySendError::Disconnected(_)) => {
542 dropped.fetch_add(1, Ordering::Relaxed);
545 }
546 }
547}
548
549fn buffer_entity_event(inner: &mut UnixSocketSinkInner, event: &EntityEvent) {
550 match event {
551 EntityEvent::Actor(event) => {
552 inner.actors_buffer.insert(Actor {
553 id: event.id,
554 timestamp_us: timestamp_to_micros(&event.timestamp),
555 mesh_id: event.mesh_id,
556 rank: event.rank,
557 full_name: event.full_name.clone(),
558 display_name: event.display_name.clone(),
559 });
560 }
561 EntityEvent::Mesh(event) => {
562 inner.meshes_buffer.insert(Mesh {
563 id: event.id,
564 timestamp_us: timestamp_to_micros(&event.timestamp),
565 class: event.class.clone(),
566 given_name: event.given_name.clone(),
567 full_name: event.full_name.clone(),
568 shape_json: event.shape_json.clone(),
569 parent_mesh_id: event.parent_mesh_id,
570 parent_view_json: event.parent_view_json.clone(),
571 });
572 }
573 EntityEvent::ActorStatus(event) => {
574 inner
575 .actor_status_events_buffer
576 .insert(ActorStatusEventRow {
577 id: event.id,
578 timestamp_us: timestamp_to_micros(&event.timestamp),
579 actor_id: event.actor_id,
580 new_status: event.new_status.clone(),
581 reason: event.reason.clone(),
582 });
583 }
584 EntityEvent::SentMessage(event) => {
585 inner.sent_messages_buffer.insert(SentMessage {
586 id: generate_sent_message_id(event.sender_actor_id),
587 timestamp_us: timestamp_to_micros(&event.timestamp),
588 sender_actor_id: event.sender_actor_id,
589 actor_mesh_id: event.actor_mesh_id,
590 view_json: event.view_json.clone(),
591 shape_json: event.shape_json.clone(),
592 });
593 }
594 EntityEvent::Message(event) => {
595 inner.messages_buffer.insert(Message {
596 id: event.id,
597 timestamp_us: timestamp_to_micros(&event.timestamp),
598 from_actor_id: event.from_actor_id,
599 to_actor_id: event.to_actor_id,
600 endpoint: event.endpoint.clone(),
601 port_index: event.port_index,
602 });
603 }
604 EntityEvent::MessageStatus(event) => {
605 inner
606 .message_status_events_buffer
607 .insert(MessageStatusEventRow {
608 id: event.id,
609 timestamp_us: timestamp_to_micros(&event.timestamp),
610 message_id: event.message_id,
611 status: event.status.clone(),
612 });
613 }
614 }
615}
616
617fn writer_loop(
619 path: PathBuf,
620 receiver: mpsc::Receiver<TelemetryTableBuffer>,
621 dropped: Arc<AtomicU64>,
622) {
623 let mut stream = None;
624
625 while let Ok(mut buffer) = receiver.recv() {
626 let (table_name, batch) = match &mut buffer {
633 TelemetryTableBuffer::Spans(buffer) => (SPANS, buffer.drain_to_record_batch()),
634 TelemetryTableBuffer::SpanEvents(buffer) => {
635 (SPAN_EVENTS, buffer.drain_to_record_batch())
636 }
637 TelemetryTableBuffer::Events(buffer) => (EVENTS, buffer.drain_to_record_batch()),
638 TelemetryTableBuffer::Actors(buffer) => (ACTORS, buffer.drain_to_record_batch()),
639 TelemetryTableBuffer::Meshes(buffer) => (MESHES, buffer.drain_to_record_batch()),
640 TelemetryTableBuffer::ActorStatusEvents(buffer) => {
641 (ACTOR_STATUS_EVENTS, buffer.drain_to_record_batch())
642 }
643 TelemetryTableBuffer::SentMessages(buffer) => {
644 (SENT_MESSAGES, buffer.drain_to_record_batch())
645 }
646 TelemetryTableBuffer::Messages(buffer) => (MESSAGES, buffer.drain_to_record_batch()),
647 TelemetryTableBuffer::MessageStatusEvents(buffer) => {
648 (MESSAGE_STATUS_EVENTS, buffer.drain_to_record_batch())
649 }
650 TelemetryTableBuffer::MetricGauges(buffer) => {
651 (METRIC_GAUGES, buffer.drain_to_record_batch())
652 }
653 TelemetryTableBuffer::MetricSums(buffer) => {
654 (METRIC_SUMS, buffer.drain_to_record_batch())
655 }
656 TelemetryTableBuffer::MetricHistograms(buffer) => {
657 (METRIC_HISTOGRAMS, buffer.drain_to_record_batch())
658 }
659 };
660 let Ok(batch) = batch else {
661 dropped.fetch_add(1, Ordering::Relaxed);
662 continue;
663 };
664 if batch.num_rows() == 0 {
665 continue;
666 }
667
668 let Ok(payload) = monarch_telemetry_schema::serialize_batch(&batch) else {
669 dropped.fetch_add(1, Ordering::Relaxed);
670 continue;
671 };
672 if payload.len() > MAX_FRAME_LEN {
673 dropped.fetch_add(1, Ordering::Relaxed);
674 continue;
675 }
676
677 let mut frame = Vec::with_capacity(2 + table_name.len() + 4 + payload.len());
680 if write_frame_header(&mut frame, table_name, payload.len()).is_err() {
681 dropped.fetch_add(1, Ordering::Relaxed);
682 continue;
683 }
684 frame.extend_from_slice(&payload);
685
686 if write_frame(&path, &mut stream, &frame).is_err() {
687 dropped.fetch_add(1, Ordering::Relaxed);
688 stream = None;
689 }
690 }
691}
692
693fn write_frame(
694 path: &PathBuf,
695 stream: &mut Option<UnixStream>,
696 frame: &[u8],
697) -> std::io::Result<()> {
698 if stream.is_none() {
699 *stream = Some(UnixStream::connect(path)?);
700 }
701
702 match stream
705 .as_mut()
706 .expect("stream should exist")
707 .write_all(frame)
708 {
709 Ok(()) => Ok(()),
710 Err(error) => {
711 *stream = None;
712 Err(error)
713 }
714 }
715}
716
717fn fields_to_json(fields: &[(&str, FieldValue)]) -> String {
718 monarch_telemetry_schema::fields_to_json(fields.iter().map(|(key, value)| {
719 let json_value = match value {
720 FieldValue::Bool(b) => serde_json::Value::Bool(*b),
721 FieldValue::I64(i) => serde_json::Value::Number((*i).into()),
722 FieldValue::U64(u) => serde_json::Value::Number((*u).into()),
723 FieldValue::F64(f) => serde_json::Number::from_f64(*f)
724 .map(serde_json::Value::Number)
725 .unwrap_or(serde_json::Value::Null),
726 FieldValue::Str(s) => serde_json::Value::String(s.clone()),
727 FieldValue::Debug(d) => serde_json::Value::String(d.clone()),
728 };
729 (*key, json_value)
730 }))
731}
732
733fn timestamp_to_micros(timestamp: &SystemTime) -> i64 {
734 timestamp
735 .duration_since(std::time::UNIX_EPOCH)
736 .unwrap_or_default()
737 .as_micros() as i64
738}
739
740#[cfg(test)]
741pub(crate) fn adapter_for_test(sink: Arc<UnixSocketSink>) -> Box<dyn TraceEventSink> {
742 Box::new(UnixSocketSinkAdapter {
743 sink,
744 target_filter: get_tracing_targets(),
745 })
746}
747
748#[cfg(test)]
749mod tests {
750 use std::io::ErrorKind;
751 use std::io::Read;
752 use std::os::unix::net::UnixListener;
753 use std::os::unix::net::UnixStream;
754 use std::sync::atomic::AtomicU64;
755 use std::sync::atomic::Ordering;
756 use std::sync::mpsc;
757 use std::time::Duration;
758 use std::time::Instant;
759 use std::time::SystemTime;
760
761 use monarch_record_batch::RecordBatchBuffer;
762 use monarch_telemetry_schema::metric_tables::MetricGauge;
763 use monarch_telemetry_schema::metric_tables::MetricHistogram;
764 use monarch_telemetry_schema::metric_tables::MetricSum;
765
766 use super::*;
767 use crate::ActorEvent;
768 use crate::ActorStatusEvent;
769 use crate::MeshEvent;
770 use crate::MessageEvent;
771 use crate::MessageStatusEvent;
772 use crate::SentMessageEvent;
773
774 static TEST_SEQ: AtomicU64 = AtomicU64::new(0);
775
776 struct Frame {
777 table_name: String,
778 payload: Vec<u8>,
779 }
780
781 fn socket_path(name: &str) -> PathBuf {
782 let seq = TEST_SEQ.fetch_add(1, Ordering::Relaxed);
783 let dir = std::env::temp_dir().join(format!(
784 "monarch_unix_socket_sink_{}_{}",
785 std::process::id(),
786 seq
787 ));
788 std::fs::create_dir_all(&dir).unwrap();
789 dir.join(name)
790 }
791
792 fn timestamp() -> SystemTime {
793 SystemTime::UNIX_EPOCH + Duration::from_micros(123)
794 }
795
796 fn fields() -> crate::trace_dispatcher::TraceFields {
797 let mut fields = crate::trace_dispatcher::TraceFields::new();
798 fields.push(("count", FieldValue::U64(3)));
799 fields
800 }
801
802 fn span() -> TraceEvent {
803 TraceEvent::NewSpan {
804 id: 7,
805 name: "test_span",
806 target: "test_target",
807 level: tracing::Level::INFO,
808 fields: fields(),
809 timestamp: timestamp(),
810 parent_id: None,
811 thread_name: "test_thread",
812 file: Some("test.rs"),
813 line: Some(42),
814 }
815 }
816
817 fn span_enter() -> TraceEvent {
818 TraceEvent::SpanEnter {
819 id: 7,
820 timestamp: timestamp(),
821 thread_name: "test_thread",
822 }
823 }
824
825 fn event() -> TraceEvent {
826 TraceEvent::Event {
827 name: "test_event",
828 target: "test_target",
829 level: tracing::Level::INFO,
830 fields: fields(),
831 timestamp: timestamp(),
832 parent_span: Some(7),
833 thread_id: "99",
834 thread_name: "test_thread",
835 module_path: Some("test_module"),
836 file: Some("test.rs"),
837 line: Some(43),
838 }
839 }
840
841 fn entity_events() -> Vec<TraceEvent> {
842 vec![
843 TraceEvent::Entity(EntityEvent::Actor(ActorEvent {
844 id: 1,
845 timestamp: timestamp(),
846 mesh_id: 2,
847 rank: 3,
848 full_name: "actor/full".to_string(),
849 display_name: Some("actor".to_string()),
850 })),
851 TraceEvent::Entity(EntityEvent::Mesh(MeshEvent {
852 id: 2,
853 timestamp: timestamp(),
854 class: "Host".to_string(),
855 given_name: "hosts".to_string(),
856 full_name: "hosts/full".to_string(),
857 shape_json: r#"{"dims":[1]}"#.to_string(),
858 parent_mesh_id: None,
859 parent_view_json: None,
860 })),
861 TraceEvent::Entity(EntityEvent::ActorStatus(ActorStatusEvent {
862 id: 3,
863 timestamp: timestamp(),
864 actor_id: 1,
865 new_status: "Running".to_string(),
866 reason: Some("test".to_string()),
867 })),
868 TraceEvent::Entity(EntityEvent::SentMessage(SentMessageEvent {
869 timestamp: timestamp(),
870 sender_actor_id: 1,
871 actor_mesh_id: 2,
872 view_json: r#"{"rank":0}"#.to_string(),
873 shape_json: r#"{"dims":[1]}"#.to_string(),
874 })),
875 TraceEvent::Entity(EntityEvent::Message(MessageEvent {
876 timestamp: timestamp(),
877 id: 4,
878 from_actor_id: 1,
879 to_actor_id: 5,
880 endpoint: Some("endpoint".to_string()),
881 port_index: Some(6),
882 })),
883 TraceEvent::Entity(EntityEvent::MessageStatus(MessageStatusEvent {
884 timestamp: timestamp(),
885 id: 7,
886 message_id: 4,
887 status: "complete".to_string(),
888 })),
889 ]
890 }
891
892 fn metric_gauge_buffer() -> MetricGaugeBuffer {
893 let mut buffer = MetricGaugeBuffer::default();
894 buffer.insert(MetricGauge {
895 name: "load".to_string(),
896 timestamp_us: 123,
897 start_timestamp_us: None,
898 scope_name: "test".to_string(),
899 unit: "1".to_string(),
900 attributes_json: "{}".to_string(),
901 resource_attributes_json: "{}".to_string(),
902 value_f64: Some(1.5),
903 value_i64: None,
904 value_u64: None,
905 });
906 buffer
907 }
908
909 fn metric_sum_buffer() -> MetricSumBuffer {
910 let mut buffer = MetricSumBuffer::default();
911 buffer.insert(MetricSum {
912 name: "mailbox.posts".to_string(),
913 timestamp_us: 123,
914 start_timestamp_us: 100,
915 scope_name: "hyperactor::mailbox".to_string(),
916 unit: "1".to_string(),
917 temporality: "delta".to_string(),
918 is_monotonic: true,
919 attributes_json: r#"{"actor_id":"actor"}"#.to_string(),
920 resource_attributes_json: r#"{"execution_id":"test"}"#.to_string(),
921 sum_f64: None,
922 sum_i64: None,
923 sum_u64: Some(3),
924 });
925 buffer
926 }
927
928 fn metric_histogram_buffer() -> MetricHistogramBuffer {
929 let mut buffer = MetricHistogramBuffer::default();
930 buffer.insert(MetricHistogram {
931 name: "latency".to_string(),
932 timestamp_us: 123,
933 start_timestamp_us: 100,
934 scope_name: "test".to_string(),
935 unit: "ms".to_string(),
936 temporality: "delta".to_string(),
937 attributes_json: "{}".to_string(),
938 resource_attributes_json: "{}".to_string(),
939 count: 1,
940 sum_f64: Some(5.0),
941 sum_i64: None,
942 sum_u64: None,
943 min_f64: Some(5.0),
944 min_i64: None,
945 min_u64: None,
946 max_f64: Some(5.0),
947 max_i64: None,
948 max_u64: None,
949 bounds_json: "[10.0]".to_string(),
950 bucket_counts_json: "[1,0]".to_string(),
951 });
952 buffer
953 }
954
955 fn read_frame(stream: &mut UnixStream) -> Frame {
956 let mut name_len_bytes = [0; 2];
957 stream.read_exact(&mut name_len_bytes).unwrap();
958 let name_len = u16::from_be_bytes(name_len_bytes) as usize;
959
960 let mut name_bytes = vec![0; name_len];
961 stream.read_exact(&mut name_bytes).unwrap();
962 let table_name = String::from_utf8(name_bytes).unwrap();
963
964 let mut payload_len_bytes = [0; 4];
965 stream.read_exact(&mut payload_len_bytes).unwrap();
966 let payload_len = u32::from_be_bytes(payload_len_bytes) as usize;
967 assert!((1..=MAX_FRAME_LEN).contains(&payload_len));
968
969 let mut payload = vec![0; payload_len];
970 stream.read_exact(&mut payload).unwrap();
971 Frame {
972 table_name,
973 payload,
974 }
975 }
976
977 fn read_frame_tables(stream: UnixStream, count: usize) -> Vec<String> {
978 read_frames(stream, count)
979 .into_iter()
980 .map(|frame| frame.table_name)
981 .collect()
982 }
983
984 fn read_frames(mut stream: UnixStream, count: usize) -> Vec<Frame> {
985 stream
986 .set_read_timeout(Some(Duration::from_secs(5)))
987 .unwrap();
988
989 (0..count).map(|_| read_frame(&mut stream)).collect()
990 }
991
992 fn assert_no_frame_available(stream: &mut UnixStream) {
993 stream
994 .set_read_timeout(Some(Duration::from_millis(500)))
995 .unwrap();
996 let mut name_len_bytes = [0; 2];
997 match stream.read_exact(&mut name_len_bytes) {
998 Ok(()) => panic!("unexpected extra telemetry frame"),
999 Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {}
1000 Err(error) => panic!("unexpected read error: {error}"),
1001 }
1002 }
1003
1004 fn wait_for_dropped_frames(sink: &UnixSocketSink, expected: u64) {
1005 let deadline = Instant::now() + Duration::from_secs(5);
1006 while Instant::now() < deadline {
1007 if sink.dropped_frames() == expected {
1008 return;
1009 }
1010 std::thread::sleep(Duration::from_millis(10));
1011 }
1012 assert_eq!(sink.dropped_frames(), expected);
1013 }
1014
1015 fn assert_frame_batch<B>(frame: &Frame)
1016 where
1017 B: RecordBatchBuffer + Default,
1018 {
1019 let batch = monarch_telemetry_schema::deserialize_one_batch(&frame.payload).unwrap();
1020 let expected = B::default().drain_to_record_batch().unwrap();
1021
1022 assert_eq!(batch.num_rows(), 1);
1023 assert_eq!(batch.schema(), expected.schema());
1024 }
1025
1026 fn assert_entity_frame_schema(frame: &Frame) {
1027 match frame.table_name.as_str() {
1028 ACTORS => assert_frame_batch::<ActorBuffer>(frame),
1029 MESHES => assert_frame_batch::<MeshBuffer>(frame),
1030 ACTOR_STATUS_EVENTS => assert_frame_batch::<ActorStatusEventBuffer>(frame),
1031 SENT_MESSAGES => assert_frame_batch::<SentMessageBuffer>(frame),
1032 MESSAGES => assert_frame_batch::<MessageBuffer>(frame),
1033 MESSAGE_STATUS_EVENTS => assert_frame_batch::<MessageStatusEventBuffer>(frame),
1034 other => panic!("unexpected entity table frame: {other}"),
1035 }
1036 }
1037
1038 #[test]
1039 fn inactive_flush_discards_buffered_events_without_counting_drops() {
1040 let sink = UnixSocketSink::new();
1041
1042 sink.consume_shared(&span()).unwrap();
1043 sink.consume_shared(&span_enter()).unwrap();
1044 sink.consume_shared(&event()).unwrap();
1045 for event in entity_events() {
1046 sink.consume_shared(&event).unwrap();
1047 }
1048
1049 {
1050 let inner = sink.inner.lock().unwrap();
1051 assert_eq!(inner.spans_buffer.len(), 1);
1052 assert_eq!(inner.span_events_buffer.len(), 1);
1053 assert_eq!(inner.events_buffer.len(), 1);
1054 assert_eq!(inner.actors_buffer.len(), 1);
1055 assert_eq!(inner.meshes_buffer.len(), 1);
1056 assert_eq!(inner.actor_status_events_buffer.len(), 1);
1057 assert_eq!(inner.sent_messages_buffer.len(), 1);
1058 assert_eq!(inner.messages_buffer.len(), 1);
1059 assert_eq!(inner.message_status_events_buffer.len(), 1);
1060 }
1061
1062 sink.flush_shared().unwrap();
1063
1064 {
1065 let inner = sink.inner.lock().unwrap();
1066 assert_eq!(inner.spans_buffer.len(), 0);
1067 assert_eq!(inner.span_events_buffer.len(), 0);
1068 assert_eq!(inner.events_buffer.len(), 0);
1069 assert_eq!(inner.actors_buffer.len(), 0);
1070 assert_eq!(inner.meshes_buffer.len(), 0);
1071 assert_eq!(inner.actor_status_events_buffer.len(), 0);
1072 assert_eq!(inner.sent_messages_buffer.len(), 0);
1073 assert_eq!(inner.messages_buffer.len(), 0);
1074 assert_eq!(inner.message_status_events_buffer.len(), 0);
1075 }
1076 assert!(!sink.is_active());
1077 assert_eq!(sink.dropped_frames(), 0);
1078 }
1079
1080 #[test]
1081 fn set_path_is_idempotent_for_same_path() {
1082 let path = socket_path("same.sock");
1083 let sink = UnixSocketSink::new();
1084
1085 sink.set_path(path.clone()).unwrap();
1086 sink.set_path(path).unwrap();
1087 }
1088
1089 #[test]
1090 fn set_path_rejects_different_path_after_activation() {
1091 let sink = UnixSocketSink::new();
1092
1093 sink.set_path(socket_path("first.sock")).unwrap();
1094
1095 assert!(sink.set_path(socket_path("second.sock")).is_err());
1096 }
1097
1098 #[test]
1099 fn active_flush_sends_one_frame_per_non_empty_trace_table() {
1100 let path = socket_path("telemetry.sock");
1101 let listener = UnixListener::bind(&path).unwrap();
1102 let (sender, receiver) = mpsc::channel();
1103 let read_handle = std::thread::spawn(move || {
1104 let (stream, _addr) = listener.accept().unwrap();
1105 sender.send(read_frame_tables(stream, 3)).unwrap();
1106 });
1107
1108 let sink = UnixSocketSink::new();
1109 sink.set_path(path).unwrap();
1110 sink.consume_shared(&span()).unwrap();
1111 sink.consume_shared(&span_enter()).unwrap();
1112 sink.consume_shared(&event()).unwrap();
1113
1114 sink.flush_shared().unwrap();
1115
1116 let tables = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
1117 read_handle.join().unwrap();
1118 assert_eq!(
1119 tables,
1120 vec![
1121 SPANS.to_string(),
1122 SPAN_EVENTS.to_string(),
1123 EVENTS.to_string()
1124 ]
1125 );
1126 assert_eq!(sink.dropped_frames(), 0);
1127 }
1128
1129 #[test]
1130 fn active_flush_sends_one_frame_per_non_empty_entity_table() {
1131 let path = socket_path("entities.sock");
1132 let listener = UnixListener::bind(&path).unwrap();
1133 let (sender, receiver) = mpsc::channel();
1134 let read_handle = std::thread::spawn(move || {
1135 let (stream, _addr) = listener.accept().unwrap();
1136 sender.send(read_frames(stream, 6)).unwrap();
1137 });
1138
1139 let sink = UnixSocketSink::new();
1140 sink.set_path(path).unwrap();
1141 for event in entity_events() {
1142 sink.consume_shared(&event).unwrap();
1143 }
1144
1145 sink.flush_shared().unwrap();
1146
1147 let frames = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
1148 read_handle.join().unwrap();
1149 assert_eq!(
1150 frames
1151 .iter()
1152 .map(|frame| frame.table_name.as_str())
1153 .collect::<Vec<_>>(),
1154 vec![
1155 ACTORS,
1156 MESHES,
1157 ACTOR_STATUS_EVENTS,
1158 SENT_MESSAGES,
1159 MESSAGES,
1160 MESSAGE_STATUS_EVENTS,
1161 ]
1162 );
1163 frames.iter().for_each(assert_entity_frame_schema);
1164 assert_eq!(sink.dropped_frames(), 0);
1165 }
1166
1167 #[test]
1168 fn active_sink_sends_metric_frames() {
1169 let path = socket_path("metrics.sock");
1170 let listener = UnixListener::bind(&path).unwrap();
1171 let (sender, receiver) = mpsc::channel();
1172 let read_handle = std::thread::spawn(move || {
1173 let (stream, _addr) = listener.accept().unwrap();
1174 sender.send(read_frames(stream, 3)).unwrap();
1175 });
1176
1177 let sink = UnixSocketSink::new();
1178 sink.set_path(path).unwrap();
1179 sink.send_metric_buffers(
1180 metric_gauge_buffer(),
1181 metric_sum_buffer(),
1182 metric_histogram_buffer(),
1183 );
1184
1185 let frames = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
1186 read_handle.join().unwrap();
1187 assert_eq!(frames[0].table_name, METRIC_GAUGES);
1188 assert_frame_batch::<MetricGaugeBuffer>(&frames[0]);
1189 assert_eq!(frames[1].table_name, METRIC_SUMS);
1190 assert_frame_batch::<MetricSumBuffer>(&frames[1]);
1191 assert_eq!(frames[2].table_name, METRIC_HISTOGRAMS);
1192 assert_frame_batch::<MetricHistogramBuffer>(&frames[2]);
1193 assert_eq!(sink.dropped_frames(), 0);
1194 }
1195
1196 #[test]
1197 fn poisoned_sink_counts_dropped_metric_frames() {
1198 let sink = Arc::new(UnixSocketSink::new());
1199 let poison_sink = Arc::clone(&sink);
1200 assert!(
1201 std::thread::spawn(move || {
1202 let _guard = poison_sink.inner.lock().unwrap();
1203 panic!("poison metric sink lock");
1204 })
1205 .join()
1206 .is_err()
1207 );
1208
1209 sink.send_metric_buffers(
1210 metric_gauge_buffer(),
1211 metric_sum_buffer(),
1212 metric_histogram_buffer(),
1213 );
1214
1215 assert_eq!(sink.dropped_frames(), 3);
1216 }
1217
1218 #[test]
1219 fn active_flush_omits_empty_trace_tables() {
1220 let path = socket_path("events_only.sock");
1221 let listener = UnixListener::bind(&path).unwrap();
1222 let (sender, receiver) = mpsc::channel();
1223 let read_handle = std::thread::spawn(move || {
1224 let (mut stream, _addr) = listener.accept().unwrap();
1225 stream
1226 .set_read_timeout(Some(Duration::from_secs(5)))
1227 .unwrap();
1228 let table_name = read_frame(&mut stream).table_name;
1229 assert_no_frame_available(&mut stream);
1230 sender.send(table_name).unwrap();
1231 });
1232
1233 let sink = UnixSocketSink::new();
1234 sink.set_path(path).unwrap();
1235 sink.consume_shared(&event()).unwrap();
1236
1237 sink.flush_shared().unwrap();
1238
1239 let table = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
1240 read_handle.join().unwrap();
1241 assert_eq!(table, EVENTS);
1242 assert_eq!(sink.dropped_frames(), 0);
1243 }
1244
1245 #[test]
1246 fn flush_counts_missing_sidecar_connection_as_dropped_frame() {
1247 let sink = UnixSocketSink::new();
1248 sink.set_path(socket_path("missing.sock")).unwrap();
1249 sink.consume_shared(&event()).unwrap();
1250
1251 sink.flush_shared().unwrap();
1252
1253 wait_for_dropped_frames(&sink, 1);
1254 }
1255}