ftui_runtime/
voi_telemetry.rs1#![forbid(unsafe_code)]
2
3use std::sync::{LazyLock, RwLock};
6
7use crate::voi_sampling::VoiSamplerSnapshot;
8
9static INLINE_AUTO_VOI_SNAPSHOT: LazyLock<RwLock<Option<VoiSamplerSnapshot>>> =
10 LazyLock::new(|| RwLock::new(None));
11
12#[cfg(test)]
13use std::sync::Mutex;
14
15#[cfg(test)]
18static TEST_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
19
20pub fn set_inline_auto_voi_snapshot(snapshot: Option<VoiSamplerSnapshot>) {
22 #[cfg(test)]
23 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
24
25 if let Ok(mut guard) = INLINE_AUTO_VOI_SNAPSHOT.write() {
26 *guard = snapshot;
27 }
28}
29
30#[must_use]
32pub fn inline_auto_voi_snapshot() -> Option<VoiSamplerSnapshot> {
33 #[cfg(test)]
34 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
35
36 INLINE_AUTO_VOI_SNAPSHOT
37 .read()
38 .ok()
39 .and_then(|guard| guard.clone())
40}
41
42pub fn clear_inline_auto_voi_snapshot() {
44 set_inline_auto_voi_snapshot(None);
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::voi_sampling::{VoiDecision, VoiLogEntry, VoiObservation, VoiSamplerSnapshot};
51
52 fn set_and_get_snapshot(snapshot: VoiSamplerSnapshot) -> VoiSamplerSnapshot {
53 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
54 let mut guard = INLINE_AUTO_VOI_SNAPSHOT
55 .write()
56 .expect("snapshot lock poisoned");
57 *guard = Some(snapshot);
58 guard.clone().expect("snapshot should be present")
59 }
60
61 fn make_snapshot(captured_ms: u64) -> VoiSamplerSnapshot {
62 VoiSamplerSnapshot {
63 captured_ms,
64 alpha: 2.0,
65 beta: 18.0,
66 posterior_mean: 0.1,
67 posterior_variance: 0.004,
68 expected_variance_after: 0.003,
69 voi_gain: 0.5,
70 last_decision: None,
71 last_observation: None,
72 recent_logs: Vec::new(),
73 }
74 }
75
76 #[test]
77 fn initially_none() {
78 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
79 let mut guard = INLINE_AUTO_VOI_SNAPSHOT
80 .write()
81 .expect("snapshot lock poisoned");
82 *guard = None;
83 assert!(guard.is_none());
84 }
85
86 #[test]
87 fn store_and_retrieve() {
88 let retrieved = set_and_get_snapshot(make_snapshot(1000));
89 assert_eq!(retrieved.captured_ms, 1000);
90 assert!((retrieved.alpha - 2.0).abs() < f64::EPSILON);
91 assert!((retrieved.posterior_mean - 0.1).abs() < f64::EPSILON);
92 clear_inline_auto_voi_snapshot();
93 }
94
95 #[test]
96 fn overwrite_replaces_previous() {
97 let _ = set_and_get_snapshot(make_snapshot(100));
98 let snap = set_and_get_snapshot(make_snapshot(200));
99 assert_eq!(snap.captured_ms, 200);
100 clear_inline_auto_voi_snapshot();
101 }
102
103 #[test]
104 fn clear_removes_snapshot() {
105 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
106 let mut guard = INLINE_AUTO_VOI_SNAPSHOT
107 .write()
108 .expect("snapshot lock poisoned");
109 *guard = Some(make_snapshot(50));
110 *guard = None;
111 assert!(guard.is_none());
112 }
113
114 #[test]
115 fn set_none_clears() {
116 let _lock = TEST_LOCK.lock().expect("test lock poisoned");
117 let mut guard = INLINE_AUTO_VOI_SNAPSHOT
118 .write()
119 .expect("snapshot lock poisoned");
120 *guard = Some(make_snapshot(77));
121 *guard = None;
122 assert!(guard.is_none());
123 }
124
125 #[test]
126 fn snapshot_with_decision() {
127 let mut snap = make_snapshot(500);
128 snap.last_decision = Some(VoiDecision {
129 event_idx: 42,
130 should_sample: true,
131 forced_by_interval: false,
132 blocked_by_min_interval: false,
133 voi_gain: 1.2,
134 score: 0.8,
135 cost: 0.3,
136 log_bayes_factor: 2.5,
137 posterior_mean: 0.1,
138 posterior_variance: 0.004,
139 e_value: 15.0,
140 e_threshold: 20.0,
141 boundary_score: 0.7,
142 events_since_sample: 10,
143 time_since_sample_ms: 500.0,
144 reason: "voi_gain",
145 });
146 let retrieved = set_and_get_snapshot(snap);
147 let decision = retrieved.last_decision.as_ref().unwrap();
148 assert_eq!(decision.event_idx, 42);
149 assert!(decision.should_sample);
150 assert!((decision.voi_gain - 1.2).abs() < f64::EPSILON);
151 clear_inline_auto_voi_snapshot();
152 }
153
154 #[test]
155 fn snapshot_with_observation() {
156 let mut snap = make_snapshot(600);
157 snap.last_observation = Some(VoiObservation {
158 event_idx: 100,
159 sample_idx: 5,
160 violated: true,
161 posterior_mean: 0.15,
162 posterior_variance: 0.003,
163 alpha: 3.0,
164 beta: 17.0,
165 e_value: 25.0,
166 e_threshold: 20.0,
167 });
168 let retrieved = set_and_get_snapshot(snap);
169 let obs = retrieved.last_observation.as_ref().unwrap();
170 assert_eq!(obs.event_idx, 100);
171 assert!(obs.violated);
172 assert!((obs.alpha - 3.0).abs() < f64::EPSILON);
173 clear_inline_auto_voi_snapshot();
174 }
175
176 #[test]
177 fn snapshot_with_recent_logs() {
178 let mut snap = make_snapshot(700);
179 snap.recent_logs = vec![
180 VoiLogEntry::Decision(VoiDecision {
181 event_idx: 1,
182 should_sample: false,
183 forced_by_interval: false,
184 blocked_by_min_interval: true,
185 voi_gain: 0.1,
186 score: 0.2,
187 cost: 0.5,
188 log_bayes_factor: -1.0,
189 posterior_mean: 0.05,
190 posterior_variance: 0.002,
191 e_value: 0.8,
192 e_threshold: 20.0,
193 boundary_score: 0.3,
194 events_since_sample: 2,
195 time_since_sample_ms: 50.0,
196 reason: "blocked_min_interval",
197 }),
198 VoiLogEntry::Observation(VoiObservation {
199 event_idx: 2,
200 sample_idx: 1,
201 violated: false,
202 posterior_mean: 0.06,
203 posterior_variance: 0.002,
204 alpha: 2.0,
205 beta: 18.0,
206 e_value: 1.0,
207 e_threshold: 20.0,
208 }),
209 ];
210 let retrieved = set_and_get_snapshot(snap);
211 assert_eq!(retrieved.recent_logs.len(), 2);
212 clear_inline_auto_voi_snapshot();
213 }
214}