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