kimun_notes/components/text_editor/
widener_metrics.rs1use std::sync::atomic::{AtomicU64, Ordering};
33
34#[derive(Debug, Clone, Copy)]
38pub enum BailReason {
39 LineCountChange,
41 NoDamage,
44 KindGuard,
47 LazyDepth,
49 BlankTransition,
52 CapTrip,
55 VerifyFailed,
58 DownstreamFlip,
67}
68
69#[derive(Debug, Clone, Copy)]
71pub enum SuccessPath {
72 ResetBoundary,
76 WidenToSafe,
80}
81
82pub struct WidenerMetrics {
83 pub incremental_reset: AtomicU64,
84 pub incremental_fallback: AtomicU64,
85 pub full_line_count_change: AtomicU64,
86 pub full_no_damage: AtomicU64,
87 pub full_kind_guard: AtomicU64,
88 pub full_lazy_depth: AtomicU64,
89 pub full_blank_transition: AtomicU64,
90 pub full_cap_trip: AtomicU64,
91 pub full_verify_failed: AtomicU64,
92 pub full_downstream_flip: AtomicU64,
93}
94
95impl WidenerMetrics {
96 const fn new() -> Self {
97 Self {
98 incremental_reset: AtomicU64::new(0),
99 incremental_fallback: AtomicU64::new(0),
100 full_line_count_change: AtomicU64::new(0),
101 full_no_damage: AtomicU64::new(0),
102 full_kind_guard: AtomicU64::new(0),
103 full_lazy_depth: AtomicU64::new(0),
104 full_blank_transition: AtomicU64::new(0),
105 full_cap_trip: AtomicU64::new(0),
106 full_verify_failed: AtomicU64::new(0),
107 full_downstream_flip: AtomicU64::new(0),
108 }
109 }
110
111 pub fn bail<T>(&self, reason: BailReason) -> Option<T> {
114 let counter = match reason {
115 BailReason::LineCountChange => &self.full_line_count_change,
116 BailReason::NoDamage => &self.full_no_damage,
117 BailReason::KindGuard => &self.full_kind_guard,
118 BailReason::LazyDepth => &self.full_lazy_depth,
119 BailReason::BlankTransition => &self.full_blank_transition,
120 BailReason::CapTrip => &self.full_cap_trip,
121 BailReason::VerifyFailed => &self.full_verify_failed,
122 BailReason::DownstreamFlip => &self.full_downstream_flip,
123 };
124 counter.fetch_add(1, Ordering::Relaxed);
125 None
126 }
127
128 pub fn ok(&self, path: SuccessPath) {
130 let counter = match path {
131 SuccessPath::ResetBoundary => &self.incremental_reset,
132 SuccessPath::WidenToSafe => &self.incremental_fallback,
133 };
134 counter.fetch_add(1, Ordering::Relaxed);
135 }
136
137 pub fn snapshot(&self) -> Snapshot {
139 Snapshot {
140 incremental_reset: self.incremental_reset.load(Ordering::Relaxed),
141 incremental_fallback: self.incremental_fallback.load(Ordering::Relaxed),
142 full_line_count_change: self.full_line_count_change.load(Ordering::Relaxed),
143 full_no_damage: self.full_no_damage.load(Ordering::Relaxed),
144 full_kind_guard: self.full_kind_guard.load(Ordering::Relaxed),
145 full_lazy_depth: self.full_lazy_depth.load(Ordering::Relaxed),
146 full_blank_transition: self.full_blank_transition.load(Ordering::Relaxed),
147 full_cap_trip: self.full_cap_trip.load(Ordering::Relaxed),
148 full_verify_failed: self.full_verify_failed.load(Ordering::Relaxed),
149 full_downstream_flip: self.full_downstream_flip.load(Ordering::Relaxed),
150 }
151 }
152}
153
154pub static METRICS: WidenerMetrics = WidenerMetrics::new();
155
156#[derive(Debug, Clone, Copy)]
157pub struct Snapshot {
158 pub incremental_reset: u64,
159 pub incremental_fallback: u64,
160 pub full_line_count_change: u64,
161 pub full_no_damage: u64,
162 pub full_kind_guard: u64,
163 pub full_lazy_depth: u64,
164 pub full_blank_transition: u64,
165 pub full_cap_trip: u64,
166 pub full_verify_failed: u64,
167 pub full_downstream_flip: u64,
168}
169
170impl Snapshot {
171 pub fn attempted(&self) -> u64 {
172 self.incremental_reset
173 + self.incremental_fallback
174 + self.full_line_count_change
175 + self.full_no_damage
176 + self.full_kind_guard
177 + self.full_lazy_depth
178 + self.full_blank_transition
179 + self.full_cap_trip
180 + self.full_verify_failed
181 + self.full_downstream_flip
182 }
183
184 pub fn successful_incremental(&self) -> u64 {
185 self.incremental_reset + self.incremental_fallback
186 }
187
188 pub fn successful_incremental_rate(&self) -> f64 {
189 let denom = self.attempted();
190 if denom == 0 {
191 0.0
192 } else {
193 self.successful_incremental() as f64 / denom as f64
194 }
195 }
196
197 pub fn fast_path_share(&self) -> f64 {
201 let denom = self.successful_incremental();
202 if denom == 0 {
203 0.0
204 } else {
205 self.incremental_reset as f64 / denom as f64
206 }
207 }
208
209 pub fn heuristic_path_share(&self) -> f64 {
210 let denom = self.successful_incremental();
211 if denom == 0 {
212 0.0
213 } else {
214 self.incremental_fallback as f64 / denom as f64
215 }
216 }
217
218 pub fn guard_sprawl_rate(&self) -> f64 {
219 let denom = self.attempted();
220 if denom == 0 {
221 0.0
222 } else {
223 (self.full_kind_guard + self.full_lazy_depth + self.full_blank_transition) as f64
224 / denom as f64
225 }
226 }
227
228 pub fn verify_hit_rate(&self) -> f64 {
229 let denom = self.full_verify_failed + self.incremental_fallback;
236 if denom == 0 {
237 0.0
238 } else {
239 self.full_verify_failed as f64 / denom as f64
240 }
241 }
242}
243
244pub fn dump_if_enabled() {
247 if std::env::var("KIMUN_DUMP_WIDENER_METRICS").as_deref() != Ok("1") {
248 return;
249 }
250 let s = METRICS.snapshot();
251 eprintln!(
252 "[widener-metrics] session totals\n \
253 incremental_reset = {:>10} ({:5.1}%)\n \
254 incremental_fallback = {:>10} ({:5.1}%)\n \
255 full_line_count_change = {:>10} ({:5.1}%)\n \
256 full_no_damage = {:>10} ({:5.1}%)\n \
257 full_kind_guard = {:>10} ({:5.1}%)\n \
258 full_lazy_depth = {:>10} ({:5.1}%)\n \
259 full_blank_transition = {:>10} ({:5.1}%)\n \
260 full_cap_trip = {:>10} ({:5.1}%)\n \
261 full_verify_failed = {:>10} ({:5.1}%)\n \
262 attempted (categorised) = {:>10}\n \
263 ---\n \
264 successful_incremental_rate = {:5.1}%\n \
265 fast_path_share = {:5.1}%\n \
266 heuristic_path_share = {:5.1}%\n \
267 guard_sprawl_rate = {:5.1}%\n \
268 verify_hit_rate = {:5.1}%",
269 s.incremental_reset,
270 pct(s.incremental_reset, s.attempted()),
271 s.incremental_fallback,
272 pct(s.incremental_fallback, s.attempted()),
273 s.full_line_count_change,
274 pct(s.full_line_count_change, s.attempted()),
275 s.full_no_damage,
276 pct(s.full_no_damage, s.attempted()),
277 s.full_kind_guard,
278 pct(s.full_kind_guard, s.attempted()),
279 s.full_lazy_depth,
280 pct(s.full_lazy_depth, s.attempted()),
281 s.full_blank_transition,
282 pct(s.full_blank_transition, s.attempted()),
283 s.full_cap_trip,
284 pct(s.full_cap_trip, s.attempted()),
285 s.full_verify_failed,
286 pct(s.full_verify_failed, s.attempted()),
287 s.attempted(),
288 s.successful_incremental_rate() * 100.0,
289 s.fast_path_share() * 100.0,
290 s.heuristic_path_share() * 100.0,
291 s.guard_sprawl_rate() * 100.0,
292 s.verify_hit_rate() * 100.0,
293 );
294}
295
296fn pct(numer: u64, denom: u64) -> f64 {
297 if denom == 0 {
298 0.0
299 } else {
300 (numer as f64 / denom as f64) * 100.0
301 }
302}