teksilo_telemetry/queue/
mem.rs1use std::collections::VecDeque;
7use std::sync::Mutex;
8
9use teksilo_core::telemetry::OwnedEvent;
10
11use super::EventQueue;
12
13pub struct InMemoryEventQueue {
19 inner: Mutex<VecDeque<OwnedEvent>>,
20 capacity: usize,
21}
22
23impl InMemoryEventQueue {
24 pub fn new() -> Self {
25 Self::with_capacity(10_000)
26 }
27
28 pub fn with_capacity(capacity: usize) -> Self {
29 Self {
30 inner: Mutex::new(VecDeque::with_capacity(capacity.min(256))),
31 capacity,
32 }
33 }
34}
35
36impl Default for InMemoryEventQueue {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl EventQueue for InMemoryEventQueue {
43 fn push(&self, event: OwnedEvent) {
44 let mut q = self.inner.lock().expect("queue mutex poisoned");
45 if q.len() >= self.capacity {
46 q.pop_front();
47 }
48 q.push_back(event);
49 }
50
51 fn len(&self) -> usize {
52 self.inner.lock().expect("queue mutex poisoned").len()
53 }
54
55 fn drain_batch(&self, n: usize) -> Vec<OwnedEvent> {
56 let mut q = self.inner.lock().expect("queue mutex poisoned");
57 let take = n.min(q.len());
58 q.drain(..take).collect()
59 }
60
61 fn discard_all(&self) {
62 let mut q = self.inner.lock().expect("queue mutex poisoned");
63 q.clear();
64 }
65
66 fn peek_recent(&self, n: usize) -> Vec<OwnedEvent> {
67 let q = self.inner.lock().expect("queue mutex poisoned");
68 q.iter().rev().take(n).cloned().collect::<Vec<_>>()
69 }
70}
71
72impl std::fmt::Debug for InMemoryEventQueue {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("InMemoryEventQueue")
75 .field("len", &self.len())
76 .field("capacity", &self.capacity)
77 .finish()
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use std::time::SystemTime;
85 use teksilo_core::telemetry::EventCategory;
86
87 fn ev(name: &str) -> OwnedEvent {
88 OwnedEvent {
89 name: name.to_string(),
90 category: EventCategory::Intent,
91 timestamp: SystemTime::UNIX_EPOCH,
92 install_id: None,
93 session_id: "test".into(),
94 schema_version: 1,
95 props: vec![],
96 }
97 }
98
99 #[test]
100 fn push_pop_round_trip() {
101 let q = InMemoryEventQueue::new();
102 q.push(ev("a"));
103 q.push(ev("b"));
104 assert_eq!(q.len(), 2);
105 let batch = q.drain_batch(10);
106 assert_eq!(batch.len(), 2);
107 assert_eq!(batch[0].name, "a");
108 assert_eq!(batch[1].name, "b");
109 assert!(q.is_empty());
110 }
111
112 #[test]
113 fn capacity_drops_oldest() {
114 let q = InMemoryEventQueue::with_capacity(3);
115 for name in ["a", "b", "c", "d", "e"] {
116 q.push(ev(name));
117 }
118 assert_eq!(q.len(), 3);
119 let batch = q.drain_batch(10);
120 let names: Vec<String> = batch.iter().map(|e| e.name.clone()).collect();
121 assert_eq!(names, vec!["c".to_string(), "d".into(), "e".into()]);
122 }
123
124 #[test]
125 fn discard_all_empties() {
126 let q = InMemoryEventQueue::new();
127 q.push(ev("a"));
128 q.push(ev("b"));
129 q.discard_all();
130 assert!(q.is_empty());
131 }
132
133 #[test]
134 fn peek_recent_returns_newest_first() {
135 let q = InMemoryEventQueue::new();
136 q.push(ev("a"));
137 q.push(ev("b"));
138 q.push(ev("c"));
139 let recent = q.peek_recent(2);
140 assert_eq!(recent[0].name, "c");
141 assert_eq!(recent[1].name, "b");
142 }
143}