1use std::cell::Cell;
21use std::collections::BTreeSet;
22use std::sync::atomic::{AtomicUsize, Ordering};
23use std::sync::{Mutex, OnceLock};
24
25use crate::event::{EventId, Tid};
26use crate::explorer::{Execution, ExecutionKind};
27use crate::graph::ExecutionGraph;
28
29pub trait Observer {
31 fn on_event_added(&self, _g: &ExecutionGraph, _e: EventId) {}
33 fn on_rf_choice(&self, _g: &ExecutionGraph, _r: EventId, _src: Option<EventId>) {}
36 fn on_inconsistent(&self, _g: &ExecutionGraph) {}
38 fn on_backward_revisit(
41 &self,
42 _g: &ExecutionGraph,
43 _r: EventId,
44 _s: EventId,
45 _deleted: &BTreeSet<EventId>,
46 ) {
47 }
48 fn on_revisit_rejected(&self, _g: &ExecutionGraph, _r: EventId, _s: EventId) {}
50 fn on_execution(&self, _exec: &Execution, _kind: ExecutionKind) {}
52 fn on_thread_blocked(&self, _g: &ExecutionGraph, _tid: Tid) {}
54}
55
56thread_local! {
59 static WORKER_ID: Cell<usize> = const { Cell::new(0) };
62}
63
64pub(crate) fn set_worker_id(w: usize) {
67 WORKER_ID.with(|c| c.set(w));
68}
69
70pub(crate) fn worker_id() -> usize {
74 WORKER_ID.with(Cell::get)
75}
76
77pub(crate) fn default_shards() -> usize {
81 static N: OnceLock<usize> = OnceLock::new();
82 *N.get_or_init(|| std::thread::available_parallelism().map_or(1, |n| n.get()))
83}
84
85#[derive(Clone, Copy, Debug, Default)]
87pub struct NullObserver;
88
89impl Observer for NullObserver {}
90
91#[repr(align(64))]
95#[derive(Debug, Default)]
96struct Shard {
97 events_added: AtomicUsize,
98 rf_choices: AtomicUsize,
99 inconsistent: AtomicUsize,
100 backward_revisits: AtomicUsize,
101 revisits_rejected: AtomicUsize,
102 full: AtomicUsize,
103 blocked: AtomicUsize,
104 errors: AtomicUsize,
105 threads_blocked: AtomicUsize,
106}
107
108#[derive(Debug)]
112pub struct CountingObserver {
113 shards: Vec<Shard>,
114}
115
116impl Default for CountingObserver {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl CountingObserver {
123 pub fn new() -> Self {
126 Self::with_shards(default_shards())
127 }
128
129 pub fn with_shards(shards: usize) -> Self {
132 CountingObserver {
133 shards: (0..shards.max(1)).map(|_| Shard::default()).collect(),
134 }
135 }
136
137 fn shard(&self) -> &Shard {
139 &self.shards[worker_id() % self.shards.len()]
140 }
141
142 fn total(&self, pick: impl Fn(&Shard) -> &AtomicUsize) -> usize {
143 self.shards
144 .iter()
145 .map(|s| pick(s).load(Ordering::Relaxed))
146 .sum()
147 }
148
149 pub fn events_added(&self) -> usize {
150 self.total(|s| &s.events_added)
151 }
152 pub fn rf_choices(&self) -> usize {
153 self.total(|s| &s.rf_choices)
154 }
155 pub fn inconsistent(&self) -> usize {
156 self.total(|s| &s.inconsistent)
157 }
158 pub fn backward_revisits(&self) -> usize {
159 self.total(|s| &s.backward_revisits)
160 }
161 pub fn revisits_rejected(&self) -> usize {
162 self.total(|s| &s.revisits_rejected)
163 }
164 pub fn full(&self) -> usize {
165 self.total(|s| &s.full)
166 }
167 pub fn blocked(&self) -> usize {
168 self.total(|s| &s.blocked)
169 }
170 pub fn errors(&self) -> usize {
171 self.total(|s| &s.errors)
172 }
173 pub fn threads_blocked(&self) -> usize {
174 self.total(|s| &s.threads_blocked)
175 }
176
177 pub fn terminal(&self) -> usize {
179 self.full() + self.blocked()
180 }
181}
182
183impl Observer for CountingObserver {
184 fn on_event_added(&self, _g: &ExecutionGraph, _e: EventId) {
185 self.shard().events_added.fetch_add(1, Ordering::Relaxed);
186 }
187 fn on_rf_choice(&self, _g: &ExecutionGraph, _r: EventId, _src: Option<EventId>) {
188 self.shard().rf_choices.fetch_add(1, Ordering::Relaxed);
189 }
190 fn on_inconsistent(&self, _g: &ExecutionGraph) {
191 self.shard().inconsistent.fetch_add(1, Ordering::Relaxed);
192 }
193 fn on_backward_revisit(
194 &self,
195 _g: &ExecutionGraph,
196 _r: EventId,
197 _s: EventId,
198 _deleted: &BTreeSet<EventId>,
199 ) {
200 self.shard()
201 .backward_revisits
202 .fetch_add(1, Ordering::Relaxed);
203 }
204 fn on_revisit_rejected(&self, _g: &ExecutionGraph, _r: EventId, _s: EventId) {
205 self.shard()
206 .revisits_rejected
207 .fetch_add(1, Ordering::Relaxed);
208 }
209 fn on_execution(&self, _exec: &Execution, kind: ExecutionKind) {
210 let shard = self.shard();
211 match kind {
212 ExecutionKind::Full => &shard.full,
213 ExecutionKind::Blocked => &shard.blocked,
214 ExecutionKind::Error => &shard.errors,
215 }
216 .fetch_add(1, Ordering::Relaxed);
217 }
218 fn on_thread_blocked(&self, _g: &ExecutionGraph, _tid: Tid) {
219 self.shard().threads_blocked.fetch_add(1, Ordering::Relaxed);
220 }
221}
222
223#[derive(Clone, Debug)]
225pub struct Step {
226 pub kind: StepKind,
227 pub graph: ExecutionGraph,
229}
230
231#[derive(Clone, Debug)]
233pub enum StepKind {
234 EventAdded {
235 e: EventId,
236 },
237 RfChoice {
238 r: EventId,
239 src: Option<EventId>,
240 },
241 Inconsistent,
242 BackwardRevisit {
243 r: EventId,
244 s: EventId,
245 deleted: Vec<EventId>,
246 },
247 RevisitRejected {
248 r: EventId,
249 s: EventId,
250 },
251 Execution {
252 kind: ExecutionKind,
253 },
254 ThreadBlocked {
255 tid: Tid,
256 },
257}
258
259#[derive(Debug, Default)]
262pub struct RecordingObserver {
263 steps: Mutex<Vec<Step>>,
264}
265
266impl RecordingObserver {
267 pub fn new() -> Self {
268 Self::default()
269 }
270
271 pub fn len(&self) -> usize {
272 self.steps.lock().unwrap().len()
273 }
274
275 pub fn is_empty(&self) -> bool {
276 self.steps.lock().unwrap().is_empty()
277 }
278
279 pub fn steps(&self) -> Vec<Step> {
281 self.steps.lock().unwrap().clone()
282 }
283
284 fn record(&self, kind: StepKind, graph: &ExecutionGraph) {
285 self.steps.lock().unwrap().push(Step {
286 kind,
287 graph: graph.clone(),
288 });
289 }
290}
291
292impl Observer for RecordingObserver {
293 fn on_event_added(&self, g: &ExecutionGraph, e: EventId) {
294 self.record(StepKind::EventAdded { e }, g);
295 }
296 fn on_rf_choice(&self, g: &ExecutionGraph, r: EventId, src: Option<EventId>) {
297 self.record(StepKind::RfChoice { r, src }, g);
298 }
299 fn on_inconsistent(&self, g: &ExecutionGraph) {
300 self.record(StepKind::Inconsistent, g);
301 }
302 fn on_backward_revisit(
303 &self,
304 g: &ExecutionGraph,
305 r: EventId,
306 s: EventId,
307 deleted: &BTreeSet<EventId>,
308 ) {
309 let deleted = deleted.iter().copied().collect();
310 self.record(StepKind::BackwardRevisit { r, s, deleted }, g);
311 }
312 fn on_revisit_rejected(&self, g: &ExecutionGraph, r: EventId, s: EventId) {
313 self.record(StepKind::RevisitRejected { r, s }, g);
314 }
315 fn on_execution(&self, exec: &Execution, kind: ExecutionKind) {
316 self.record(StepKind::Execution { kind }, exec.graph());
317 }
318 fn on_thread_blocked(&self, g: &ExecutionGraph, tid: Tid) {
319 self.record(StepKind::ThreadBlocked { tid }, g);
320 }
321}
322
323#[derive(Debug, Default)]
330pub struct ExecutionCollector {
331 inner: Mutex<Collected>,
332}
333
334#[derive(Debug, Default)]
335struct Collected {
336 full: Vec<Execution>,
337 blocked: Vec<Execution>,
338 errors: Vec<Execution>,
339}
340
341impl ExecutionCollector {
342 pub fn new() -> Self {
343 Self::default()
344 }
345
346 pub fn full(&self) -> Vec<Execution> {
348 self.inner.lock().unwrap().full.clone()
349 }
350 pub fn blocked(&self) -> Vec<Execution> {
352 self.inner.lock().unwrap().blocked.clone()
353 }
354 pub fn errors(&self) -> Vec<Execution> {
356 self.inner.lock().unwrap().errors.clone()
357 }
358 pub fn terminals(&self) -> Vec<Execution> {
360 let c = self.inner.lock().unwrap();
361 c.full.iter().chain(c.blocked.iter()).cloned().collect()
362 }
363
364 pub fn full_count(&self) -> usize {
365 self.inner.lock().unwrap().full.len()
366 }
367 pub fn blocked_count(&self) -> usize {
368 self.inner.lock().unwrap().blocked.len()
369 }
370 pub fn error_count(&self) -> usize {
371 self.inner.lock().unwrap().errors.len()
372 }
373 pub fn terminal_count(&self) -> usize {
375 let c = self.inner.lock().unwrap();
376 c.full.len() + c.blocked.len()
377 }
378
379 pub fn full_keys(&self) -> Vec<String> {
381 self.inner
382 .lock()
383 .unwrap()
384 .full
385 .iter()
386 .map(Execution::canonical_key)
387 .collect()
388 }
389 pub fn terminal_keys(&self) -> Vec<String> {
391 let c = self.inner.lock().unwrap();
392 c.full
393 .iter()
394 .chain(c.blocked.iter())
395 .map(Execution::canonical_key)
396 .collect()
397 }
398 pub fn error_keys(&self) -> Vec<String> {
400 self.inner
401 .lock()
402 .unwrap()
403 .errors
404 .iter()
405 .map(Execution::canonical_key)
406 .collect()
407 }
408}
409
410impl Observer for ExecutionCollector {
411 fn on_execution(&self, exec: &Execution, kind: ExecutionKind) {
412 let mut c = self.inner.lock().unwrap();
413 match kind {
414 ExecutionKind::Full => c.full.push(exec.clone()),
415 ExecutionKind::Blocked => c.blocked.push(exec.clone()),
416 ExecutionKind::Error => c.errors.push(exec.clone()),
417 }
418 }
419}
420
421impl<A: Observer, B: Observer> Observer for (A, B) {
423 fn on_event_added(&self, g: &ExecutionGraph, e: EventId) {
424 self.0.on_event_added(g, e);
425 self.1.on_event_added(g, e);
426 }
427 fn on_rf_choice(&self, g: &ExecutionGraph, r: EventId, src: Option<EventId>) {
428 self.0.on_rf_choice(g, r, src);
429 self.1.on_rf_choice(g, r, src);
430 }
431 fn on_inconsistent(&self, g: &ExecutionGraph) {
432 self.0.on_inconsistent(g);
433 self.1.on_inconsistent(g);
434 }
435 fn on_backward_revisit(
436 &self,
437 g: &ExecutionGraph,
438 r: EventId,
439 s: EventId,
440 deleted: &BTreeSet<EventId>,
441 ) {
442 self.0.on_backward_revisit(g, r, s, deleted);
443 self.1.on_backward_revisit(g, r, s, deleted);
444 }
445 fn on_revisit_rejected(&self, g: &ExecutionGraph, r: EventId, s: EventId) {
446 self.0.on_revisit_rejected(g, r, s);
447 self.1.on_revisit_rejected(g, r, s);
448 }
449 fn on_execution(&self, exec: &Execution, kind: ExecutionKind) {
450 self.0.on_execution(exec, kind);
451 self.1.on_execution(exec, kind);
452 }
453 fn on_thread_blocked(&self, g: &ExecutionGraph, tid: Tid) {
454 self.0.on_thread_blocked(g, tid);
455 self.1.on_thread_blocked(g, tid);
456 }
457}