/ crates / metrics / src / recorder.rs
recorder.rs
  1  /// Event recorder with thread-safe append-only log
  2  use crate::BotEvent;
  3  use parking_lot::RwLock;
  4  use std::sync::Arc;
  5  
  6  /// Thread-safe event recorder
  7  #[derive(Clone)]
  8  pub struct EventRecorder {
  9      events: Arc<RwLock<Vec<BotEvent>>>,
 10      max_events: usize,
 11  }
 12  
 13  impl EventRecorder {
 14      /// Create a new event recorder
 15      pub fn new() -> Self {
 16          Self::with_capacity(100_000)
 17      }
 18  
 19      /// Create a recorder with a maximum capacity
 20      pub fn with_capacity(max_events: usize) -> Self {
 21          Self {
 22              events: Arc::new(RwLock::new(Vec::new())),
 23              max_events,
 24          }
 25      }
 26  
 27      /// Record a single event
 28      pub fn record(&self, event: BotEvent) {
 29          let mut events = self.events.write();
 30  
 31          // If at capacity, remove oldest events (sliding window)
 32          if events.len() >= self.max_events {
 33              let remove_count = events.len() - self.max_events + 1;
 34              events.drain(0..remove_count);
 35          }
 36  
 37          events.push(event);
 38      }
 39  
 40      /// Record multiple events
 41      pub fn record_batch(&self, batch: Vec<BotEvent>) {
 42          let mut events = self.events.write();
 43  
 44          for event in batch {
 45              if events.len() >= self.max_events {
 46                  events.remove(0);
 47              }
 48              events.push(event);
 49          }
 50      }
 51  
 52      /// Get all events
 53      pub fn get_all(&self) -> Vec<BotEvent> {
 54          self.events.read().clone()
 55      }
 56  
 57      /// Get events since a timestamp
 58      pub fn get_since(&self, timestamp_ms: i64) -> Vec<BotEvent> {
 59          self.events
 60              .read()
 61              .iter()
 62              .filter(|e| e.timestamp_ms() >= timestamp_ms)
 63              .cloned()
 64              .collect()
 65      }
 66  
 67      /// Get events for a specific bot
 68      pub fn get_for_bot(&self, bot_id: &str) -> Vec<BotEvent> {
 69          self.events
 70              .read()
 71              .iter()
 72              .filter(|e| e.bot_id() == Some(bot_id))
 73              .cloned()
 74              .collect()
 75      }
 76  
 77      /// Get total event count
 78      pub fn count(&self) -> usize {
 79          self.events.read().len()
 80      }
 81  
 82      /// Get error events
 83      pub fn get_errors(&self) -> Vec<BotEvent> {
 84          self.events
 85              .read()
 86              .iter()
 87              .filter(|e| e.is_error())
 88              .cloned()
 89              .collect()
 90      }
 91  
 92      /// Clear all events
 93      pub fn clear(&self) {
 94          self.events.write().clear();
 95      }
 96  }
 97  
 98  impl Default for EventRecorder {
 99      fn default() -> Self {
100          Self::new()
101      }
102  }
103  
104  #[cfg(test)]
105  mod tests {
106      use super::*;
107  
108      #[test]
109      fn test_record_event() {
110          let recorder = EventRecorder::new();
111  
112          recorder.record(BotEvent::BotStarted {
113              bot_id: "bot-1".to_string(),
114              role: "trader".to_string(),
115              timestamp_ms: 1000,
116          });
117  
118          assert_eq!(recorder.count(), 1);
119      }
120  
121      #[test]
122      fn test_capacity_limit() {
123          let recorder = EventRecorder::with_capacity(10);
124  
125          // Record 15 events
126          for i in 0..15 {
127              recorder.record(BotEvent::BotStarted {
128                  bot_id: format!("bot-{}", i),
129                  role: "trader".to_string(),
130                  timestamp_ms: i as i64,
131              });
132          }
133  
134          // Should only keep 10
135          assert_eq!(recorder.count(), 10);
136  
137          // Oldest events should be removed
138          let events = recorder.get_all();
139          assert_eq!(events[0].timestamp_ms(), 5);
140      }
141  
142      #[test]
143      fn test_get_since() {
144          let recorder = EventRecorder::new();
145  
146          recorder.record(BotEvent::BotStarted {
147              bot_id: "bot-1".to_string(),
148              role: "trader".to_string(),
149              timestamp_ms: 1000,
150          });
151  
152          recorder.record(BotEvent::BotStarted {
153              bot_id: "bot-2".to_string(),
154              role: "trader".to_string(),
155              timestamp_ms: 2000,
156          });
157  
158          let recent = recorder.get_since(1500);
159          assert_eq!(recent.len(), 1);
160      }
161  
162      #[test]
163      fn test_get_for_bot() {
164          let recorder = EventRecorder::new();
165  
166          recorder.record(BotEvent::BotStarted {
167              bot_id: "bot-1".to_string(),
168              role: "trader".to_string(),
169              timestamp_ms: 1000,
170          });
171  
172          recorder.record(BotEvent::BotStarted {
173              bot_id: "bot-2".to_string(),
174              role: "trader".to_string(),
175              timestamp_ms: 2000,
176          });
177  
178          let bot1_events = recorder.get_for_bot("bot-1");
179          assert_eq!(bot1_events.len(), 1);
180      }
181  }