// Copyright © 2021 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // use serde::Serialize; use std::borrow::Cow; use std::collections::HashMap; use std::fs::File; use std::io; use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant}; static mut MONITOR: Option = None; #[derive(Serialize)] struct Event<'a> { timestamp: Duration, source: &'a str, event: &'a str, properties: Option<&'a HashMap, Cow<'a, str>>>, } pub struct Monitor { pub rx: flume::Receiver, pub file: File, } struct MonitorHandle { tx: flume::Sender, start: Instant, } fn set_file_nonblocking(file: &File) -> io::Result<()> { let fd = file.as_raw_fd(); // SAFETY: FFI call to configure the fd let ret = unsafe { let mut flags = libc::fcntl(fd, libc::F_GETFL); flags |= libc::O_NONBLOCK; libc::fcntl(fd, libc::F_SETFL, flags) }; if ret < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } /// This function must only be called once from the main thread before any threads /// are created to avoid race conditions. pub fn set_monitor(file: File) -> io::Result { // SAFETY: there is only one caller of this function, so MONITOR is written to only once assert!(unsafe { MONITOR.is_none() }); set_file_nonblocking(&file)?; let (tx, rx) = flume::unbounded(); let monitor = Monitor { rx, file }; // SAFETY: MONITOR is None. Nobody else can hold a reference to it. unsafe { MONITOR = Some(MonitorHandle { tx, start: Instant::now(), }); }; Ok(monitor) } pub fn event_log(source: &str, event: &str, properties: Option<&HashMap, Cow>>) { // SAFETY: `MONITOR` is always in a valid state (None or Some), because it // is set only once before any threads are spawned, and it's not mutated // afterwards. This function only creates immutable references to `MONITOR`. // Because `MONITOR.tx` is `Sync`, it's safe to share `MONITOR` across // threads, making this function thread-safe. if let Some(monitor_handle) = unsafe { MONITOR.as_ref() } { let event = Event { timestamp: monitor_handle.start.elapsed(), source, event, properties, }; if let Ok(event) = serde_json::to_string_pretty(&event) { monitor_handle.tx.send(event).ok(); } } } /* Through the use of Cow<'a, str> it is possible to use String as well as &str as the parameters: e.g. event!("cpu_manager", "create_vcpu", "id", cpu_id.to_string()); */ #[macro_export] macro_rules! event { ($source:expr, $event:expr) => { $crate::event_log($source, $event, None) }; ($source:expr, $event:expr, $($key:expr, $value:expr),*) => { { let mut properties = ::std::collections::HashMap::new(); $( properties.insert($key.into(), $value.into()); )+ $crate::event_log($source, $event, Some(&properties)) } }; }