Skip to main content

ftui_runtime/
evidence_telemetry.rs

1#![forbid(unsafe_code)]
2
3//! Evidence telemetry snapshots for runtime explainability overlays.
4//!
5//! These snapshots provide a low-overhead, in-memory view of the most recent
6//! diff, resize, and budget decisions so demo screens can render cockpit
7//! views without parsing JSONL logs.
8
9use std::sync::{LazyLock, RwLock};
10
11use ftui_render::budget::{BudgetDecision, DegradationLevel};
12use ftui_render::diff_strategy::{DiffStrategy, StrategyEvidence};
13
14use crate::bocpd::BocpdEvidence;
15use crate::resize_coalescer::Regime;
16
17/// Snapshot of the most recent diff-strategy decision.
18#[derive(Debug, Clone)]
19pub struct DiffDecisionSnapshot {
20    pub event_idx: u64,
21    pub screen_mode: String,
22    pub cols: u16,
23    pub rows: u16,
24    pub evidence: StrategyEvidence,
25    pub span_count: usize,
26    pub span_coverage_pct: f64,
27    pub max_span_len: usize,
28    pub scan_cost_estimate: usize,
29    pub fallback_reason: String,
30    pub tile_used: bool,
31    pub tile_fallback: String,
32    pub strategy_used: DiffStrategy,
33}
34
35/// Snapshot of the most recent resize/coalescer decision.
36#[derive(Debug, Clone)]
37pub struct ResizeDecisionSnapshot {
38    pub event_idx: u64,
39    pub action: &'static str,
40    pub dt_ms: f64,
41    pub event_rate: f64,
42    pub regime: Regime,
43    pub pending_size: Option<(u16, u16)>,
44    pub applied_size: Option<(u16, u16)>,
45    pub time_since_render_ms: f64,
46    pub bocpd: Option<BocpdEvidence>,
47}
48
49/// Conformal evidence snapshot for budget decisions.
50#[derive(Debug, Clone)]
51pub struct ConformalSnapshot {
52    pub bucket_key: String,
53    pub sample_count: usize,
54    pub upper_us: f64,
55    pub risk: bool,
56}
57
58/// Snapshot of the most recent budget decision.
59#[derive(Debug, Clone)]
60pub struct BudgetDecisionSnapshot {
61    pub frame_idx: u64,
62    pub decision: BudgetDecision,
63    pub controller_decision: BudgetDecision,
64    pub degradation_before: DegradationLevel,
65    pub degradation_after: DegradationLevel,
66    pub frame_time_us: f64,
67    pub budget_us: f64,
68    pub pid_output: f64,
69    pub e_value: f64,
70    pub frames_observed: u32,
71    pub frames_since_change: u32,
72    pub in_warmup: bool,
73    pub conformal: Option<ConformalSnapshot>,
74}
75
76static DIFF_SNAPSHOT: LazyLock<RwLock<Option<DiffDecisionSnapshot>>> =
77    LazyLock::new(|| RwLock::new(None));
78static RESIZE_SNAPSHOT: LazyLock<RwLock<Option<ResizeDecisionSnapshot>>> =
79    LazyLock::new(|| RwLock::new(None));
80static BUDGET_SNAPSHOT: LazyLock<RwLock<Option<BudgetDecisionSnapshot>>> =
81    LazyLock::new(|| RwLock::new(None));
82
83/// Store the latest diff decision snapshot.
84pub fn set_diff_snapshot(snapshot: Option<DiffDecisionSnapshot>) {
85    if let Ok(mut guard) = DIFF_SNAPSHOT.write() {
86        *guard = snapshot;
87    }
88}
89
90/// Fetch the latest diff decision snapshot.
91#[must_use]
92pub fn diff_snapshot() -> Option<DiffDecisionSnapshot> {
93    DIFF_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
94}
95
96/// Clear any stored diff snapshot.
97pub fn clear_diff_snapshot() {
98    set_diff_snapshot(None);
99}
100
101/// Store the latest resize decision snapshot.
102pub fn set_resize_snapshot(snapshot: Option<ResizeDecisionSnapshot>) {
103    if let Ok(mut guard) = RESIZE_SNAPSHOT.write() {
104        *guard = snapshot;
105    }
106}
107
108/// Fetch the latest resize decision snapshot.
109#[must_use]
110pub fn resize_snapshot() -> Option<ResizeDecisionSnapshot> {
111    RESIZE_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
112}
113
114/// Clear any stored resize snapshot.
115pub fn clear_resize_snapshot() {
116    set_resize_snapshot(None);
117}
118
119/// Store the latest budget decision snapshot.
120pub fn set_budget_snapshot(snapshot: Option<BudgetDecisionSnapshot>) {
121    if let Ok(mut guard) = BUDGET_SNAPSHOT.write() {
122        *guard = snapshot;
123    }
124}
125
126/// Fetch the latest budget decision snapshot.
127#[must_use]
128pub fn budget_snapshot() -> Option<BudgetDecisionSnapshot> {
129    BUDGET_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
130}
131
132/// Clear any stored budget snapshot.
133pub fn clear_budget_snapshot() {
134    set_budget_snapshot(None);
135}