113 lines
3.1 KiB
Rust
Raw Normal View History

// 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<MonitorHandle> = None;
#[derive(Serialize)]
struct Event<'a> {
timestamp: Duration,
source: &'a str,
event: &'a str,
properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
}
pub struct Monitor {
pub rx: flume::Receiver<String>,
pub file: File,
}
struct MonitorHandle {
tx: flume::Sender<String>,
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<Monitor> {
// 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<str>, Cow<str>>>) {
// 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))
}
};
}