Skip to main content

rosace_trace/
lib.rs

1pub mod bus;
2pub mod event;
3pub mod log;
4pub mod subscribers;
5
6pub use bus::{TraceSubscriber, TracingBus, TRACING_BUS};
7pub use event::{LogLevel, RosaceTrace, TraceCategory};
8pub use log::{max_level, set_max_level};
9// `init_from_env` reads process env/stderr — native only (it is
10// `#[cfg(not(wasm32))]` in `log`), so its re-export must be gated too or the
11// whole wasm/web build fails to resolve this import.
12#[cfg(not(target_arch = "wasm32"))]
13pub use log::init_from_env;
14pub use subscribers::{flight_recorder, install_flight_recorder, install_log_console};
15pub use subscribers::perfetto::to_chrome_trace_json;
16
17#[cfg(test)]
18mod tests {
19    use std::sync::{
20        atomic::{AtomicUsize, Ordering},
21        Arc,
22    };
23
24    use crate::bus::{TraceSubscriber, TRACING_BUS};
25    use crate::event::{ComponentId, RosaceTrace};
26    use crate::subscribers::ring_buffer::RingBufferSubscriber;
27    use crate::{location, trace};
28
29    /// Test-only subscriber that counts received events.
30    struct CountSubscriber(Arc<AtomicUsize>);
31
32    impl TraceSubscriber for CountSubscriber {
33        fn on_trace(&self, _event: &RosaceTrace) {
34            self.0.fetch_add(1, Ordering::SeqCst);
35        }
36    }
37
38    #[test]
39    fn trace_emits_in_debug() {
40        let counter = Arc::new(AtomicUsize::new(0));
41        let sub = Arc::new(CountSubscriber(counter.clone()));
42        TRACING_BUS.add_subscriber(sub);
43
44        let before = counter.load(Ordering::SeqCst);
45
46        trace!(RosaceTrace::ComponentMount {
47            id: ComponentId(1),
48            name: "TestComponent",
49            location: location!(),
50        });
51
52        let after = counter.load(Ordering::SeqCst);
53        assert_eq!(after - before, 1);
54
55        TRACING_BUS.clear_subscribers();
56    }
57
58    #[test]
59    fn ring_buffer_captures_events() {
60        let buf = Arc::new(RingBufferSubscriber::new(1000));
61        TRACING_BUS.add_subscriber(buf.clone());
62
63        trace!(RosaceTrace::ComponentUnmount {
64            id: ComponentId(2),
65            name: "AnotherComponent",
66        });
67
68        let snap = buf.snapshot();
69        assert!(!snap.is_empty());
70
71        TRACING_BUS.clear_subscribers();
72    }
73
74    #[test]
75    fn ring_buffer_evicts_oldest_when_full() {
76        let buf = RingBufferSubscriber::new(3);
77
78        for i in 0..5u64 {
79            buf.on_trace(&RosaceTrace::ComponentUnmount {
80                id: ComponentId(i),
81                name: "X",
82            });
83        }
84
85        // Capacity 3 — first two were evicted.
86        assert_eq!(buf.len(), 3);
87        let snap = buf.snapshot();
88        if let RosaceTrace::ComponentUnmount { id, .. } = &snap[0] {
89            assert_eq!(id.0, 2);
90        } else {
91            panic!("unexpected event type");
92        }
93    }
94
95    #[test]
96    fn multiple_events_all_captured() {
97        // Use a local bus to avoid interference from parallel tests sharing TRACING_BUS.
98        use crate::bus::TracingBus;
99        let local_bus = TracingBus::new();
100        let buf = Arc::new(RingBufferSubscriber::new(1000));
101        local_bus.add_subscriber(buf.clone());
102
103        for i in 0..10u64 {
104            local_bus.emit(RosaceTrace::AtomRead {
105                atom: crate::event::AtomId(i),
106                component: ComponentId(0),
107            });
108        }
109
110        assert_eq!(buf.len(), 10);
111    }
112}
113
114#[cfg(test)]
115mod flight_recorder_tests {
116    use crate::event::{AtomId, ComponentId, RosaceTrace, TraceValue};
117    use crate::subscribers::ring_buffer::RingBufferSubscriber;
118    use std::sync::Arc;
119
120    /// The O1 exit bar (D123): a flight recorder built on the high-frequency
121    /// filter captures meaningful events and NEVER the per-frame firehose
122    /// that hung the earlier attempt.
123    #[test]
124    fn flight_recorder_keeps_meaningful_events_and_drops_the_firehose() {
125        let rec = Arc::new(RingBufferSubscriber::filtered(100, |e| !e.is_high_frequency()));
126        let bus = crate::bus::TracingBus::new();
127        bus.add_subscriber(rec.clone());
128
129        // Simulate a frame's worth of noise + two meaningful events.
130        for _ in 0..500 {
131            bus.emit(RosaceTrace::AtomRead { atom: AtomId(1), component: ComponentId(1) });
132            bus.emit(RosaceTrace::FrameStart { frame: 0, timestamp: std::time::Instant::now() });
133            bus.emit(RosaceTrace::FrameEnd {
134                frame: 0, duration: std::time::Duration::from_millis(1), dropped: false,
135            });
136        }
137        bus.emit(RosaceTrace::AtomWrite {
138            atom: AtomId(2), old: TraceValue::Opaque, new: TraceValue::Opaque,
139            by: ComponentId(3), location: crate::location!(),
140        });
141        bus.emit(RosaceTrace::ComponentMount {
142            id: ComponentId(4), name: "Widget", location: crate::location!(),
143        });
144
145        let snap = rec.snapshot();
146        assert_eq!(snap.len(), 2, "1500 high-frequency events must be dropped; only the 2 meaningful ones kept");
147        assert!(snap.iter().all(|e| !e.is_high_frequency()));
148    }
149}