kimun_notes/components/text_editor/
widener_metrics.rs1use std::sync::atomic::{AtomicU64, Ordering};
32
33#[derive(Debug, Clone, Copy)]
37pub enum BailReason {
38 LineCountChange,
40 NoDamage,
43 KindGuard,
46 LazyDepth,
48 BlankTransition,
51 CapTrip,
54 VerifyFailed,
57}
58
59#[derive(Debug, Clone, Copy)]
61pub enum SuccessPath {
62 ResetBoundary,
66 WidenToSafe,
70}
71
72pub struct WidenerMetrics {
73 pub incremental_reset: AtomicU64,
74 pub incremental_fallback: AtomicU64,
75 pub full_line_count_change: AtomicU64,
76 pub full_no_damage: AtomicU64,
77 pub full_kind_guard: AtomicU64,
78 pub full_lazy_depth: AtomicU64,
79 pub full_blank_transition: AtomicU64,
80 pub full_cap_trip: AtomicU64,
81 pub full_verify_failed: AtomicU64,
82}
83
84impl WidenerMetrics {
85 const fn new() -> Self {
86 Self {
87 incremental_reset: AtomicU64::new(0),
88 incremental_fallback: AtomicU64::new(0),
89 full_line_count_change: AtomicU64::new(0),
90 full_no_damage: AtomicU64::new(0),
91 full_kind_guard: AtomicU64::new(0),
92 full_lazy_depth: AtomicU64::new(0),
93 full_blank_transition: AtomicU64::new(0),
94 full_cap_trip: AtomicU64::new(0),
95 full_verify_failed: AtomicU64::new(0),
96 }
97 }
98
99 pub fn bail<T>(&self, reason: BailReason) -> Option<T> {
102 let counter = match reason {
103 BailReason::LineCountChange => &self.full_line_count_change,
104 BailReason::NoDamage => &self.full_no_damage,
105 BailReason::KindGuard => &self.full_kind_guard,
106 BailReason::LazyDepth => &self.full_lazy_depth,
107 BailReason::BlankTransition => &self.full_blank_transition,
108 BailReason::CapTrip => &self.full_cap_trip,
109 BailReason::VerifyFailed => &self.full_verify_failed,
110 };
111 counter.fetch_add(1, Ordering::Relaxed);
112 None
113 }
114
115 pub fn ok(&self, path: SuccessPath) {
117 let counter = match path {
118 SuccessPath::ResetBoundary => &self.incremental_reset,
119 SuccessPath::WidenToSafe => &self.incremental_fallback,
120 };
121 counter.fetch_add(1, Ordering::Relaxed);
122 }
123
124 pub fn snapshot(&self) -> Snapshot {
126 Snapshot {
127 incremental_reset: self.incremental_reset.load(Ordering::Relaxed),
128 incremental_fallback: self.incremental_fallback.load(Ordering::Relaxed),
129 full_line_count_change: self.full_line_count_change.load(Ordering::Relaxed),
130 full_no_damage: self.full_no_damage.load(Ordering::Relaxed),
131 full_kind_guard: self.full_kind_guard.load(Ordering::Relaxed),
132 full_lazy_depth: self.full_lazy_depth.load(Ordering::Relaxed),
133 full_blank_transition: self.full_blank_transition.load(Ordering::Relaxed),
134 full_cap_trip: self.full_cap_trip.load(Ordering::Relaxed),
135 full_verify_failed: self.full_verify_failed.load(Ordering::Relaxed),
136 }
137 }
138}
139
140pub static METRICS: WidenerMetrics = WidenerMetrics::new();
141
142#[derive(Debug, Clone, Copy)]
143pub struct Snapshot {
144 pub incremental_reset: u64,
145 pub incremental_fallback: u64,
146 pub full_line_count_change: u64,
147 pub full_no_damage: u64,
148 pub full_kind_guard: u64,
149 pub full_lazy_depth: u64,
150 pub full_blank_transition: u64,
151 pub full_cap_trip: u64,
152 pub full_verify_failed: u64,
153}
154
155impl Snapshot {
156 pub fn attempted(&self) -> u64 {
157 self.incremental_reset
158 + self.incremental_fallback
159 + self.full_line_count_change
160 + self.full_no_damage
161 + self.full_kind_guard
162 + self.full_lazy_depth
163 + self.full_blank_transition
164 + self.full_cap_trip
165 + self.full_verify_failed
166 }
167
168 pub fn successful_incremental(&self) -> u64 {
169 self.incremental_reset + self.incremental_fallback
170 }
171
172 pub fn successful_incremental_rate(&self) -> f64 {
173 let denom = self.attempted();
174 if denom == 0 {
175 0.0
176 } else {
177 self.successful_incremental() as f64 / denom as f64
178 }
179 }
180
181 pub fn fast_path_share(&self) -> f64 {
185 let denom = self.successful_incremental();
186 if denom == 0 {
187 0.0
188 } else {
189 self.incremental_reset as f64 / denom as f64
190 }
191 }
192
193 pub fn heuristic_path_share(&self) -> f64 {
194 let denom = self.successful_incremental();
195 if denom == 0 {
196 0.0
197 } else {
198 self.incremental_fallback as f64 / denom as f64
199 }
200 }
201
202 pub fn guard_sprawl_rate(&self) -> f64 {
203 let denom = self.attempted();
204 if denom == 0 {
205 0.0
206 } else {
207 (self.full_kind_guard + self.full_lazy_depth + self.full_blank_transition) as f64
208 / denom as f64
209 }
210 }
211
212 pub fn verify_hit_rate(&self) -> f64 {
213 let denom = self.full_verify_failed + self.incremental_fallback;
216 if denom == 0 {
217 0.0
218 } else {
219 self.full_verify_failed as f64 / denom as f64
220 }
221 }
222}
223
224pub fn dump_if_enabled() {
227 if std::env::var("KIMUN_DUMP_WIDENER_METRICS").as_deref() != Ok("1") {
228 return;
229 }
230 let s = METRICS.snapshot();
231 eprintln!(
232 "[widener-metrics] session totals\n \
233 incremental_reset = {:>10} ({:5.1}%)\n \
234 incremental_fallback = {:>10} ({:5.1}%)\n \
235 full_line_count_change = {:>10} ({:5.1}%)\n \
236 full_no_damage = {:>10} ({:5.1}%)\n \
237 full_kind_guard = {:>10} ({:5.1}%)\n \
238 full_lazy_depth = {:>10} ({:5.1}%)\n \
239 full_blank_transition = {:>10} ({:5.1}%)\n \
240 full_cap_trip = {:>10} ({:5.1}%)\n \
241 full_verify_failed = {:>10} ({:5.1}%)\n \
242 attempted (categorised) = {:>10}\n \
243 ---\n \
244 successful_incremental_rate = {:5.1}%\n \
245 fast_path_share = {:5.1}%\n \
246 heuristic_path_share = {:5.1}%\n \
247 guard_sprawl_rate = {:5.1}%\n \
248 verify_hit_rate = {:5.1}%",
249 s.incremental_reset,
250 pct(s.incremental_reset, s.attempted()),
251 s.incremental_fallback,
252 pct(s.incremental_fallback, s.attempted()),
253 s.full_line_count_change,
254 pct(s.full_line_count_change, s.attempted()),
255 s.full_no_damage,
256 pct(s.full_no_damage, s.attempted()),
257 s.full_kind_guard,
258 pct(s.full_kind_guard, s.attempted()),
259 s.full_lazy_depth,
260 pct(s.full_lazy_depth, s.attempted()),
261 s.full_blank_transition,
262 pct(s.full_blank_transition, s.attempted()),
263 s.full_cap_trip,
264 pct(s.full_cap_trip, s.attempted()),
265 s.full_verify_failed,
266 pct(s.full_verify_failed, s.attempted()),
267 s.attempted(),
268 s.successful_incremental_rate() * 100.0,
269 s.fast_path_share() * 100.0,
270 s.heuristic_path_share() * 100.0,
271 s.guard_sprawl_rate() * 100.0,
272 s.verify_hit_rate() * 100.0,
273 );
274}
275
276fn pct(numer: u64, denom: u64) -> f64 {
277 if denom == 0 {
278 0.0
279 } else {
280 (numer as f64 / denom as f64) * 100.0
281 }
282}