1use std::sync::Mutex;
30use std::time::Instant;
31
32#[derive(Clone, Debug)]
34pub struct SimEvent {
35 pub timestamp_us: u64,
37 pub node: usize,
39 pub face: Option<u32>,
41 pub kind: EventKind,
43 pub name: String,
45 pub detail: Option<String>,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum EventKind {
52 InterestIn,
54 InterestOut,
56 DataIn,
58 DataOut,
60 CacheHit,
62 CacheInsert,
64 PitInsert,
66 PitSatisfy,
68 PitExpire,
70 NackIn,
72 NackOut,
74 FaceUp,
76 FaceDown,
78 StrategyDecision,
80 Custom(String),
82}
83
84impl std::fmt::Display for EventKind {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 Self::InterestIn => write!(f, "interest-in"),
88 Self::InterestOut => write!(f, "interest-out"),
89 Self::DataIn => write!(f, "data-in"),
90 Self::DataOut => write!(f, "data-out"),
91 Self::CacheHit => write!(f, "cache-hit"),
92 Self::CacheInsert => write!(f, "cache-insert"),
93 Self::PitInsert => write!(f, "pit-insert"),
94 Self::PitSatisfy => write!(f, "pit-satisfy"),
95 Self::PitExpire => write!(f, "pit-expire"),
96 Self::NackIn => write!(f, "nack-in"),
97 Self::NackOut => write!(f, "nack-out"),
98 Self::FaceUp => write!(f, "face-up"),
99 Self::FaceDown => write!(f, "face-down"),
100 Self::StrategyDecision => write!(f, "strategy-decision"),
101 Self::Custom(s) => write!(f, "{s}"),
102 }
103 }
104}
105
106pub struct SimTracer {
111 start: Instant,
112 events: Mutex<Vec<SimEvent>>,
113}
114
115impl SimTracer {
116 pub fn new() -> Self {
117 Self {
118 start: Instant::now(),
119 events: Mutex::new(Vec::new()),
120 }
121 }
122
123 pub fn record(&self, event: SimEvent) {
125 self.events.lock().unwrap().push(event);
126 }
127
128 pub fn record_now(
130 &self,
131 node: usize,
132 face: Option<u32>,
133 kind: EventKind,
134 name: impl Into<String>,
135 detail: Option<String>,
136 ) {
137 let ts = self.start.elapsed().as_micros() as u64;
138 self.record(SimEvent {
139 timestamp_us: ts,
140 node,
141 face,
142 kind,
143 name: name.into(),
144 detail,
145 });
146 }
147
148 pub fn events(&self) -> Vec<SimEvent> {
150 self.events.lock().unwrap().clone()
151 }
152
153 pub fn len(&self) -> usize {
155 self.events.lock().unwrap().len()
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.events.lock().unwrap().is_empty()
161 }
162
163 pub fn clear(&self) {
165 self.events.lock().unwrap().clear();
166 }
167
168 pub fn events_for_node(&self, node: usize) -> Vec<SimEvent> {
170 self.events
171 .lock()
172 .unwrap()
173 .iter()
174 .filter(|e| e.node == node)
175 .cloned()
176 .collect()
177 }
178
179 pub fn events_of_kind(&self, kind: &EventKind) -> Vec<SimEvent> {
181 self.events
182 .lock()
183 .unwrap()
184 .iter()
185 .filter(|e| &e.kind == kind)
186 .cloned()
187 .collect()
188 }
189
190 pub fn to_json(&self) -> String {
192 let events = self.events.lock().unwrap();
193 let mut output = String::new();
194 output.push('[');
195 for (i, event) in events.iter().enumerate() {
196 if i > 0 {
197 output.push(',');
198 }
199 output.push('\n');
200 output.push_str(&format!(
201 r#" {{"t":{},"node":{},"face":{},"kind":"{}","name":"{}""#,
202 event.timestamp_us,
203 event.node,
204 event.face.map_or("null".to_string(), |f| f.to_string()),
205 event.kind,
206 event.name,
207 ));
208 if let Some(ref detail) = event.detail {
209 output.push_str(&format!(r#","detail":"{detail}""#));
210 }
211 output.push('}');
212 }
213 output.push_str("\n]");
214 output
215 }
216}
217
218impl Default for SimTracer {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn tracer_records_and_retrieves() {
230 let tracer = SimTracer::new();
231 tracer.record_now(0, Some(1), EventKind::InterestIn, "/test", None);
232 tracer.record_now(1, Some(2), EventKind::DataOut, "/test", Some("ok".into()));
233
234 assert_eq!(tracer.len(), 2);
235 let events = tracer.events();
236 assert_eq!(events[0].kind, EventKind::InterestIn);
237 assert_eq!(events[1].detail.as_deref(), Some("ok"));
238 }
239
240 #[test]
241 fn filter_by_node() {
242 let tracer = SimTracer::new();
243 tracer.record_now(0, None, EventKind::FaceUp, "/", None);
244 tracer.record_now(1, None, EventKind::FaceUp, "/", None);
245 tracer.record_now(0, None, EventKind::InterestIn, "/test", None);
246
247 let node0 = tracer.events_for_node(0);
248 assert_eq!(node0.len(), 2);
249 }
250
251 #[test]
252 fn json_output() {
253 let tracer = SimTracer::new();
254 tracer.record(SimEvent {
255 timestamp_us: 100,
256 node: 0,
257 face: Some(1),
258 kind: EventKind::CacheHit,
259 name: "/test".into(),
260 detail: None,
261 });
262 let json = tracer.to_json();
263 assert!(json.contains("cache-hit"));
264 assert!(json.contains("/test"));
265 }
266}