1#![allow(internal_features)]
10#![cfg_attr(target_os = "macos", feature(thread_id_value))]
11#![feature(sync_unsafe_cell)]
12#![feature(mpmc_channel)]
13#![feature(formatting_options)]
14#![recursion_limit = "256"]
15
16pub const MAST_HPC_JOB_NAME_ENV: &str = "MAST_HPC_JOB_NAME";
18
19const LOG_LEVEL_INFO: &str = "info";
21const LOG_LEVEL_DEBUG: &str = "debug";
22
23const SPAN_FIELD_RECORDING: &str = "recording";
25#[allow(dead_code)]
26const SPAN_FIELD_RECORDER: &str = "recorder";
27
28pub const SUBJECT_KEY: &str = "subject";
32
33const ENV_VALUE_LOCAL: &str = "local";
35const ENV_VALUE_MAST_EMULATOR: &str = "mast_emulator";
36const ENV_VALUE_MAST: &str = "mast";
37const ENV_VALUE_TEST: &str = "test";
38#[allow(dead_code)]
39const ENV_VALUE_LOCAL_MAST_SIMULATOR: &str = "local_mast_simulator";
40
41#[allow(non_upper_case_globals)]
53pub const skip_record: bool = true;
55
56mod config;
57pub mod in_memory_reader;
58#[cfg(all(fbcode_build, target_os = "linux"))]
59mod meta;
60mod otel;
61pub(crate) mod otlp;
62mod pool;
63mod rate_limit;
64pub mod recorder;
65pub mod sinks;
66mod spool;
67pub mod sqlite;
68pub mod task;
69pub mod trace;
70pub mod trace_dispatcher;
71mod unix_sink;
72
73use std::collections::hash_map::DefaultHasher;
75use std::hash::Hash;
76use std::hash::Hasher;
77use std::io::Write;
78use std::sync::Arc;
79use std::sync::LazyLock;
80use std::sync::Mutex;
81use std::sync::atomic::AtomicU64;
82use std::sync::atomic::Ordering;
83use std::sync::mpsc;
84use std::time::Instant;
85use std::time::SystemTime;
86
87pub use opentelemetry;
88pub use opentelemetry::Key;
89pub use opentelemetry::KeyValue;
90pub use opentelemetry::Value;
91pub use opentelemetry::global::meter;
92pub use trace_dispatcher::DispatcherControl;
93pub use trace_dispatcher::FieldValue;
94pub use trace_dispatcher::TraceEvent;
95pub use trace_dispatcher::TraceEventSink;
96use trace_dispatcher::TraceFields;
97pub use tracing;
98pub use tracing::Level;
99use tracing_appender::rolling::RollingFileAppender;
100pub use unix_sink::set_unix_socket_sink_path;
101pub use unix_sink::unix_socket_sink_dropped_frames;
102pub use unix_sink::unix_socket_sink_is_active;
103
104#[cfg(all(fbcode_build, target_os = "linux"))]
105use crate::config::ENABLE_OTEL_METRICS;
106#[cfg(all(fbcode_build, target_os = "linux"))]
107use crate::config::ENABLE_OTEL_TRACING;
108use crate::config::ENABLE_RECORDER_TRACING;
109use crate::config::ENABLE_SQLITE_TRACING;
110use crate::config::MONARCH_LOG_SUFFIX;
111use crate::recorder::Recorder;
112
113pub fn hash_to_u64(value: &impl Hash) -> u64 {
115 let mut hasher = DefaultHasher::new();
116 value.hash(&mut hasher);
117 hasher.finish()
118}
119
120pub trait TelemetryClock {
121 fn now(&self) -> tokio::time::Instant;
122 fn system_time_now(&self) -> std::time::SystemTime;
123}
124
125pub struct DefaultTelemetryClock {}
126
127impl TelemetryClock for DefaultTelemetryClock {
128 fn now(&self) -> tokio::time::Instant {
129 tokio::time::Instant::now()
130 }
131
132 fn system_time_now(&self) -> std::time::SystemTime {
133 std::time::SystemTime::now()
134 }
135}
136
137pub fn username() -> String {
138 let env = env::Env::current();
139 match env {
140 env::Env::Mast => {
141 std::env::var("MAST_JOB_OWNER_UNIXNAME").unwrap_or_else(|_| "mast_owner".to_string())
142 }
143 _ => whoami::username(),
144 }
145}
146
147pub fn log_file_path(
150 env: env::Env,
151 suffix: Option<&str>,
152) -> Result<(String, String), anyhow::Error> {
153 let suffix = suffix
154 .map(|s| {
155 if s.is_empty() {
156 String::new()
157 } else {
158 format!("_{}", s)
159 }
160 })
161 .unwrap_or_default();
162 match env {
163 env::Env::Local | env::Env::MastEmulator => {
164 let username = if whoami::username().is_empty() {
165 "monarch".to_string()
166 } else {
167 whoami::username()
168 };
169 Ok((
170 format!("/tmp/{}", username),
171 format!("monarch_log{}", suffix),
172 ))
173 }
174 env::Env::Mast => Ok((
175 "/logs/".to_string(),
176 format!("dedicated_log_monarch{}", suffix),
177 )),
178 _ => Err(anyhow::anyhow!(
179 "file writer unsupported for environment {}",
180 env
181 )),
182 }
183}
184
185fn try_create_appender(
186 path: &str,
187 filename: &str,
188 create_dir: bool,
189) -> Result<RollingFileAppender, Box<dyn std::error::Error>> {
190 if create_dir {
191 std::fs::create_dir_all(path)?;
192 }
193 Ok(RollingFileAppender::builder()
194 .filename_prefix(filename)
195 .filename_suffix("log")
196 .build(path)?)
197}
198
199fn writer() -> Box<dyn Write + Send> {
200 match env::Env::current() {
201 env::Env::Test => Box::new(std::io::stderr()),
202 env::Env::Local | env::Env::MastEmulator | env::Env::Mast => {
203 let suffix = hyperactor_config::global::try_get_cloned(MONARCH_LOG_SUFFIX);
204 let (path, filename) = log_file_path(env::Env::current(), suffix.as_deref()).unwrap();
205 match try_create_appender(&path, &filename, true) {
206 Ok(file_appender) => Box::new(file_appender),
207 Err(e) => {
208 eprintln!(
209 "unable to create log file in {}: {}. Falling back to stderr",
210 path, e
211 );
212 Box::new(std::io::stderr())
213 }
214 }
215 }
216 }
217}
218
219static TELEMETRY_CLOCK: LazyLock<Arc<Mutex<Box<dyn TelemetryClock + Send>>>> =
220 LazyLock::new(|| Arc::new(Mutex::new(Box::new(DefaultTelemetryClock {}))));
221
222static SYNTHETIC_TRACE_EVENT_SENDER: Mutex<Option<mpsc::SyncSender<TraceEvent>>> = Mutex::new(None);
229
230static SINK_CONTROL_CHANNEL: LazyLock<(
234 mpsc::Sender<DispatcherControl>,
235 Mutex<Option<mpsc::Receiver<DispatcherControl>>>,
236)> = LazyLock::new(|| {
237 let (sender, receiver) = mpsc::channel();
238 (sender, Mutex::new(Some(receiver)))
239});
240
241const SYNTHETIC_USER_SPAN_ID_BASE: u64 = 1 << 63;
242static USER_SPAN_SEQ: AtomicU64 = AtomicU64::new(SYNTHETIC_USER_SPAN_ID_BASE);
243const SYNTHETIC_USER_SPAN_TRACK_NAME: &str = "python";
244
245pub(crate) fn set_synthetic_trace_event_sender(sender: mpsc::SyncSender<TraceEvent>) {
248 *SYNTHETIC_TRACE_EVENT_SENDER
249 .lock()
250 .expect("SYNTHETIC_TRACE_EVENT_SENDER mutex should not be poisoned") = Some(sender);
251}
252
253pub(crate) fn emit_trace_event(event: TraceEvent) -> bool {
256 match synthetic_trace_event_sender() {
257 Some(sender) => sender.try_send(event).is_ok(),
258 None => false,
259 }
260}
261
262pub fn start_user_span(
264 name: &'static str,
265 target: &'static str,
266 fields: impl IntoIterator<Item = (&'static str, FieldValue)>,
267) -> u64 {
268 if SYNTHETIC_TRACE_EVENT_SENDER
269 .lock()
270 .expect("SYNTHETIC_TRACE_EVENT_SENDER mutex should not be poisoned")
271 .is_none()
272 {
273 return 0;
274 }
275
276 let id = USER_SPAN_SEQ.fetch_add(1, Ordering::Relaxed);
277
278 let fields = fields.into_iter().collect::<TraceFields>();
279
280 let _ = emit_trace_event(TraceEvent::NewSpan {
281 id,
282 name,
283 target,
284 level: tracing::Level::INFO,
285 fields,
286 timestamp: SystemTime::now(),
287 parent_id: None,
288 thread_name: SYNTHETIC_USER_SPAN_TRACK_NAME,
289 file: None,
290 line: None,
291 });
292
293 let _ = emit_trace_event(TraceEvent::SpanEnter {
294 id,
295 timestamp: SystemTime::now(),
296 thread_name: SYNTHETIC_USER_SPAN_TRACK_NAME,
297 });
298
299 id
300}
301
302pub fn end_user_span(id: u64) {
304 if id == 0 {
305 return;
306 }
307
308 let _ = emit_trace_event(TraceEvent::SpanExit {
309 id,
310 timestamp: SystemTime::now(),
311 thread_name: SYNTHETIC_USER_SPAN_TRACK_NAME,
312 });
313
314 let _ = emit_trace_event(TraceEvent::SpanClose {
315 id,
316 timestamp: SystemTime::now(),
317 });
318}
319
320#[derive(Debug, Clone)]
322pub struct ActorEvent {
323 pub id: u64,
325 pub timestamp: SystemTime,
327 pub mesh_id: u64,
329 pub rank: u64,
331 pub full_name: String,
333 pub display_name: Option<String>,
335}
336
337pub fn notify_actor_created(event: ActorEvent) {
339 emit_entity_event(EntityEvent::Actor(event));
340}
341
342#[derive(Debug, Clone)]
344pub struct MeshEvent {
345 pub id: u64,
347 pub timestamp: SystemTime,
349 pub class: String,
351 pub given_name: String,
353 pub full_name: String,
355 pub shape_json: String,
357 pub parent_mesh_id: Option<u64>,
359 pub parent_view_json: Option<String>,
361}
362
363pub fn notify_mesh_created(event: MeshEvent) {
365 emit_entity_event(EntityEvent::Mesh(event));
366}
367
368#[derive(Debug, Clone)]
370pub struct ActorStatusEvent {
371 pub id: u64,
373 pub timestamp: SystemTime,
375 pub actor_id: u64,
377 pub new_status: String,
379 pub reason: Option<String>,
381}
382
383pub fn notify_actor_status_changed(event: ActorStatusEvent) {
385 emit_entity_event(EntityEvent::ActorStatus(event));
386}
387
388#[derive(Debug, Clone)]
393pub struct SentMessageEvent {
394 pub timestamp: SystemTime,
395 pub sender_actor_id: u64,
397 pub actor_mesh_id: u64,
399 pub view_json: String,
404 pub shape_json: String,
407}
408
409pub fn notify_sent_message(event: SentMessageEvent) {
411 emit_entity_event(EntityEvent::SentMessage(event));
412}
413
414#[derive(Debug, Clone)]
416pub struct MessageEvent {
417 pub timestamp: SystemTime,
418 pub id: u64,
420 pub from_actor_id: u64,
422 pub to_actor_id: u64,
424 pub endpoint: Option<String>,
426 pub port_index: Option<u64>,
428}
429
430pub fn notify_message(event: MessageEvent) {
432 emit_entity_event(EntityEvent::Message(event));
433}
434
435#[derive(Debug, Clone)]
437pub struct MessageStatusEvent {
438 pub timestamp: SystemTime,
439 pub id: u64,
441 pub message_id: u64,
443 pub status: String,
445}
446
447pub fn notify_message_status(event: MessageStatusEvent) {
449 emit_entity_event(EntityEvent::MessageStatus(event));
450}
451
452static ACTOR_STATUS_SEQ: AtomicU64 = AtomicU64::new(1);
453
454pub fn generate_actor_status_event_id(actor_id: u64) -> u64 {
459 let seq = ACTOR_STATUS_SEQ.fetch_add(1, Ordering::Relaxed);
460 hash_to_u64(&(actor_id, seq))
461}
462
463static SEND_SEQ: AtomicU64 = AtomicU64::new(1);
464
465pub fn generate_sent_message_id(sender_actor_id: u64) -> u64 {
467 let seq = SEND_SEQ.fetch_add(1, Ordering::Relaxed);
468 hash_to_u64(&(sender_actor_id, seq))
469}
470
471static RECV_MSG_SEQ: AtomicU64 = AtomicU64::new(1);
472
473pub fn generate_message_id(to_actor_id: u64) -> u64 {
478 let seq = RECV_MSG_SEQ.fetch_add(1, Ordering::Relaxed);
479 hash_to_u64(&(to_actor_id, seq))
480}
481
482static STATUS_EVENT_SEQ: AtomicU64 = AtomicU64::new(1);
483
484pub fn generate_status_event_id(message_id: u64) -> u64 {
489 let seq = STATUS_EVENT_SEQ.fetch_add(1, Ordering::Relaxed);
490 hash_to_u64(&(message_id, seq))
491}
492
493#[derive(Debug, Clone)]
499pub enum EntityEvent {
500 Actor(ActorEvent),
502 Mesh(MeshEvent),
504 ActorStatus(ActorStatusEvent),
506 SentMessage(SentMessageEvent),
508 Message(MessageEvent),
510 MessageStatus(MessageStatusEvent),
512}
513
514fn emit_entity_event(event: EntityEvent) {
516 if let Some(sender) = synthetic_trace_event_sender() {
517 let _ = sender.try_send(TraceEvent::Entity(event));
518 }
519}
520
521fn synthetic_trace_event_sender() -> Option<mpsc::SyncSender<TraceEvent>> {
522 SYNTHETIC_TRACE_EVENT_SENDER
523 .lock()
524 .expect("SYNTHETIC_TRACE_EVENT_SENDER mutex should not be poisoned")
525 .clone()
526}
527
528pub fn register_sink(sink: Box<dyn TraceEventSink>) {
545 let sender = &SINK_CONTROL_CHANNEL.0;
546 if let Err(e) = sender.send(DispatcherControl::AddSink(sink)) {
547 eprintln!("[telemetry] failed to register sink: {}", e);
548 }
549}
550
551pub(crate) fn take_sink_control_receiver() -> Option<mpsc::Receiver<DispatcherControl>> {
554 SINK_CONTROL_CHANNEL.1.lock().unwrap().take()
555}
556
557pub fn recorder() -> &'static Recorder {
560 static RECORDER: std::sync::OnceLock<Recorder> = std::sync::OnceLock::new();
561 RECORDER.get_or_init(Recorder::new)
562}
563
564pub fn swap_telemetry_clock(clock: impl TelemetryClock + Send + 'static) {
567 *TELEMETRY_CLOCK.lock().unwrap() = Box::new(clock);
568}
569
570#[macro_export]
574macro_rules! key_value {
575 ($key:expr, $val:expr) => {
576 $crate::opentelemetry::KeyValue::new(
577 $crate::opentelemetry::Key::new($key),
578 $crate::opentelemetry::Value::from($val),
579 )
580 };
581}
582#[macro_export]
598macro_rules! kv_pairs {
599 ($($k:expr => $v:expr),* $(,)?) => {
600 &[$($crate::key_value!($k, $v),)*]
601 };
602}
603
604#[derive(Debug, Clone, Copy)]
605pub enum TimeUnit {
606 Millis,
607 Micros,
608 Nanos,
609}
610
611impl TimeUnit {
612 pub fn as_str(&self) -> &'static str {
613 match self {
614 TimeUnit::Millis => "ms",
615 TimeUnit::Micros => "us",
616 TimeUnit::Nanos => "ns",
617 }
618 }
619}
620pub struct Timer(opentelemetry::metrics::Histogram<u64>, TimeUnit);
621
622impl<'a> Timer {
623 pub fn new(data: opentelemetry::metrics::Histogram<u64>, unit: TimeUnit) -> Self {
624 Timer(data, unit)
625 }
626 pub fn start(&'static self, pairs: &'a [opentelemetry::KeyValue]) -> TimerGuard<'a> {
627 TimerGuard {
628 data: self,
629 pairs,
630 start: Instant::now(),
631 }
632 }
633
634 pub fn record(&'static self, dur: std::time::Duration, pairs: &'a [opentelemetry::KeyValue]) {
635 let dur = match self.1 {
636 TimeUnit::Millis => dur.as_millis(),
637 TimeUnit::Micros => dur.as_micros(),
638 TimeUnit::Nanos => dur.as_nanos(),
639 } as u64;
640
641 self.0.record(dur, pairs);
642 }
643}
644pub struct TimerGuard<'a> {
645 data: &'static Timer,
646 pairs: &'a [opentelemetry::KeyValue],
647 start: Instant,
648}
649
650impl Drop for TimerGuard<'_> {
651 fn drop(&mut self) {
652 let now = Instant::now();
653 let dur = now.duration_since(self.start);
654 self.data.record(dur, self.pairs);
655 }
656}
657
658#[macro_export]
675macro_rules! declare_static_timer {
676 ($name:ident, $key:expr, $unit:path) => {
677 #[doc = "a global histogram timer named: "]
678 #[doc = $key]
679 pub static $name: std::sync::LazyLock<$crate::Timer> = std::sync::LazyLock::new(|| {
680 $crate::Timer::new(
681 $crate::meter(module_path!())
682 .u64_histogram(format!("{}.{}", $key, $unit.as_str()))
683 .with_unit($unit.as_str())
684 .build(),
685 $unit,
686 )
687 });
688 };
689}
690
691#[macro_export]
713macro_rules! declare_static_counter {
714 ($name:ident, $key:expr) => {
715 #[doc = "a global counter named: "]
716 #[doc = $key]
717 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Counter<u64>> =
718 std::sync::LazyLock::new(|| $crate::meter(module_path!()).u64_counter($key).build());
719 };
720}
721
722#[macro_export]
744macro_rules! declare_static_up_down_counter {
745 ($name:ident, $key:expr) => {
746 #[doc = "a global up down counter named: "]
747 #[doc = $key]
748 pub static $name: std::sync::LazyLock<opentelemetry::metrics::UpDownCounter<i64>> =
749 std::sync::LazyLock::new(|| {
750 $crate::meter(module_path!())
751 .i64_up_down_counter($key)
752 .build()
753 });
754 };
755}
756
757#[macro_export]
779macro_rules! declare_static_gauge {
780 ($name:ident, $key:expr) => {
781 #[doc = "a global gauge named: "]
782 #[doc = $key]
783 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Gauge<f64>> =
784 std::sync::LazyLock::new(|| $crate::meter(module_path!()).f64_gauge($key).build());
785 };
786}
787#[macro_export]
805macro_rules! declare_observable_gauge {
806 ($name:ident, $key:expr, $cb:expr) => {
807 #[doc = "a global gauge named: "]
808 #[doc = $key]
809 pub static $name: std::sync::LazyLock<opentelemetry::metrics::ObservableGauge<f64>> =
810 std::sync::LazyLock::new(|| {
811 $crate::meter(module_path!())
812 .f64_observable_gauge($key)
813 .with_callback($cb)
814 .build()
815 });
816 };
817}
818#[macro_export]
840macro_rules! declare_static_histogram {
841 ($name:ident, $key:expr) => {
842 #[doc = "a global histogram named: "]
843 #[doc = $key]
844 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Histogram<f64>> =
845 std::sync::LazyLock::new(|| {
846 hyperactor_telemetry::meter(module_path!())
847 .f64_histogram($key)
848 .build()
849 });
850 };
851}
852
853pub fn initialize_logging(clock: impl TelemetryClock + Send + 'static) {
862 initialize_logging_with_log_prefix(clock, None);
863}
864
865pub fn initialize_logging_for_test() {
867 initialize_logging(DefaultTelemetryClock {});
868}
869
870pub fn initialize_logging_with_log_prefix(
885 clock: impl TelemetryClock + Send + 'static,
886 prefix_env_var: Option<String>,
887) {
888 let should_install_subscriber = !tracing::dispatcher::has_been_set();
889
890 swap_telemetry_clock(clock);
891 if !should_install_subscriber {
892 tracing::debug!("logging already initialized for this process");
893 }
894 let file_log_level = match env::Env::current() {
895 env::Env::Local => LOG_LEVEL_INFO,
896 env::Env::MastEmulator => LOG_LEVEL_INFO,
897 env::Env::Mast => LOG_LEVEL_INFO,
898 env::Env::Test => LOG_LEVEL_DEBUG,
899 };
900
901 use tracing_subscriber::Registry;
902 use tracing_subscriber::layer::SubscriberExt;
903 use tracing_subscriber::util::SubscriberInitExt;
904
905 #[cfg(all(fbcode_build, target_os = "linux"))]
906 {
907 if should_install_subscriber {
908 let mut sinks: Vec<Box<dyn trace_dispatcher::TraceEventSink>> = Vec::new();
909 sinks.push(Box::new(sinks::glog::GlogSink::new(
910 writer(),
911 prefix_env_var.clone(),
912 file_log_level,
913 )));
914
915 let sqlite_enabled = hyperactor_config::global::get(ENABLE_SQLITE_TRACING);
916
917 if sqlite_enabled {
918 match create_sqlite_sink() {
919 Ok(sink) => {
920 sinks.push(Box::new(sink));
921 }
922 Err(e) => {
923 tracing::warn!("failed to create SqliteSink: {}", e);
924 }
925 }
926 }
927
928 if hyperactor_config::global::get(sinks::perfetto::PERFETTO_TRACE_MODE)
929 != sinks::perfetto::PerfettoTraceMode::Off
930 {
931 let exec_id = env::execution_id();
932 let process_name = std::env::var("HYPERACTOR_PROCESS_NAME")
933 .unwrap_or_else(|_| "client".to_string());
934 match sinks::perfetto::PerfettoFileSink::new(
935 sinks::perfetto::default_trace_dir(),
936 &exec_id,
937 &process_name,
938 ) {
939 Ok(sink) => {
940 sinks.push(Box::new(sink));
941 }
942 Err(e) => {
943 tracing::warn!("failed to create PerfettoFileSink: {}", e);
944 }
945 }
946 }
947
948 if hyperactor_config::global::get(ENABLE_OTEL_TRACING) {
949 use crate::meta;
950
951 sinks.push(Box::new(
952 meta::scuba_sink::ScubaSink::new(meta::tracing_resource())
953 .with_target_filter(crate::config::get_tracing_targets()),
954 ));
955 }
956
957 sinks.push(unix_sink::install_unix_socket_sink_inactive());
958
959 let dispatcher = trace_dispatcher::TraceEventDispatcher::new(sinks);
960 let synthetic_sender = dispatcher.sender();
961
962 if let Err(err) = Registry::default()
963 .with(if hyperactor_config::global::get(ENABLE_RECORDER_TRACING) {
964 Some(recorder().layer())
965 } else {
966 None
967 })
968 .with(dispatcher)
969 .try_init()
970 {
971 tracing::debug!("logging already initialized for this process: {}", err);
972 } else {
973 set_synthetic_trace_event_sender(synthetic_sender);
974 }
975 }
976 let exec_id = env::execution_id();
977 let process_name =
978 std::env::var("HYPERACTOR_PROCESS_NAME").unwrap_or_else(|_| "client".to_string());
979
980 tracing::info!(
982 target: "execution",
983 execution_id = exec_id,
984 environment = %env::Env::current(),
985 args = ?std::env::args(),
986 build_mode = build_info::BuildInfo::get_build_mode(),
987 compiler = build_info::BuildInfo::get_compiler(),
988 compiler_version = build_info::BuildInfo::get_compiler_version(),
989 buck_rule = build_info::BuildInfo::get_rule(),
990 package_name = build_info::BuildInfo::get_package_name(),
991 package_release = build_info::BuildInfo::get_package_release(),
992 upstream_revision = build_info::BuildInfo::get_upstream_revision(),
993 revision = build_info::BuildInfo::get_revision(),
994 process_name = process_name,
995 "logging_initialized",
996 );
997 meta::log_execution_event(
999 &exec_id,
1000 &env::Env::current().to_string(),
1001 std::env::args().collect(),
1002 build_info::BuildInfo::get_build_mode(),
1003 build_info::BuildInfo::get_compiler(),
1004 build_info::BuildInfo::get_compiler_version(),
1005 build_info::BuildInfo::get_rule(),
1006 build_info::BuildInfo::get_package_name(),
1007 build_info::BuildInfo::get_package_release(),
1008 build_info::BuildInfo::get_upstream_revision(),
1009 build_info::BuildInfo::get_revision(),
1010 &process_name,
1011 );
1012
1013 if hyperactor_config::global::get(ENABLE_OTEL_METRICS) {
1014 otel::init_metrics();
1015 }
1016 }
1017 #[cfg(not(all(fbcode_build, target_os = "linux")))]
1018 {
1019 let registry =
1020 Registry::default().with(if hyperactor_config::global::get(ENABLE_RECORDER_TRACING) {
1021 Some(recorder().layer())
1022 } else {
1023 None
1024 });
1025
1026 if should_install_subscriber {
1027 let mut sinks: Vec<Box<dyn trace_dispatcher::TraceEventSink>> = Vec::new();
1028
1029 let sqlite_enabled = hyperactor_config::global::get(ENABLE_SQLITE_TRACING);
1030
1031 if sqlite_enabled {
1032 match create_sqlite_sink() {
1033 Ok(sink) => {
1034 sinks.push(Box::new(sink));
1035 }
1036 Err(e) => {
1037 tracing::warn!("failed to create SqliteSink: {}", e);
1038 }
1039 }
1040 }
1041
1042 sinks.push(Box::new(sinks::glog::GlogSink::new(
1043 writer(),
1044 prefix_env_var.clone(),
1045 file_log_level,
1046 )));
1047
1048 if let Some(log_sink) = otlp::otlp_log_sink() {
1049 sinks.push(log_sink);
1050 }
1051
1052 sinks.push(unix_sink::install_unix_socket_sink_inactive());
1053
1054 let dispatcher = trace_dispatcher::TraceEventDispatcher::new(sinks);
1055 let synthetic_sender = dispatcher.sender();
1056
1057 if let Err(err) = registry.with(dispatcher).try_init() {
1058 tracing::debug!("logging already initialized for this process: {}", err);
1059 } else {
1060 set_synthetic_trace_event_sender(synthetic_sender);
1061 }
1062 }
1063
1064 otel::init_metrics();
1065 }
1066}
1067
1068fn create_sqlite_sink() -> anyhow::Result<sinks::sqlite::SqliteSink> {
1069 let (db_path, _) = log_file_path(env::Env::current(), Some("traces"))
1070 .expect("failed to determine trace db path");
1071 let db_file = format!("{}/hyperactor_trace_{}.db", db_path, std::process::id());
1072
1073 sinks::sqlite::SqliteSink::new_with_file(&db_file, 100)
1074}
1075
1076#[macro_export]
1089macro_rules! context_span {
1090 (target: $target:expr, parent: $parent:expr, $name:expr, $($field:tt)*) => {
1091 ::tracing::error_span!(
1092 target: $target,
1093 parent: $parent,
1094 $name,
1095 skip_record = $crate::skip_record,
1096 $($field)*
1097 )
1098 };
1099 (target: $target:expr, parent: $parent:expr, $name:expr) => {
1100 ::tracing::error_span!(
1101 target: $target,
1102 parent: $parent,
1103 $name,
1104 skip_record = $crate::skip_record,
1105 )
1106 };
1107 (parent: $parent:expr, $name:expr, $($field:tt)*) => {
1108 ::tracing::error_span!(
1109 target: module_path!(),
1110 parent: $parent,
1111 $name,
1112 skip_record = $crate::skip_record,
1113 $($field)*
1114 )
1115 };
1116 (parent: $parent:expr, $name:expr) => {
1117 ::tracing::error_span!(
1118 parent: $parent,
1119 $name,
1120 skip_record = $crate::skip_record,
1121 )
1122 };
1123 (target: $target:expr, $name:expr, $($field:tt)*) => {
1124 ::tracing::error_span!(
1125 target: $target,
1126 $name,
1127 skip_record = $crate::skip_record,
1128 $($field)*
1129 )
1130 };
1131 (target: $target:expr, $name:expr) => {
1132 ::tracing::error_span!(
1133 target: $target,
1134 $name,
1135 skip_record = $crate::skip_record,
1136 )
1137 };
1138 ($name:expr, $($field:tt)*) => {
1139 ::tracing::error_span!(
1140 target: module_path!(),
1141 $name,
1142 skip_record = $crate::skip_record,
1143 $($field)*
1144 )
1145 };
1146 ($name:expr) => {
1147 ::tracing::error_span!(
1148 $name,
1149 skip_record = $crate::skip_record,
1150 )
1151 };
1152}
1153
1154pub mod env {
1155 pub const HYPERACTOR_EXECUTION_ID_ENV: &str = "HYPERACTOR_EXECUTION_ID";
1157 pub const OTEL_EXPORTER: &str = "HYPERACTOR_OTEL_EXPORTER";
1158 pub const MAST_ENVIRONMENT: &str = "MAST_ENVIRONMENT";
1159
1160 pub fn execution_id() -> String {
1167 let id = std::env::var(HYPERACTOR_EXECUTION_ID_ENV).unwrap_or_else(|_| {
1168 let username = crate::username();
1170 let now = {
1171 let now = std::time::SystemTime::now();
1172 let datetime: chrono::DateTime<chrono::Local> = now.into();
1173 datetime.format("%b-%d_%H:%M").to_string()
1174 };
1175 let random_number: u16 = (rand::random::<u32>() % 1000) as u16;
1176 let execution_id = format!("{}_{}_{}", username, now, random_number);
1177 execution_id
1178 });
1179 unsafe {
1182 std::env::set_var(HYPERACTOR_EXECUTION_ID_ENV, id.clone());
1183 }
1184 id
1185 }
1186
1187 #[derive(PartialEq)]
1188 pub enum Env {
1189 Local,
1190 Mast,
1191 MastEmulator,
1192 Test,
1193 }
1194
1195 impl std::fmt::Display for Env {
1196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1197 write!(
1198 f,
1199 "{}",
1200 match self {
1201 Self::Local => crate::ENV_VALUE_LOCAL,
1202 Self::MastEmulator => crate::ENV_VALUE_MAST_EMULATOR,
1203 Self::Mast => crate::ENV_VALUE_MAST,
1204 Self::Test => crate::ENV_VALUE_TEST,
1205 }
1206 )
1207 }
1208 }
1209
1210 impl Env {
1211 #[cfg(test)]
1212 pub fn current() -> Self {
1213 Self::Test
1214 }
1215
1216 #[cfg(not(test))]
1217 pub fn current() -> Self {
1218 match std::env::var(MAST_ENVIRONMENT).unwrap_or_default().as_str() {
1219 crate::ENV_VALUE_LOCAL_MAST_SIMULATOR => Self::MastEmulator,
1221 _ => match std::env::var(crate::MAST_HPC_JOB_NAME_ENV).is_ok() {
1222 true => Self::Mast,
1223 false => Self::Local,
1224 },
1225 }
1226 }
1227 }
1228}
1229
1230#[cfg(test)]
1231mod test {
1232 use opentelemetry::*;
1233 extern crate self as hyperactor_telemetry;
1234 use super::*;
1235
1236 #[test]
1237 fn infer_kv_pair_types() {
1238 assert_eq!(
1239 key_value!("str", "str"),
1240 KeyValue::new(Key::new("str"), Value::String("str".into()))
1241 );
1242 assert_eq!(
1243 key_value!("str", 25),
1244 KeyValue::new(Key::new("str"), Value::I64(25))
1245 );
1246 assert_eq!(
1247 key_value!("str", 1.1),
1248 KeyValue::new(Key::new("str"), Value::F64(1.1))
1249 );
1250 }
1251 #[test]
1252 fn kv_pair_slices() {
1253 assert_eq!(
1254 kv_pairs!("1" => "1", "2" => 2, "3" => 3.0),
1255 &[
1256 key_value!("1", "1"),
1257 key_value!("2", 2),
1258 key_value!("3", 3.0),
1259 ],
1260 );
1261 }
1262
1263 #[test]
1264 fn test_static_gauge() {
1265 declare_static_gauge!(TEST_GAUGE, "test_gauge");
1267 declare_static_gauge!(MEMORY_GAUGE, "memory_usage");
1268
1269 TEST_GAUGE.record(42.5, kv_pairs!("component" => "test", "unit" => "MB"));
1272 MEMORY_GAUGE.record(512.0, kv_pairs!("type" => "heap", "process" => "test"));
1273
1274 TEST_GAUGE.record(50.0, &[]);
1276 }
1277}