ruvector_sparse_inference/precision/
telemetry.rs

1//! Telemetry and statistics for precision lanes
2//!
3//! Tracks lane usage, transitions, and performance metrics.
4
5use super::lanes::PrecisionLane;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::time::{Duration, Instant};
9
10/// Statistics for a single precision lane
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct LaneStats {
13    /// Total operations in this lane
14    pub operations: u64,
15
16    /// Total time spent in this lane (nanoseconds)
17    pub total_time_ns: u64,
18
19    /// Average operation time (nanoseconds)
20    pub avg_time_ns: u64,
21
22    /// Peak operation time (nanoseconds)
23    pub peak_time_ns: u64,
24
25    /// Total bytes processed
26    pub bytes_processed: u64,
27
28    /// Average active set size
29    pub avg_active_set_size: f32,
30
31    /// Error count
32    pub errors: u64,
33
34    /// Escalations from this lane
35    pub escalations: u64,
36
37    /// Demotions to this lane
38    pub demotions: u64,
39}
40
41impl LaneStats {
42    /// Record a new operation
43    pub fn record_operation(&mut self, duration_ns: u64, bytes: u64, active_set_size: usize) {
44        self.operations += 1;
45        self.total_time_ns += duration_ns;
46        self.bytes_processed += bytes;
47
48        // Update average
49        let ops = self.operations as f32;
50        self.avg_time_ns = (self.total_time_ns / self.operations) as u64;
51        self.avg_active_set_size =
52            (self.avg_active_set_size * (ops - 1.0) + active_set_size as f32) / ops;
53
54        // Update peak
55        if duration_ns > self.peak_time_ns {
56            self.peak_time_ns = duration_ns;
57        }
58    }
59
60    /// Record an error
61    pub fn record_error(&mut self) {
62        self.errors += 1;
63    }
64
65    /// Record an escalation from this lane
66    pub fn record_escalation(&mut self) {
67        self.escalations += 1;
68    }
69
70    /// Record a demotion to this lane
71    pub fn record_demotion(&mut self) {
72        self.demotions += 1;
73    }
74
75    /// Get throughput in bytes per second
76    pub fn throughput_bps(&self) -> f64 {
77        if self.total_time_ns == 0 {
78            return 0.0;
79        }
80        (self.bytes_processed as f64 * 1_000_000_000.0) / self.total_time_ns as f64
81    }
82}
83
84/// Comprehensive telemetry for all precision lanes
85#[derive(Debug, Clone)]
86pub struct LaneTelemetry {
87    /// Per-lane statistics
88    pub lane_stats: HashMap<PrecisionLane, LaneStats>,
89
90    /// Current lane
91    pub current_lane: PrecisionLane,
92
93    /// Total lane transitions
94    pub transitions: u64,
95
96    /// Transition history (recent 100)
97    transition_history: Vec<LaneTransition>,
98
99    /// Start time
100    start_time: Option<Instant>,
101
102    /// Session duration (seconds)
103    pub session_duration_secs: f64,
104}
105
106/// Record of a lane transition
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct LaneTransition {
109    /// Source lane
110    pub from: PrecisionLane,
111
112    /// Destination lane
113    pub to: PrecisionLane,
114
115    /// Reason for transition
116    pub reason: TransitionReason,
117
118    /// Timestamp (seconds since session start)
119    pub timestamp_secs: f64,
120}
121
122/// Reason for lane transition
123#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
124pub enum TransitionReason {
125    /// Novelty threshold exceeded
126    Novelty,
127    /// Drift persisted
128    DriftPersistence,
129    /// Stability returned
130    StabilityReturned,
131    /// Velocity stalled
132    VelocityStalled,
133    /// Active set shrunk
134    ActiveSetShrunk,
135    /// Manual override
136    Manual,
137    /// Initialization
138    Init,
139}
140
141impl LaneTelemetry {
142    /// Create new telemetry tracker
143    pub fn new(initial_lane: PrecisionLane) -> Self {
144        let mut lane_stats = HashMap::new();
145        lane_stats.insert(PrecisionLane::Bit3, LaneStats::default());
146        lane_stats.insert(PrecisionLane::Bit5, LaneStats::default());
147        lane_stats.insert(PrecisionLane::Bit7, LaneStats::default());
148        lane_stats.insert(PrecisionLane::Float32, LaneStats::default());
149
150        Self {
151            lane_stats,
152            current_lane: initial_lane,
153            transitions: 0,
154            transition_history: Vec::with_capacity(100),
155            start_time: Some(Instant::now()),
156            session_duration_secs: 0.0,
157        }
158    }
159
160    /// Start a new session
161    pub fn start_session(&mut self) {
162        self.start_time = Some(Instant::now());
163    }
164
165    /// Record an operation in the current lane
166    pub fn record_operation(&mut self, duration: Duration, bytes: u64, active_set_size: usize) {
167        let duration_ns = duration.as_nanos() as u64;
168
169        if let Some(stats) = self.lane_stats.get_mut(&self.current_lane) {
170            stats.record_operation(duration_ns, bytes, active_set_size);
171        }
172
173        // Update session duration
174        if let Some(start) = self.start_time {
175            self.session_duration_secs = start.elapsed().as_secs_f64();
176        }
177    }
178
179    /// Record a lane transition
180    pub fn record_transition(&mut self, from: PrecisionLane, to: PrecisionLane, reason: TransitionReason) {
181        self.transitions += 1;
182        self.current_lane = to;
183
184        // Record escalation/demotion in stats
185        if to.bits() > from.bits() {
186            if let Some(stats) = self.lane_stats.get_mut(&from) {
187                stats.record_escalation();
188            }
189        } else {
190            if let Some(stats) = self.lane_stats.get_mut(&to) {
191                stats.record_demotion();
192            }
193        }
194
195        // Add to history
196        let timestamp_secs = self.start_time
197            .map(|s| s.elapsed().as_secs_f64())
198            .unwrap_or(0.0);
199
200        let transition = LaneTransition {
201            from,
202            to,
203            reason,
204            timestamp_secs,
205        };
206
207        if self.transition_history.len() >= 100 {
208            self.transition_history.remove(0);
209        }
210        self.transition_history.push(transition);
211    }
212
213    /// Record an error in the current lane
214    pub fn record_error(&mut self) {
215        if let Some(stats) = self.lane_stats.get_mut(&self.current_lane) {
216            stats.record_error();
217        }
218    }
219
220    /// Get statistics for a specific lane
221    pub fn get_lane_stats(&self, lane: PrecisionLane) -> Option<&LaneStats> {
222        self.lane_stats.get(&lane)
223    }
224
225    /// Get total operations across all lanes
226    pub fn total_operations(&self) -> u64 {
227        self.lane_stats.values().map(|s| s.operations).sum()
228    }
229
230    /// Get total errors across all lanes
231    pub fn total_errors(&self) -> u64 {
232        self.lane_stats.values().map(|s| s.errors).sum()
233    }
234
235    /// Get lane usage distribution (percentage)
236    pub fn lane_distribution(&self) -> HashMap<PrecisionLane, f32> {
237        let total = self.total_operations() as f32;
238        if total == 0.0 {
239            return HashMap::new();
240        }
241
242        self.lane_stats
243            .iter()
244            .map(|(lane, stats)| (*lane, (stats.operations as f32 / total) * 100.0))
245            .collect()
246    }
247
248    /// Get transition history
249    pub fn transition_history(&self) -> &[LaneTransition] {
250        &self.transition_history
251    }
252
253    /// Generate summary report
254    pub fn summary_report(&self) -> TelemetrySummary {
255        TelemetrySummary {
256            session_duration_secs: self.session_duration_secs,
257            total_operations: self.total_operations(),
258            total_transitions: self.transitions,
259            total_errors: self.total_errors(),
260            lane_distribution: self.lane_distribution(),
261            avg_operations_per_sec: if self.session_duration_secs > 0.0 {
262                self.total_operations() as f64 / self.session_duration_secs
263            } else {
264                0.0
265            },
266            current_lane: self.current_lane,
267        }
268    }
269}
270
271/// Summary of telemetry data
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct TelemetrySummary {
274    pub session_duration_secs: f64,
275    pub total_operations: u64,
276    pub total_transitions: u64,
277    pub total_errors: u64,
278    pub lane_distribution: HashMap<PrecisionLane, f32>,
279    pub avg_operations_per_sec: f64,
280    pub current_lane: PrecisionLane,
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_lane_stats_recording() {
289        let mut stats = LaneStats::default();
290
291        stats.record_operation(1000, 64, 100);
292        stats.record_operation(2000, 64, 100);
293
294        assert_eq!(stats.operations, 2);
295        assert_eq!(stats.total_time_ns, 3000);
296        assert_eq!(stats.avg_time_ns, 1500);
297        assert_eq!(stats.bytes_processed, 128);
298    }
299
300    #[test]
301    fn test_telemetry_transitions() {
302        let mut telemetry = LaneTelemetry::new(PrecisionLane::Bit5);
303
304        telemetry.record_transition(
305            PrecisionLane::Bit5,
306            PrecisionLane::Bit7,
307            TransitionReason::Novelty,
308        );
309
310        assert_eq!(telemetry.transitions, 1);
311        assert_eq!(telemetry.current_lane, PrecisionLane::Bit7);
312        assert_eq!(telemetry.transition_history.len(), 1);
313    }
314
315    #[test]
316    fn test_lane_distribution() {
317        let mut telemetry = LaneTelemetry::new(PrecisionLane::Bit5);
318
319        // Simulate operations in different lanes
320        for _ in 0..30 {
321            telemetry.current_lane = PrecisionLane::Bit3;
322            telemetry.record_operation(Duration::from_nanos(100), 8, 10);
323        }
324        for _ in 0..50 {
325            telemetry.current_lane = PrecisionLane::Bit5;
326            telemetry.record_operation(Duration::from_nanos(200), 16, 50);
327        }
328        for _ in 0..20 {
329            telemetry.current_lane = PrecisionLane::Bit7;
330            telemetry.record_operation(Duration::from_nanos(500), 32, 100);
331        }
332
333        let distribution = telemetry.lane_distribution();
334
335        assert!((distribution[&PrecisionLane::Bit3] - 30.0).abs() < 0.1);
336        assert!((distribution[&PrecisionLane::Bit5] - 50.0).abs() < 0.1);
337        assert!((distribution[&PrecisionLane::Bit7] - 20.0).abs() < 0.1);
338    }
339}