Skip to main content

icydb_core/metrics/
state.rs

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