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