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#[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 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 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 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 #[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 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}