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 clear_inline_auto_voi_snapshot();
79 assert!(inline_auto_voi_snapshot().is_none());
80 }
81
82 #[test]
83 fn store_and_retrieve() {
84 let retrieved = set_and_get_snapshot(make_snapshot(1000));
85 assert_eq!(retrieved.captured_ms, 1000);
86 assert!((retrieved.alpha - 2.0).abs() < f64::EPSILON);
87 assert!((retrieved.posterior_mean - 0.1).abs() < f64::EPSILON);
88 clear_inline_auto_voi_snapshot();
89 }
90
91 #[test]
92 fn overwrite_replaces_previous() {
93 let _ = set_and_get_snapshot(make_snapshot(100));
94 let snap = set_and_get_snapshot(make_snapshot(200));
95 assert_eq!(snap.captured_ms, 200);
96 clear_inline_auto_voi_snapshot();
97 }
98
99 #[test]
100 fn clear_removes_snapshot() {
101 set_inline_auto_voi_snapshot(Some(make_snapshot(50)));
102 clear_inline_auto_voi_snapshot();
103 assert!(inline_auto_voi_snapshot().is_none());
104 }
105
106 #[test]
107 fn set_none_clears() {
108 set_inline_auto_voi_snapshot(Some(make_snapshot(77)));
109 set_inline_auto_voi_snapshot(None);
110 let _ = inline_auto_voi_snapshot();
113 }
114
115 #[test]
116 fn snapshot_with_decision() {
117 let mut snap = make_snapshot(500);
118 snap.last_decision = Some(VoiDecision {
119 event_idx: 42,
120 should_sample: true,
121 forced_by_interval: false,
122 blocked_by_min_interval: false,
123 voi_gain: 1.2,
124 score: 0.8,
125 cost: 0.3,
126 log_bayes_factor: 2.5,
127 posterior_mean: 0.1,
128 posterior_variance: 0.004,
129 e_value: 15.0,
130 e_threshold: 20.0,
131 boundary_score: 0.7,
132 events_since_sample: 10,
133 time_since_sample_ms: 500.0,
134 reason: "voi_gain",
135 });
136 let retrieved = set_and_get_snapshot(snap);
137 let decision = retrieved.last_decision.as_ref().unwrap();
138 assert_eq!(decision.event_idx, 42);
139 assert!(decision.should_sample);
140 assert!((decision.voi_gain - 1.2).abs() < f64::EPSILON);
141 clear_inline_auto_voi_snapshot();
142 }
143
144 #[test]
145 fn snapshot_with_observation() {
146 let mut snap = make_snapshot(600);
147 snap.last_observation = Some(VoiObservation {
148 event_idx: 100,
149 sample_idx: 5,
150 violated: true,
151 posterior_mean: 0.15,
152 posterior_variance: 0.003,
153 alpha: 3.0,
154 beta: 17.0,
155 e_value: 25.0,
156 e_threshold: 20.0,
157 });
158 let retrieved = set_and_get_snapshot(snap);
159 let obs = retrieved.last_observation.as_ref().unwrap();
160 assert_eq!(obs.event_idx, 100);
161 assert!(obs.violated);
162 assert!((obs.alpha - 3.0).abs() < f64::EPSILON);
163 clear_inline_auto_voi_snapshot();
164 }
165
166 #[test]
167 fn snapshot_with_recent_logs() {
168 let mut snap = make_snapshot(700);
169 snap.recent_logs = vec![
170 VoiLogEntry::Decision(VoiDecision {
171 event_idx: 1,
172 should_sample: false,
173 forced_by_interval: false,
174 blocked_by_min_interval: true,
175 voi_gain: 0.1,
176 score: 0.2,
177 cost: 0.5,
178 log_bayes_factor: -1.0,
179 posterior_mean: 0.05,
180 posterior_variance: 0.002,
181 e_value: 0.8,
182 e_threshold: 20.0,
183 boundary_score: 0.3,
184 events_since_sample: 2,
185 time_since_sample_ms: 50.0,
186 reason: "blocked_min_interval",
187 }),
188 VoiLogEntry::Observation(VoiObservation {
189 event_idx: 2,
190 sample_idx: 1,
191 violated: false,
192 posterior_mean: 0.06,
193 posterior_variance: 0.002,
194 alpha: 2.0,
195 beta: 18.0,
196 e_value: 1.0,
197 e_threshold: 20.0,
198 }),
199 ];
200 let retrieved = set_and_get_snapshot(snap);
201 assert_eq!(retrieved.recent_logs.len(), 2);
202 clear_inline_auto_voi_snapshot();
203 }
204}