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 metrics_table;
61mod otel;
62pub(crate) mod otlp;
63mod pool;
64mod rate_limit;
65pub mod recorder;
66pub mod sinks;
67mod spool;
68pub mod sqlite;
69pub mod task;
70pub mod trace;
71pub mod trace_dispatcher;
72mod unix_sink;
73
74use std::collections::hash_map::DefaultHasher;
76use std::hash::Hash;
77use std::hash::Hasher;
78use std::io::Write;
79use std::sync::Arc;
80use std::sync::LazyLock;
81use std::sync::Mutex;
82use std::sync::atomic::AtomicU64;
83use std::sync::atomic::Ordering;
84use std::sync::mpsc;
85use std::time::Instant;
86use std::time::SystemTime;
87
88pub use opentelemetry;
89pub use opentelemetry::Key;
90pub use opentelemetry::KeyValue;
91pub use opentelemetry::Value;
92pub use opentelemetry::global::meter;
93pub use trace_dispatcher::DispatcherControl;
94pub use trace_dispatcher::FieldValue;
95pub use trace_dispatcher::TraceEvent;
96pub use trace_dispatcher::TraceEventSink;
97use trace_dispatcher::TraceFields;
98pub use tracing;
99pub use tracing::Level;
100use tracing_appender::rolling::RollingFileAppender;
101pub use unix_sink::set_unix_socket_sink_path;
102pub use unix_sink::unix_socket_sink_dropped_frames;
103pub use unix_sink::unix_socket_sink_is_active;
104
105#[cfg(all(fbcode_build, target_os = "linux"))]
106use crate::config::ENABLE_OTEL_METRICS;
107#[cfg(all(fbcode_build, target_os = "linux"))]
108use crate::config::ENABLE_OTEL_TRACING;
109use crate::config::ENABLE_RECORDER_TRACING;
110use crate::config::ENABLE_SQLITE_TRACING;
111use crate::config::MONARCH_LOG_SUFFIX;
112use crate::recorder::Recorder;
113
114pub fn hash_to_u64(value: &impl Hash) -> u64 {
116 let mut hasher = DefaultHasher::new();
117 value.hash(&mut hasher);
118 hasher.finish()
119}
120
121pub trait TelemetryClock {
122 fn now(&self) -> tokio::time::Instant;
123 fn system_time_now(&self) -> std::time::SystemTime;
124}
125
126pub struct DefaultTelemetryClock {}
127
128impl TelemetryClock for DefaultTelemetryClock {
129 fn now(&self) -> tokio::time::Instant {
130 tokio::time::Instant::now()
131 }
132
133 fn system_time_now(&self) -> std::time::SystemTime {
134 std::time::SystemTime::now()
135 }
136}
137
138pub fn username() -> String {
139 let env = env::Env::current();
140 match env {
141 env::Env::Mast => {
142 std::env::var("MAST_JOB_OWNER_UNIXNAME").unwrap_or_else(|_| "mast_owner".to_string())
143 }
144 _ => whoami::username().unwrap_or_else(|_| "unknown".to_owned()),
145 }
146}
147
148pub fn log_file_path(
151 env: env::Env,
152 suffix: Option<&str>,
153) -> Result<(String, String), anyhow::Error> {
154 let suffix = suffix
155 .map(|s| {
156 if s.is_empty() {
157 String::new()
158 } else {
159 format!("_{}", s)
160 }
161 })
162 .unwrap_or_default();
163 match env {
164 env::Env::Local | env::Env::MastEmulator => {
165 let mut username = whoami::username().unwrap_or_else(|_| "unknown".to_owned());
166 if username.is_empty() {
167 username.push_str("monarch");
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]
676macro_rules! declare_static_timer {
677 ($name:ident, $key:expr, $unit:path) => {
678 #[doc = "a global histogram timer named: "]
679 #[doc = $key]
680 pub static $name: std::sync::LazyLock<$crate::Timer> = std::sync::LazyLock::new(|| {
681 $crate::Timer::new(
682 $crate::meter(module_path!())
683 .u64_histogram(format!("{}.{}", $key, $unit.as_str()))
684 .with_unit($unit.as_str())
685 .build(),
686 $unit,
687 )
688 });
689 };
690}
691
692#[macro_export]
714macro_rules! declare_static_counter {
715 ($name:ident, $key:expr) => {
716 #[doc = "a global counter named: "]
717 #[doc = $key]
718 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Counter<u64>> =
719 std::sync::LazyLock::new(|| $crate::meter(module_path!()).u64_counter($key).build());
720 };
721}
722
723#[macro_export]
745macro_rules! declare_static_up_down_counter {
746 ($name:ident, $key:expr) => {
747 #[doc = "a global up down counter named: "]
748 #[doc = $key]
749 pub static $name: std::sync::LazyLock<opentelemetry::metrics::UpDownCounter<i64>> =
750 std::sync::LazyLock::new(|| {
751 $crate::meter(module_path!())
752 .i64_up_down_counter($key)
753 .build()
754 });
755 };
756}
757
758#[macro_export]
780macro_rules! declare_static_gauge {
781 ($name:ident, $key:expr) => {
782 #[doc = "a global gauge named: "]
783 #[doc = $key]
784 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Gauge<f64>> =
785 std::sync::LazyLock::new(|| $crate::meter(module_path!()).f64_gauge($key).build());
786 };
787}
788#[macro_export]
806macro_rules! declare_observable_gauge {
807 ($name:ident, $key:expr, $cb:expr) => {
808 #[doc = "a global gauge named: "]
809 #[doc = $key]
810 pub static $name: std::sync::LazyLock<opentelemetry::metrics::ObservableGauge<f64>> =
811 std::sync::LazyLock::new(|| {
812 $crate::meter(module_path!())
813 .f64_observable_gauge($key)
814 .with_callback($cb)
815 .build()
816 });
817 };
818}
819#[macro_export]
841macro_rules! declare_static_histogram {
842 ($name:ident, $key:expr) => {
843 #[doc = "a global histogram named: "]
844 #[doc = $key]
845 pub static $name: std::sync::LazyLock<opentelemetry::metrics::Histogram<f64>> =
846 std::sync::LazyLock::new(|| {
847 hyperactor_telemetry::meter(module_path!())
848 .f64_histogram($key)
849 .build()
850 });
851 };
852}
853
854pub fn initialize_logging(clock: impl TelemetryClock + Send + 'static) {
863 initialize_logging_with_log_prefix(clock, None);
864}
865
866pub fn initialize_logging_for_test() {
868 initialize_logging(DefaultTelemetryClock {});
869}
870
871pub fn initialize_logging_with_log_prefix(
886 clock: impl TelemetryClock + Send + 'static,
887 prefix_env_var: Option<String>,
888) {
889 let should_install_subscriber = !tracing::dispatcher::has_been_set();
890
891 swap_telemetry_clock(clock);
892 if !should_install_subscriber {
893 tracing::debug!("logging already initialized for this process");
894 }
895 let file_log_level = match env::Env::current() {
896 env::Env::Local => LOG_LEVEL_INFO,
897 env::Env::MastEmulator => LOG_LEVEL_INFO,
898 env::Env::Mast => LOG_LEVEL_INFO,
899 env::Env::Test => LOG_LEVEL_DEBUG,
900 };
901
902 use tracing_subscriber::Registry;
903 use tracing_subscriber::layer::SubscriberExt;
904 use tracing_subscriber::util::SubscriberInitExt;
905
906 #[cfg(all(fbcode_build, target_os = "linux"))]
907 {
908 if should_install_subscriber {
909 let mut sinks: Vec<Box<dyn trace_dispatcher::TraceEventSink>> = Vec::new();
910 sinks.push(Box::new(sinks::glog::GlogSink::new(
911 writer(),
912 prefix_env_var.clone(),
913 file_log_level,
914 )));
915
916 let sqlite_enabled = hyperactor_config::global::get(ENABLE_SQLITE_TRACING);
917
918 if sqlite_enabled {
919 match create_sqlite_sink() {
920 Ok(sink) => {
921 sinks.push(Box::new(sink));
922 }
923 Err(e) => {
924 tracing::warn!("failed to create SqliteSink: {}", e);
925 }
926 }
927 }
928
929 if hyperactor_config::global::get(sinks::perfetto::PERFETTO_TRACE_MODE)
930 != sinks::perfetto::PerfettoTraceMode::Off
931 {
932 let exec_id = env::execution_id();
933 let process_name = std::env::var("HYPERACTOR_PROCESS_NAME")
934 .unwrap_or_else(|_| "client".to_string());
935 match sinks::perfetto::PerfettoFileSink::new(
936 sinks::perfetto::default_trace_dir(),
937 &exec_id,
938 &process_name,
939 ) {
940 Ok(sink) => {
941 sinks.push(Box::new(sink));
942 }
943 Err(e) => {
944 tracing::warn!("failed to create PerfettoFileSink: {}", e);
945 }
946 }
947 }
948
949 if hyperactor_config::global::get(ENABLE_OTEL_TRACING) {
950 use crate::meta;
951
952 sinks.push(Box::new(
953 meta::scuba_sink::ScubaSink::new(meta::tracing_resource())
954 .with_target_filter(crate::config::get_tracing_targets()),
955 ));
956 }
957
958 sinks.push(unix_sink::install_unix_socket_sink_inactive());
959
960 let dispatcher = trace_dispatcher::TraceEventDispatcher::new(sinks);
961 let synthetic_sender = dispatcher.sender();
962
963 if let Err(err) = Registry::default()
964 .with(if hyperactor_config::global::get(ENABLE_RECORDER_TRACING) {
965 Some(recorder().layer())
966 } else {
967 None
968 })
969 .with(dispatcher)
970 .try_init()
971 {
972 tracing::debug!("logging already initialized for this process: {}", err);
973 } else {
974 set_synthetic_trace_event_sender(synthetic_sender);
975 }
976 }
977 let exec_id = env::execution_id();
978 let process_name =
979 std::env::var("HYPERACTOR_PROCESS_NAME").unwrap_or_else(|_| "client".to_string());
980
981 tracing::info!(
983 target: "execution",
984 execution_id = exec_id,
985 environment = %env::Env::current(),
986 args = ?std::env::args(),
987 build_mode = build_info::BuildInfo::get_build_mode(),
988 compiler = build_info::BuildInfo::get_compiler(),
989 compiler_version = build_info::BuildInfo::get_compiler_version(),
990 buck_rule = build_info::BuildInfo::get_rule(),
991 package_name = build_info::BuildInfo::get_package_name(),
992 package_release = build_info::BuildInfo::get_package_release(),
993 upstream_revision = build_info::BuildInfo::get_upstream_revision(),
994 revision = build_info::BuildInfo::get_revision(),
995 process_name = process_name,
996 "logging_initialized",
997 );
998 meta::log_execution_event(
1000 &exec_id,
1001 &env::Env::current().to_string(),
1002 std::env::args().collect(),
1003 build_info::BuildInfo::get_build_mode(),
1004 build_info::BuildInfo::get_compiler(),
1005 build_info::BuildInfo::get_compiler_version(),
1006 build_info::BuildInfo::get_rule(),
1007 build_info::BuildInfo::get_package_name(),
1008 build_info::BuildInfo::get_package_release(),
1009 build_info::BuildInfo::get_upstream_revision(),
1010 build_info::BuildInfo::get_revision(),
1011 &process_name,
1012 );
1013
1014 if hyperactor_config::global::get(ENABLE_OTEL_METRICS) {
1015 otel::init_metrics();
1016 }
1017 }
1018 #[cfg(not(all(fbcode_build, target_os = "linux")))]
1019 {
1020 let registry =
1021 Registry::default().with(if hyperactor_config::global::get(ENABLE_RECORDER_TRACING) {
1022 Some(recorder().layer())
1023 } else {
1024 None
1025 });
1026
1027 if should_install_subscriber {
1028 let mut sinks: Vec<Box<dyn trace_dispatcher::TraceEventSink>> = Vec::new();
1029
1030 let sqlite_enabled = hyperactor_config::global::get(ENABLE_SQLITE_TRACING);
1031
1032 if sqlite_enabled {
1033 match create_sqlite_sink() {
1034 Ok(sink) => {
1035 sinks.push(Box::new(sink));
1036 }
1037 Err(e) => {
1038 tracing::warn!("failed to create SqliteSink: {}", e);
1039 }
1040 }
1041 }
1042
1043 sinks.push(Box::new(sinks::glog::GlogSink::new(
1044 writer(),
1045 prefix_env_var.clone(),
1046 file_log_level,
1047 )));
1048
1049 if let Some(log_sink) = otlp::otlp_log_sink() {
1050 sinks.push(log_sink);
1051 }
1052
1053 sinks.push(unix_sink::install_unix_socket_sink_inactive());
1054
1055 let dispatcher = trace_dispatcher::TraceEventDispatcher::new(sinks);
1056 let synthetic_sender = dispatcher.sender();
1057
1058 if let Err(err) = registry.with(dispatcher).try_init() {
1059 tracing::debug!("logging already initialized for this process: {}", err);
1060 } else {
1061 set_synthetic_trace_event_sender(synthetic_sender);
1062 }
1063 }
1064
1065 otel::init_metrics();
1066 }
1067}
1068
1069fn create_sqlite_sink() -> anyhow::Result<sinks::sqlite::SqliteSink> {
1070 let (db_path, _) = log_file_path(env::Env::current(), Some("traces"))
1071 .expect("failed to determine trace db path");
1072 let db_file = format!("{}/hyperactor_trace_{}.db", db_path, std::process::id());
1073
1074 sinks::sqlite::SqliteSink::new_with_file(&db_file, 100)
1075}
1076
1077#[macro_export]
1090macro_rules! context_span {
1091 (target: $target:expr, parent: $parent:expr, $name:expr, $($field:tt)*) => {
1092 ::tracing::error_span!(
1093 target: $target,
1094 parent: $parent,
1095 $name,
1096 skip_record = $crate::skip_record,
1097 $($field)*
1098 )
1099 };
1100 (target: $target:expr, parent: $parent:expr, $name:expr) => {
1101 ::tracing::error_span!(
1102 target: $target,
1103 parent: $parent,
1104 $name,
1105 skip_record = $crate::skip_record,
1106 )
1107 };
1108 (parent: $parent:expr, $name:expr, $($field:tt)*) => {
1109 ::tracing::error_span!(
1110 target: module_path!(),
1111 parent: $parent,
1112 $name,
1113 skip_record = $crate::skip_record,
1114 $($field)*
1115 )
1116 };
1117 (parent: $parent:expr, $name:expr) => {
1118 ::tracing::error_span!(
1119 parent: $parent,
1120 $name,
1121 skip_record = $crate::skip_record,
1122 )
1123 };
1124 (target: $target:expr, $name:expr, $($field:tt)*) => {
1125 ::tracing::error_span!(
1126 target: $target,
1127 $name,
1128 skip_record = $crate::skip_record,
1129 $($field)*
1130 )
1131 };
1132 (target: $target:expr, $name:expr) => {
1133 ::tracing::error_span!(
1134 target: $target,
1135 $name,
1136 skip_record = $crate::skip_record,
1137 )
1138 };
1139 ($name:expr, $($field:tt)*) => {
1140 ::tracing::error_span!(
1141 target: module_path!(),
1142 $name,
1143 skip_record = $crate::skip_record,
1144 $($field)*
1145 )
1146 };
1147 ($name:expr) => {
1148 ::tracing::error_span!(
1149 $name,
1150 skip_record = $crate::skip_record,
1151 )
1152 };
1153}
1154
1155pub mod env {
1156 pub const HYPERACTOR_EXECUTION_ID_ENV: &str = "HYPERACTOR_EXECUTION_ID";
1158 pub const OTEL_EXPORTER: &str = "HYPERACTOR_OTEL_EXPORTER";
1159 pub const MAST_ENVIRONMENT: &str = "MAST_ENVIRONMENT";
1160
1161 pub fn execution_id() -> String {
1168 let id = std::env::var(HYPERACTOR_EXECUTION_ID_ENV).unwrap_or_else(|_| {
1169 let username = crate::username();
1171 let now = {
1172 let now = std::time::SystemTime::now();
1173 let datetime: chrono::DateTime<chrono::Local> = now.into();
1174 datetime.format("%b-%d_%H:%M").to_string()
1175 };
1176 let random_number: u16 = (rand::random::<u32>() % 1000) as u16;
1177 let execution_id = format!("{}_{}_{}", username, now, random_number);
1178 execution_id
1179 });
1180 unsafe {
1183 std::env::set_var(HYPERACTOR_EXECUTION_ID_ENV, id.clone());
1184 }
1185 id
1186 }
1187
1188 #[derive(PartialEq)]
1189 pub enum Env {
1190 Local,
1191 Mast,
1192 MastEmulator,
1193 Test,
1194 }
1195
1196 impl std::fmt::Display for Env {
1197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1198 write!(
1199 f,
1200 "{}",
1201 match self {
1202 Self::Local => crate::ENV_VALUE_LOCAL,
1203 Self::MastEmulator => crate::ENV_VALUE_MAST_EMULATOR,
1204 Self::Mast => crate::ENV_VALUE_MAST,
1205 Self::Test => crate::ENV_VALUE_TEST,
1206 }
1207 )
1208 }
1209 }
1210
1211 impl Env {
1212 #[cfg(test)]
1213 pub fn current() -> Self {
1214 Self::Test
1215 }
1216
1217 #[cfg(not(test))]
1218 pub fn current() -> Self {
1219 match std::env::var(MAST_ENVIRONMENT).unwrap_or_default().as_str() {
1220 crate::ENV_VALUE_LOCAL_MAST_SIMULATOR => Self::MastEmulator,
1222 _ => match std::env::var(crate::MAST_HPC_JOB_NAME_ENV).is_ok() {
1223 true => Self::Mast,
1224 false => Self::Local,
1225 },
1226 }
1227 }
1228 }
1229}
1230
1231#[cfg(test)]
1232mod test {
1233 use opentelemetry::*;
1234 extern crate self as hyperactor_telemetry;
1235 use super::*;
1236
1237 #[test]
1238 fn infer_kv_pair_types() {
1239 assert_eq!(
1240 key_value!("str", "str"),
1241 KeyValue::new(Key::new("str"), Value::String("str".into()))
1242 );
1243 assert_eq!(
1244 key_value!("str", 25),
1245 KeyValue::new(Key::new("str"), Value::I64(25))
1246 );
1247 assert_eq!(
1248 key_value!("str", 1.1),
1249 KeyValue::new(Key::new("str"), Value::F64(1.1))
1250 );
1251 }
1252 #[test]
1253 fn kv_pair_slices() {
1254 assert_eq!(
1255 kv_pairs!("1" => "1", "2" => 2, "3" => 3.0),
1256 &[
1257 key_value!("1", "1"),
1258 key_value!("2", 2),
1259 key_value!("3", 3.0),
1260 ],
1261 );
1262 }
1263
1264 #[test]
1265 fn test_static_gauge() {
1266 declare_static_gauge!(TEST_GAUGE, "test_gauge");
1268 declare_static_gauge!(MEMORY_GAUGE, "memory_usage");
1269
1270 TEST_GAUGE.record(42.5, kv_pairs!("component" => "test", "unit" => "MB"));
1273 MEMORY_GAUGE.record(512.0, kv_pairs!("type" => "heap", "process" => "test"));
1274
1275 TEST_GAUGE.record(50.0, &[]);
1277 }
1278}