1use thiserror::Error;
7
8const VTL_TRANSITIONS_PATH: &str = "/sys/class/misc/mshv_vtl_low/mshv_vtl_transitions";
11
12#[derive(Debug, Error)]
14#[expect(missing_docs)]
15pub enum VpStatsError {
16 #[error("failed to read {VTL_TRANSITIONS_PATH}")]
17 Read(#[source] std::io::Error),
18 #[error("stats are not utf-8")]
19 NotUtf8(#[source] std::str::Utf8Error),
20 #[error("stats are missing the expected header line")]
21 MissingHeader,
22 #[error("failed to parse stats line")]
23 ParseLine,
24}
25
26#[derive(Debug, Clone)]
28pub struct HclVpStats {
29 pub vtl_transitions: u64,
37}
38
39pub fn vp_stats() -> Result<Vec<Option<HclVpStats>>, VpStatsError> {
44 let data = std::fs::read(VTL_TRANSITIONS_PATH).map_err(VpStatsError::Read)?;
45 let data = std::str::from_utf8(&data).map_err(VpStatsError::NotUtf8)?;
46
47 let complete = &data[..data.rfind('\n').map_or(0, |i| i + 1)];
51 let mut lines = complete.lines();
52 if !lines.next().is_some_and(|l| l.starts_with("cpu#")) {
53 return Err(VpStatsError::MissingHeader);
54 }
55 let mut stats = Vec::new();
56 for line in lines {
57 let (cpu, rest) = line.split_once(' ').ok_or(VpStatsError::ParseLine)?;
58 let n: usize = cpu
59 .strip_prefix("cpu")
60 .ok_or(VpStatsError::ParseLine)?
61 .parse()
62 .map_err(|_| VpStatsError::ParseLine)?;
63
64 let vtl_transitions = rest
65 .split(' ')
66 .next()
67 .ok_or(VpStatsError::ParseLine)?
68 .parse()
69 .map_err(|_| VpStatsError::ParseLine)?;
70
71 if stats.len() <= n {
72 stats.resize_with(n + 1, || None);
73 }
74 stats[n] = Some(HclVpStats { vtl_transitions });
75 }
76 Ok(stats)
77}