1use crate::rng::DetRng;
9use crate::seed::{SeedTree, VortexSeed};
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct SeqNo(pub u64);
15
16#[derive(Debug, Clone)]
18pub struct SimLogEntry {
19 pub seq: SeqNo,
21 pub timestamp_us: u64,
23 pub subsystem: Subsystem,
25 pub description: String,
27 pub payload: Option<Vec<u8>>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Subsystem {
34 Executor,
36 Fs,
38 Clock,
40 Alloc,
42 Network,
44 Process,
46 Storage,
48 Custom,
50}
51
52impl fmt::Display for Subsystem {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Subsystem::Executor => write!(f, "executor"),
56 Subsystem::Fs => write!(f, "fs"),
57 Subsystem::Clock => write!(f, "clock"),
58 Subsystem::Alloc => write!(f, "alloc"),
59 Subsystem::Network => write!(f, "network"),
60 Subsystem::Process => write!(f, "process"),
61 Subsystem::Storage => write!(f, "storage"),
62 Subsystem::Custom => write!(f, "custom"),
63 }
64 }
65}
66
67pub struct SimEventLog {
73 entries: Vec<SimLogEntry>,
74 next_seq: u64,
75}
76
77impl SimEventLog {
78 pub fn new() -> Self {
80 Self {
81 entries: Vec::new(),
82 next_seq: 0,
83 }
84 }
85
86 pub fn record(
88 &mut self,
89 timestamp_us: u64,
90 subsystem: Subsystem,
91 description: String,
92 payload: Option<Vec<u8>>,
93 ) -> SeqNo {
94 let seq = SeqNo(self.next_seq);
95 self.next_seq += 1;
96 self.entries.push(SimLogEntry {
97 seq,
98 timestamp_us,
99 subsystem,
100 description,
101 payload,
102 });
103 seq
104 }
105
106 pub fn record_simple(
108 &mut self,
109 timestamp_us: u64,
110 subsystem: Subsystem,
111 description: impl Into<String>,
112 ) -> SeqNo {
113 self.record(timestamp_us, subsystem, description.into(), None)
114 }
115
116 pub fn entries(&self) -> &[SimLogEntry] {
118 &self.entries
119 }
120
121 pub fn len(&self) -> usize {
123 self.entries.len()
124 }
125
126 pub fn is_empty(&self) -> bool {
128 self.entries.is_empty()
129 }
130
131 pub fn diff(a: &SimEventLog, b: &SimEventLog) -> Option<LogDivergence> {
137 let min_len = a.entries.len().min(b.entries.len());
138 for i in 0..min_len {
139 let ea = &a.entries[i];
140 let eb = &b.entries[i];
141 if ea.subsystem != eb.subsystem
142 || ea.description != eb.description
143 || ea.timestamp_us != eb.timestamp_us
144 || ea.payload != eb.payload
145 {
146 return Some(LogDivergence {
147 seq: SeqNo(i as u64),
148 entry_a: ea.clone(),
149 entry_b: eb.clone(),
150 kind: DivergenceKind::ContentMismatch,
151 });
152 }
153 }
154 if a.entries.len() != b.entries.len() {
155 let longer = if a.entries.len() > b.entries.len() {
156 "a"
157 } else {
158 "b"
159 };
160 return Some(LogDivergence {
161 seq: SeqNo(min_len as u64),
162 entry_a: a
163 .entries
164 .get(min_len)
165 .cloned()
166 .unwrap_or_else(|| SimLogEntry {
167 seq: SeqNo(min_len as u64),
168 timestamp_us: 0,
169 subsystem: Subsystem::Custom,
170 description: format!("<log {longer} has no more entries>"),
171 payload: None,
172 }),
173 entry_b: b
174 .entries
175 .get(min_len)
176 .cloned()
177 .unwrap_or_else(|| SimLogEntry {
178 seq: SeqNo(min_len as u64),
179 timestamp_us: 0,
180 subsystem: Subsystem::Custom,
181 description: format!("<log {longer} has no more entries>"),
182 payload: None,
183 }),
184 kind: DivergenceKind::LengthMismatch {
185 len_a: a.entries.len(),
186 len_b: b.entries.len(),
187 },
188 });
189 }
190 None
191 }
192}
193
194impl Default for SimEventLog {
195 fn default() -> Self {
196 Self::new()
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct LogDivergence {
203 pub seq: SeqNo,
205 pub entry_a: SimLogEntry,
207 pub entry_b: SimLogEntry,
209 pub kind: DivergenceKind,
211}
212
213#[derive(Debug, Clone)]
215pub enum DivergenceKind {
216 ContentMismatch,
218 LengthMismatch { len_a: usize, len_b: usize },
220}
221
222impl fmt::Display for LogDivergence {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 match &self.kind {
225 DivergenceKind::ContentMismatch => {
226 write!(
227 f,
228 "Divergence at seq {}: A=[{}] {:?} vs B=[{}] {:?}",
229 self.seq.0,
230 self.entry_a.subsystem,
231 self.entry_a.description,
232 self.entry_b.subsystem,
233 self.entry_b.description,
234 )
235 }
236 DivergenceKind::LengthMismatch { len_a, len_b } => {
237 write!(
238 f,
239 "Length mismatch at seq {}: log A has {} entries, log B has {} entries",
240 self.seq.0, len_a, len_b,
241 )
242 }
243 }
244 }
245}
246
247pub struct SimContext {
253 seed: VortexSeed,
254 seed_tree: SeedTree,
255 logical_time_us: u64,
256 event_log: SimEventLog,
257}
258
259impl SimContext {
260 pub fn new(seed: VortexSeed) -> Self {
262 Self {
263 seed,
264 seed_tree: SeedTree::new(seed),
265 logical_time_us: 0,
266 event_log: SimEventLog::new(),
267 }
268 }
269
270 pub fn from_u64(seed: u64) -> Self {
272 Self::new(VortexSeed::from_u64(seed))
273 }
274
275 pub fn seed(&self) -> VortexSeed {
277 self.seed
278 }
279
280 pub fn seed_tree(&self) -> &SeedTree {
282 &self.seed_tree
283 }
284
285 pub fn rng_for(&self, domain: &str) -> DetRng {
287 let sub_seed = self.seed_tree.derive(domain);
288 DetRng::new(sub_seed.to_u64())
289 }
290
291 pub fn logical_time_us(&self) -> u64 {
293 self.logical_time_us
294 }
295
296 pub fn advance_time_us(&mut self, delta_us: u64) {
298 self.logical_time_us += delta_us;
299 }
300
301 pub fn set_time_us(&mut self, time_us: u64) {
303 self.logical_time_us = time_us;
304 }
305
306 pub fn log_event(&mut self, subsystem: Subsystem, description: impl Into<String>) -> SeqNo {
308 self.event_log
309 .record_simple(self.logical_time_us, subsystem, description)
310 }
311
312 pub fn log_event_with_payload(
314 &mut self,
315 subsystem: Subsystem,
316 description: impl Into<String>,
317 payload: Vec<u8>,
318 ) -> SeqNo {
319 self.event_log.record(
320 self.logical_time_us,
321 subsystem,
322 description.into(),
323 Some(payload),
324 )
325 }
326
327 pub fn event_log(&self) -> &SimEventLog {
329 &self.event_log
330 }
331
332 pub fn into_event_log(self) -> SimEventLog {
334 self.event_log
335 }
336}
337
338impl fmt::Debug for SimContext {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 f.debug_struct("SimContext")
341 .field("seed", &self.seed)
342 .field("logical_time_us", &self.logical_time_us)
343 .field("events_recorded", &self.event_log.len())
344 .finish()
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_context_creation() {
354 let ctx = SimContext::from_u64(42);
355 assert_eq!(ctx.seed(), VortexSeed::from_u64(42));
356 assert_eq!(ctx.logical_time_us(), 0);
357 assert!(ctx.event_log().is_empty());
358 }
359
360 #[test]
361 fn test_context_time_advance() {
362 let mut ctx = SimContext::from_u64(42);
363 ctx.advance_time_us(1000);
364 assert_eq!(ctx.logical_time_us(), 1000);
365 ctx.advance_time_us(500);
366 assert_eq!(ctx.logical_time_us(), 1500);
367 }
368
369 #[test]
370 fn test_context_event_logging() {
371 let mut ctx = SimContext::from_u64(42);
372 ctx.advance_time_us(100);
373 let seq1 = ctx.log_event(Subsystem::Fs, "open /data/wal.log");
374 ctx.advance_time_us(50);
375 let seq2 = ctx.log_event(Subsystem::Fs, "write 4096 bytes");
376
377 assert_eq!(seq1, SeqNo(0));
378 assert_eq!(seq2, SeqNo(1));
379 assert_eq!(ctx.event_log().len(), 2);
380 assert_eq!(ctx.event_log().entries()[0].timestamp_us, 100);
381 assert_eq!(ctx.event_log().entries()[1].timestamp_us, 150);
382 }
383
384 #[test]
385 fn test_context_deterministic_rngs() {
386 let ctx1 = SimContext::from_u64(42);
387 let ctx2 = SimContext::from_u64(42);
388
389 let mut rng1 = ctx1.rng_for("fs");
390 let mut rng2 = ctx2.rng_for("fs");
391 let seq1: Vec<u64> = (0..100).map(|_| rng1.next_u64()).collect();
392 let seq2: Vec<u64> = (0..100).map(|_| rng2.next_u64()).collect();
393 assert_eq!(seq1, seq2);
394 }
395
396 #[test]
397 fn test_context_different_domains_different_rngs() {
398 let ctx = SimContext::from_u64(42);
399 let mut fs_rng = ctx.rng_for("fs");
400 let mut net_rng = ctx.rng_for("net");
401 assert_ne!(fs_rng.next_u64(), net_rng.next_u64());
402 }
403
404 #[test]
405 fn test_event_log_diff_identical() {
406 let mut log1 = SimEventLog::new();
407 let mut log2 = SimEventLog::new();
408 log1.record_simple(0, Subsystem::Fs, "open file");
409 log1.record_simple(100, Subsystem::Fs, "write data");
410 log2.record_simple(0, Subsystem::Fs, "open file");
411 log2.record_simple(100, Subsystem::Fs, "write data");
412 assert!(SimEventLog::diff(&log1, &log2).is_none());
413 }
414
415 #[test]
416 fn test_event_log_diff_content_mismatch() {
417 let mut log1 = SimEventLog::new();
418 let mut log2 = SimEventLog::new();
419 log1.record_simple(0, Subsystem::Fs, "open file A");
420 log2.record_simple(0, Subsystem::Fs, "open file B");
421
422 let d = SimEventLog::diff(&log1, &log2).unwrap();
423 assert_eq!(d.seq, SeqNo(0));
424 assert!(matches!(d.kind, DivergenceKind::ContentMismatch));
425 }
426
427 #[test]
428 fn test_event_log_diff_length_mismatch() {
429 let mut log1 = SimEventLog::new();
430 let mut log2 = SimEventLog::new();
431 log1.record_simple(0, Subsystem::Fs, "open file");
432 log1.record_simple(100, Subsystem::Fs, "write data");
433 log2.record_simple(0, Subsystem::Fs, "open file");
434
435 let d = SimEventLog::diff(&log1, &log2).unwrap();
436 assert_eq!(d.seq, SeqNo(1));
437 assert!(matches!(
438 d.kind,
439 DivergenceKind::LengthMismatch { len_a: 2, len_b: 1 }
440 ));
441 }
442
443 #[test]
444 fn test_event_log_diff_display() {
445 let mut log1 = SimEventLog::new();
446 let mut log2 = SimEventLog::new();
447 log1.record_simple(0, Subsystem::Executor, "wake task A");
448 log2.record_simple(0, Subsystem::Executor, "wake task B");
449
450 let d = SimEventLog::diff(&log1, &log2).unwrap();
451 let display = format!("{d}");
452 assert!(display.contains("Divergence"));
453 assert!(display.contains("task A"));
454 assert!(display.contains("task B"));
455 }
456}