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 use std::cmp::Reverse; 6 use std::fmt; 7 use std::sync::Arc; 8 use std::time::Duration; 9 use std::time::Instant; 10 11 use sync::Mutex; 12 13 /// Helper enum to distinguish between read stats and write stats. 14 #[derive(Clone, Copy)] 15 pub(crate) enum BusOperation { 16 Read, 17 Write, 18 } 19 20 /// Identifying information about a device on the Bus, used for statistics. 21 #[derive(Clone, Default, Eq, PartialEq, Debug)] 22 struct DeviceStatisticsIdentifier { 23 /// Name of the device 24 name: String, 25 /// Id of the device 26 id: u32, 27 /// Base address where the device was added to the bus. 28 base: u64, 29 /// Length of address range this device entry covers. 30 len: u64, 31 } 32 33 impl DeviceStatisticsIdentifier { new(name: String, id: u32, base: u64, len: u64) -> DeviceStatisticsIdentifier34 fn new(name: String, id: u32, base: u64, len: u64) -> DeviceStatisticsIdentifier { 35 DeviceStatisticsIdentifier { 36 name, 37 id, 38 base, 39 len, 40 } 41 } 42 43 /// Get a json representation of `self`. json(&self) -> serde_json::Value44 fn json(&self) -> serde_json::Value { 45 serde_json::json!({ 46 "name": self.name, 47 "id": self.id, 48 "base": self.base, 49 "len": self.len}) 50 } 51 } 52 53 /// Statistics about how a device has been accessed via a Bus. 54 #[derive(Clone, Default, Eq, PartialEq, Debug)] 55 struct DeviceStatistics { 56 /// Counter of the number of reads performed. 57 read_counter: u64, 58 /// Total duration of reads performed. 59 read_duration: Duration, 60 /// Counter of the number of writes performed. 61 write_counter: u64, 62 /// Total duration of writes performed. 63 write_duration: Duration, 64 } 65 66 impl DeviceStatistics { 67 /// Increment either a read counter or a write counter, depending on `stat`. Also add the 68 /// time elapsed since `start` to read_duration or write_duration respectively. increment(&mut self, stat: BusOperation, start: Instant)69 fn increment(&mut self, stat: BusOperation, start: Instant) { 70 let (counter, duration) = match stat { 71 BusOperation::Read => (&mut self.read_counter, &mut self.read_duration), 72 BusOperation::Write => (&mut self.write_counter, &mut self.write_duration), 73 }; 74 75 // We use saturating_add because we don't want any disruptions to emulator running due to 76 // statistics 77 *counter = counter.saturating_add(1); 78 *duration = duration 79 .checked_add(start.elapsed()) 80 .unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0 81 } 82 83 /// Get the accumulated count and duration of a particular Operation get(&self, stat: BusOperation) -> (u64, Duration)84 fn get(&self, stat: BusOperation) -> (u64, Duration) { 85 match stat { 86 BusOperation::Read => (self.read_counter, self.read_duration), 87 BusOperation::Write => (self.write_counter, self.write_duration), 88 } 89 } 90 91 /// Merge another DeviceStat into this one. merge(&mut self, other: &DeviceStatistics)92 fn merge(&mut self, other: &DeviceStatistics) { 93 self.read_counter = self.read_counter.saturating_add(other.read_counter); 94 self.read_duration = self 95 .read_duration 96 .checked_add(other.read_duration) 97 .unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0 98 99 self.write_counter = self.write_counter.saturating_add(other.write_counter); 100 self.write_duration = self 101 .write_duration 102 .checked_add(other.write_duration) 103 .unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0 104 } 105 106 /// Get a json representation of `self`. json(&self) -> serde_json::Value107 fn json(&self) -> serde_json::Value { 108 serde_json::json!({ 109 "reads": self.read_counter, 110 "read_duration": { 111 "seconds": self.read_duration.as_secs(), 112 "subsecond_nanos": self.read_duration.subsec_nanos(), 113 }, 114 "writes": self.write_counter, 115 "write_duration": { 116 "seconds": self.write_duration.as_secs(), 117 "subsecond_nanos": self.write_duration.subsec_nanos(), 118 }, 119 }) 120 } 121 } 122 123 /// Statistics about how a bus has been accessed. 124 #[derive(Clone, Default, Debug)] 125 pub struct BusStatistics { 126 /// Whether or not statistics have been enabled to measure Bus Reads/Writes. 127 enabled: bool, 128 /// Vec of per-device statistics, indexed by BusEntry.index. 129 device_stats: Vec<DeviceStatistics>, 130 /// Global information about all devices inserted into any bus. 131 device_identifiers: Arc<Mutex<Vec<DeviceStatisticsIdentifier>>>, 132 } 133 134 impl BusStatistics { new() -> BusStatistics135 pub fn new() -> BusStatistics { 136 BusStatistics::default() 137 } 138 139 /// Enable or disable statistics gathering. set_enabled(&mut self, enabled: bool)140 pub fn set_enabled(&mut self, enabled: bool) { 141 self.enabled = enabled; 142 } 143 144 /// Get the start time of the stat that is to be recorded. 145 /// 146 /// If the BusStatistics instance is not enabled this will return None. start_stat(&self) -> Option<Instant>147 pub(crate) fn start_stat(&self) -> Option<Instant> { 148 if !self.enabled { 149 return None; 150 } 151 Some(Instant::now()) 152 } 153 154 /// Record the end of the stat. 155 /// 156 /// The start value return from start_stat should be passed as `start`. If `start` is None or 157 /// if the BusStatistics instance is not enabled this will do nothing. The counters and 158 /// durations will silently overflow to prevent interference with vm operation. end_stat( &mut self, stat: BusOperation, start: Option<Instant>, device_index: usize, )159 pub(crate) fn end_stat( 160 &mut self, 161 stat: BusOperation, 162 start: Option<Instant>, 163 device_index: usize, 164 ) { 165 if !self.enabled { 166 return; 167 } 168 169 if let Some(start) = start { 170 // Make sure the device_stats is large enough 171 if self.device_stats.len() < device_index + 1 { 172 self.device_stats 173 .resize(device_index + 1, DeviceStatistics::default()); 174 } 175 176 self.device_stats[device_index].increment(stat, start); 177 } 178 } 179 180 /// Get the next available device index. 181 /// 182 /// When adding a BusEntry to the bus, the Bus should call this function to get the index for 183 /// the entry. This BusStatistics will then save the device `name` and `id` associated with the 184 /// device index for displaying statistics later. next_device_index(&self, name: String, id: u32, base: u64, len: u64) -> usize185 pub(crate) fn next_device_index(&self, name: String, id: u32, base: u64, len: u64) -> usize { 186 let mut device_identifiers = self.device_identifiers.lock(); 187 let idx = device_identifiers.len(); 188 device_identifiers.push(DeviceStatisticsIdentifier::new(name, id, base, len)); 189 idx 190 } 191 192 /// Merge several BusStatistics into one. merged(stats: &[Arc<Mutex<BusStatistics>>]) -> BusStatistics193 pub fn merged(stats: &[Arc<Mutex<BusStatistics>>]) -> BusStatistics { 194 if stats.is_empty() { 195 return BusStatistics::new(); 196 } 197 198 let device_count = stats[0].lock().device_identifiers.lock().len(); 199 200 let mut merged = BusStatistics { 201 enabled: stats[0].lock().enabled, 202 device_stats: Vec::with_capacity(device_count), 203 device_identifiers: stats[0].lock().device_identifiers.clone(), 204 }; 205 206 for idx in 0..device_count { 207 let mut device_stat = DeviceStatistics::default(); 208 // Merge all DeviceStatistics 209 for other in stats { 210 let other = other.lock(); 211 // Not all vcpu Buses may have stats for all devices. 212 if let Some(other_stats) = other.device_stats.get(idx) { 213 device_stat.merge(other_stats); 214 } 215 } 216 217 merged.device_stats.push(device_stat); 218 } 219 220 merged 221 } 222 223 /// Get a json representation of `self`. Returns an array of maps, where each map contains the 224 /// read an write statistics for a particular device. json(&self) -> serde_json::Value225 pub fn json(&self) -> serde_json::Value { 226 let mut devices = serde_json::json!([]); 227 let devices_vec = devices.as_array_mut().unwrap(); 228 for (device_identifier, device_stat) in self 229 .device_identifiers 230 .lock() 231 .iter() 232 .zip(self.device_stats.iter()) 233 { 234 devices_vec.push( 235 serde_json::json!({"info": device_identifier.json(), "stats": device_stat.json()}), 236 ); 237 } 238 devices 239 } 240 } 241 242 impl std::fmt::Display for BusStatistics { 243 /// BusStatistics' Display is split into two tables, Reads and Writes. fmt(&self, f: &mut fmt::Formatter) -> fmt::Result244 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 for (opname, op) in &[("Read", BusOperation::Read), ("Write", BusOperation::Write)] { 246 writeln!( 247 f, 248 "Device Name Device Id Address Range {:<15}s{:<15} Duration", 249 opname, 250 opname 251 )?; 252 253 let mut device_indices: Vec<usize> = (0..self.device_stats.len()).collect(); 254 // Sort indices by op duration 255 device_indices.sort_by_key(|i| Reverse(self.device_stats[*i].get(*op).1)); 256 257 for i in device_indices.iter() { 258 let device_identifier = &self.device_identifiers.lock()[*i]; 259 let (count, duration) = self.device_stats[*i].get(*op); 260 #[allow(clippy::format_in_format_args)] 261 writeln!( 262 f, 263 "{:<30}0x{:<13x}{:<25}{:<15}{:<15}", 264 device_identifier.name, 265 device_identifier.id, 266 format!( 267 "0x{:x}-0x{:x}", 268 device_identifier.base, 269 device_identifier.base + device_identifier.len 270 ), 271 count, 272 // Alignment not implemented by Debug 273 format!("{:?}", duration), 274 )?; 275 } 276 writeln!(f)?; 277 } 278 279 Ok(()) 280 } 281 } 282