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