ftui_runtime/
evidence_telemetry.rs1#![forbid(unsafe_code)]
2
3use 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#[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#[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#[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#[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
83pub fn set_diff_snapshot(snapshot: Option<DiffDecisionSnapshot>) {
85 if let Ok(mut guard) = DIFF_SNAPSHOT.write() {
86 *guard = snapshot;
87 }
88}
89
90#[must_use]
92pub fn diff_snapshot() -> Option<DiffDecisionSnapshot> {
93 DIFF_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
94}
95
96pub fn clear_diff_snapshot() {
98 set_diff_snapshot(None);
99}
100
101pub fn set_resize_snapshot(snapshot: Option<ResizeDecisionSnapshot>) {
103 if let Ok(mut guard) = RESIZE_SNAPSHOT.write() {
104 *guard = snapshot;
105 }
106}
107
108#[must_use]
110pub fn resize_snapshot() -> Option<ResizeDecisionSnapshot> {
111 RESIZE_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
112}
113
114pub fn clear_resize_snapshot() {
116 set_resize_snapshot(None);
117}
118
119pub fn set_budget_snapshot(snapshot: Option<BudgetDecisionSnapshot>) {
121 if let Ok(mut guard) = BUDGET_SNAPSHOT.write() {
122 *guard = snapshot;
123 }
124}
125
126#[must_use]
128pub fn budget_snapshot() -> Option<BudgetDecisionSnapshot> {
129 BUDGET_SNAPSHOT.read().ok().and_then(|guard| guard.clone())
130}
131
132pub fn clear_budget_snapshot() {
134 set_budget_snapshot(None);
135}