Skip to main content

icydb_core/metrics/
state.rs

1//! Module: metrics::state
2//! Responsibility: mutable runtime metrics state and outward report DTOs.
3//! Does not own: instrumentation call sites or sink routing.
4//! Boundary: in-memory metrics state behind the crate-level sink/report surface.
5//!
6//! Runtime metrics are update-only by contract.
7//! Query-side instrumentation is intentionally not surfaced by `report`, so
8//! query metrics are non-existent by design under IC query semantics.
9
10use candid::CandidType;
11use canic_cdk::utils::time::now_millis;
12use serde::Deserialize;
13use std::{cell::RefCell, collections::BTreeMap};
14
15#[derive(Clone, Debug)]
16pub(crate) struct EventState {
17    pub(crate) ops: EventOps,
18    pub(crate) perf: EventPerf,
19    pub(crate) entities: BTreeMap<String, EntityCounters>,
20    pub(crate) window_start_ms: u64,
21}
22
23impl Default for EventState {
24    fn default() -> Self {
25        Self {
26            ops: EventOps::default(),
27            perf: EventPerf::default(),
28            entities: BTreeMap::new(),
29            window_start_ms: now_millis(),
30        }
31    }
32}
33
34///
35/// MetricRatio
36///
37/// MetricRatio carries a derived metric as an exact raw numerator and
38/// denominator pair. Callers can choose their own decimal rendering policy
39/// without losing precision inside the canister metrics layer.
40///
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct MetricRatio {
43    numerator: u64,
44    denominator: u64,
45}
46
47impl MetricRatio {
48    /// Returns the ratio numerator.
49    #[must_use]
50    pub const fn numerator(&self) -> u64 {
51        self.numerator
52    }
53
54    /// Returns the ratio denominator.
55    #[must_use]
56    pub const fn denominator(&self) -> u64 {
57        self.denominator
58    }
59
60    /// Returns the raw ratio pair.
61    #[must_use]
62    pub const fn into_parts(self) -> (u64, u64) {
63        (self.numerator, self.denominator)
64    }
65}
66
67// Convert raw counter pairs into optional ratio values without encoding a
68// sentinel for "no activity". Consumers can distinguish absent denominators
69// from legitimate zero-valued work.
70const fn ratio(numerator: u64, denominator: u64) -> Option<MetricRatio> {
71    if denominator == 0 {
72        return None;
73    }
74
75    Some(MetricRatio {
76        numerator,
77        denominator,
78    })
79}
80
81#[cfg_attr(doc, doc = "EventOps\n\nOperation counters.")]
82#[derive(CandidType, Clone, Debug, Default, Deserialize)]
83pub struct EventOps {
84    // Executor entrypoints
85    pub(crate) load_calls: u64,
86    pub(crate) save_calls: u64,
87    pub(crate) delete_calls: u64,
88    pub(crate) save_insert_calls: u64,
89    pub(crate) save_update_calls: u64,
90    pub(crate) save_replace_calls: u64,
91    pub(crate) exec_success: u64,
92    pub(crate) exec_error_corruption: u64,
93    pub(crate) exec_error_incompatible_persisted_format: u64,
94    pub(crate) exec_error_not_found: u64,
95    pub(crate) exec_error_internal: u64,
96    pub(crate) exec_error_conflict: u64,
97    pub(crate) exec_error_unsupported: u64,
98    pub(crate) exec_error_invariant_violation: u64,
99    pub(crate) exec_aborted: u64,
100    pub(crate) cache_shared_query_plan_hits: u64,
101    pub(crate) cache_shared_query_plan_misses: u64,
102    pub(crate) cache_shared_query_plan_inserts: u64,
103    pub(crate) cache_shared_query_plan_entries: u64,
104    pub(crate) cache_shared_query_plan_miss_cold: u64,
105    pub(crate) cache_shared_query_plan_miss_distinct_key: u64,
106    pub(crate) cache_shared_query_plan_miss_method_version: u64,
107    pub(crate) cache_shared_query_plan_miss_schema_fingerprint: u64,
108    pub(crate) cache_shared_query_plan_miss_visibility: u64,
109    pub(crate) cache_sql_compiled_command_hits: u64,
110    pub(crate) cache_sql_compiled_command_misses: u64,
111    pub(crate) cache_sql_compiled_command_inserts: u64,
112    pub(crate) cache_sql_compiled_command_entries: u64,
113    pub(crate) cache_sql_compiled_command_miss_cold: u64,
114    pub(crate) cache_sql_compiled_command_miss_distinct_key: u64,
115    pub(crate) cache_sql_compiled_command_miss_method_version: u64,
116    pub(crate) cache_sql_compiled_command_miss_schema_fingerprint: u64,
117    pub(crate) cache_sql_compiled_command_miss_surface: u64,
118    pub(crate) schema_reconcile_checks: u64,
119    pub(crate) schema_reconcile_exact_match: u64,
120    pub(crate) schema_reconcile_first_create: u64,
121    pub(crate) schema_reconcile_latest_snapshot_corrupt: u64,
122    pub(crate) schema_reconcile_rejected_field_slot: u64,
123    pub(crate) schema_reconcile_rejected_other: u64,
124    pub(crate) schema_reconcile_rejected_row_layout: u64,
125    pub(crate) schema_reconcile_rejected_schema_version: u64,
126    pub(crate) schema_reconcile_store_write_error: u64,
127    pub(crate) schema_store_snapshots: u64,
128    pub(crate) schema_store_encoded_bytes: u64,
129    pub(crate) schema_store_latest_snapshot_bytes: u64,
130    pub(crate) sql_compile_rejects: u64,
131    pub(crate) sql_compile_reject_cache_key: u64,
132    pub(crate) sql_compile_reject_parse: u64,
133    pub(crate) sql_compile_reject_semantic: u64,
134
135    // Planner kinds
136    pub(crate) plan_index: u64,
137    pub(crate) plan_keys: u64,
138    pub(crate) plan_range: u64,
139    pub(crate) plan_full_scan: u64,
140    pub(crate) plan_by_key: u64,
141    pub(crate) plan_by_keys: u64,
142    pub(crate) plan_key_range: u64,
143    pub(crate) plan_index_prefix: u64,
144    pub(crate) plan_index_multi_lookup: u64,
145    pub(crate) plan_index_range: u64,
146    pub(crate) plan_explicit_full_scan: u64,
147    pub(crate) plan_union: u64,
148    pub(crate) plan_intersection: u64,
149    pub(crate) plan_grouped_hash_materialized: u64,
150    pub(crate) plan_grouped_ordered_materialized: u64,
151    pub(crate) plan_choice_conflicting_primary_key_children_access_preferred: u64,
152    pub(crate) plan_choice_constant_false_predicate: u64,
153    pub(crate) plan_choice_empty_child_access_preferred: u64,
154    pub(crate) plan_choice_full_scan_access: u64,
155    pub(crate) plan_choice_intent_key_access_override: u64,
156    pub(crate) plan_choice_limit_zero_window: u64,
157    pub(crate) plan_choice_non_index_access: u64,
158    pub(crate) plan_choice_planner_composite_non_index: u64,
159    pub(crate) plan_choice_planner_full_scan_fallback: u64,
160    pub(crate) plan_choice_planner_key_set_access: u64,
161    pub(crate) plan_choice_planner_primary_key_lookup: u64,
162    pub(crate) plan_choice_planner_primary_key_range: u64,
163    pub(crate) plan_choice_required_order_primary_key_range_preferred: u64,
164    pub(crate) plan_choice_singleton_primary_key_child_access_preferred: u64,
165    pub(crate) prepared_shape_already_finalized: u64,
166    pub(crate) prepared_shape_generated_fallback: u64,
167
168    // Rows touched
169    pub(crate) rows_loaded: u64,
170    pub(crate) rows_saved: u64,
171    pub(crate) rows_inserted: u64,
172    pub(crate) rows_updated: u64,
173    pub(crate) rows_replaced: u64,
174    pub(crate) rows_scanned: u64,
175    pub(crate) rows_filtered: u64,
176    pub(crate) rows_aggregated: u64,
177    pub(crate) rows_emitted: u64,
178    pub(crate) load_candidate_rows_scanned: u64,
179    pub(crate) load_candidate_rows_filtered: u64,
180    pub(crate) load_result_rows_emitted: u64,
181    pub(crate) rows_deleted: u64,
182    pub(crate) sql_insert_calls: u64,
183    pub(crate) sql_insert_select_calls: u64,
184    pub(crate) sql_update_calls: u64,
185    pub(crate) sql_delete_calls: u64,
186    pub(crate) sql_write_matched_rows: u64,
187    pub(crate) sql_write_mutated_rows: u64,
188    pub(crate) sql_write_returning_rows: u64,
189    pub(crate) sql_write_error_insert: u64,
190    pub(crate) sql_write_error_insert_select: u64,
191    pub(crate) sql_write_error_update: u64,
192    pub(crate) sql_write_error_delete: u64,
193    pub(crate) sql_write_error_corruption: u64,
194    pub(crate) sql_write_error_incompatible_persisted_format: u64,
195    pub(crate) sql_write_error_not_found: u64,
196    pub(crate) sql_write_error_internal: u64,
197    pub(crate) sql_write_error_conflict: u64,
198    pub(crate) sql_write_error_unsupported: u64,
199    pub(crate) sql_write_error_invariant_violation: u64,
200
201    // Index maintenance
202    pub(crate) index_inserts: u64,
203    pub(crate) index_removes: u64,
204    pub(crate) reverse_index_inserts: u64,
205    pub(crate) reverse_index_removes: u64,
206    pub(crate) relation_reverse_lookups: u64,
207    pub(crate) relation_delete_blocks: u64,
208    pub(crate) write_rows_touched: u64,
209    pub(crate) write_index_entries_changed: u64,
210    pub(crate) write_reverse_index_entries_changed: u64,
211    pub(crate) write_relation_checks: u64,
212    pub(crate) unique_violations: u64,
213    pub(crate) non_atomic_partial_commits: u64,
214    pub(crate) non_atomic_partial_rows_committed: u64,
215}
216
217impl EventOps {
218    #[must_use]
219    pub const fn load_calls(&self) -> u64 {
220        self.load_calls
221    }
222
223    #[must_use]
224    pub const fn save_calls(&self) -> u64 {
225        self.save_calls
226    }
227
228    #[must_use]
229    pub const fn delete_calls(&self) -> u64 {
230        self.delete_calls
231    }
232
233    #[must_use]
234    pub const fn save_insert_calls(&self) -> u64 {
235        self.save_insert_calls
236    }
237
238    #[must_use]
239    pub const fn save_update_calls(&self) -> u64 {
240        self.save_update_calls
241    }
242
243    #[must_use]
244    pub const fn save_replace_calls(&self) -> u64 {
245        self.save_replace_calls
246    }
247
248    #[must_use]
249    pub const fn exec_success(&self) -> u64 {
250        self.exec_success
251    }
252
253    #[must_use]
254    pub const fn exec_error_corruption(&self) -> u64 {
255        self.exec_error_corruption
256    }
257
258    #[must_use]
259    pub const fn exec_error_incompatible_persisted_format(&self) -> u64 {
260        self.exec_error_incompatible_persisted_format
261    }
262
263    #[must_use]
264    pub const fn exec_error_not_found(&self) -> u64 {
265        self.exec_error_not_found
266    }
267
268    #[must_use]
269    pub const fn exec_error_internal(&self) -> u64 {
270        self.exec_error_internal
271    }
272
273    #[must_use]
274    pub const fn exec_error_conflict(&self) -> u64 {
275        self.exec_error_conflict
276    }
277
278    #[must_use]
279    pub const fn exec_error_unsupported(&self) -> u64 {
280        self.exec_error_unsupported
281    }
282
283    #[must_use]
284    pub const fn exec_error_invariant_violation(&self) -> u64 {
285        self.exec_error_invariant_violation
286    }
287
288    #[must_use]
289    pub const fn exec_aborted(&self) -> u64 {
290        self.exec_aborted
291    }
292
293    #[must_use]
294    pub const fn cache_shared_query_plan_hits(&self) -> u64 {
295        self.cache_shared_query_plan_hits
296    }
297
298    #[must_use]
299    pub const fn cache_shared_query_plan_misses(&self) -> u64 {
300        self.cache_shared_query_plan_misses
301    }
302
303    #[must_use]
304    pub const fn cache_shared_query_plan_inserts(&self) -> u64 {
305        self.cache_shared_query_plan_inserts
306    }
307
308    #[must_use]
309    pub const fn cache_shared_query_plan_entries(&self) -> u64 {
310        self.cache_shared_query_plan_entries
311    }
312
313    #[must_use]
314    pub const fn cache_shared_query_plan_miss_cold(&self) -> u64 {
315        self.cache_shared_query_plan_miss_cold
316    }
317
318    #[must_use]
319    pub const fn cache_shared_query_plan_miss_distinct_key(&self) -> u64 {
320        self.cache_shared_query_plan_miss_distinct_key
321    }
322
323    #[must_use]
324    pub const fn cache_shared_query_plan_miss_method_version(&self) -> u64 {
325        self.cache_shared_query_plan_miss_method_version
326    }
327
328    #[must_use]
329    pub const fn cache_shared_query_plan_miss_schema_fingerprint(&self) -> u64 {
330        self.cache_shared_query_plan_miss_schema_fingerprint
331    }
332
333    #[must_use]
334    pub const fn cache_shared_query_plan_miss_visibility(&self) -> u64 {
335        self.cache_shared_query_plan_miss_visibility
336    }
337
338    #[must_use]
339    pub const fn cache_sql_compiled_command_hits(&self) -> u64 {
340        self.cache_sql_compiled_command_hits
341    }
342
343    #[must_use]
344    pub const fn cache_sql_compiled_command_misses(&self) -> u64 {
345        self.cache_sql_compiled_command_misses
346    }
347
348    #[must_use]
349    pub const fn cache_sql_compiled_command_inserts(&self) -> u64 {
350        self.cache_sql_compiled_command_inserts
351    }
352
353    #[must_use]
354    pub const fn cache_sql_compiled_command_entries(&self) -> u64 {
355        self.cache_sql_compiled_command_entries
356    }
357
358    #[must_use]
359    pub const fn cache_sql_compiled_command_miss_cold(&self) -> u64 {
360        self.cache_sql_compiled_command_miss_cold
361    }
362
363    #[must_use]
364    pub const fn cache_sql_compiled_command_miss_distinct_key(&self) -> u64 {
365        self.cache_sql_compiled_command_miss_distinct_key
366    }
367
368    #[must_use]
369    pub const fn cache_sql_compiled_command_miss_method_version(&self) -> u64 {
370        self.cache_sql_compiled_command_miss_method_version
371    }
372
373    #[must_use]
374    pub const fn cache_sql_compiled_command_miss_schema_fingerprint(&self) -> u64 {
375        self.cache_sql_compiled_command_miss_schema_fingerprint
376    }
377
378    #[must_use]
379    pub const fn cache_sql_compiled_command_miss_surface(&self) -> u64 {
380        self.cache_sql_compiled_command_miss_surface
381    }
382
383    #[must_use]
384    pub const fn schema_reconcile_checks(&self) -> u64 {
385        self.schema_reconcile_checks
386    }
387
388    #[must_use]
389    pub const fn schema_reconcile_exact_match(&self) -> u64 {
390        self.schema_reconcile_exact_match
391    }
392
393    #[must_use]
394    pub const fn schema_reconcile_first_create(&self) -> u64 {
395        self.schema_reconcile_first_create
396    }
397
398    #[must_use]
399    pub const fn schema_reconcile_latest_snapshot_corrupt(&self) -> u64 {
400        self.schema_reconcile_latest_snapshot_corrupt
401    }
402
403    #[must_use]
404    pub const fn schema_reconcile_rejected_field_slot(&self) -> u64 {
405        self.schema_reconcile_rejected_field_slot
406    }
407
408    #[must_use]
409    pub const fn schema_reconcile_rejected_other(&self) -> u64 {
410        self.schema_reconcile_rejected_other
411    }
412
413    #[must_use]
414    pub const fn schema_reconcile_rejected_row_layout(&self) -> u64 {
415        self.schema_reconcile_rejected_row_layout
416    }
417
418    #[must_use]
419    pub const fn schema_reconcile_rejected_schema_version(&self) -> u64 {
420        self.schema_reconcile_rejected_schema_version
421    }
422
423    #[must_use]
424    pub const fn schema_reconcile_store_write_error(&self) -> u64 {
425        self.schema_reconcile_store_write_error
426    }
427
428    #[must_use]
429    pub const fn schema_store_snapshots(&self) -> u64 {
430        self.schema_store_snapshots
431    }
432
433    #[must_use]
434    pub const fn schema_store_encoded_bytes(&self) -> u64 {
435        self.schema_store_encoded_bytes
436    }
437
438    #[must_use]
439    pub const fn schema_store_latest_snapshot_bytes(&self) -> u64 {
440        self.schema_store_latest_snapshot_bytes
441    }
442
443    #[must_use]
444    pub const fn sql_compile_rejects(&self) -> u64 {
445        self.sql_compile_rejects
446    }
447
448    #[must_use]
449    pub const fn sql_compile_reject_cache_key(&self) -> u64 {
450        self.sql_compile_reject_cache_key
451    }
452
453    #[must_use]
454    pub const fn sql_compile_reject_parse(&self) -> u64 {
455        self.sql_compile_reject_parse
456    }
457
458    #[must_use]
459    pub const fn sql_compile_reject_semantic(&self) -> u64 {
460        self.sql_compile_reject_semantic
461    }
462
463    #[must_use]
464    pub const fn plan_index(&self) -> u64 {
465        self.plan_index
466    }
467
468    #[must_use]
469    pub const fn plan_keys(&self) -> u64 {
470        self.plan_keys
471    }
472
473    #[must_use]
474    pub const fn plan_range(&self) -> u64 {
475        self.plan_range
476    }
477
478    #[must_use]
479    pub const fn plan_full_scan(&self) -> u64 {
480        self.plan_full_scan
481    }
482
483    #[must_use]
484    pub const fn plan_by_key(&self) -> u64 {
485        self.plan_by_key
486    }
487
488    #[must_use]
489    pub const fn plan_by_keys(&self) -> u64 {
490        self.plan_by_keys
491    }
492
493    #[must_use]
494    pub const fn plan_key_range(&self) -> u64 {
495        self.plan_key_range
496    }
497
498    #[must_use]
499    pub const fn plan_index_prefix(&self) -> u64 {
500        self.plan_index_prefix
501    }
502
503    #[must_use]
504    pub const fn plan_index_multi_lookup(&self) -> u64 {
505        self.plan_index_multi_lookup
506    }
507
508    #[must_use]
509    pub const fn plan_index_range(&self) -> u64 {
510        self.plan_index_range
511    }
512
513    #[must_use]
514    pub const fn plan_explicit_full_scan(&self) -> u64 {
515        self.plan_explicit_full_scan
516    }
517
518    #[must_use]
519    pub const fn plan_union(&self) -> u64 {
520        self.plan_union
521    }
522
523    #[must_use]
524    pub const fn plan_intersection(&self) -> u64 {
525        self.plan_intersection
526    }
527
528    #[must_use]
529    pub const fn plan_grouped_hash_materialized(&self) -> u64 {
530        self.plan_grouped_hash_materialized
531    }
532
533    #[must_use]
534    pub const fn plan_grouped_ordered_materialized(&self) -> u64 {
535        self.plan_grouped_ordered_materialized
536    }
537
538    #[must_use]
539    pub const fn plan_choice_conflicting_primary_key_children_access_preferred(&self) -> u64 {
540        self.plan_choice_conflicting_primary_key_children_access_preferred
541    }
542
543    #[must_use]
544    pub const fn plan_choice_constant_false_predicate(&self) -> u64 {
545        self.plan_choice_constant_false_predicate
546    }
547
548    #[must_use]
549    pub const fn plan_choice_empty_child_access_preferred(&self) -> u64 {
550        self.plan_choice_empty_child_access_preferred
551    }
552
553    #[must_use]
554    pub const fn plan_choice_full_scan_access(&self) -> u64 {
555        self.plan_choice_full_scan_access
556    }
557
558    #[must_use]
559    pub const fn plan_choice_intent_key_access_override(&self) -> u64 {
560        self.plan_choice_intent_key_access_override
561    }
562
563    #[must_use]
564    pub const fn plan_choice_limit_zero_window(&self) -> u64 {
565        self.plan_choice_limit_zero_window
566    }
567
568    #[must_use]
569    pub const fn plan_choice_non_index_access(&self) -> u64 {
570        self.plan_choice_non_index_access
571    }
572
573    #[must_use]
574    pub const fn plan_choice_planner_composite_non_index(&self) -> u64 {
575        self.plan_choice_planner_composite_non_index
576    }
577
578    #[must_use]
579    pub const fn plan_choice_planner_full_scan_fallback(&self) -> u64 {
580        self.plan_choice_planner_full_scan_fallback
581    }
582
583    #[must_use]
584    pub const fn plan_choice_planner_key_set_access(&self) -> u64 {
585        self.plan_choice_planner_key_set_access
586    }
587
588    #[must_use]
589    pub const fn plan_choice_planner_primary_key_lookup(&self) -> u64 {
590        self.plan_choice_planner_primary_key_lookup
591    }
592
593    #[must_use]
594    pub const fn plan_choice_planner_primary_key_range(&self) -> u64 {
595        self.plan_choice_planner_primary_key_range
596    }
597
598    #[must_use]
599    pub const fn plan_choice_required_order_primary_key_range_preferred(&self) -> u64 {
600        self.plan_choice_required_order_primary_key_range_preferred
601    }
602
603    #[must_use]
604    pub const fn plan_choice_singleton_primary_key_child_access_preferred(&self) -> u64 {
605        self.plan_choice_singleton_primary_key_child_access_preferred
606    }
607
608    #[must_use]
609    pub const fn prepared_shape_already_finalized(&self) -> u64 {
610        self.prepared_shape_already_finalized
611    }
612
613    #[must_use]
614    pub const fn prepared_shape_generated_fallback(&self) -> u64 {
615        self.prepared_shape_generated_fallback
616    }
617
618    #[must_use]
619    pub const fn rows_loaded(&self) -> u64 {
620        self.rows_loaded
621    }
622
623    #[must_use]
624    pub const fn rows_saved(&self) -> u64 {
625        self.rows_saved
626    }
627
628    #[must_use]
629    pub const fn rows_inserted(&self) -> u64 {
630        self.rows_inserted
631    }
632
633    #[must_use]
634    pub const fn rows_updated(&self) -> u64 {
635        self.rows_updated
636    }
637
638    #[must_use]
639    pub const fn rows_replaced(&self) -> u64 {
640        self.rows_replaced
641    }
642
643    #[must_use]
644    pub const fn rows_scanned(&self) -> u64 {
645        self.rows_scanned
646    }
647
648    #[must_use]
649    pub const fn rows_filtered(&self) -> u64 {
650        self.rows_filtered
651    }
652
653    #[must_use]
654    pub const fn rows_aggregated(&self) -> u64 {
655        self.rows_aggregated
656    }
657
658    #[must_use]
659    pub const fn rows_emitted(&self) -> u64 {
660        self.rows_emitted
661    }
662
663    #[must_use]
664    pub const fn load_candidate_rows_scanned(&self) -> u64 {
665        self.load_candidate_rows_scanned
666    }
667
668    #[must_use]
669    pub const fn load_candidate_rows_filtered(&self) -> u64 {
670        self.load_candidate_rows_filtered
671    }
672
673    #[must_use]
674    pub const fn load_result_rows_emitted(&self) -> u64 {
675        self.load_result_rows_emitted
676    }
677
678    #[must_use]
679    pub const fn rows_deleted(&self) -> u64 {
680        self.rows_deleted
681    }
682
683    #[must_use]
684    pub const fn sql_insert_calls(&self) -> u64 {
685        self.sql_insert_calls
686    }
687
688    #[must_use]
689    pub const fn sql_insert_select_calls(&self) -> u64 {
690        self.sql_insert_select_calls
691    }
692
693    #[must_use]
694    pub const fn sql_update_calls(&self) -> u64 {
695        self.sql_update_calls
696    }
697
698    #[must_use]
699    pub const fn sql_delete_calls(&self) -> u64 {
700        self.sql_delete_calls
701    }
702
703    #[must_use]
704    pub const fn sql_write_matched_rows(&self) -> u64 {
705        self.sql_write_matched_rows
706    }
707
708    #[must_use]
709    pub const fn sql_write_mutated_rows(&self) -> u64 {
710        self.sql_write_mutated_rows
711    }
712
713    #[must_use]
714    pub const fn sql_write_returning_rows(&self) -> u64 {
715        self.sql_write_returning_rows
716    }
717
718    #[must_use]
719    pub const fn sql_write_error_insert(&self) -> u64 {
720        self.sql_write_error_insert
721    }
722
723    #[must_use]
724    pub const fn sql_write_error_insert_select(&self) -> u64 {
725        self.sql_write_error_insert_select
726    }
727
728    #[must_use]
729    pub const fn sql_write_error_update(&self) -> u64 {
730        self.sql_write_error_update
731    }
732
733    #[must_use]
734    pub const fn sql_write_error_delete(&self) -> u64 {
735        self.sql_write_error_delete
736    }
737
738    #[must_use]
739    pub const fn sql_write_error_corruption(&self) -> u64 {
740        self.sql_write_error_corruption
741    }
742
743    #[must_use]
744    pub const fn sql_write_error_incompatible_persisted_format(&self) -> u64 {
745        self.sql_write_error_incompatible_persisted_format
746    }
747
748    #[must_use]
749    pub const fn sql_write_error_not_found(&self) -> u64 {
750        self.sql_write_error_not_found
751    }
752
753    #[must_use]
754    pub const fn sql_write_error_internal(&self) -> u64 {
755        self.sql_write_error_internal
756    }
757
758    #[must_use]
759    pub const fn sql_write_error_conflict(&self) -> u64 {
760        self.sql_write_error_conflict
761    }
762
763    #[must_use]
764    pub const fn sql_write_error_unsupported(&self) -> u64 {
765        self.sql_write_error_unsupported
766    }
767
768    #[must_use]
769    pub const fn sql_write_error_invariant_violation(&self) -> u64 {
770        self.sql_write_error_invariant_violation
771    }
772
773    #[must_use]
774    pub const fn index_inserts(&self) -> u64 {
775        self.index_inserts
776    }
777
778    #[must_use]
779    pub const fn index_removes(&self) -> u64 {
780        self.index_removes
781    }
782
783    #[must_use]
784    pub const fn reverse_index_inserts(&self) -> u64 {
785        self.reverse_index_inserts
786    }
787
788    #[must_use]
789    pub const fn reverse_index_removes(&self) -> u64 {
790        self.reverse_index_removes
791    }
792
793    #[must_use]
794    pub const fn relation_reverse_lookups(&self) -> u64 {
795        self.relation_reverse_lookups
796    }
797
798    #[must_use]
799    pub const fn relation_delete_blocks(&self) -> u64 {
800        self.relation_delete_blocks
801    }
802
803    #[must_use]
804    pub const fn write_rows_touched(&self) -> u64 {
805        self.write_rows_touched
806    }
807
808    #[must_use]
809    pub const fn write_index_entries_changed(&self) -> u64 {
810        self.write_index_entries_changed
811    }
812
813    #[must_use]
814    pub const fn write_reverse_index_entries_changed(&self) -> u64 {
815        self.write_reverse_index_entries_changed
816    }
817
818    #[must_use]
819    pub const fn write_relation_checks(&self) -> u64 {
820        self.write_relation_checks
821    }
822
823    #[must_use]
824    pub const fn unique_violations(&self) -> u64 {
825        self.unique_violations
826    }
827
828    #[must_use]
829    pub const fn non_atomic_partial_commits(&self) -> u64 {
830        self.non_atomic_partial_commits
831    }
832
833    #[must_use]
834    pub const fn non_atomic_partial_rows_committed(&self) -> u64 {
835        self.non_atomic_partial_rows_committed
836    }
837
838    /// Returns result rows emitted per load candidate row scanned.
839    #[must_use]
840    pub const fn load_selectivity_ratio(&self) -> Option<MetricRatio> {
841        ratio(
842            self.load_result_rows_emitted,
843            self.load_candidate_rows_scanned,
844        )
845    }
846
847    /// Returns candidate rows filtered per load candidate row scanned.
848    #[must_use]
849    pub const fn load_filter_ratio(&self) -> Option<MetricRatio> {
850        ratio(
851            self.load_candidate_rows_filtered,
852            self.load_candidate_rows_scanned,
853        )
854    }
855
856    /// Returns SQL-mutated rows per SQL-matched row.
857    #[must_use]
858    pub const fn sql_write_mutation_ratio(&self) -> Option<MetricRatio> {
859        ratio(self.sql_write_mutated_rows, self.sql_write_matched_rows)
860    }
861
862    /// Returns SQL `RETURNING` rows per SQL-mutated row.
863    #[must_use]
864    pub const fn sql_write_returning_ratio(&self) -> Option<MetricRatio> {
865        ratio(self.sql_write_returning_rows, self.sql_write_mutated_rows)
866    }
867
868    /// Returns primary index entries changed per write row touched.
869    #[must_use]
870    pub const fn write_index_entries_per_row(&self) -> Option<MetricRatio> {
871        ratio(self.write_index_entries_changed, self.write_rows_touched)
872    }
873
874    /// Returns reverse-index entries changed per write row touched.
875    #[must_use]
876    pub const fn write_reverse_index_entries_per_row(&self) -> Option<MetricRatio> {
877        ratio(
878            self.write_reverse_index_entries_changed,
879            self.write_rows_touched,
880        )
881    }
882
883    /// Returns relation checks performed per write row touched.
884    #[must_use]
885    pub const fn write_relation_checks_per_row(&self) -> Option<MetricRatio> {
886        ratio(self.write_relation_checks, self.write_rows_touched)
887    }
888}
889
890#[derive(Clone, Debug, Default)]
891pub(crate) struct EntityCounters {
892    pub(crate) load_calls: u64,
893    pub(crate) save_calls: u64,
894    pub(crate) delete_calls: u64,
895    pub(crate) save_insert_calls: u64,
896    pub(crate) save_update_calls: u64,
897    pub(crate) save_replace_calls: u64,
898    pub(crate) exec_success: u64,
899    pub(crate) exec_error_corruption: u64,
900    pub(crate) exec_error_incompatible_persisted_format: u64,
901    pub(crate) exec_error_not_found: u64,
902    pub(crate) exec_error_internal: u64,
903    pub(crate) exec_error_conflict: u64,
904    pub(crate) exec_error_unsupported: u64,
905    pub(crate) exec_error_invariant_violation: u64,
906    pub(crate) exec_aborted: u64,
907    pub(crate) cache_shared_query_plan_hits: u64,
908    pub(crate) cache_shared_query_plan_misses: u64,
909    pub(crate) cache_shared_query_plan_inserts: u64,
910    pub(crate) cache_shared_query_plan_miss_cold: u64,
911    pub(crate) cache_shared_query_plan_miss_distinct_key: u64,
912    pub(crate) cache_shared_query_plan_miss_method_version: u64,
913    pub(crate) cache_shared_query_plan_miss_schema_fingerprint: u64,
914    pub(crate) cache_shared_query_plan_miss_visibility: u64,
915    pub(crate) cache_sql_compiled_command_hits: u64,
916    pub(crate) cache_sql_compiled_command_misses: u64,
917    pub(crate) cache_sql_compiled_command_inserts: u64,
918    pub(crate) cache_sql_compiled_command_miss_cold: u64,
919    pub(crate) cache_sql_compiled_command_miss_distinct_key: u64,
920    pub(crate) cache_sql_compiled_command_miss_method_version: u64,
921    pub(crate) cache_sql_compiled_command_miss_schema_fingerprint: u64,
922    pub(crate) cache_sql_compiled_command_miss_surface: u64,
923    pub(crate) schema_reconcile_checks: u64,
924    pub(crate) schema_reconcile_exact_match: u64,
925    pub(crate) schema_reconcile_first_create: u64,
926    pub(crate) schema_reconcile_latest_snapshot_corrupt: u64,
927    pub(crate) schema_reconcile_rejected_field_slot: u64,
928    pub(crate) schema_reconcile_rejected_other: u64,
929    pub(crate) schema_reconcile_rejected_row_layout: u64,
930    pub(crate) schema_reconcile_rejected_schema_version: u64,
931    pub(crate) schema_reconcile_store_write_error: u64,
932    pub(crate) schema_store_snapshots: u64,
933    pub(crate) schema_store_encoded_bytes: u64,
934    pub(crate) schema_store_latest_snapshot_bytes: u64,
935    pub(crate) sql_compile_rejects: u64,
936    pub(crate) sql_compile_reject_cache_key: u64,
937    pub(crate) sql_compile_reject_parse: u64,
938    pub(crate) sql_compile_reject_semantic: u64,
939    pub(crate) plan_index: u64,
940    pub(crate) plan_keys: u64,
941    pub(crate) plan_range: u64,
942    pub(crate) plan_full_scan: u64,
943    pub(crate) plan_by_key: u64,
944    pub(crate) plan_by_keys: u64,
945    pub(crate) plan_key_range: u64,
946    pub(crate) plan_index_prefix: u64,
947    pub(crate) plan_index_multi_lookup: u64,
948    pub(crate) plan_index_range: u64,
949    pub(crate) plan_explicit_full_scan: u64,
950    pub(crate) plan_union: u64,
951    pub(crate) plan_intersection: u64,
952    pub(crate) plan_grouped_hash_materialized: u64,
953    pub(crate) plan_grouped_ordered_materialized: u64,
954    pub(crate) plan_choice_conflicting_primary_key_children_access_preferred: u64,
955    pub(crate) plan_choice_constant_false_predicate: u64,
956    pub(crate) plan_choice_empty_child_access_preferred: u64,
957    pub(crate) plan_choice_full_scan_access: u64,
958    pub(crate) plan_choice_intent_key_access_override: u64,
959    pub(crate) plan_choice_limit_zero_window: u64,
960    pub(crate) plan_choice_non_index_access: u64,
961    pub(crate) plan_choice_planner_composite_non_index: u64,
962    pub(crate) plan_choice_planner_full_scan_fallback: u64,
963    pub(crate) plan_choice_planner_key_set_access: u64,
964    pub(crate) plan_choice_planner_primary_key_lookup: u64,
965    pub(crate) plan_choice_planner_primary_key_range: u64,
966    pub(crate) plan_choice_required_order_primary_key_range_preferred: u64,
967    pub(crate) plan_choice_singleton_primary_key_child_access_preferred: u64,
968    pub(crate) prepared_shape_already_finalized: u64,
969    pub(crate) prepared_shape_generated_fallback: u64,
970    pub(crate) rows_loaded: u64,
971    pub(crate) rows_saved: u64,
972    pub(crate) rows_inserted: u64,
973    pub(crate) rows_updated: u64,
974    pub(crate) rows_replaced: u64,
975    pub(crate) rows_scanned: u64,
976    pub(crate) rows_filtered: u64,
977    pub(crate) rows_aggregated: u64,
978    pub(crate) rows_emitted: u64,
979    pub(crate) load_candidate_rows_scanned: u64,
980    pub(crate) load_candidate_rows_filtered: u64,
981    pub(crate) load_result_rows_emitted: u64,
982    pub(crate) rows_deleted: u64,
983    pub(crate) sql_insert_calls: u64,
984    pub(crate) sql_insert_select_calls: u64,
985    pub(crate) sql_update_calls: u64,
986    pub(crate) sql_delete_calls: u64,
987    pub(crate) sql_write_matched_rows: u64,
988    pub(crate) sql_write_mutated_rows: u64,
989    pub(crate) sql_write_returning_rows: u64,
990    pub(crate) sql_write_error_insert: u64,
991    pub(crate) sql_write_error_insert_select: u64,
992    pub(crate) sql_write_error_update: u64,
993    pub(crate) sql_write_error_delete: u64,
994    pub(crate) sql_write_error_corruption: u64,
995    pub(crate) sql_write_error_incompatible_persisted_format: u64,
996    pub(crate) sql_write_error_not_found: u64,
997    pub(crate) sql_write_error_internal: u64,
998    pub(crate) sql_write_error_conflict: u64,
999    pub(crate) sql_write_error_unsupported: u64,
1000    pub(crate) sql_write_error_invariant_violation: u64,
1001    pub(crate) index_inserts: u64,
1002    pub(crate) index_removes: u64,
1003    pub(crate) reverse_index_inserts: u64,
1004    pub(crate) reverse_index_removes: u64,
1005    pub(crate) relation_reverse_lookups: u64,
1006    pub(crate) relation_delete_blocks: u64,
1007    pub(crate) write_rows_touched: u64,
1008    pub(crate) write_index_entries_changed: u64,
1009    pub(crate) write_reverse_index_entries_changed: u64,
1010    pub(crate) write_relation_checks: u64,
1011    pub(crate) unique_violations: u64,
1012    pub(crate) non_atomic_partial_commits: u64,
1013    pub(crate) non_atomic_partial_rows_committed: u64,
1014}
1015
1016#[cfg_attr(doc, doc = "EventPerf\n\nInstruction totals and maxima.")]
1017#[derive(CandidType, Clone, Debug, Default, Deserialize)]
1018pub struct EventPerf {
1019    // Instruction totals per executor (ic_cdk::api::performance_counter(1))
1020    pub(crate) load_inst_total: u128,
1021    pub(crate) save_inst_total: u128,
1022    pub(crate) delete_inst_total: u128,
1023
1024    // Maximum observed instruction deltas
1025    pub(crate) load_inst_max: u64,
1026    pub(crate) save_inst_max: u64,
1027    pub(crate) delete_inst_max: u64,
1028}
1029
1030impl EventPerf {
1031    #[must_use]
1032    pub const fn new(
1033        load_inst_total: u128,
1034        save_inst_total: u128,
1035        delete_inst_total: u128,
1036        load_inst_max: u64,
1037        save_inst_max: u64,
1038        delete_inst_max: u64,
1039    ) -> Self {
1040        Self {
1041            load_inst_total,
1042            save_inst_total,
1043            delete_inst_total,
1044            load_inst_max,
1045            save_inst_max,
1046            delete_inst_max,
1047        }
1048    }
1049
1050    #[must_use]
1051    pub const fn load_inst_total(&self) -> u128 {
1052        self.load_inst_total
1053    }
1054
1055    #[must_use]
1056    pub const fn save_inst_total(&self) -> u128 {
1057        self.save_inst_total
1058    }
1059
1060    #[must_use]
1061    pub const fn delete_inst_total(&self) -> u128 {
1062        self.delete_inst_total
1063    }
1064
1065    #[must_use]
1066    pub const fn load_inst_max(&self) -> u64 {
1067        self.load_inst_max
1068    }
1069
1070    #[must_use]
1071    pub const fn save_inst_max(&self) -> u64 {
1072        self.save_inst_max
1073    }
1074
1075    #[must_use]
1076    pub const fn delete_inst_max(&self) -> u64 {
1077        self.delete_inst_max
1078    }
1079}
1080
1081thread_local! {
1082    static EVENT_STATE: RefCell<EventState> = RefCell::new(EventState::default());
1083}
1084
1085// Borrow metrics immutably.
1086pub(crate) fn with_state<R>(f: impl FnOnce(&EventState) -> R) -> R {
1087    EVENT_STATE.with(|m| f(&m.borrow()))
1088}
1089
1090// Borrow metrics mutably.
1091pub(crate) fn with_state_mut<R>(f: impl FnOnce(&mut EventState) -> R) -> R {
1092    EVENT_STATE.with(|m| f(&mut m.borrow_mut()))
1093}
1094
1095// Reset all counters (useful in tests).
1096pub(super) fn reset() {
1097    with_state_mut(|m| *m = EventState::default());
1098}
1099
1100// Reset all event state: counters, perf, and serialize counters.
1101pub(crate) fn reset_all() {
1102    reset();
1103}
1104
1105// Accumulate instruction counts and track a max.
1106pub(super) fn add_instructions(total: &mut u128, max: &mut u64, delta_inst: u64) {
1107    *total = total.saturating_add(u128::from(delta_inst));
1108    if delta_inst > *max {
1109        *max = delta_inst;
1110    }
1111}
1112
1113#[cfg_attr(doc, doc = "EventReport\n\nMetrics query payload.")]
1114#[derive(CandidType, Clone, Debug, Default, Deserialize)]
1115pub struct EventReport {
1116    counters: Option<EventCounters>,
1117    entity_counters: Vec<EntitySummary>,
1118    window_filter_matched: bool,
1119    requested_window_start_ms: Option<u64>,
1120    active_window_start_ms: u64,
1121}
1122
1123impl EventReport {
1124    #[must_use]
1125    pub(crate) const fn new(
1126        counters: Option<EventCounters>,
1127        entity_counters: Vec<EntitySummary>,
1128        window_filter_matched: bool,
1129        requested_window_start_ms: Option<u64>,
1130        active_window_start_ms: u64,
1131    ) -> Self {
1132        Self {
1133            counters,
1134            entity_counters,
1135            window_filter_matched,
1136            requested_window_start_ms,
1137            active_window_start_ms,
1138        }
1139    }
1140
1141    #[must_use]
1142    pub const fn counters(&self) -> Option<&EventCounters> {
1143        self.counters.as_ref()
1144    }
1145
1146    #[must_use]
1147    pub fn entity_counters(&self) -> &[EntitySummary] {
1148        &self.entity_counters
1149    }
1150
1151    #[must_use]
1152    pub const fn window_filter_matched(&self) -> bool {
1153        self.window_filter_matched
1154    }
1155
1156    #[must_use]
1157    pub const fn requested_window_start_ms(&self) -> Option<u64> {
1158        self.requested_window_start_ms
1159    }
1160
1161    #[must_use]
1162    pub const fn active_window_start_ms(&self) -> u64 {
1163        self.active_window_start_ms
1164    }
1165
1166    #[must_use]
1167    pub fn into_counters(self) -> Option<EventCounters> {
1168        self.counters
1169    }
1170
1171    #[must_use]
1172    pub fn into_entity_counters(self) -> Vec<EntitySummary> {
1173        self.entity_counters
1174    }
1175}
1176
1177//
1178// EventCounters
1179//
1180// Top-level metrics counters returned by `icydb_metrics()`.
1181// This keeps aggregate ops/perf totals while leaving per-entity detail to the
1182// separate `entity_counters` payload.
1183//
1184
1185#[derive(CandidType, Clone, Debug, Default, Deserialize)]
1186pub struct EventCounters {
1187    pub(crate) ops: EventOps,
1188    pub(crate) perf: EventPerf,
1189    pub(crate) window_start_ms: u64,
1190    pub(crate) window_end_ms: u64,
1191    pub(crate) window_duration_ms: u64,
1192}
1193
1194impl EventCounters {
1195    #[must_use]
1196    pub(crate) const fn new(
1197        ops: EventOps,
1198        perf: EventPerf,
1199        window_start_ms: u64,
1200        window_end_ms: u64,
1201    ) -> Self {
1202        Self {
1203            ops,
1204            perf,
1205            window_start_ms,
1206            window_end_ms,
1207            window_duration_ms: window_end_ms.saturating_sub(window_start_ms),
1208        }
1209    }
1210
1211    #[must_use]
1212    pub const fn ops(&self) -> &EventOps {
1213        &self.ops
1214    }
1215
1216    #[must_use]
1217    pub const fn perf(&self) -> &EventPerf {
1218        &self.perf
1219    }
1220
1221    #[must_use]
1222    pub const fn window_start_ms(&self) -> u64 {
1223        self.window_start_ms
1224    }
1225
1226    #[must_use]
1227    pub const fn window_end_ms(&self) -> u64 {
1228        self.window_end_ms
1229    }
1230
1231    #[must_use]
1232    pub const fn window_duration_ms(&self) -> u64 {
1233        self.window_duration_ms
1234    }
1235}
1236
1237#[cfg_attr(doc, doc = "EntitySummary\n\nPer-entity metrics summary.")]
1238#[derive(CandidType, Clone, Debug, Default, Deserialize)]
1239pub struct EntitySummary {
1240    path: String,
1241    load_calls: u64,
1242    save_calls: u64,
1243    delete_calls: u64,
1244    save_insert_calls: u64,
1245    save_update_calls: u64,
1246    save_replace_calls: u64,
1247    exec_success: u64,
1248    exec_error_corruption: u64,
1249    exec_error_incompatible_persisted_format: u64,
1250    exec_error_not_found: u64,
1251    exec_error_internal: u64,
1252    exec_error_conflict: u64,
1253    exec_error_unsupported: u64,
1254    exec_error_invariant_violation: u64,
1255    exec_aborted: u64,
1256    cache_shared_query_plan_hits: u64,
1257    cache_shared_query_plan_misses: u64,
1258    cache_shared_query_plan_inserts: u64,
1259    cache_shared_query_plan_miss_cold: u64,
1260    cache_shared_query_plan_miss_distinct_key: u64,
1261    cache_shared_query_plan_miss_method_version: u64,
1262    cache_shared_query_plan_miss_schema_fingerprint: u64,
1263    cache_shared_query_plan_miss_visibility: u64,
1264    cache_sql_compiled_command_hits: u64,
1265    cache_sql_compiled_command_misses: u64,
1266    cache_sql_compiled_command_inserts: u64,
1267    cache_sql_compiled_command_miss_cold: u64,
1268    cache_sql_compiled_command_miss_distinct_key: u64,
1269    cache_sql_compiled_command_miss_method_version: u64,
1270    cache_sql_compiled_command_miss_schema_fingerprint: u64,
1271    cache_sql_compiled_command_miss_surface: u64,
1272    schema_reconcile_checks: u64,
1273    schema_reconcile_exact_match: u64,
1274    schema_reconcile_first_create: u64,
1275    schema_reconcile_latest_snapshot_corrupt: u64,
1276    schema_reconcile_rejected_field_slot: u64,
1277    schema_reconcile_rejected_other: u64,
1278    schema_reconcile_rejected_row_layout: u64,
1279    schema_reconcile_rejected_schema_version: u64,
1280    schema_reconcile_store_write_error: u64,
1281    schema_store_snapshots: u64,
1282    schema_store_encoded_bytes: u64,
1283    schema_store_latest_snapshot_bytes: u64,
1284    sql_compile_rejects: u64,
1285    sql_compile_reject_cache_key: u64,
1286    sql_compile_reject_parse: u64,
1287    sql_compile_reject_semantic: u64,
1288    plan_index: u64,
1289    plan_keys: u64,
1290    plan_range: u64,
1291    plan_full_scan: u64,
1292    plan_by_key: u64,
1293    plan_by_keys: u64,
1294    plan_key_range: u64,
1295    plan_index_prefix: u64,
1296    plan_index_multi_lookup: u64,
1297    plan_index_range: u64,
1298    plan_explicit_full_scan: u64,
1299    plan_union: u64,
1300    plan_intersection: u64,
1301    plan_grouped_hash_materialized: u64,
1302    plan_grouped_ordered_materialized: u64,
1303    plan_choice_conflicting_primary_key_children_access_preferred: u64,
1304    plan_choice_constant_false_predicate: u64,
1305    plan_choice_empty_child_access_preferred: u64,
1306    plan_choice_full_scan_access: u64,
1307    plan_choice_intent_key_access_override: u64,
1308    plan_choice_limit_zero_window: u64,
1309    plan_choice_non_index_access: u64,
1310    plan_choice_planner_composite_non_index: u64,
1311    plan_choice_planner_full_scan_fallback: u64,
1312    plan_choice_planner_key_set_access: u64,
1313    plan_choice_planner_primary_key_lookup: u64,
1314    plan_choice_planner_primary_key_range: u64,
1315    plan_choice_required_order_primary_key_range_preferred: u64,
1316    plan_choice_singleton_primary_key_child_access_preferred: u64,
1317    prepared_shape_already_finalized: u64,
1318    prepared_shape_generated_fallback: u64,
1319    rows_loaded: u64,
1320    rows_saved: u64,
1321    rows_inserted: u64,
1322    rows_updated: u64,
1323    rows_replaced: u64,
1324    rows_scanned: u64,
1325    rows_filtered: u64,
1326    rows_aggregated: u64,
1327    rows_emitted: u64,
1328    load_candidate_rows_scanned: u64,
1329    load_candidate_rows_filtered: u64,
1330    load_result_rows_emitted: u64,
1331    rows_deleted: u64,
1332    sql_insert_calls: u64,
1333    sql_insert_select_calls: u64,
1334    sql_update_calls: u64,
1335    sql_delete_calls: u64,
1336    sql_write_matched_rows: u64,
1337    sql_write_mutated_rows: u64,
1338    sql_write_returning_rows: u64,
1339    sql_write_error_insert: u64,
1340    sql_write_error_insert_select: u64,
1341    sql_write_error_update: u64,
1342    sql_write_error_delete: u64,
1343    sql_write_error_corruption: u64,
1344    sql_write_error_incompatible_persisted_format: u64,
1345    sql_write_error_not_found: u64,
1346    sql_write_error_internal: u64,
1347    sql_write_error_conflict: u64,
1348    sql_write_error_unsupported: u64,
1349    sql_write_error_invariant_violation: u64,
1350    index_inserts: u64,
1351    index_removes: u64,
1352    reverse_index_inserts: u64,
1353    reverse_index_removes: u64,
1354    relation_reverse_lookups: u64,
1355    relation_delete_blocks: u64,
1356    write_rows_touched: u64,
1357    write_index_entries_changed: u64,
1358    write_reverse_index_entries_changed: u64,
1359    write_relation_checks: u64,
1360    unique_violations: u64,
1361    non_atomic_partial_commits: u64,
1362    non_atomic_partial_rows_committed: u64,
1363}
1364
1365impl EntitySummary {
1366    #[must_use]
1367    pub const fn path(&self) -> &str {
1368        self.path.as_str()
1369    }
1370
1371    #[must_use]
1372    pub const fn load_calls(&self) -> u64 {
1373        self.load_calls
1374    }
1375
1376    #[must_use]
1377    pub const fn save_calls(&self) -> u64 {
1378        self.save_calls
1379    }
1380
1381    #[must_use]
1382    pub const fn delete_calls(&self) -> u64 {
1383        self.delete_calls
1384    }
1385
1386    #[must_use]
1387    pub const fn save_insert_calls(&self) -> u64 {
1388        self.save_insert_calls
1389    }
1390
1391    #[must_use]
1392    pub const fn save_update_calls(&self) -> u64 {
1393        self.save_update_calls
1394    }
1395
1396    #[must_use]
1397    pub const fn save_replace_calls(&self) -> u64 {
1398        self.save_replace_calls
1399    }
1400
1401    #[must_use]
1402    pub const fn exec_success(&self) -> u64 {
1403        self.exec_success
1404    }
1405
1406    #[must_use]
1407    pub const fn exec_error_corruption(&self) -> u64 {
1408        self.exec_error_corruption
1409    }
1410
1411    #[must_use]
1412    pub const fn exec_error_incompatible_persisted_format(&self) -> u64 {
1413        self.exec_error_incompatible_persisted_format
1414    }
1415
1416    #[must_use]
1417    pub const fn exec_error_not_found(&self) -> u64 {
1418        self.exec_error_not_found
1419    }
1420
1421    #[must_use]
1422    pub const fn exec_error_internal(&self) -> u64 {
1423        self.exec_error_internal
1424    }
1425
1426    #[must_use]
1427    pub const fn exec_error_conflict(&self) -> u64 {
1428        self.exec_error_conflict
1429    }
1430
1431    #[must_use]
1432    pub const fn exec_error_unsupported(&self) -> u64 {
1433        self.exec_error_unsupported
1434    }
1435
1436    #[must_use]
1437    pub const fn exec_error_invariant_violation(&self) -> u64 {
1438        self.exec_error_invariant_violation
1439    }
1440
1441    #[must_use]
1442    pub const fn exec_aborted(&self) -> u64 {
1443        self.exec_aborted
1444    }
1445
1446    #[must_use]
1447    pub const fn cache_shared_query_plan_hits(&self) -> u64 {
1448        self.cache_shared_query_plan_hits
1449    }
1450
1451    #[must_use]
1452    pub const fn cache_shared_query_plan_misses(&self) -> u64 {
1453        self.cache_shared_query_plan_misses
1454    }
1455
1456    #[must_use]
1457    pub const fn cache_shared_query_plan_inserts(&self) -> u64 {
1458        self.cache_shared_query_plan_inserts
1459    }
1460
1461    #[must_use]
1462    pub const fn cache_shared_query_plan_miss_cold(&self) -> u64 {
1463        self.cache_shared_query_plan_miss_cold
1464    }
1465
1466    #[must_use]
1467    pub const fn cache_shared_query_plan_miss_distinct_key(&self) -> u64 {
1468        self.cache_shared_query_plan_miss_distinct_key
1469    }
1470
1471    #[must_use]
1472    pub const fn cache_shared_query_plan_miss_method_version(&self) -> u64 {
1473        self.cache_shared_query_plan_miss_method_version
1474    }
1475
1476    #[must_use]
1477    pub const fn cache_shared_query_plan_miss_schema_fingerprint(&self) -> u64 {
1478        self.cache_shared_query_plan_miss_schema_fingerprint
1479    }
1480
1481    #[must_use]
1482    pub const fn cache_shared_query_plan_miss_visibility(&self) -> u64 {
1483        self.cache_shared_query_plan_miss_visibility
1484    }
1485
1486    #[must_use]
1487    pub const fn cache_sql_compiled_command_hits(&self) -> u64 {
1488        self.cache_sql_compiled_command_hits
1489    }
1490
1491    #[must_use]
1492    pub const fn cache_sql_compiled_command_misses(&self) -> u64 {
1493        self.cache_sql_compiled_command_misses
1494    }
1495
1496    #[must_use]
1497    pub const fn cache_sql_compiled_command_inserts(&self) -> u64 {
1498        self.cache_sql_compiled_command_inserts
1499    }
1500
1501    #[must_use]
1502    pub const fn cache_sql_compiled_command_miss_cold(&self) -> u64 {
1503        self.cache_sql_compiled_command_miss_cold
1504    }
1505
1506    #[must_use]
1507    pub const fn cache_sql_compiled_command_miss_distinct_key(&self) -> u64 {
1508        self.cache_sql_compiled_command_miss_distinct_key
1509    }
1510
1511    #[must_use]
1512    pub const fn cache_sql_compiled_command_miss_method_version(&self) -> u64 {
1513        self.cache_sql_compiled_command_miss_method_version
1514    }
1515
1516    #[must_use]
1517    pub const fn cache_sql_compiled_command_miss_schema_fingerprint(&self) -> u64 {
1518        self.cache_sql_compiled_command_miss_schema_fingerprint
1519    }
1520
1521    #[must_use]
1522    pub const fn cache_sql_compiled_command_miss_surface(&self) -> u64 {
1523        self.cache_sql_compiled_command_miss_surface
1524    }
1525
1526    #[must_use]
1527    pub const fn schema_reconcile_checks(&self) -> u64 {
1528        self.schema_reconcile_checks
1529    }
1530
1531    #[must_use]
1532    pub const fn schema_reconcile_exact_match(&self) -> u64 {
1533        self.schema_reconcile_exact_match
1534    }
1535
1536    #[must_use]
1537    pub const fn schema_reconcile_first_create(&self) -> u64 {
1538        self.schema_reconcile_first_create
1539    }
1540
1541    #[must_use]
1542    pub const fn schema_reconcile_latest_snapshot_corrupt(&self) -> u64 {
1543        self.schema_reconcile_latest_snapshot_corrupt
1544    }
1545
1546    #[must_use]
1547    pub const fn schema_reconcile_rejected_field_slot(&self) -> u64 {
1548        self.schema_reconcile_rejected_field_slot
1549    }
1550
1551    #[must_use]
1552    pub const fn schema_reconcile_rejected_other(&self) -> u64 {
1553        self.schema_reconcile_rejected_other
1554    }
1555
1556    #[must_use]
1557    pub const fn schema_reconcile_rejected_row_layout(&self) -> u64 {
1558        self.schema_reconcile_rejected_row_layout
1559    }
1560
1561    #[must_use]
1562    pub const fn schema_reconcile_rejected_schema_version(&self) -> u64 {
1563        self.schema_reconcile_rejected_schema_version
1564    }
1565
1566    #[must_use]
1567    pub const fn schema_reconcile_store_write_error(&self) -> u64 {
1568        self.schema_reconcile_store_write_error
1569    }
1570
1571    #[must_use]
1572    pub const fn schema_store_snapshots(&self) -> u64 {
1573        self.schema_store_snapshots
1574    }
1575
1576    #[must_use]
1577    pub const fn schema_store_encoded_bytes(&self) -> u64 {
1578        self.schema_store_encoded_bytes
1579    }
1580
1581    #[must_use]
1582    pub const fn schema_store_latest_snapshot_bytes(&self) -> u64 {
1583        self.schema_store_latest_snapshot_bytes
1584    }
1585
1586    #[must_use]
1587    pub const fn sql_compile_rejects(&self) -> u64 {
1588        self.sql_compile_rejects
1589    }
1590
1591    #[must_use]
1592    pub const fn sql_compile_reject_cache_key(&self) -> u64 {
1593        self.sql_compile_reject_cache_key
1594    }
1595
1596    #[must_use]
1597    pub const fn sql_compile_reject_parse(&self) -> u64 {
1598        self.sql_compile_reject_parse
1599    }
1600
1601    #[must_use]
1602    pub const fn sql_compile_reject_semantic(&self) -> u64 {
1603        self.sql_compile_reject_semantic
1604    }
1605
1606    #[must_use]
1607    pub const fn plan_index(&self) -> u64 {
1608        self.plan_index
1609    }
1610
1611    #[must_use]
1612    pub const fn plan_keys(&self) -> u64 {
1613        self.plan_keys
1614    }
1615
1616    #[must_use]
1617    pub const fn plan_range(&self) -> u64 {
1618        self.plan_range
1619    }
1620
1621    #[must_use]
1622    pub const fn plan_full_scan(&self) -> u64 {
1623        self.plan_full_scan
1624    }
1625
1626    #[must_use]
1627    pub const fn plan_by_key(&self) -> u64 {
1628        self.plan_by_key
1629    }
1630
1631    #[must_use]
1632    pub const fn plan_by_keys(&self) -> u64 {
1633        self.plan_by_keys
1634    }
1635
1636    #[must_use]
1637    pub const fn plan_key_range(&self) -> u64 {
1638        self.plan_key_range
1639    }
1640
1641    #[must_use]
1642    pub const fn plan_index_prefix(&self) -> u64 {
1643        self.plan_index_prefix
1644    }
1645
1646    #[must_use]
1647    pub const fn plan_index_multi_lookup(&self) -> u64 {
1648        self.plan_index_multi_lookup
1649    }
1650
1651    #[must_use]
1652    pub const fn plan_index_range(&self) -> u64 {
1653        self.plan_index_range
1654    }
1655
1656    #[must_use]
1657    pub const fn plan_explicit_full_scan(&self) -> u64 {
1658        self.plan_explicit_full_scan
1659    }
1660
1661    #[must_use]
1662    pub const fn plan_union(&self) -> u64 {
1663        self.plan_union
1664    }
1665
1666    #[must_use]
1667    pub const fn plan_intersection(&self) -> u64 {
1668        self.plan_intersection
1669    }
1670
1671    #[must_use]
1672    pub const fn plan_grouped_hash_materialized(&self) -> u64 {
1673        self.plan_grouped_hash_materialized
1674    }
1675
1676    #[must_use]
1677    pub const fn plan_grouped_ordered_materialized(&self) -> u64 {
1678        self.plan_grouped_ordered_materialized
1679    }
1680
1681    #[must_use]
1682    pub const fn plan_choice_conflicting_primary_key_children_access_preferred(&self) -> u64 {
1683        self.plan_choice_conflicting_primary_key_children_access_preferred
1684    }
1685
1686    #[must_use]
1687    pub const fn plan_choice_constant_false_predicate(&self) -> u64 {
1688        self.plan_choice_constant_false_predicate
1689    }
1690
1691    #[must_use]
1692    pub const fn plan_choice_empty_child_access_preferred(&self) -> u64 {
1693        self.plan_choice_empty_child_access_preferred
1694    }
1695
1696    #[must_use]
1697    pub const fn plan_choice_full_scan_access(&self) -> u64 {
1698        self.plan_choice_full_scan_access
1699    }
1700
1701    #[must_use]
1702    pub const fn plan_choice_intent_key_access_override(&self) -> u64 {
1703        self.plan_choice_intent_key_access_override
1704    }
1705
1706    #[must_use]
1707    pub const fn plan_choice_limit_zero_window(&self) -> u64 {
1708        self.plan_choice_limit_zero_window
1709    }
1710
1711    #[must_use]
1712    pub const fn plan_choice_non_index_access(&self) -> u64 {
1713        self.plan_choice_non_index_access
1714    }
1715
1716    #[must_use]
1717    pub const fn plan_choice_planner_composite_non_index(&self) -> u64 {
1718        self.plan_choice_planner_composite_non_index
1719    }
1720
1721    #[must_use]
1722    pub const fn plan_choice_planner_full_scan_fallback(&self) -> u64 {
1723        self.plan_choice_planner_full_scan_fallback
1724    }
1725
1726    #[must_use]
1727    pub const fn plan_choice_planner_key_set_access(&self) -> u64 {
1728        self.plan_choice_planner_key_set_access
1729    }
1730
1731    #[must_use]
1732    pub const fn plan_choice_planner_primary_key_lookup(&self) -> u64 {
1733        self.plan_choice_planner_primary_key_lookup
1734    }
1735
1736    #[must_use]
1737    pub const fn plan_choice_planner_primary_key_range(&self) -> u64 {
1738        self.plan_choice_planner_primary_key_range
1739    }
1740
1741    #[must_use]
1742    pub const fn plan_choice_required_order_primary_key_range_preferred(&self) -> u64 {
1743        self.plan_choice_required_order_primary_key_range_preferred
1744    }
1745
1746    #[must_use]
1747    pub const fn plan_choice_singleton_primary_key_child_access_preferred(&self) -> u64 {
1748        self.plan_choice_singleton_primary_key_child_access_preferred
1749    }
1750
1751    #[must_use]
1752    pub const fn prepared_shape_already_finalized(&self) -> u64 {
1753        self.prepared_shape_already_finalized
1754    }
1755
1756    #[must_use]
1757    pub const fn prepared_shape_generated_fallback(&self) -> u64 {
1758        self.prepared_shape_generated_fallback
1759    }
1760
1761    #[must_use]
1762    pub const fn rows_loaded(&self) -> u64 {
1763        self.rows_loaded
1764    }
1765
1766    #[must_use]
1767    pub const fn rows_saved(&self) -> u64 {
1768        self.rows_saved
1769    }
1770
1771    #[must_use]
1772    pub const fn rows_inserted(&self) -> u64 {
1773        self.rows_inserted
1774    }
1775
1776    #[must_use]
1777    pub const fn rows_updated(&self) -> u64 {
1778        self.rows_updated
1779    }
1780
1781    #[must_use]
1782    pub const fn rows_replaced(&self) -> u64 {
1783        self.rows_replaced
1784    }
1785
1786    #[must_use]
1787    pub const fn rows_scanned(&self) -> u64 {
1788        self.rows_scanned
1789    }
1790
1791    #[must_use]
1792    pub const fn rows_filtered(&self) -> u64 {
1793        self.rows_filtered
1794    }
1795
1796    #[must_use]
1797    pub const fn rows_aggregated(&self) -> u64 {
1798        self.rows_aggregated
1799    }
1800
1801    #[must_use]
1802    pub const fn rows_emitted(&self) -> u64 {
1803        self.rows_emitted
1804    }
1805
1806    #[must_use]
1807    pub const fn load_candidate_rows_scanned(&self) -> u64 {
1808        self.load_candidate_rows_scanned
1809    }
1810
1811    #[must_use]
1812    pub const fn load_candidate_rows_filtered(&self) -> u64 {
1813        self.load_candidate_rows_filtered
1814    }
1815
1816    #[must_use]
1817    pub const fn load_result_rows_emitted(&self) -> u64 {
1818        self.load_result_rows_emitted
1819    }
1820
1821    #[must_use]
1822    pub const fn rows_deleted(&self) -> u64 {
1823        self.rows_deleted
1824    }
1825
1826    #[must_use]
1827    pub const fn sql_insert_calls(&self) -> u64 {
1828        self.sql_insert_calls
1829    }
1830
1831    #[must_use]
1832    pub const fn sql_insert_select_calls(&self) -> u64 {
1833        self.sql_insert_select_calls
1834    }
1835
1836    #[must_use]
1837    pub const fn sql_update_calls(&self) -> u64 {
1838        self.sql_update_calls
1839    }
1840
1841    #[must_use]
1842    pub const fn sql_delete_calls(&self) -> u64 {
1843        self.sql_delete_calls
1844    }
1845
1846    #[must_use]
1847    pub const fn sql_write_matched_rows(&self) -> u64 {
1848        self.sql_write_matched_rows
1849    }
1850
1851    #[must_use]
1852    pub const fn sql_write_mutated_rows(&self) -> u64 {
1853        self.sql_write_mutated_rows
1854    }
1855
1856    #[must_use]
1857    pub const fn sql_write_returning_rows(&self) -> u64 {
1858        self.sql_write_returning_rows
1859    }
1860
1861    #[must_use]
1862    pub const fn sql_write_error_insert(&self) -> u64 {
1863        self.sql_write_error_insert
1864    }
1865
1866    #[must_use]
1867    pub const fn sql_write_error_insert_select(&self) -> u64 {
1868        self.sql_write_error_insert_select
1869    }
1870
1871    #[must_use]
1872    pub const fn sql_write_error_update(&self) -> u64 {
1873        self.sql_write_error_update
1874    }
1875
1876    #[must_use]
1877    pub const fn sql_write_error_delete(&self) -> u64 {
1878        self.sql_write_error_delete
1879    }
1880
1881    #[must_use]
1882    pub const fn sql_write_error_corruption(&self) -> u64 {
1883        self.sql_write_error_corruption
1884    }
1885
1886    #[must_use]
1887    pub const fn sql_write_error_incompatible_persisted_format(&self) -> u64 {
1888        self.sql_write_error_incompatible_persisted_format
1889    }
1890
1891    #[must_use]
1892    pub const fn sql_write_error_not_found(&self) -> u64 {
1893        self.sql_write_error_not_found
1894    }
1895
1896    #[must_use]
1897    pub const fn sql_write_error_internal(&self) -> u64 {
1898        self.sql_write_error_internal
1899    }
1900
1901    #[must_use]
1902    pub const fn sql_write_error_conflict(&self) -> u64 {
1903        self.sql_write_error_conflict
1904    }
1905
1906    #[must_use]
1907    pub const fn sql_write_error_unsupported(&self) -> u64 {
1908        self.sql_write_error_unsupported
1909    }
1910
1911    #[must_use]
1912    pub const fn sql_write_error_invariant_violation(&self) -> u64 {
1913        self.sql_write_error_invariant_violation
1914    }
1915
1916    #[must_use]
1917    pub const fn index_inserts(&self) -> u64 {
1918        self.index_inserts
1919    }
1920
1921    #[must_use]
1922    pub const fn index_removes(&self) -> u64 {
1923        self.index_removes
1924    }
1925
1926    #[must_use]
1927    pub const fn reverse_index_inserts(&self) -> u64 {
1928        self.reverse_index_inserts
1929    }
1930
1931    #[must_use]
1932    pub const fn reverse_index_removes(&self) -> u64 {
1933        self.reverse_index_removes
1934    }
1935
1936    #[must_use]
1937    pub const fn relation_reverse_lookups(&self) -> u64 {
1938        self.relation_reverse_lookups
1939    }
1940
1941    #[must_use]
1942    pub const fn relation_delete_blocks(&self) -> u64 {
1943        self.relation_delete_blocks
1944    }
1945
1946    #[must_use]
1947    pub const fn write_rows_touched(&self) -> u64 {
1948        self.write_rows_touched
1949    }
1950
1951    #[must_use]
1952    pub const fn write_index_entries_changed(&self) -> u64 {
1953        self.write_index_entries_changed
1954    }
1955
1956    #[must_use]
1957    pub const fn write_reverse_index_entries_changed(&self) -> u64 {
1958        self.write_reverse_index_entries_changed
1959    }
1960
1961    #[must_use]
1962    pub const fn write_relation_checks(&self) -> u64 {
1963        self.write_relation_checks
1964    }
1965
1966    #[must_use]
1967    pub const fn unique_violations(&self) -> u64 {
1968        self.unique_violations
1969    }
1970
1971    #[must_use]
1972    pub const fn non_atomic_partial_commits(&self) -> u64 {
1973        self.non_atomic_partial_commits
1974    }
1975
1976    #[must_use]
1977    pub const fn non_atomic_partial_rows_committed(&self) -> u64 {
1978        self.non_atomic_partial_rows_committed
1979    }
1980
1981    /// Returns result rows emitted per load candidate row scanned.
1982    #[must_use]
1983    pub const fn load_selectivity_ratio(&self) -> Option<MetricRatio> {
1984        ratio(
1985            self.load_result_rows_emitted,
1986            self.load_candidate_rows_scanned,
1987        )
1988    }
1989
1990    /// Returns candidate rows filtered per load candidate row scanned.
1991    #[must_use]
1992    pub const fn load_filter_ratio(&self) -> Option<MetricRatio> {
1993        ratio(
1994            self.load_candidate_rows_filtered,
1995            self.load_candidate_rows_scanned,
1996        )
1997    }
1998
1999    /// Returns SQL-mutated rows per SQL-matched row.
2000    #[must_use]
2001    pub const fn sql_write_mutation_ratio(&self) -> Option<MetricRatio> {
2002        ratio(self.sql_write_mutated_rows, self.sql_write_matched_rows)
2003    }
2004
2005    /// Returns SQL `RETURNING` rows per SQL-mutated row.
2006    #[must_use]
2007    pub const fn sql_write_returning_ratio(&self) -> Option<MetricRatio> {
2008        ratio(self.sql_write_returning_rows, self.sql_write_mutated_rows)
2009    }
2010
2011    /// Returns primary index entries changed per write row touched.
2012    #[must_use]
2013    pub const fn write_index_entries_per_row(&self) -> Option<MetricRatio> {
2014        ratio(self.write_index_entries_changed, self.write_rows_touched)
2015    }
2016
2017    /// Returns reverse-index entries changed per write row touched.
2018    #[must_use]
2019    pub const fn write_reverse_index_entries_per_row(&self) -> Option<MetricRatio> {
2020        ratio(
2021            self.write_reverse_index_entries_changed,
2022            self.write_rows_touched,
2023        )
2024    }
2025
2026    /// Returns relation checks performed per write row touched.
2027    #[must_use]
2028    pub const fn write_relation_checks_per_row(&self) -> Option<MetricRatio> {
2029        ratio(self.write_relation_checks, self.write_rows_touched)
2030    }
2031
2032    // Rank entity summaries by all visible activity so write-heavy or
2033    // maintenance-heavy entities are not hidden below read-heavy entities.
2034    #[expect(clippy::too_many_lines)]
2035    const fn activity_score(&self) -> u64 {
2036        self.load_calls
2037            .saturating_add(self.save_calls)
2038            .saturating_add(self.delete_calls)
2039            .saturating_add(self.save_insert_calls)
2040            .saturating_add(self.save_update_calls)
2041            .saturating_add(self.save_replace_calls)
2042            .saturating_add(self.exec_success)
2043            .saturating_add(self.exec_error_corruption)
2044            .saturating_add(self.exec_error_incompatible_persisted_format)
2045            .saturating_add(self.exec_error_not_found)
2046            .saturating_add(self.exec_error_internal)
2047            .saturating_add(self.exec_error_conflict)
2048            .saturating_add(self.exec_error_unsupported)
2049            .saturating_add(self.exec_error_invariant_violation)
2050            .saturating_add(self.exec_aborted)
2051            .saturating_add(self.cache_shared_query_plan_hits)
2052            .saturating_add(self.cache_shared_query_plan_misses)
2053            .saturating_add(self.cache_shared_query_plan_inserts)
2054            .saturating_add(self.cache_shared_query_plan_miss_cold)
2055            .saturating_add(self.cache_shared_query_plan_miss_distinct_key)
2056            .saturating_add(self.cache_shared_query_plan_miss_method_version)
2057            .saturating_add(self.cache_shared_query_plan_miss_schema_fingerprint)
2058            .saturating_add(self.cache_shared_query_plan_miss_visibility)
2059            .saturating_add(self.cache_sql_compiled_command_hits)
2060            .saturating_add(self.cache_sql_compiled_command_misses)
2061            .saturating_add(self.cache_sql_compiled_command_inserts)
2062            .saturating_add(self.cache_sql_compiled_command_miss_cold)
2063            .saturating_add(self.cache_sql_compiled_command_miss_distinct_key)
2064            .saturating_add(self.cache_sql_compiled_command_miss_method_version)
2065            .saturating_add(self.cache_sql_compiled_command_miss_schema_fingerprint)
2066            .saturating_add(self.cache_sql_compiled_command_miss_surface)
2067            .saturating_add(self.schema_reconcile_checks)
2068            .saturating_add(self.schema_reconcile_exact_match)
2069            .saturating_add(self.schema_reconcile_first_create)
2070            .saturating_add(self.schema_reconcile_latest_snapshot_corrupt)
2071            .saturating_add(self.schema_reconcile_rejected_field_slot)
2072            .saturating_add(self.schema_reconcile_rejected_other)
2073            .saturating_add(self.schema_reconcile_rejected_row_layout)
2074            .saturating_add(self.schema_reconcile_rejected_schema_version)
2075            .saturating_add(self.schema_reconcile_store_write_error)
2076            .saturating_add(self.schema_store_snapshots)
2077            .saturating_add(self.schema_store_encoded_bytes)
2078            .saturating_add(self.schema_store_latest_snapshot_bytes)
2079            .saturating_add(self.sql_compile_rejects)
2080            .saturating_add(self.sql_compile_reject_cache_key)
2081            .saturating_add(self.sql_compile_reject_parse)
2082            .saturating_add(self.sql_compile_reject_semantic)
2083            .saturating_add(self.plan_index)
2084            .saturating_add(self.plan_keys)
2085            .saturating_add(self.plan_range)
2086            .saturating_add(self.plan_full_scan)
2087            .saturating_add(self.plan_by_key)
2088            .saturating_add(self.plan_by_keys)
2089            .saturating_add(self.plan_key_range)
2090            .saturating_add(self.plan_index_prefix)
2091            .saturating_add(self.plan_index_multi_lookup)
2092            .saturating_add(self.plan_index_range)
2093            .saturating_add(self.plan_explicit_full_scan)
2094            .saturating_add(self.plan_union)
2095            .saturating_add(self.plan_intersection)
2096            .saturating_add(self.plan_grouped_hash_materialized)
2097            .saturating_add(self.plan_grouped_ordered_materialized)
2098            .saturating_add(self.plan_choice_conflicting_primary_key_children_access_preferred)
2099            .saturating_add(self.plan_choice_constant_false_predicate)
2100            .saturating_add(self.plan_choice_empty_child_access_preferred)
2101            .saturating_add(self.plan_choice_full_scan_access)
2102            .saturating_add(self.plan_choice_intent_key_access_override)
2103            .saturating_add(self.plan_choice_limit_zero_window)
2104            .saturating_add(self.plan_choice_non_index_access)
2105            .saturating_add(self.plan_choice_planner_composite_non_index)
2106            .saturating_add(self.plan_choice_planner_full_scan_fallback)
2107            .saturating_add(self.plan_choice_planner_key_set_access)
2108            .saturating_add(self.plan_choice_planner_primary_key_lookup)
2109            .saturating_add(self.plan_choice_planner_primary_key_range)
2110            .saturating_add(self.plan_choice_required_order_primary_key_range_preferred)
2111            .saturating_add(self.plan_choice_singleton_primary_key_child_access_preferred)
2112            .saturating_add(self.prepared_shape_already_finalized)
2113            .saturating_add(self.prepared_shape_generated_fallback)
2114            .saturating_add(self.rows_loaded)
2115            .saturating_add(self.rows_saved)
2116            .saturating_add(self.rows_inserted)
2117            .saturating_add(self.rows_updated)
2118            .saturating_add(self.rows_replaced)
2119            .saturating_add(self.rows_scanned)
2120            .saturating_add(self.rows_filtered)
2121            .saturating_add(self.rows_aggregated)
2122            .saturating_add(self.rows_emitted)
2123            .saturating_add(self.load_candidate_rows_scanned)
2124            .saturating_add(self.load_candidate_rows_filtered)
2125            .saturating_add(self.load_result_rows_emitted)
2126            .saturating_add(self.rows_deleted)
2127            .saturating_add(self.sql_insert_calls)
2128            .saturating_add(self.sql_insert_select_calls)
2129            .saturating_add(self.sql_update_calls)
2130            .saturating_add(self.sql_delete_calls)
2131            .saturating_add(self.sql_write_matched_rows)
2132            .saturating_add(self.sql_write_mutated_rows)
2133            .saturating_add(self.sql_write_returning_rows)
2134            .saturating_add(self.sql_write_error_insert)
2135            .saturating_add(self.sql_write_error_insert_select)
2136            .saturating_add(self.sql_write_error_update)
2137            .saturating_add(self.sql_write_error_delete)
2138            .saturating_add(self.sql_write_error_corruption)
2139            .saturating_add(self.sql_write_error_incompatible_persisted_format)
2140            .saturating_add(self.sql_write_error_not_found)
2141            .saturating_add(self.sql_write_error_internal)
2142            .saturating_add(self.sql_write_error_conflict)
2143            .saturating_add(self.sql_write_error_unsupported)
2144            .saturating_add(self.sql_write_error_invariant_violation)
2145            .saturating_add(self.index_inserts)
2146            .saturating_add(self.index_removes)
2147            .saturating_add(self.reverse_index_inserts)
2148            .saturating_add(self.reverse_index_removes)
2149            .saturating_add(self.relation_reverse_lookups)
2150            .saturating_add(self.relation_delete_blocks)
2151            .saturating_add(self.write_rows_touched)
2152            .saturating_add(self.write_index_entries_changed)
2153            .saturating_add(self.write_reverse_index_entries_changed)
2154            .saturating_add(self.write_relation_checks)
2155            .saturating_add(self.unique_violations)
2156            .saturating_add(self.non_atomic_partial_commits)
2157            .saturating_add(self.non_atomic_partial_rows_committed)
2158    }
2159}
2160
2161// Project mutable per-entity counters into the stable report DTO.
2162//
2163// Keeping this projection out of `report_window_start` leaves the window
2164// filtering logic readable while still making every report field explicit.
2165#[expect(clippy::too_many_lines)]
2166fn entity_summary_from_counters(path: &str, ops: &EntityCounters) -> EntitySummary {
2167    EntitySummary {
2168        path: path.to_string(),
2169        load_calls: ops.load_calls,
2170        save_calls: ops.save_calls,
2171        delete_calls: ops.delete_calls,
2172        save_insert_calls: ops.save_insert_calls,
2173        save_update_calls: ops.save_update_calls,
2174        save_replace_calls: ops.save_replace_calls,
2175        exec_success: ops.exec_success,
2176        exec_error_corruption: ops.exec_error_corruption,
2177        exec_error_incompatible_persisted_format: ops.exec_error_incompatible_persisted_format,
2178        exec_error_not_found: ops.exec_error_not_found,
2179        exec_error_internal: ops.exec_error_internal,
2180        exec_error_conflict: ops.exec_error_conflict,
2181        exec_error_unsupported: ops.exec_error_unsupported,
2182        exec_error_invariant_violation: ops.exec_error_invariant_violation,
2183        exec_aborted: ops.exec_aborted,
2184        cache_shared_query_plan_hits: ops.cache_shared_query_plan_hits,
2185        cache_shared_query_plan_misses: ops.cache_shared_query_plan_misses,
2186        cache_shared_query_plan_inserts: ops.cache_shared_query_plan_inserts,
2187        cache_shared_query_plan_miss_cold: ops.cache_shared_query_plan_miss_cold,
2188        cache_shared_query_plan_miss_distinct_key: ops.cache_shared_query_plan_miss_distinct_key,
2189        cache_shared_query_plan_miss_method_version: ops
2190            .cache_shared_query_plan_miss_method_version,
2191        cache_shared_query_plan_miss_schema_fingerprint: ops
2192            .cache_shared_query_plan_miss_schema_fingerprint,
2193        cache_shared_query_plan_miss_visibility: ops.cache_shared_query_plan_miss_visibility,
2194        cache_sql_compiled_command_hits: ops.cache_sql_compiled_command_hits,
2195        cache_sql_compiled_command_misses: ops.cache_sql_compiled_command_misses,
2196        cache_sql_compiled_command_inserts: ops.cache_sql_compiled_command_inserts,
2197        cache_sql_compiled_command_miss_cold: ops.cache_sql_compiled_command_miss_cold,
2198        cache_sql_compiled_command_miss_distinct_key: ops
2199            .cache_sql_compiled_command_miss_distinct_key,
2200        cache_sql_compiled_command_miss_method_version: ops
2201            .cache_sql_compiled_command_miss_method_version,
2202        cache_sql_compiled_command_miss_schema_fingerprint: ops
2203            .cache_sql_compiled_command_miss_schema_fingerprint,
2204        cache_sql_compiled_command_miss_surface: ops.cache_sql_compiled_command_miss_surface,
2205        schema_reconcile_checks: ops.schema_reconcile_checks,
2206        schema_reconcile_exact_match: ops.schema_reconcile_exact_match,
2207        schema_reconcile_first_create: ops.schema_reconcile_first_create,
2208        schema_reconcile_latest_snapshot_corrupt: ops.schema_reconcile_latest_snapshot_corrupt,
2209        schema_reconcile_rejected_field_slot: ops.schema_reconcile_rejected_field_slot,
2210        schema_reconcile_rejected_other: ops.schema_reconcile_rejected_other,
2211        schema_reconcile_rejected_row_layout: ops.schema_reconcile_rejected_row_layout,
2212        schema_reconcile_rejected_schema_version: ops.schema_reconcile_rejected_schema_version,
2213        schema_reconcile_store_write_error: ops.schema_reconcile_store_write_error,
2214        schema_store_snapshots: ops.schema_store_snapshots,
2215        schema_store_encoded_bytes: ops.schema_store_encoded_bytes,
2216        schema_store_latest_snapshot_bytes: ops.schema_store_latest_snapshot_bytes,
2217        sql_compile_rejects: ops.sql_compile_rejects,
2218        sql_compile_reject_cache_key: ops.sql_compile_reject_cache_key,
2219        sql_compile_reject_parse: ops.sql_compile_reject_parse,
2220        sql_compile_reject_semantic: ops.sql_compile_reject_semantic,
2221        plan_index: ops.plan_index,
2222        plan_keys: ops.plan_keys,
2223        plan_range: ops.plan_range,
2224        plan_full_scan: ops.plan_full_scan,
2225        plan_by_key: ops.plan_by_key,
2226        plan_by_keys: ops.plan_by_keys,
2227        plan_key_range: ops.plan_key_range,
2228        plan_index_prefix: ops.plan_index_prefix,
2229        plan_index_multi_lookup: ops.plan_index_multi_lookup,
2230        plan_index_range: ops.plan_index_range,
2231        plan_explicit_full_scan: ops.plan_explicit_full_scan,
2232        plan_union: ops.plan_union,
2233        plan_intersection: ops.plan_intersection,
2234        plan_grouped_hash_materialized: ops.plan_grouped_hash_materialized,
2235        plan_grouped_ordered_materialized: ops.plan_grouped_ordered_materialized,
2236        plan_choice_conflicting_primary_key_children_access_preferred: ops
2237            .plan_choice_conflicting_primary_key_children_access_preferred,
2238        plan_choice_constant_false_predicate: ops.plan_choice_constant_false_predicate,
2239        plan_choice_empty_child_access_preferred: ops.plan_choice_empty_child_access_preferred,
2240        plan_choice_full_scan_access: ops.plan_choice_full_scan_access,
2241        plan_choice_intent_key_access_override: ops.plan_choice_intent_key_access_override,
2242        plan_choice_limit_zero_window: ops.plan_choice_limit_zero_window,
2243        plan_choice_non_index_access: ops.plan_choice_non_index_access,
2244        plan_choice_planner_composite_non_index: ops.plan_choice_planner_composite_non_index,
2245        plan_choice_planner_full_scan_fallback: ops.plan_choice_planner_full_scan_fallback,
2246        plan_choice_planner_key_set_access: ops.plan_choice_planner_key_set_access,
2247        plan_choice_planner_primary_key_lookup: ops.plan_choice_planner_primary_key_lookup,
2248        plan_choice_planner_primary_key_range: ops.plan_choice_planner_primary_key_range,
2249        plan_choice_required_order_primary_key_range_preferred: ops
2250            .plan_choice_required_order_primary_key_range_preferred,
2251        plan_choice_singleton_primary_key_child_access_preferred: ops
2252            .plan_choice_singleton_primary_key_child_access_preferred,
2253        prepared_shape_already_finalized: ops.prepared_shape_already_finalized,
2254        prepared_shape_generated_fallback: ops.prepared_shape_generated_fallback,
2255        rows_loaded: ops.rows_loaded,
2256        rows_saved: ops.rows_saved,
2257        rows_inserted: ops.rows_inserted,
2258        rows_updated: ops.rows_updated,
2259        rows_replaced: ops.rows_replaced,
2260        rows_scanned: ops.rows_scanned,
2261        rows_filtered: ops.rows_filtered,
2262        rows_aggregated: ops.rows_aggregated,
2263        rows_emitted: ops.rows_emitted,
2264        load_candidate_rows_scanned: ops.load_candidate_rows_scanned,
2265        load_candidate_rows_filtered: ops.load_candidate_rows_filtered,
2266        load_result_rows_emitted: ops.load_result_rows_emitted,
2267        rows_deleted: ops.rows_deleted,
2268        sql_insert_calls: ops.sql_insert_calls,
2269        sql_insert_select_calls: ops.sql_insert_select_calls,
2270        sql_update_calls: ops.sql_update_calls,
2271        sql_delete_calls: ops.sql_delete_calls,
2272        sql_write_matched_rows: ops.sql_write_matched_rows,
2273        sql_write_mutated_rows: ops.sql_write_mutated_rows,
2274        sql_write_returning_rows: ops.sql_write_returning_rows,
2275        sql_write_error_insert: ops.sql_write_error_insert,
2276        sql_write_error_insert_select: ops.sql_write_error_insert_select,
2277        sql_write_error_update: ops.sql_write_error_update,
2278        sql_write_error_delete: ops.sql_write_error_delete,
2279        sql_write_error_corruption: ops.sql_write_error_corruption,
2280        sql_write_error_incompatible_persisted_format: ops
2281            .sql_write_error_incompatible_persisted_format,
2282        sql_write_error_not_found: ops.sql_write_error_not_found,
2283        sql_write_error_internal: ops.sql_write_error_internal,
2284        sql_write_error_conflict: ops.sql_write_error_conflict,
2285        sql_write_error_unsupported: ops.sql_write_error_unsupported,
2286        sql_write_error_invariant_violation: ops.sql_write_error_invariant_violation,
2287        index_inserts: ops.index_inserts,
2288        index_removes: ops.index_removes,
2289        reverse_index_inserts: ops.reverse_index_inserts,
2290        reverse_index_removes: ops.reverse_index_removes,
2291        relation_reverse_lookups: ops.relation_reverse_lookups,
2292        relation_delete_blocks: ops.relation_delete_blocks,
2293        write_rows_touched: ops.write_rows_touched,
2294        write_index_entries_changed: ops.write_index_entries_changed,
2295        write_reverse_index_entries_changed: ops.write_reverse_index_entries_changed,
2296        write_relation_checks: ops.write_relation_checks,
2297        unique_violations: ops.unique_violations,
2298        non_atomic_partial_commits: ops.non_atomic_partial_commits,
2299        non_atomic_partial_rows_committed: ops.non_atomic_partial_rows_committed,
2300    }
2301}
2302
2303// Build a metrics report gated by `window_start_ms`.
2304//
2305// This is a window-start filter:
2306// - If `window_start_ms` is `None`, return the current window.
2307// - If `window_start_ms <= state.window_start_ms`, return the current window.
2308// - If `window_start_ms > state.window_start_ms`, return an empty report.
2309//
2310// IcyDB stores aggregate counters only, so it cannot produce a precise
2311// sub-window report after `state.window_start_ms`.
2312#[must_use]
2313pub(super) fn report_window_start(window_start_ms: Option<u64>) -> EventReport {
2314    let snap = with_state(Clone::clone);
2315    if let Some(requested_window_start_ms) = window_start_ms
2316        && requested_window_start_ms > snap.window_start_ms
2317    {
2318        return EventReport::new(
2319            None,
2320            Vec::new(),
2321            false,
2322            window_start_ms,
2323            snap.window_start_ms,
2324        );
2325    }
2326
2327    let mut entity_counters: Vec<EntitySummary> = Vec::new();
2328    for (path, ops) in &snap.entities {
2329        entity_counters.push(entity_summary_from_counters(path, ops));
2330    }
2331
2332    entity_counters.sort_by(|a, b| {
2333        b.activity_score()
2334            .cmp(&a.activity_score())
2335            .then_with(|| b.rows_loaded.cmp(&a.rows_loaded))
2336            .then_with(|| b.rows_saved.cmp(&a.rows_saved))
2337            .then_with(|| b.rows_scanned.cmp(&a.rows_scanned))
2338            .then_with(|| b.rows_deleted.cmp(&a.rows_deleted))
2339            .then_with(|| a.path.cmp(&b.path))
2340    });
2341
2342    EventReport::new(
2343        Some(EventCounters::new(
2344            snap.ops.clone(),
2345            snap.perf.clone(),
2346            snap.window_start_ms,
2347            now_millis(),
2348        )),
2349        entity_counters,
2350        true,
2351        window_start_ms,
2352        snap.window_start_ms,
2353    )
2354}