1use std::{
2 collections::HashMap,
3 fmt::Display,
4 time::{Duration, Instant},
5};
6
7use uhyve_interface::HypercallAddress;
8
9#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
10pub enum VmExit {
11 MMIORead,
12 MMIOWrite,
13 PCIRead,
14 PCIWrite,
15 Debug,
16 Hypercall(HypercallAddress),
17}
18
19#[derive(Debug, Clone)]
20pub(crate) struct CpuStats {
21 id: usize,
22 vm_exits: HashMap<VmExit, usize>,
23 runtime: Option<Duration>,
24 start_time: Option<Instant>,
25}
26impl CpuStats {
27 pub(crate) fn new(id: usize) -> Self {
28 Self {
29 id,
30 vm_exits: HashMap::new(),
31 runtime: None,
32 start_time: None,
33 }
34 }
35
36 #[inline]
37 pub(crate) fn increment_val(&mut self, val: VmExit) {
38 *self.vm_exits.entry(val).or_insert(0) += 1;
39 }
40
41 pub(crate) fn start_time_measurement(&mut self) {
42 let _ = self.start_time.insert(Instant::now());
43 }
44
45 pub(crate) fn stop_time_measurement(&mut self) {
46 if let Some(start_time) = self.start_time {
47 self.runtime = Some(start_time.elapsed());
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
53pub struct VmStats {
54 pub vm_exits: HashMap<VmExit, HashMap<usize, usize>>,
56 pub cpu_runtimes: Vec<(usize, Duration)>,
58}
59impl VmStats {
60 pub(crate) fn new(cpu_stats: &[CpuStats]) -> Self {
61 let mut stats = Self {
62 vm_exits: HashMap::new(),
63 cpu_runtimes: Vec::new(),
64 };
65 for cpu in cpu_stats.iter() {
66 for (exit, count) in cpu.vm_exits.iter() {
67 stats
68 .vm_exits
69 .entry(*exit)
70 .or_default()
71 .insert(cpu.id, *count);
72 }
73 if let Some(runtime) = cpu.runtime {
74 stats.cpu_runtimes.push((cpu.id, runtime));
75 }
76 }
77 stats
78 .cpu_runtimes
79 .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
80
81 stats
82 }
83}
84impl Display for VmStats {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 let cpu_id_start = self
87 .vm_exits
88 .values()
89 .map(|counts| *counts.keys().min().unwrap())
90 .min()
91 .unwrap_or(0);
92
93 let cpu_id_end = self
94 .vm_exits
95 .values()
96 .map(|counts| *counts.keys().max().unwrap())
97 .max()
98 .unwrap_or(0);
99 write!(f, "VM exits: total ")?;
100 for i in cpu_id_start..=cpu_id_end {
101 write!(f, " {:>6.} ", format!("cpu{i}"))?;
102 }
103 writeln!(f)?;
104 for (exit, counts) in self.vm_exits.iter() {
105 let total: usize = counts.values().sum();
106 write!(f, " {:<28} {total:>6.} ", format!("{exit:?}:"))?;
107 for i in cpu_id_start..=cpu_id_end {
108 if let Some(cnt) = counts.get(&i) {
109 write!(f, " {cnt:>6.} ")?;
110 } else {
111 write!(f, " ")?;
112 }
113 }
114 writeln!(f)?;
115 }
116 writeln!(f, "CPU runtimes:")?;
117 self.cpu_runtimes
118 .iter()
119 .for_each(|(id, rt)| writeln!(f, " cpu {id}: {rt:?}").unwrap());
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_stats() {
130 let mut s1 = CpuStats::new(1);
131 s1.start_time_measurement();
132 s1.increment_val(VmExit::PCIRead);
133 s1.increment_val(VmExit::PCIRead);
134 s1.increment_val(VmExit::Hypercall(HypercallAddress::Uart));
135 s1.increment_val(VmExit::Hypercall(HypercallAddress::Uart));
136 s1.increment_val(VmExit::Hypercall(HypercallAddress::FileOpen));
137 s1.stop_time_measurement();
138 println!("{s1:?}");
139
140 let mut s2 = CpuStats::new(2);
141 s2.start_time_measurement();
142 s2.increment_val(VmExit::PCIRead);
143 s2.increment_val(VmExit::MMIOWrite);
144 s2.increment_val(VmExit::Hypercall(HypercallAddress::Uart));
145 s2.increment_val(VmExit::Hypercall(HypercallAddress::FileClose));
146 s2.stop_time_measurement();
147 println!("{s2:?}");
148
149 let vm_stats = VmStats::new(&[s1, s2]);
150 println!("{vm_stats}");
151
152 assert_eq!(vm_stats.vm_exits.get(&VmExit::PCIRead).unwrap().len(), 2);
153 assert_eq!(
154 vm_stats
155 .vm_exits
156 .get(&VmExit::PCIRead)
157 .unwrap()
158 .values()
159 .sum::<usize>(),
160 3
161 );
162 assert_eq!(
163 vm_stats
164 .vm_exits
165 .get(&VmExit::MMIOWrite)
166 .unwrap()
167 .values()
168 .sum::<usize>(),
169 1
170 );
171 assert_eq!(
172 vm_stats
173 .vm_exits
174 .get(&VmExit::Hypercall(HypercallAddress::Uart))
175 .unwrap()
176 .values()
177 .sum::<usize>(),
178 3
179 );
180 assert_eq!(vm_stats.cpu_runtimes.len(), 2);
181 }
182}