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