xref: /aosp_15_r20/external/crosvm/devices/src/bus_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 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