uhyvelib/
stats.rs

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	/// Number of Vm exits per CPU
55	pub vm_exits: HashMap<VmExit, HashMap<usize, usize>>,
56	/// total runtime per cpu (`(cpu_id, runtime)`)
57	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}