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