xref: /cloud-hypervisor/event_monitor/src/lib.rs (revision 07d1208dd53a207a65b649b8952780dfd0ca59d9)
1 // Copyright © 2021 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use serde::Serialize;
7 use std::borrow::Cow;
8 use std::collections::HashMap;
9 use std::fs::File;
10 use std::io;
11 use std::os::unix::io::AsRawFd;
12 use std::sync::Arc;
13 use std::time::{Duration, Instant};
14 
15 static mut MONITOR: Option<MonitorHandle> = None;
16 
17 #[derive(Serialize)]
18 struct Event<'a> {
19     timestamp: Duration,
20     source: &'a str,
21     event: &'a str,
22     properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
23 }
24 
25 pub struct Monitor {
26     pub rx: flume::Receiver<String>,
27     pub file: Option<File>,
28     pub broadcast: Vec<flume::Sender<Arc<String>>>,
29 }
30 
31 impl Monitor {
32     pub fn new(rx: flume::Receiver<String>, file: Option<File>) -> Self {
33         Self {
34             rx,
35             file,
36             broadcast: vec![],
37         }
38     }
39 
40     pub fn subscribe(&mut self) -> flume::Receiver<Arc<String>> {
41         let (tx, rx) = flume::unbounded();
42         self.broadcast.push(tx);
43         rx
44     }
45 }
46 
47 struct MonitorHandle {
48     tx: flume::Sender<String>,
49     start: Instant,
50 }
51 
52 fn set_file_nonblocking(file: &File) -> io::Result<()> {
53     let fd = file.as_raw_fd();
54 
55     // SAFETY: FFI call to configure the fd
56     let ret = unsafe {
57         let mut flags = libc::fcntl(fd, libc::F_GETFL);
58         flags |= libc::O_NONBLOCK;
59         libc::fcntl(fd, libc::F_SETFL, flags)
60     };
61 
62     if ret < 0 {
63         Err(io::Error::last_os_error())
64     } else {
65         Ok(())
66     }
67 }
68 
69 /// This function must only be called once from the main thread before any threads
70 /// are created to avoid race conditions.
71 pub fn set_monitor(file: Option<File>) -> io::Result<Monitor> {
72     // SAFETY: there is only one caller of this function, so MONITOR is written to only once
73     assert!(unsafe { MONITOR.is_none() });
74 
75     if let Some(ref file) = file {
76         set_file_nonblocking(file)?;
77     }
78 
79     let (tx, rx) = flume::unbounded();
80     let monitor = Monitor::new(rx, file);
81 
82     // SAFETY: MONITOR is None. Nobody else can hold a reference to it.
83     unsafe {
84         MONITOR = Some(MonitorHandle {
85             tx,
86             start: Instant::now(),
87         });
88     };
89 
90     Ok(monitor)
91 }
92 
93 pub fn event_log(source: &str, event: &str, properties: Option<&HashMap<Cow<str>, Cow<str>>>) {
94     // SAFETY: `MONITOR` is always in a valid state (None or Some), because it
95     // is set only once before any threads are spawned, and it's not mutated
96     // afterwards. This function only creates immutable references to `MONITOR`.
97     // Because `MONITOR.tx` is `Sync`, it's safe to share `MONITOR` across
98     // threads, making this function thread-safe.
99     if let Some(monitor_handle) = unsafe { MONITOR.as_ref() } {
100         let event = Event {
101             timestamp: monitor_handle.start.elapsed(),
102             source,
103             event,
104             properties,
105         };
106 
107         if let Ok(event) = serde_json::to_string_pretty(&event) {
108             monitor_handle.tx.send(event).ok();
109         }
110     }
111 }
112 
113 /*
114     Through the use of Cow<'a, str> it is possible to use String as well as
115     &str as the parameters:
116     e.g.
117     event!("cpu_manager", "create_vcpu", "id", cpu_id.to_string());
118 */
119 #[macro_export]
120 macro_rules! event {
121     ($source:expr, $event:expr) => {
122         $crate::event_log($source, $event, None)
123     };
124     ($source:expr, $event:expr, $($key:expr, $value:expr),*) => {
125         {
126             let mut properties = ::std::collections::HashMap::new();
127             $(
128                 properties.insert($key.into(), $value.into());
129             )+
130             $crate::event_log($source, $event, Some(&properties))
131         }
132      };
133 }
134