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