xref: /aosp_15_r20/external/crosvm/base/src/periodic_logger.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2024 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker 
5*bb4ee6a4SAndroid Build Coastguard Worker // TODO(b/318439696): Remove once it is used
6*bb4ee6a4SAndroid Build Coastguard Worker #![allow(dead_code)]
7*bb4ee6a4SAndroid Build Coastguard Worker 
8*bb4ee6a4SAndroid Build Coastguard Worker use std::collections::HashMap;
9*bb4ee6a4SAndroid Build Coastguard Worker use std::fmt::Write;
10*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::atomic::AtomicU32;
11*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::atomic::Ordering;
12*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::Arc;
13*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::RwLock;
14*bb4ee6a4SAndroid Build Coastguard Worker use std::time::Duration;
15*bb4ee6a4SAndroid Build Coastguard Worker 
16*bb4ee6a4SAndroid Build Coastguard Worker use thiserror::Error as ThisError;
17*bb4ee6a4SAndroid Build Coastguard Worker 
18*bb4ee6a4SAndroid Build Coastguard Worker use crate::EventToken;
19*bb4ee6a4SAndroid Build Coastguard Worker use crate::Timer;
20*bb4ee6a4SAndroid Build Coastguard Worker use crate::TimerTrait;
21*bb4ee6a4SAndroid Build Coastguard Worker use crate::WaitContext;
22*bb4ee6a4SAndroid Build Coastguard Worker use crate::WorkerThread;
23*bb4ee6a4SAndroid Build Coastguard Worker 
24*bb4ee6a4SAndroid Build Coastguard Worker /// Utility class that helps count and log high frequency events periodically.
25*bb4ee6a4SAndroid Build Coastguard Worker pub struct PeriodicLogger {
26*bb4ee6a4SAndroid Build Coastguard Worker     // Name that is printed out to differentiate between other `PeriodicLogger`s
27*bb4ee6a4SAndroid Build Coastguard Worker     name: String,
28*bb4ee6a4SAndroid Build Coastguard Worker     // Interval to log
29*bb4ee6a4SAndroid Build Coastguard Worker     interval: Duration,
30*bb4ee6a4SAndroid Build Coastguard Worker     // Map of event counters that are periodically logged
31*bb4ee6a4SAndroid Build Coastguard Worker     counters: Arc<RwLock<HashMap<String, AtomicU32>>>,
32*bb4ee6a4SAndroid Build Coastguard Worker     // The periodic logger thread
33*bb4ee6a4SAndroid Build Coastguard Worker     worker_thread: Option<WorkerThread<Result<(), PeriodicLoggerError>>>,
34*bb4ee6a4SAndroid Build Coastguard Worker }
35*bb4ee6a4SAndroid Build Coastguard Worker 
36*bb4ee6a4SAndroid Build Coastguard Worker impl PeriodicLogger {
new(name: String, interval: Duration) -> Self37*bb4ee6a4SAndroid Build Coastguard Worker     pub fn new(name: String, interval: Duration) -> Self {
38*bb4ee6a4SAndroid Build Coastguard Worker         PeriodicLogger {
39*bb4ee6a4SAndroid Build Coastguard Worker             name,
40*bb4ee6a4SAndroid Build Coastguard Worker             interval,
41*bb4ee6a4SAndroid Build Coastguard Worker             counters: Arc::new(RwLock::new(HashMap::new())),
42*bb4ee6a4SAndroid Build Coastguard Worker             worker_thread: None,
43*bb4ee6a4SAndroid Build Coastguard Worker         }
44*bb4ee6a4SAndroid Build Coastguard Worker     }
45*bb4ee6a4SAndroid Build Coastguard Worker 
46*bb4ee6a4SAndroid Build Coastguard Worker     /// Add a new event item to be counted.
add_counter_item(&self, name: String) -> Result<(), PeriodicLoggerError>47*bb4ee6a4SAndroid Build Coastguard Worker     pub fn add_counter_item(&self, name: String) -> Result<(), PeriodicLoggerError> {
48*bb4ee6a4SAndroid Build Coastguard Worker         // This write lock will likely be acquired infrequently.
49*bb4ee6a4SAndroid Build Coastguard Worker         let mut counters_write_lock = self
50*bb4ee6a4SAndroid Build Coastguard Worker             .counters
51*bb4ee6a4SAndroid Build Coastguard Worker             .write()
52*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| PeriodicLoggerError::WriteLockError(e.to_string()))?;
53*bb4ee6a4SAndroid Build Coastguard Worker 
54*bb4ee6a4SAndroid Build Coastguard Worker         if counters_write_lock.contains_key(&name) {
55*bb4ee6a4SAndroid Build Coastguard Worker             return Err(PeriodicLoggerError::CounterAlreadyExist(name));
56*bb4ee6a4SAndroid Build Coastguard Worker         }
57*bb4ee6a4SAndroid Build Coastguard Worker 
58*bb4ee6a4SAndroid Build Coastguard Worker         counters_write_lock.insert(name, AtomicU32::new(0));
59*bb4ee6a4SAndroid Build Coastguard Worker         Ok(())
60*bb4ee6a4SAndroid Build Coastguard Worker     }
61*bb4ee6a4SAndroid Build Coastguard Worker 
62*bb4ee6a4SAndroid Build Coastguard Worker     /// Increment event counter by an `amount`
increment_counter(&self, name: String, amount: u32) -> Result<(), PeriodicLoggerError>63*bb4ee6a4SAndroid Build Coastguard Worker     pub fn increment_counter(&self, name: String, amount: u32) -> Result<(), PeriodicLoggerError> {
64*bb4ee6a4SAndroid Build Coastguard Worker         match self.counters.read() {
65*bb4ee6a4SAndroid Build Coastguard Worker             Ok(counters_map) => {
66*bb4ee6a4SAndroid Build Coastguard Worker                 if let Some(atomic_counter) = counters_map.get(&name) {
67*bb4ee6a4SAndroid Build Coastguard Worker                     atomic_counter.fetch_add(amount, Ordering::Relaxed);
68*bb4ee6a4SAndroid Build Coastguard Worker                     Ok(())
69*bb4ee6a4SAndroid Build Coastguard Worker                 } else {
70*bb4ee6a4SAndroid Build Coastguard Worker                     Err(PeriodicLoggerError::CounterDoesNotExist(name))
71*bb4ee6a4SAndroid Build Coastguard Worker                 }
72*bb4ee6a4SAndroid Build Coastguard Worker             }
73*bb4ee6a4SAndroid Build Coastguard Worker             Err(e) => Err(PeriodicLoggerError::ReadLockError(e.to_string())),
74*bb4ee6a4SAndroid Build Coastguard Worker         }
75*bb4ee6a4SAndroid Build Coastguard Worker     }
76*bb4ee6a4SAndroid Build Coastguard Worker 
77*bb4ee6a4SAndroid Build Coastguard Worker     /// Starts a thread that will log the count of events within a `self.interval` time period.
78*bb4ee6a4SAndroid Build Coastguard Worker     /// All counters will be reset to 0 after logging.
start_logging_thread(&mut self) -> Result<(), PeriodicLoggerError>79*bb4ee6a4SAndroid Build Coastguard Worker     pub fn start_logging_thread(&mut self) -> Result<(), PeriodicLoggerError> {
80*bb4ee6a4SAndroid Build Coastguard Worker         if self.worker_thread.is_some() {
81*bb4ee6a4SAndroid Build Coastguard Worker             return Err(PeriodicLoggerError::ThreadAlreadyStarted);
82*bb4ee6a4SAndroid Build Coastguard Worker         }
83*bb4ee6a4SAndroid Build Coastguard Worker 
84*bb4ee6a4SAndroid Build Coastguard Worker         #[derive(EventToken)]
85*bb4ee6a4SAndroid Build Coastguard Worker         enum Token {
86*bb4ee6a4SAndroid Build Coastguard Worker             Exit,
87*bb4ee6a4SAndroid Build Coastguard Worker             PeriodicLog,
88*bb4ee6a4SAndroid Build Coastguard Worker         }
89*bb4ee6a4SAndroid Build Coastguard Worker 
90*bb4ee6a4SAndroid Build Coastguard Worker         let cloned_counter = self.counters.clone();
91*bb4ee6a4SAndroid Build Coastguard Worker         let interval_copy = self.interval;
92*bb4ee6a4SAndroid Build Coastguard Worker         let name_copy = self.name.clone();
93*bb4ee6a4SAndroid Build Coastguard Worker         self.worker_thread = Some(WorkerThread::start(
94*bb4ee6a4SAndroid Build Coastguard Worker             format!("PeriodicLogger_{}", self.name),
95*bb4ee6a4SAndroid Build Coastguard Worker             move |kill_evt| {
96*bb4ee6a4SAndroid Build Coastguard Worker                 let mut timer = Timer::new().map_err(PeriodicLoggerError::TimerNewError)?;
97*bb4ee6a4SAndroid Build Coastguard Worker                 timer
98*bb4ee6a4SAndroid Build Coastguard Worker                     .reset_repeating(interval_copy)
99*bb4ee6a4SAndroid Build Coastguard Worker                     .map_err(PeriodicLoggerError::TimerResetError)?;
100*bb4ee6a4SAndroid Build Coastguard Worker 
101*bb4ee6a4SAndroid Build Coastguard Worker                 let wait_ctx = WaitContext::build_with(&[
102*bb4ee6a4SAndroid Build Coastguard Worker                     (&kill_evt, Token::Exit),
103*bb4ee6a4SAndroid Build Coastguard Worker                     (&timer, Token::PeriodicLog),
104*bb4ee6a4SAndroid Build Coastguard Worker                 ])
105*bb4ee6a4SAndroid Build Coastguard Worker                 .map_err(PeriodicLoggerError::WaitContextBuildError)?;
106*bb4ee6a4SAndroid Build Coastguard Worker 
107*bb4ee6a4SAndroid Build Coastguard Worker                 'outer: loop {
108*bb4ee6a4SAndroid Build Coastguard Worker                     let events = wait_ctx.wait().expect("wait failed");
109*bb4ee6a4SAndroid Build Coastguard Worker                     for event in events.iter().filter(|e| e.is_readable) {
110*bb4ee6a4SAndroid Build Coastguard Worker                         match event.token {
111*bb4ee6a4SAndroid Build Coastguard Worker                             Token::Exit => {
112*bb4ee6a4SAndroid Build Coastguard Worker                                 break 'outer;
113*bb4ee6a4SAndroid Build Coastguard Worker                             }
114*bb4ee6a4SAndroid Build Coastguard Worker                             Token::PeriodicLog => {
115*bb4ee6a4SAndroid Build Coastguard Worker                                 timer.mark_waited().unwrap();
116*bb4ee6a4SAndroid Build Coastguard Worker 
117*bb4ee6a4SAndroid Build Coastguard Worker                                 let counter_map = cloned_counter.read().map_err(|e| {
118*bb4ee6a4SAndroid Build Coastguard Worker                                     PeriodicLoggerError::ReadLockError(e.to_string())
119*bb4ee6a4SAndroid Build Coastguard Worker                                 })?;
120*bb4ee6a4SAndroid Build Coastguard Worker 
121*bb4ee6a4SAndroid Build Coastguard Worker                                 let mut logged_string =
122*bb4ee6a4SAndroid Build Coastguard Worker                                     format!("{} {:?}:", name_copy, interval_copy);
123*bb4ee6a4SAndroid Build Coastguard Worker                                 for (counter_name, counter_value) in counter_map.iter() {
124*bb4ee6a4SAndroid Build Coastguard Worker                                     let value = counter_value.swap(0, Ordering::Relaxed);
125*bb4ee6a4SAndroid Build Coastguard Worker                                     let _ =
126*bb4ee6a4SAndroid Build Coastguard Worker                                         write!(logged_string, "\n    {}: {}", counter_name, value);
127*bb4ee6a4SAndroid Build Coastguard Worker                                 }
128*bb4ee6a4SAndroid Build Coastguard Worker 
129*bb4ee6a4SAndroid Build Coastguard Worker                                 // Log all counters
130*bb4ee6a4SAndroid Build Coastguard Worker                                 crate::info!("{}", logged_string);
131*bb4ee6a4SAndroid Build Coastguard Worker                             }
132*bb4ee6a4SAndroid Build Coastguard Worker                         }
133*bb4ee6a4SAndroid Build Coastguard Worker                     }
134*bb4ee6a4SAndroid Build Coastguard Worker                 }
135*bb4ee6a4SAndroid Build Coastguard Worker                 Ok(())
136*bb4ee6a4SAndroid Build Coastguard Worker             },
137*bb4ee6a4SAndroid Build Coastguard Worker         ));
138*bb4ee6a4SAndroid Build Coastguard Worker 
139*bb4ee6a4SAndroid Build Coastguard Worker         Ok(())
140*bb4ee6a4SAndroid Build Coastguard Worker     }
141*bb4ee6a4SAndroid Build Coastguard Worker }
142*bb4ee6a4SAndroid Build Coastguard Worker 
143*bb4ee6a4SAndroid Build Coastguard Worker #[derive(Debug, ThisError, PartialEq)]
144*bb4ee6a4SAndroid Build Coastguard Worker pub enum PeriodicLoggerError {
145*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Periodic logger thread already started.")]
146*bb4ee6a4SAndroid Build Coastguard Worker     ThreadAlreadyStarted,
147*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed to acquire write lock: {0}")]
148*bb4ee6a4SAndroid Build Coastguard Worker     WriteLockError(String),
149*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed to acquire read lock: {0}")]
150*bb4ee6a4SAndroid Build Coastguard Worker     ReadLockError(String),
151*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Counter already exists: {0}")]
152*bb4ee6a4SAndroid Build Coastguard Worker     CounterAlreadyExist(String),
153*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Counter does not exist: {0}")]
154*bb4ee6a4SAndroid Build Coastguard Worker     CounterDoesNotExist(String),
155*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed to build WaitContext: {0}")]
156*bb4ee6a4SAndroid Build Coastguard Worker     WaitContextBuildError(crate::Error),
157*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed to wait on WaitContext: {0}")]
158*bb4ee6a4SAndroid Build Coastguard Worker     WaitContextWaitError(crate::Error),
159*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed to reset Timer: {0}")]
160*bb4ee6a4SAndroid Build Coastguard Worker     TimerResetError(crate::Error),
161*bb4ee6a4SAndroid Build Coastguard Worker     #[error("Failed initialize Timer: {0}")]
162*bb4ee6a4SAndroid Build Coastguard Worker     TimerNewError(crate::Error),
163*bb4ee6a4SAndroid Build Coastguard Worker }
164*bb4ee6a4SAndroid Build Coastguard Worker 
165*bb4ee6a4SAndroid Build Coastguard Worker #[cfg(test)]
166*bb4ee6a4SAndroid Build Coastguard Worker mod tests {
167*bb4ee6a4SAndroid Build Coastguard Worker     use std::thread;
168*bb4ee6a4SAndroid Build Coastguard Worker 
169*bb4ee6a4SAndroid Build Coastguard Worker     use super::*;
170*bb4ee6a4SAndroid Build Coastguard Worker 
171*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
periodic_add()172*bb4ee6a4SAndroid Build Coastguard Worker     fn periodic_add() {
173*bb4ee6a4SAndroid Build Coastguard Worker         let periodic_logger = PeriodicLogger::new("test".to_string(), Duration::from_secs(3));
174*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger
175*bb4ee6a4SAndroid Build Coastguard Worker             .add_counter_item("counter_1".to_string())
176*bb4ee6a4SAndroid Build Coastguard Worker             .unwrap();
177*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger
178*bb4ee6a4SAndroid Build Coastguard Worker             .increment_counter("counter_1".to_string(), 2)
179*bb4ee6a4SAndroid Build Coastguard Worker             .unwrap();
180*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger
181*bb4ee6a4SAndroid Build Coastguard Worker             .increment_counter("counter_1".to_string(), 5)
182*bb4ee6a4SAndroid Build Coastguard Worker             .unwrap();
183*bb4ee6a4SAndroid Build Coastguard Worker 
184*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(periodic_logger.counters.read().unwrap().len(), 1);
185*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(
186*bb4ee6a4SAndroid Build Coastguard Worker             periodic_logger
187*bb4ee6a4SAndroid Build Coastguard Worker                 .counters
188*bb4ee6a4SAndroid Build Coastguard Worker                 .read()
189*bb4ee6a4SAndroid Build Coastguard Worker                 .unwrap()
190*bb4ee6a4SAndroid Build Coastguard Worker                 .get("counter_1")
191*bb4ee6a4SAndroid Build Coastguard Worker                 .unwrap()
192*bb4ee6a4SAndroid Build Coastguard Worker                 .load(Ordering::Relaxed),
193*bb4ee6a4SAndroid Build Coastguard Worker             7
194*bb4ee6a4SAndroid Build Coastguard Worker         );
195*bb4ee6a4SAndroid Build Coastguard Worker     }
196*bb4ee6a4SAndroid Build Coastguard Worker 
197*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
worker_thread_cannot_start_twice()198*bb4ee6a4SAndroid Build Coastguard Worker     fn worker_thread_cannot_start_twice() {
199*bb4ee6a4SAndroid Build Coastguard Worker         let mut periodic_logger = PeriodicLogger::new("test".to_string(), Duration::from_secs(3));
200*bb4ee6a4SAndroid Build Coastguard Worker         assert!(periodic_logger.start_logging_thread().is_ok());
201*bb4ee6a4SAndroid Build Coastguard Worker         assert!(periodic_logger.start_logging_thread().is_err());
202*bb4ee6a4SAndroid Build Coastguard Worker     }
203*bb4ee6a4SAndroid Build Coastguard Worker 
204*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
add_same_counter_item_twice_return_err()205*bb4ee6a4SAndroid Build Coastguard Worker     fn add_same_counter_item_twice_return_err() {
206*bb4ee6a4SAndroid Build Coastguard Worker         let periodic_logger = PeriodicLogger::new("test".to_string(), Duration::from_secs(3));
207*bb4ee6a4SAndroid Build Coastguard Worker         assert!(periodic_logger
208*bb4ee6a4SAndroid Build Coastguard Worker             .add_counter_item("counter_1".to_string())
209*bb4ee6a4SAndroid Build Coastguard Worker             .is_ok());
210*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(
211*bb4ee6a4SAndroid Build Coastguard Worker             periodic_logger.add_counter_item("counter_1".to_string()),
212*bb4ee6a4SAndroid Build Coastguard Worker             Err(PeriodicLoggerError::CounterAlreadyExist(
213*bb4ee6a4SAndroid Build Coastguard Worker                 "counter_1".to_string()
214*bb4ee6a4SAndroid Build Coastguard Worker             ))
215*bb4ee6a4SAndroid Build Coastguard Worker         );
216*bb4ee6a4SAndroid Build Coastguard Worker     }
217*bb4ee6a4SAndroid Build Coastguard Worker 
218*bb4ee6a4SAndroid Build Coastguard Worker     /// Ignored because this is intended to be ran locally
219*bb4ee6a4SAndroid Build Coastguard Worker     #[ignore]
220*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
periodic_logger_smoke_test()221*bb4ee6a4SAndroid Build Coastguard Worker     fn periodic_logger_smoke_test() {
222*bb4ee6a4SAndroid Build Coastguard Worker         let mut periodic_logger = PeriodicLogger::new("test".to_string(), Duration::from_secs(3));
223*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger
224*bb4ee6a4SAndroid Build Coastguard Worker             .add_counter_item("counter_1".to_string())
225*bb4ee6a4SAndroid Build Coastguard Worker             .unwrap();
226*bb4ee6a4SAndroid Build Coastguard Worker 
227*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger.start_logging_thread().unwrap();
228*bb4ee6a4SAndroid Build Coastguard Worker         periodic_logger
229*bb4ee6a4SAndroid Build Coastguard Worker             .increment_counter("counter_1".to_string(), 5)
230*bb4ee6a4SAndroid Build Coastguard Worker             .unwrap();
231*bb4ee6a4SAndroid Build Coastguard Worker 
232*bb4ee6a4SAndroid Build Coastguard Worker         thread::sleep(Duration::from_secs(5));
233*bb4ee6a4SAndroid Build Coastguard Worker     }
234*bb4ee6a4SAndroid Build Coastguard Worker }
235