xref: /aosp_15_r20/external/crosvm/src/crosvm/sys/windows/stats.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #![allow(dead_code)]
6 
7 use std::cmp::Reverse;
8 use std::fmt;
9 use std::sync::Arc;
10 use std::time::Duration;
11 use std::time::Instant;
12 
13 use devices::BusStatistics;
14 use hypervisor::VcpuExit;
15 use sync::Mutex;
16 
17 const ERROR_RETRY_I32: i32 = winapi::shared::winerror::ERROR_RETRY as i32;
18 
19 /// Statistics about the number and duration of VM exits.
20 #[derive(Clone, Eq, PartialEq, Debug)]
21 pub struct VmExitStatistics {
22     /// Whether or not statistics have been enabled to measure VM exits.
23     enabled: bool,
24     /// Counter of the number of VM exits per-exit-type. The index into the Vec can be determined
25     /// from a &Result<VcpuExit> via the `exit_to_index` function.
26     exit_counters: Vec<u64>,
27     /// Sum of the duration of VM exits per-exit-type. The index into the Vec can be determined
28     /// from a &Result<VcpuExit> via the `exit_to_index` function.
29     exit_durations: Vec<Duration>,
30 }
31 
32 impl VmExitStatistics {
new() -> VmExitStatistics33     pub fn new() -> VmExitStatistics {
34         VmExitStatistics {
35             enabled: false,
36             // We have a known number of exit types, and thus a known number of exit indices
37             exit_counters: vec![0; MAX_EXIT_INT + 1],
38             exit_durations: vec![Duration::new(0, 0); MAX_EXIT_INT + 1],
39         }
40     }
41 
42     /// Enable or disable statistics gathering.
set_enabled(&mut self, enabled: bool)43     pub fn set_enabled(&mut self, enabled: bool) {
44         self.enabled = enabled;
45     }
46 
47     /// Get the start time of the stat that is to be recorded.
48     ///
49     /// If the VmExitStatistics instance is not enabled this will return None.
start_stat(&self) -> Option<Instant>50     pub fn start_stat(&self) -> Option<Instant> {
51         if !self.enabled {
52             return None;
53         }
54         Some(Instant::now())
55     }
56 
57     /// Record the end of the stat.
58     ///
59     /// The start value return from start_stat should be passed as `start`. If `start` is None or
60     /// if the VmExitStatistics instance is not enabled this will do nothing. The counters and
61     /// durations will silently overflow to prevent interference with vm operation.
end_stat(&mut self, exit: &base::Result<VcpuExit>, start: Option<Instant>)62     pub fn end_stat(&mut self, exit: &base::Result<VcpuExit>, start: Option<Instant>) {
63         if !self.enabled || start.is_none() {
64             return;
65         }
66 
67         let exit_index = exit_to_index(exit);
68 
69         // We overflow because we don't want any disruptions to emulator running due to
70         // statistics
71         self.exit_counters[exit_index] = self.exit_counters[exit_index].overflowing_add(1).0;
72         self.exit_durations[exit_index] = self.exit_durations[exit_index]
73             .checked_add(start.unwrap().elapsed())
74             .unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0
75     }
76 
77     /// Merge several VmExitStatistics into one.
merged(stats: &[VmExitStatistics]) -> VmExitStatistics78     pub fn merged(stats: &[VmExitStatistics]) -> VmExitStatistics {
79         let mut merged = VmExitStatistics::new();
80         for other in stats.iter() {
81             for exit_index in 0..(MAX_EXIT_INT + 1) {
82                 // We overflow because we don't want any disruptions to emulator running due to
83                 // statistics
84                 merged.exit_counters[exit_index] = merged.exit_counters[exit_index]
85                     .overflowing_add(other.exit_counters[exit_index])
86                     .0;
87                 merged.exit_durations[exit_index] = merged.exit_durations[exit_index]
88                     .checked_add(other.exit_durations[exit_index])
89                     .unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0
90             }
91         }
92 
93         merged
94     }
95 
96     /// Get a json representation of `self`. Returns an array of maps, where each map contains the
97     /// count and duration of a particular vmexit.
json(&self) -> serde_json::Value98     pub fn json(&self) -> serde_json::Value {
99         let mut exits = serde_json::json!([]);
100         let exits_vec = exits.as_array_mut().unwrap();
101         for exit_index in 0..(MAX_EXIT_INT + 1) {
102             exits_vec.push(serde_json::json!({
103                 "exit_type": exit_index_to_str(exit_index),
104                 "count": self.exit_counters[exit_index],
105                 "duration": {
106                     "seconds": self.exit_durations[exit_index].as_secs(),
107                     "subsecond_nanos": self.exit_durations[exit_index].subsec_nanos(),
108                 }
109             }))
110         }
111         exits
112     }
113 }
114 
115 impl std::fmt::Display for VmExitStatistics {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result116     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117         writeln!(f, "Exit Type       Count           Duration")?;
118 
119         let mut exit_indices: Vec<usize> = (0..(MAX_EXIT_INT + 1)).collect();
120         // Sort exit indices by exit_duration
121         exit_indices.sort_by_key(|i| Reverse(self.exit_durations[*i]));
122 
123         for exit_index in exit_indices {
124             writeln!(
125                 f,
126                 "{:<16}{:<16}{:<16?}",
127                 exit_index_to_str(exit_index),
128                 self.exit_counters[exit_index],
129                 // Alignment not implemented by Debug
130                 self.exit_durations[exit_index],
131             )?;
132         }
133 
134         Ok(())
135     }
136 }
137 
138 /// This constant should be set to the maximum integer to which the below functions will map a
139 /// VcpuExit.
140 const MAX_EXIT_INT: usize = 13;
141 
142 /// Map Vm Exits to exit indexes, which are integers for storage in our counter Vecs.
exit_to_index(exit: &base::Result<VcpuExit>) -> usize143 fn exit_to_index(exit: &base::Result<VcpuExit>) -> usize {
144     match exit {
145         Ok(VcpuExit::Io { .. }) => 0,
146         Ok(VcpuExit::Mmio { .. }) => 1,
147         Ok(VcpuExit::IoapicEoi { .. }) => 2,
148         Ok(VcpuExit::IrqWindowOpen) => 3,
149         Ok(VcpuExit::Hlt) => 4,
150         Ok(VcpuExit::Shutdown(_)) => 5,
151         Ok(VcpuExit::FailEntry { .. }) => 6,
152         Ok(VcpuExit::SystemEventShutdown) => 7,
153         Ok(VcpuExit::SystemEventReset) => 7,
154         Ok(VcpuExit::SystemEventCrash) => 7,
155         Ok(VcpuExit::Intr) => 8,
156         Ok(VcpuExit::Cpuid { .. }) => 9,
157         Err(e) if e.errno() == ERROR_RETRY_I32 => 10,
158         Err(_) => 11,
159         Ok(VcpuExit::Canceled) => 12,
160         _ => 13,
161     }
162 }
163 
164 /// Give human readable names for each exit type that we've mapped to an exit index in
165 /// exit_to_index.
exit_index_to_str(exit: usize) -> String166 fn exit_index_to_str(exit: usize) -> String {
167     (match exit {
168         0 => "Io",
169         1 => "Mmio",
170         2 => "IoapicEoi",
171         3 => "IrqWindowOpen",
172         4 => "Hlt",
173         5 => "Shutdown",
174         6 => "FailEntry",
175         7 => "SystemEvent",
176         8 => "Intr",
177         9 => "Cpuid",
178         10 => "Retry",
179         11 => "Error",
180         12 => "Canceled",
181         _ => "Unknown",
182     })
183     .to_string()
184 }
185 
186 /// Collects, merges, and displays statistics between vcpu threads.
187 #[derive(Default, Clone, Debug)]
188 pub struct StatisticsCollector {
189     pub pio_bus_stats: Vec<Arc<Mutex<BusStatistics>>>,
190     pub mmio_bus_stats: Vec<Arc<Mutex<BusStatistics>>>,
191     pub vm_exit_stats: Vec<VmExitStatistics>,
192 }
193 
194 impl StatisticsCollector {
new() -> StatisticsCollector195     pub fn new() -> StatisticsCollector {
196         StatisticsCollector::default()
197     }
198 
199     /// Return a merged version of the pio bus statistics, mmio bus statistics, and the vm exit
200     /// statistics for all vcpus.
merged(&self) -> (BusStatistics, BusStatistics, VmExitStatistics)201     fn merged(&self) -> (BusStatistics, BusStatistics, VmExitStatistics) {
202         (
203             BusStatistics::merged(&self.pio_bus_stats),
204             BusStatistics::merged(&self.mmio_bus_stats),
205             VmExitStatistics::merged(&self.vm_exit_stats),
206         )
207     }
208 
209     /// Get a json representation of `self`. It contains two top-level keys: "vcpus" and "merged".
210     /// The "vcpus" key's value is a list of per-vcpu stats, where the "merged" stats contains the
211     /// sum of all vcpu stats.
json(&self) -> serde_json::Value212     pub fn json(&self) -> serde_json::Value {
213         let mut vcpus = serde_json::json!([]);
214         let vcpus_vec = vcpus.as_array_mut().unwrap();
215 
216         for i in 0..self.pio_bus_stats.len() {
217             vcpus_vec.push(serde_json::json!({
218                 "io": self.pio_bus_stats[i].lock().json(),
219                 "mmio": self.mmio_bus_stats[i].lock().json(),
220                 "exits": self.vm_exit_stats[i].json(),
221             }));
222         }
223 
224         let (pio, mmio, exits) = self.merged();
225 
226         serde_json::json!({
227             "merged": {
228                 "io": pio.json(),
229                 "mmio": mmio.json(),
230                 "exits": exits.json(),
231             },
232             "vcpus": vcpus
233         })
234     }
235 }
236 
237 impl std::fmt::Display for StatisticsCollector {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result238     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239         let (pio, mmio, exits) = self.merged();
240         writeln!(f, "Port IO:")?;
241         writeln!(f, "{}", pio)?;
242         writeln!(f, "MMIO:")?;
243         writeln!(f, "{}", mmio)?;
244         writeln!(f, "Vm Exits:")?;
245         writeln!(f, "{}", exits)
246     }
247 }
248