1use candid::CandidType;
11use canic_cdk::utils::time::now_millis;
12use serde::Deserialize;
13use std::{cell::RefCell, collections::BTreeMap};
14
15#[derive(Clone, Debug)]
16pub(crate) struct EventState {
17 pub(crate) ops: EventOps,
18 pub(crate) perf: EventPerf,
19 pub(crate) entities: BTreeMap<String, EntityCounters>,
20 pub(crate) window_start_ms: u64,
21}
22
23impl Default for EventState {
24 fn default() -> Self {
25 Self {
26 ops: EventOps::default(),
27 perf: EventPerf::default(),
28 entities: BTreeMap::new(),
29 window_start_ms: now_millis(),
30 }
31 }
32}
33
34#[cfg_attr(doc, doc = "EventOps\n\nOperation counters.")]
35#[derive(CandidType, Clone, Debug, Default, Deserialize)]
36pub struct EventOps {
37 pub(crate) load_calls: u64,
39 pub(crate) save_calls: u64,
40 pub(crate) delete_calls: u64,
41 pub(crate) save_insert_calls: u64,
42 pub(crate) save_update_calls: u64,
43 pub(crate) save_replace_calls: u64,
44 pub(crate) exec_success: u64,
45 pub(crate) exec_error_corruption: u64,
46 pub(crate) exec_error_incompatible_persisted_format: u64,
47 pub(crate) exec_error_not_found: u64,
48 pub(crate) exec_error_internal: u64,
49 pub(crate) exec_error_conflict: u64,
50 pub(crate) exec_error_unsupported: u64,
51 pub(crate) exec_error_invariant_violation: u64,
52 pub(crate) exec_aborted: u64,
53 pub(crate) cache_shared_query_plan_hits: u64,
54 pub(crate) cache_shared_query_plan_misses: u64,
55 pub(crate) cache_shared_query_plan_inserts: u64,
56 pub(crate) cache_shared_query_plan_entries: u64,
57 pub(crate) cache_sql_compiled_command_hits: u64,
58 pub(crate) cache_sql_compiled_command_misses: u64,
59 pub(crate) cache_sql_compiled_command_inserts: u64,
60 pub(crate) cache_sql_compiled_command_entries: u64,
61
62 pub(crate) plan_index: u64,
64 pub(crate) plan_keys: u64,
65 pub(crate) plan_range: u64,
66 pub(crate) plan_full_scan: u64,
67 pub(crate) plan_by_key: u64,
68 pub(crate) plan_by_keys: u64,
69 pub(crate) plan_key_range: u64,
70 pub(crate) plan_index_prefix: u64,
71 pub(crate) plan_index_multi_lookup: u64,
72 pub(crate) plan_index_range: u64,
73 pub(crate) plan_explicit_full_scan: u64,
74 pub(crate) plan_union: u64,
75 pub(crate) plan_intersection: u64,
76 pub(crate) plan_grouped_hash_materialized: u64,
77 pub(crate) plan_grouped_ordered_materialized: u64,
78
79 pub(crate) rows_loaded: u64,
81 pub(crate) rows_saved: u64,
82 pub(crate) rows_inserted: u64,
83 pub(crate) rows_updated: u64,
84 pub(crate) rows_replaced: u64,
85 pub(crate) rows_scanned: u64,
86 pub(crate) rows_filtered: u64,
87 pub(crate) rows_aggregated: u64,
88 pub(crate) rows_emitted: u64,
89 pub(crate) load_candidate_rows_scanned: u64,
90 pub(crate) load_candidate_rows_filtered: u64,
91 pub(crate) load_result_rows_emitted: u64,
92 pub(crate) rows_deleted: u64,
93 pub(crate) sql_insert_calls: u64,
94 pub(crate) sql_insert_select_calls: u64,
95 pub(crate) sql_update_calls: u64,
96 pub(crate) sql_delete_calls: u64,
97 pub(crate) sql_write_matched_rows: u64,
98 pub(crate) sql_write_mutated_rows: u64,
99 pub(crate) sql_write_returning_rows: u64,
100
101 pub(crate) index_inserts: u64,
103 pub(crate) index_removes: u64,
104 pub(crate) reverse_index_inserts: u64,
105 pub(crate) reverse_index_removes: u64,
106 pub(crate) relation_reverse_lookups: u64,
107 pub(crate) relation_delete_blocks: u64,
108 pub(crate) write_rows_touched: u64,
109 pub(crate) write_index_entries_changed: u64,
110 pub(crate) write_reverse_index_entries_changed: u64,
111 pub(crate) write_relation_checks: u64,
112 pub(crate) unique_violations: u64,
113 pub(crate) non_atomic_partial_commits: u64,
114 pub(crate) non_atomic_partial_rows_committed: u64,
115}
116
117impl EventOps {
118 #[must_use]
119 pub const fn load_calls(&self) -> u64 {
120 self.load_calls
121 }
122
123 #[must_use]
124 pub const fn save_calls(&self) -> u64 {
125 self.save_calls
126 }
127
128 #[must_use]
129 pub const fn delete_calls(&self) -> u64 {
130 self.delete_calls
131 }
132
133 #[must_use]
134 pub const fn save_insert_calls(&self) -> u64 {
135 self.save_insert_calls
136 }
137
138 #[must_use]
139 pub const fn save_update_calls(&self) -> u64 {
140 self.save_update_calls
141 }
142
143 #[must_use]
144 pub const fn save_replace_calls(&self) -> u64 {
145 self.save_replace_calls
146 }
147
148 #[must_use]
149 pub const fn exec_success(&self) -> u64 {
150 self.exec_success
151 }
152
153 #[must_use]
154 pub const fn exec_error_corruption(&self) -> u64 {
155 self.exec_error_corruption
156 }
157
158 #[must_use]
159 pub const fn exec_error_incompatible_persisted_format(&self) -> u64 {
160 self.exec_error_incompatible_persisted_format
161 }
162
163 #[must_use]
164 pub const fn exec_error_not_found(&self) -> u64 {
165 self.exec_error_not_found
166 }
167
168 #[must_use]
169 pub const fn exec_error_internal(&self) -> u64 {
170 self.exec_error_internal
171 }
172
173 #[must_use]
174 pub const fn exec_error_conflict(&self) -> u64 {
175 self.exec_error_conflict
176 }
177
178 #[must_use]
179 pub const fn exec_error_unsupported(&self) -> u64 {
180 self.exec_error_unsupported
181 }
182
183 #[must_use]
184 pub const fn exec_error_invariant_violation(&self) -> u64 {
185 self.exec_error_invariant_violation
186 }
187
188 #[must_use]
189 pub const fn exec_aborted(&self) -> u64 {
190 self.exec_aborted
191 }
192
193 #[must_use]
194 pub const fn cache_shared_query_plan_hits(&self) -> u64 {
195 self.cache_shared_query_plan_hits
196 }
197
198 #[must_use]
199 pub const fn cache_shared_query_plan_misses(&self) -> u64 {
200 self.cache_shared_query_plan_misses
201 }
202
203 #[must_use]
204 pub const fn cache_shared_query_plan_inserts(&self) -> u64 {
205 self.cache_shared_query_plan_inserts
206 }
207
208 #[must_use]
209 pub const fn cache_shared_query_plan_entries(&self) -> u64 {
210 self.cache_shared_query_plan_entries
211 }
212
213 #[must_use]
214 pub const fn cache_sql_compiled_command_hits(&self) -> u64 {
215 self.cache_sql_compiled_command_hits
216 }
217
218 #[must_use]
219 pub const fn cache_sql_compiled_command_misses(&self) -> u64 {
220 self.cache_sql_compiled_command_misses
221 }
222
223 #[must_use]
224 pub const fn cache_sql_compiled_command_inserts(&self) -> u64 {
225 self.cache_sql_compiled_command_inserts
226 }
227
228 #[must_use]
229 pub const fn cache_sql_compiled_command_entries(&self) -> u64 {
230 self.cache_sql_compiled_command_entries
231 }
232
233 #[must_use]
234 pub const fn plan_index(&self) -> u64 {
235 self.plan_index
236 }
237
238 #[must_use]
239 pub const fn plan_keys(&self) -> u64 {
240 self.plan_keys
241 }
242
243 #[must_use]
244 pub const fn plan_range(&self) -> u64 {
245 self.plan_range
246 }
247
248 #[must_use]
249 pub const fn plan_full_scan(&self) -> u64 {
250 self.plan_full_scan
251 }
252
253 #[must_use]
254 pub const fn plan_by_key(&self) -> u64 {
255 self.plan_by_key
256 }
257
258 #[must_use]
259 pub const fn plan_by_keys(&self) -> u64 {
260 self.plan_by_keys
261 }
262
263 #[must_use]
264 pub const fn plan_key_range(&self) -> u64 {
265 self.plan_key_range
266 }
267
268 #[must_use]
269 pub const fn plan_index_prefix(&self) -> u64 {
270 self.plan_index_prefix
271 }
272
273 #[must_use]
274 pub const fn plan_index_multi_lookup(&self) -> u64 {
275 self.plan_index_multi_lookup
276 }
277
278 #[must_use]
279 pub const fn plan_index_range(&self) -> u64 {
280 self.plan_index_range
281 }
282
283 #[must_use]
284 pub const fn plan_explicit_full_scan(&self) -> u64 {
285 self.plan_explicit_full_scan
286 }
287
288 #[must_use]
289 pub const fn plan_union(&self) -> u64 {
290 self.plan_union
291 }
292
293 #[must_use]
294 pub const fn plan_intersection(&self) -> u64 {
295 self.plan_intersection
296 }
297
298 #[must_use]
299 pub const fn plan_grouped_hash_materialized(&self) -> u64 {
300 self.plan_grouped_hash_materialized
301 }
302
303 #[must_use]
304 pub const fn plan_grouped_ordered_materialized(&self) -> u64 {
305 self.plan_grouped_ordered_materialized
306 }
307
308 #[must_use]
309 pub const fn rows_loaded(&self) -> u64 {
310 self.rows_loaded
311 }
312
313 #[must_use]
314 pub const fn rows_saved(&self) -> u64 {
315 self.rows_saved
316 }
317
318 #[must_use]
319 pub const fn rows_inserted(&self) -> u64 {
320 self.rows_inserted
321 }
322
323 #[must_use]
324 pub const fn rows_updated(&self) -> u64 {
325 self.rows_updated
326 }
327
328 #[must_use]
329 pub const fn rows_replaced(&self) -> u64 {
330 self.rows_replaced
331 }
332
333 #[must_use]
334 pub const fn rows_scanned(&self) -> u64 {
335 self.rows_scanned
336 }
337
338 #[must_use]
339 pub const fn rows_filtered(&self) -> u64 {
340 self.rows_filtered
341 }
342
343 #[must_use]
344 pub const fn rows_aggregated(&self) -> u64 {
345 self.rows_aggregated
346 }
347
348 #[must_use]
349 pub const fn rows_emitted(&self) -> u64 {
350 self.rows_emitted
351 }
352
353 #[must_use]
354 pub const fn load_candidate_rows_scanned(&self) -> u64 {
355 self.load_candidate_rows_scanned
356 }
357
358 #[must_use]
359 pub const fn load_candidate_rows_filtered(&self) -> u64 {
360 self.load_candidate_rows_filtered
361 }
362
363 #[must_use]
364 pub const fn load_result_rows_emitted(&self) -> u64 {
365 self.load_result_rows_emitted
366 }
367
368 #[must_use]
369 pub const fn rows_deleted(&self) -> u64 {
370 self.rows_deleted
371 }
372
373 #[must_use]
374 pub const fn sql_insert_calls(&self) -> u64 {
375 self.sql_insert_calls
376 }
377
378 #[must_use]
379 pub const fn sql_insert_select_calls(&self) -> u64 {
380 self.sql_insert_select_calls
381 }
382
383 #[must_use]
384 pub const fn sql_update_calls(&self) -> u64 {
385 self.sql_update_calls
386 }
387
388 #[must_use]
389 pub const fn sql_delete_calls(&self) -> u64 {
390 self.sql_delete_calls
391 }
392
393 #[must_use]
394 pub const fn sql_write_matched_rows(&self) -> u64 {
395 self.sql_write_matched_rows
396 }
397
398 #[must_use]
399 pub const fn sql_write_mutated_rows(&self) -> u64 {
400 self.sql_write_mutated_rows
401 }
402
403 #[must_use]
404 pub const fn sql_write_returning_rows(&self) -> u64 {
405 self.sql_write_returning_rows
406 }
407
408 #[must_use]
409 pub const fn index_inserts(&self) -> u64 {
410 self.index_inserts
411 }
412
413 #[must_use]
414 pub const fn index_removes(&self) -> u64 {
415 self.index_removes
416 }
417
418 #[must_use]
419 pub const fn reverse_index_inserts(&self) -> u64 {
420 self.reverse_index_inserts
421 }
422
423 #[must_use]
424 pub const fn reverse_index_removes(&self) -> u64 {
425 self.reverse_index_removes
426 }
427
428 #[must_use]
429 pub const fn relation_reverse_lookups(&self) -> u64 {
430 self.relation_reverse_lookups
431 }
432
433 #[must_use]
434 pub const fn relation_delete_blocks(&self) -> u64 {
435 self.relation_delete_blocks
436 }
437
438 #[must_use]
439 pub const fn write_rows_touched(&self) -> u64 {
440 self.write_rows_touched
441 }
442
443 #[must_use]
444 pub const fn write_index_entries_changed(&self) -> u64 {
445 self.write_index_entries_changed
446 }
447
448 #[must_use]
449 pub const fn write_reverse_index_entries_changed(&self) -> u64 {
450 self.write_reverse_index_entries_changed
451 }
452
453 #[must_use]
454 pub const fn write_relation_checks(&self) -> u64 {
455 self.write_relation_checks
456 }
457
458 #[must_use]
459 pub const fn unique_violations(&self) -> u64 {
460 self.unique_violations
461 }
462
463 #[must_use]
464 pub const fn non_atomic_partial_commits(&self) -> u64 {
465 self.non_atomic_partial_commits
466 }
467
468 #[must_use]
469 pub const fn non_atomic_partial_rows_committed(&self) -> u64 {
470 self.non_atomic_partial_rows_committed
471 }
472}
473
474#[derive(Clone, Debug, Default)]
475pub(crate) struct EntityCounters {
476 pub(crate) load_calls: u64,
477 pub(crate) save_calls: u64,
478 pub(crate) delete_calls: u64,
479 pub(crate) save_insert_calls: u64,
480 pub(crate) save_update_calls: u64,
481 pub(crate) save_replace_calls: u64,
482 pub(crate) exec_success: u64,
483 pub(crate) exec_error_corruption: u64,
484 pub(crate) exec_error_incompatible_persisted_format: u64,
485 pub(crate) exec_error_not_found: u64,
486 pub(crate) exec_error_internal: u64,
487 pub(crate) exec_error_conflict: u64,
488 pub(crate) exec_error_unsupported: u64,
489 pub(crate) exec_error_invariant_violation: u64,
490 pub(crate) exec_aborted: u64,
491 pub(crate) cache_shared_query_plan_hits: u64,
492 pub(crate) cache_shared_query_plan_misses: u64,
493 pub(crate) cache_shared_query_plan_inserts: u64,
494 pub(crate) cache_sql_compiled_command_hits: u64,
495 pub(crate) cache_sql_compiled_command_misses: u64,
496 pub(crate) cache_sql_compiled_command_inserts: u64,
497 pub(crate) plan_index: u64,
498 pub(crate) plan_keys: u64,
499 pub(crate) plan_range: u64,
500 pub(crate) plan_full_scan: u64,
501 pub(crate) plan_by_key: u64,
502 pub(crate) plan_by_keys: u64,
503 pub(crate) plan_key_range: u64,
504 pub(crate) plan_index_prefix: u64,
505 pub(crate) plan_index_multi_lookup: u64,
506 pub(crate) plan_index_range: u64,
507 pub(crate) plan_explicit_full_scan: u64,
508 pub(crate) plan_union: u64,
509 pub(crate) plan_intersection: u64,
510 pub(crate) plan_grouped_hash_materialized: u64,
511 pub(crate) plan_grouped_ordered_materialized: u64,
512 pub(crate) rows_loaded: u64,
513 pub(crate) rows_saved: u64,
514 pub(crate) rows_inserted: u64,
515 pub(crate) rows_updated: u64,
516 pub(crate) rows_replaced: u64,
517 pub(crate) rows_scanned: u64,
518 pub(crate) rows_filtered: u64,
519 pub(crate) rows_aggregated: u64,
520 pub(crate) rows_emitted: u64,
521 pub(crate) load_candidate_rows_scanned: u64,
522 pub(crate) load_candidate_rows_filtered: u64,
523 pub(crate) load_result_rows_emitted: u64,
524 pub(crate) rows_deleted: u64,
525 pub(crate) sql_insert_calls: u64,
526 pub(crate) sql_insert_select_calls: u64,
527 pub(crate) sql_update_calls: u64,
528 pub(crate) sql_delete_calls: u64,
529 pub(crate) sql_write_matched_rows: u64,
530 pub(crate) sql_write_mutated_rows: u64,
531 pub(crate) sql_write_returning_rows: u64,
532 pub(crate) index_inserts: u64,
533 pub(crate) index_removes: u64,
534 pub(crate) reverse_index_inserts: u64,
535 pub(crate) reverse_index_removes: u64,
536 pub(crate) relation_reverse_lookups: u64,
537 pub(crate) relation_delete_blocks: u64,
538 pub(crate) write_rows_touched: u64,
539 pub(crate) write_index_entries_changed: u64,
540 pub(crate) write_reverse_index_entries_changed: u64,
541 pub(crate) write_relation_checks: u64,
542 pub(crate) unique_violations: u64,
543 pub(crate) non_atomic_partial_commits: u64,
544 pub(crate) non_atomic_partial_rows_committed: u64,
545}
546
547#[cfg_attr(doc, doc = "EventPerf\n\nInstruction totals and maxima.")]
548#[derive(CandidType, Clone, Debug, Default, Deserialize)]
549pub struct EventPerf {
550 pub(crate) load_inst_total: u128,
552 pub(crate) save_inst_total: u128,
553 pub(crate) delete_inst_total: u128,
554
555 pub(crate) load_inst_max: u64,
557 pub(crate) save_inst_max: u64,
558 pub(crate) delete_inst_max: u64,
559}
560
561impl EventPerf {
562 #[must_use]
563 pub const fn new(
564 load_inst_total: u128,
565 save_inst_total: u128,
566 delete_inst_total: u128,
567 load_inst_max: u64,
568 save_inst_max: u64,
569 delete_inst_max: u64,
570 ) -> Self {
571 Self {
572 load_inst_total,
573 save_inst_total,
574 delete_inst_total,
575 load_inst_max,
576 save_inst_max,
577 delete_inst_max,
578 }
579 }
580
581 #[must_use]
582 pub const fn load_inst_total(&self) -> u128 {
583 self.load_inst_total
584 }
585
586 #[must_use]
587 pub const fn save_inst_total(&self) -> u128 {
588 self.save_inst_total
589 }
590
591 #[must_use]
592 pub const fn delete_inst_total(&self) -> u128 {
593 self.delete_inst_total
594 }
595
596 #[must_use]
597 pub const fn load_inst_max(&self) -> u64 {
598 self.load_inst_max
599 }
600
601 #[must_use]
602 pub const fn save_inst_max(&self) -> u64 {
603 self.save_inst_max
604 }
605
606 #[must_use]
607 pub const fn delete_inst_max(&self) -> u64 {
608 self.delete_inst_max
609 }
610}
611
612thread_local! {
613 static EVENT_STATE: RefCell<EventState> = RefCell::new(EventState::default());
614}
615
616pub(crate) fn with_state<R>(f: impl FnOnce(&EventState) -> R) -> R {
618 EVENT_STATE.with(|m| f(&m.borrow()))
619}
620
621pub(crate) fn with_state_mut<R>(f: impl FnOnce(&mut EventState) -> R) -> R {
623 EVENT_STATE.with(|m| f(&mut m.borrow_mut()))
624}
625
626pub(super) fn reset() {
628 with_state_mut(|m| *m = EventState::default());
629}
630
631pub(crate) fn reset_all() {
633 reset();
634}
635
636pub(super) fn add_instructions(total: &mut u128, max: &mut u64, delta_inst: u64) {
638 *total = total.saturating_add(u128::from(delta_inst));
639 if delta_inst > *max {
640 *max = delta_inst;
641 }
642}
643
644#[cfg_attr(doc, doc = "EventReport\n\nMetrics query payload.")]
645#[derive(CandidType, Clone, Debug, Default, Deserialize)]
646pub struct EventReport {
647 counters: Option<EventCounters>,
648 entity_counters: Vec<EntitySummary>,
649 window_filter_matched: bool,
650 requested_window_start_ms: Option<u64>,
651 active_window_start_ms: u64,
652}
653
654impl EventReport {
655 #[must_use]
656 pub(crate) const fn new(
657 counters: Option<EventCounters>,
658 entity_counters: Vec<EntitySummary>,
659 window_filter_matched: bool,
660 requested_window_start_ms: Option<u64>,
661 active_window_start_ms: u64,
662 ) -> Self {
663 Self {
664 counters,
665 entity_counters,
666 window_filter_matched,
667 requested_window_start_ms,
668 active_window_start_ms,
669 }
670 }
671
672 #[must_use]
673 pub const fn counters(&self) -> Option<&EventCounters> {
674 self.counters.as_ref()
675 }
676
677 #[must_use]
678 pub fn entity_counters(&self) -> &[EntitySummary] {
679 &self.entity_counters
680 }
681
682 #[must_use]
683 pub const fn window_filter_matched(&self) -> bool {
684 self.window_filter_matched
685 }
686
687 #[must_use]
688 pub const fn requested_window_start_ms(&self) -> Option<u64> {
689 self.requested_window_start_ms
690 }
691
692 #[must_use]
693 pub const fn active_window_start_ms(&self) -> u64 {
694 self.active_window_start_ms
695 }
696
697 #[must_use]
698 pub fn into_counters(self) -> Option<EventCounters> {
699 self.counters
700 }
701
702 #[must_use]
703 pub fn into_entity_counters(self) -> Vec<EntitySummary> {
704 self.entity_counters
705 }
706}
707
708#[derive(CandidType, Clone, Debug, Default, Deserialize)]
717pub struct EventCounters {
718 pub(crate) ops: EventOps,
719 pub(crate) perf: EventPerf,
720 pub(crate) window_start_ms: u64,
721 pub(crate) window_end_ms: u64,
722 pub(crate) window_duration_ms: u64,
723}
724
725impl EventCounters {
726 #[must_use]
727 pub(crate) const fn new(
728 ops: EventOps,
729 perf: EventPerf,
730 window_start_ms: u64,
731 window_end_ms: u64,
732 ) -> Self {
733 Self {
734 ops,
735 perf,
736 window_start_ms,
737 window_end_ms,
738 window_duration_ms: window_end_ms.saturating_sub(window_start_ms),
739 }
740 }
741
742 #[must_use]
743 pub const fn ops(&self) -> &EventOps {
744 &self.ops
745 }
746
747 #[must_use]
748 pub const fn perf(&self) -> &EventPerf {
749 &self.perf
750 }
751
752 #[must_use]
753 pub const fn window_start_ms(&self) -> u64 {
754 self.window_start_ms
755 }
756
757 #[must_use]
758 pub const fn window_end_ms(&self) -> u64 {
759 self.window_end_ms
760 }
761
762 #[must_use]
763 pub const fn window_duration_ms(&self) -> u64 {
764 self.window_duration_ms
765 }
766}
767
768#[cfg_attr(doc, doc = "EntitySummary\n\nPer-entity metrics summary.")]
769#[derive(CandidType, Clone, Debug, Default, Deserialize)]
770pub struct EntitySummary {
771 path: String,
772 load_calls: u64,
773 save_calls: u64,
774 delete_calls: u64,
775 save_insert_calls: u64,
776 save_update_calls: u64,
777 save_replace_calls: u64,
778 exec_success: u64,
779 exec_error_corruption: u64,
780 exec_error_incompatible_persisted_format: u64,
781 exec_error_not_found: u64,
782 exec_error_internal: u64,
783 exec_error_conflict: u64,
784 exec_error_unsupported: u64,
785 exec_error_invariant_violation: u64,
786 exec_aborted: u64,
787 cache_shared_query_plan_hits: u64,
788 cache_shared_query_plan_misses: u64,
789 cache_shared_query_plan_inserts: u64,
790 cache_sql_compiled_command_hits: u64,
791 cache_sql_compiled_command_misses: u64,
792 cache_sql_compiled_command_inserts: u64,
793 plan_index: u64,
794 plan_keys: u64,
795 plan_range: u64,
796 plan_full_scan: u64,
797 plan_by_key: u64,
798 plan_by_keys: u64,
799 plan_key_range: u64,
800 plan_index_prefix: u64,
801 plan_index_multi_lookup: u64,
802 plan_index_range: u64,
803 plan_explicit_full_scan: u64,
804 plan_union: u64,
805 plan_intersection: u64,
806 plan_grouped_hash_materialized: u64,
807 plan_grouped_ordered_materialized: u64,
808 rows_loaded: u64,
809 rows_saved: u64,
810 rows_inserted: u64,
811 rows_updated: u64,
812 rows_replaced: u64,
813 rows_scanned: u64,
814 rows_filtered: u64,
815 rows_aggregated: u64,
816 rows_emitted: u64,
817 load_candidate_rows_scanned: u64,
818 load_candidate_rows_filtered: u64,
819 load_result_rows_emitted: u64,
820 rows_deleted: u64,
821 sql_insert_calls: u64,
822 sql_insert_select_calls: u64,
823 sql_update_calls: u64,
824 sql_delete_calls: u64,
825 sql_write_matched_rows: u64,
826 sql_write_mutated_rows: u64,
827 sql_write_returning_rows: u64,
828 index_inserts: u64,
829 index_removes: u64,
830 reverse_index_inserts: u64,
831 reverse_index_removes: u64,
832 relation_reverse_lookups: u64,
833 relation_delete_blocks: u64,
834 write_rows_touched: u64,
835 write_index_entries_changed: u64,
836 write_reverse_index_entries_changed: u64,
837 write_relation_checks: u64,
838 unique_violations: u64,
839 non_atomic_partial_commits: u64,
840 non_atomic_partial_rows_committed: u64,
841}
842
843impl EntitySummary {
844 #[must_use]
845 pub const fn path(&self) -> &str {
846 self.path.as_str()
847 }
848
849 #[must_use]
850 pub const fn load_calls(&self) -> u64 {
851 self.load_calls
852 }
853
854 #[must_use]
855 pub const fn save_calls(&self) -> u64 {
856 self.save_calls
857 }
858
859 #[must_use]
860 pub const fn delete_calls(&self) -> u64 {
861 self.delete_calls
862 }
863
864 #[must_use]
865 pub const fn save_insert_calls(&self) -> u64 {
866 self.save_insert_calls
867 }
868
869 #[must_use]
870 pub const fn save_update_calls(&self) -> u64 {
871 self.save_update_calls
872 }
873
874 #[must_use]
875 pub const fn save_replace_calls(&self) -> u64 {
876 self.save_replace_calls
877 }
878
879 #[must_use]
880 pub const fn exec_success(&self) -> u64 {
881 self.exec_success
882 }
883
884 #[must_use]
885 pub const fn exec_error_corruption(&self) -> u64 {
886 self.exec_error_corruption
887 }
888
889 #[must_use]
890 pub const fn exec_error_incompatible_persisted_format(&self) -> u64 {
891 self.exec_error_incompatible_persisted_format
892 }
893
894 #[must_use]
895 pub const fn exec_error_not_found(&self) -> u64 {
896 self.exec_error_not_found
897 }
898
899 #[must_use]
900 pub const fn exec_error_internal(&self) -> u64 {
901 self.exec_error_internal
902 }
903
904 #[must_use]
905 pub const fn exec_error_conflict(&self) -> u64 {
906 self.exec_error_conflict
907 }
908
909 #[must_use]
910 pub const fn exec_error_unsupported(&self) -> u64 {
911 self.exec_error_unsupported
912 }
913
914 #[must_use]
915 pub const fn exec_error_invariant_violation(&self) -> u64 {
916 self.exec_error_invariant_violation
917 }
918
919 #[must_use]
920 pub const fn exec_aborted(&self) -> u64 {
921 self.exec_aborted
922 }
923
924 #[must_use]
925 pub const fn cache_shared_query_plan_hits(&self) -> u64 {
926 self.cache_shared_query_plan_hits
927 }
928
929 #[must_use]
930 pub const fn cache_shared_query_plan_misses(&self) -> u64 {
931 self.cache_shared_query_plan_misses
932 }
933
934 #[must_use]
935 pub const fn cache_shared_query_plan_inserts(&self) -> u64 {
936 self.cache_shared_query_plan_inserts
937 }
938
939 #[must_use]
940 pub const fn cache_sql_compiled_command_hits(&self) -> u64 {
941 self.cache_sql_compiled_command_hits
942 }
943
944 #[must_use]
945 pub const fn cache_sql_compiled_command_misses(&self) -> u64 {
946 self.cache_sql_compiled_command_misses
947 }
948
949 #[must_use]
950 pub const fn cache_sql_compiled_command_inserts(&self) -> u64 {
951 self.cache_sql_compiled_command_inserts
952 }
953
954 #[must_use]
955 pub const fn plan_index(&self) -> u64 {
956 self.plan_index
957 }
958
959 #[must_use]
960 pub const fn plan_keys(&self) -> u64 {
961 self.plan_keys
962 }
963
964 #[must_use]
965 pub const fn plan_range(&self) -> u64 {
966 self.plan_range
967 }
968
969 #[must_use]
970 pub const fn plan_full_scan(&self) -> u64 {
971 self.plan_full_scan
972 }
973
974 #[must_use]
975 pub const fn plan_by_key(&self) -> u64 {
976 self.plan_by_key
977 }
978
979 #[must_use]
980 pub const fn plan_by_keys(&self) -> u64 {
981 self.plan_by_keys
982 }
983
984 #[must_use]
985 pub const fn plan_key_range(&self) -> u64 {
986 self.plan_key_range
987 }
988
989 #[must_use]
990 pub const fn plan_index_prefix(&self) -> u64 {
991 self.plan_index_prefix
992 }
993
994 #[must_use]
995 pub const fn plan_index_multi_lookup(&self) -> u64 {
996 self.plan_index_multi_lookup
997 }
998
999 #[must_use]
1000 pub const fn plan_index_range(&self) -> u64 {
1001 self.plan_index_range
1002 }
1003
1004 #[must_use]
1005 pub const fn plan_explicit_full_scan(&self) -> u64 {
1006 self.plan_explicit_full_scan
1007 }
1008
1009 #[must_use]
1010 pub const fn plan_union(&self) -> u64 {
1011 self.plan_union
1012 }
1013
1014 #[must_use]
1015 pub const fn plan_intersection(&self) -> u64 {
1016 self.plan_intersection
1017 }
1018
1019 #[must_use]
1020 pub const fn plan_grouped_hash_materialized(&self) -> u64 {
1021 self.plan_grouped_hash_materialized
1022 }
1023
1024 #[must_use]
1025 pub const fn plan_grouped_ordered_materialized(&self) -> u64 {
1026 self.plan_grouped_ordered_materialized
1027 }
1028
1029 #[must_use]
1030 pub const fn rows_loaded(&self) -> u64 {
1031 self.rows_loaded
1032 }
1033
1034 #[must_use]
1035 pub const fn rows_saved(&self) -> u64 {
1036 self.rows_saved
1037 }
1038
1039 #[must_use]
1040 pub const fn rows_inserted(&self) -> u64 {
1041 self.rows_inserted
1042 }
1043
1044 #[must_use]
1045 pub const fn rows_updated(&self) -> u64 {
1046 self.rows_updated
1047 }
1048
1049 #[must_use]
1050 pub const fn rows_replaced(&self) -> u64 {
1051 self.rows_replaced
1052 }
1053
1054 #[must_use]
1055 pub const fn rows_scanned(&self) -> u64 {
1056 self.rows_scanned
1057 }
1058
1059 #[must_use]
1060 pub const fn rows_filtered(&self) -> u64 {
1061 self.rows_filtered
1062 }
1063
1064 #[must_use]
1065 pub const fn rows_aggregated(&self) -> u64 {
1066 self.rows_aggregated
1067 }
1068
1069 #[must_use]
1070 pub const fn rows_emitted(&self) -> u64 {
1071 self.rows_emitted
1072 }
1073
1074 #[must_use]
1075 pub const fn load_candidate_rows_scanned(&self) -> u64 {
1076 self.load_candidate_rows_scanned
1077 }
1078
1079 #[must_use]
1080 pub const fn load_candidate_rows_filtered(&self) -> u64 {
1081 self.load_candidate_rows_filtered
1082 }
1083
1084 #[must_use]
1085 pub const fn load_result_rows_emitted(&self) -> u64 {
1086 self.load_result_rows_emitted
1087 }
1088
1089 #[must_use]
1090 pub const fn rows_deleted(&self) -> u64 {
1091 self.rows_deleted
1092 }
1093
1094 #[must_use]
1095 pub const fn sql_insert_calls(&self) -> u64 {
1096 self.sql_insert_calls
1097 }
1098
1099 #[must_use]
1100 pub const fn sql_insert_select_calls(&self) -> u64 {
1101 self.sql_insert_select_calls
1102 }
1103
1104 #[must_use]
1105 pub const fn sql_update_calls(&self) -> u64 {
1106 self.sql_update_calls
1107 }
1108
1109 #[must_use]
1110 pub const fn sql_delete_calls(&self) -> u64 {
1111 self.sql_delete_calls
1112 }
1113
1114 #[must_use]
1115 pub const fn sql_write_matched_rows(&self) -> u64 {
1116 self.sql_write_matched_rows
1117 }
1118
1119 #[must_use]
1120 pub const fn sql_write_mutated_rows(&self) -> u64 {
1121 self.sql_write_mutated_rows
1122 }
1123
1124 #[must_use]
1125 pub const fn sql_write_returning_rows(&self) -> u64 {
1126 self.sql_write_returning_rows
1127 }
1128
1129 #[must_use]
1130 pub const fn index_inserts(&self) -> u64 {
1131 self.index_inserts
1132 }
1133
1134 #[must_use]
1135 pub const fn index_removes(&self) -> u64 {
1136 self.index_removes
1137 }
1138
1139 #[must_use]
1140 pub const fn reverse_index_inserts(&self) -> u64 {
1141 self.reverse_index_inserts
1142 }
1143
1144 #[must_use]
1145 pub const fn reverse_index_removes(&self) -> u64 {
1146 self.reverse_index_removes
1147 }
1148
1149 #[must_use]
1150 pub const fn relation_reverse_lookups(&self) -> u64 {
1151 self.relation_reverse_lookups
1152 }
1153
1154 #[must_use]
1155 pub const fn relation_delete_blocks(&self) -> u64 {
1156 self.relation_delete_blocks
1157 }
1158
1159 #[must_use]
1160 pub const fn write_rows_touched(&self) -> u64 {
1161 self.write_rows_touched
1162 }
1163
1164 #[must_use]
1165 pub const fn write_index_entries_changed(&self) -> u64 {
1166 self.write_index_entries_changed
1167 }
1168
1169 #[must_use]
1170 pub const fn write_reverse_index_entries_changed(&self) -> u64 {
1171 self.write_reverse_index_entries_changed
1172 }
1173
1174 #[must_use]
1175 pub const fn write_relation_checks(&self) -> u64 {
1176 self.write_relation_checks
1177 }
1178
1179 #[must_use]
1180 pub const fn unique_violations(&self) -> u64 {
1181 self.unique_violations
1182 }
1183
1184 #[must_use]
1185 pub const fn non_atomic_partial_commits(&self) -> u64 {
1186 self.non_atomic_partial_commits
1187 }
1188
1189 #[must_use]
1190 pub const fn non_atomic_partial_rows_committed(&self) -> u64 {
1191 self.non_atomic_partial_rows_committed
1192 }
1193
1194 const fn activity_score(&self) -> u64 {
1197 self.load_calls
1198 .saturating_add(self.save_calls)
1199 .saturating_add(self.delete_calls)
1200 .saturating_add(self.save_insert_calls)
1201 .saturating_add(self.save_update_calls)
1202 .saturating_add(self.save_replace_calls)
1203 .saturating_add(self.exec_success)
1204 .saturating_add(self.exec_error_corruption)
1205 .saturating_add(self.exec_error_incompatible_persisted_format)
1206 .saturating_add(self.exec_error_not_found)
1207 .saturating_add(self.exec_error_internal)
1208 .saturating_add(self.exec_error_conflict)
1209 .saturating_add(self.exec_error_unsupported)
1210 .saturating_add(self.exec_error_invariant_violation)
1211 .saturating_add(self.exec_aborted)
1212 .saturating_add(self.cache_shared_query_plan_hits)
1213 .saturating_add(self.cache_shared_query_plan_misses)
1214 .saturating_add(self.cache_shared_query_plan_inserts)
1215 .saturating_add(self.cache_sql_compiled_command_hits)
1216 .saturating_add(self.cache_sql_compiled_command_misses)
1217 .saturating_add(self.cache_sql_compiled_command_inserts)
1218 .saturating_add(self.plan_index)
1219 .saturating_add(self.plan_keys)
1220 .saturating_add(self.plan_range)
1221 .saturating_add(self.plan_full_scan)
1222 .saturating_add(self.plan_by_key)
1223 .saturating_add(self.plan_by_keys)
1224 .saturating_add(self.plan_key_range)
1225 .saturating_add(self.plan_index_prefix)
1226 .saturating_add(self.plan_index_multi_lookup)
1227 .saturating_add(self.plan_index_range)
1228 .saturating_add(self.plan_explicit_full_scan)
1229 .saturating_add(self.plan_union)
1230 .saturating_add(self.plan_intersection)
1231 .saturating_add(self.plan_grouped_hash_materialized)
1232 .saturating_add(self.plan_grouped_ordered_materialized)
1233 .saturating_add(self.rows_loaded)
1234 .saturating_add(self.rows_saved)
1235 .saturating_add(self.rows_inserted)
1236 .saturating_add(self.rows_updated)
1237 .saturating_add(self.rows_replaced)
1238 .saturating_add(self.rows_scanned)
1239 .saturating_add(self.rows_filtered)
1240 .saturating_add(self.rows_aggregated)
1241 .saturating_add(self.rows_emitted)
1242 .saturating_add(self.load_candidate_rows_scanned)
1243 .saturating_add(self.load_candidate_rows_filtered)
1244 .saturating_add(self.load_result_rows_emitted)
1245 .saturating_add(self.rows_deleted)
1246 .saturating_add(self.sql_insert_calls)
1247 .saturating_add(self.sql_insert_select_calls)
1248 .saturating_add(self.sql_update_calls)
1249 .saturating_add(self.sql_delete_calls)
1250 .saturating_add(self.sql_write_matched_rows)
1251 .saturating_add(self.sql_write_mutated_rows)
1252 .saturating_add(self.sql_write_returning_rows)
1253 .saturating_add(self.index_inserts)
1254 .saturating_add(self.index_removes)
1255 .saturating_add(self.reverse_index_inserts)
1256 .saturating_add(self.reverse_index_removes)
1257 .saturating_add(self.relation_reverse_lookups)
1258 .saturating_add(self.relation_delete_blocks)
1259 .saturating_add(self.write_rows_touched)
1260 .saturating_add(self.write_index_entries_changed)
1261 .saturating_add(self.write_reverse_index_entries_changed)
1262 .saturating_add(self.write_relation_checks)
1263 .saturating_add(self.unique_violations)
1264 .saturating_add(self.non_atomic_partial_commits)
1265 .saturating_add(self.non_atomic_partial_rows_committed)
1266 }
1267}
1268
1269fn entity_summary_from_counters(path: &str, ops: &EntityCounters) -> EntitySummary {
1274 EntitySummary {
1275 path: path.to_string(),
1276 load_calls: ops.load_calls,
1277 save_calls: ops.save_calls,
1278 delete_calls: ops.delete_calls,
1279 save_insert_calls: ops.save_insert_calls,
1280 save_update_calls: ops.save_update_calls,
1281 save_replace_calls: ops.save_replace_calls,
1282 exec_success: ops.exec_success,
1283 exec_error_corruption: ops.exec_error_corruption,
1284 exec_error_incompatible_persisted_format: ops.exec_error_incompatible_persisted_format,
1285 exec_error_not_found: ops.exec_error_not_found,
1286 exec_error_internal: ops.exec_error_internal,
1287 exec_error_conflict: ops.exec_error_conflict,
1288 exec_error_unsupported: ops.exec_error_unsupported,
1289 exec_error_invariant_violation: ops.exec_error_invariant_violation,
1290 exec_aborted: ops.exec_aborted,
1291 cache_shared_query_plan_hits: ops.cache_shared_query_plan_hits,
1292 cache_shared_query_plan_misses: ops.cache_shared_query_plan_misses,
1293 cache_shared_query_plan_inserts: ops.cache_shared_query_plan_inserts,
1294 cache_sql_compiled_command_hits: ops.cache_sql_compiled_command_hits,
1295 cache_sql_compiled_command_misses: ops.cache_sql_compiled_command_misses,
1296 cache_sql_compiled_command_inserts: ops.cache_sql_compiled_command_inserts,
1297 plan_index: ops.plan_index,
1298 plan_keys: ops.plan_keys,
1299 plan_range: ops.plan_range,
1300 plan_full_scan: ops.plan_full_scan,
1301 plan_by_key: ops.plan_by_key,
1302 plan_by_keys: ops.plan_by_keys,
1303 plan_key_range: ops.plan_key_range,
1304 plan_index_prefix: ops.plan_index_prefix,
1305 plan_index_multi_lookup: ops.plan_index_multi_lookup,
1306 plan_index_range: ops.plan_index_range,
1307 plan_explicit_full_scan: ops.plan_explicit_full_scan,
1308 plan_union: ops.plan_union,
1309 plan_intersection: ops.plan_intersection,
1310 plan_grouped_hash_materialized: ops.plan_grouped_hash_materialized,
1311 plan_grouped_ordered_materialized: ops.plan_grouped_ordered_materialized,
1312 rows_loaded: ops.rows_loaded,
1313 rows_saved: ops.rows_saved,
1314 rows_inserted: ops.rows_inserted,
1315 rows_updated: ops.rows_updated,
1316 rows_replaced: ops.rows_replaced,
1317 rows_scanned: ops.rows_scanned,
1318 rows_filtered: ops.rows_filtered,
1319 rows_aggregated: ops.rows_aggregated,
1320 rows_emitted: ops.rows_emitted,
1321 load_candidate_rows_scanned: ops.load_candidate_rows_scanned,
1322 load_candidate_rows_filtered: ops.load_candidate_rows_filtered,
1323 load_result_rows_emitted: ops.load_result_rows_emitted,
1324 rows_deleted: ops.rows_deleted,
1325 sql_insert_calls: ops.sql_insert_calls,
1326 sql_insert_select_calls: ops.sql_insert_select_calls,
1327 sql_update_calls: ops.sql_update_calls,
1328 sql_delete_calls: ops.sql_delete_calls,
1329 sql_write_matched_rows: ops.sql_write_matched_rows,
1330 sql_write_mutated_rows: ops.sql_write_mutated_rows,
1331 sql_write_returning_rows: ops.sql_write_returning_rows,
1332 index_inserts: ops.index_inserts,
1333 index_removes: ops.index_removes,
1334 reverse_index_inserts: ops.reverse_index_inserts,
1335 reverse_index_removes: ops.reverse_index_removes,
1336 relation_reverse_lookups: ops.relation_reverse_lookups,
1337 relation_delete_blocks: ops.relation_delete_blocks,
1338 write_rows_touched: ops.write_rows_touched,
1339 write_index_entries_changed: ops.write_index_entries_changed,
1340 write_reverse_index_entries_changed: ops.write_reverse_index_entries_changed,
1341 write_relation_checks: ops.write_relation_checks,
1342 unique_violations: ops.unique_violations,
1343 non_atomic_partial_commits: ops.non_atomic_partial_commits,
1344 non_atomic_partial_rows_committed: ops.non_atomic_partial_rows_committed,
1345 }
1346}
1347
1348#[must_use]
1358pub(super) fn report_window_start(window_start_ms: Option<u64>) -> EventReport {
1359 let snap = with_state(Clone::clone);
1360 if let Some(requested_window_start_ms) = window_start_ms
1361 && requested_window_start_ms > snap.window_start_ms
1362 {
1363 return EventReport::new(
1364 None,
1365 Vec::new(),
1366 false,
1367 window_start_ms,
1368 snap.window_start_ms,
1369 );
1370 }
1371
1372 let mut entity_counters: Vec<EntitySummary> = Vec::new();
1373 for (path, ops) in &snap.entities {
1374 entity_counters.push(entity_summary_from_counters(path, ops));
1375 }
1376
1377 entity_counters.sort_by(|a, b| {
1378 b.activity_score()
1379 .cmp(&a.activity_score())
1380 .then_with(|| b.rows_loaded.cmp(&a.rows_loaded))
1381 .then_with(|| b.rows_saved.cmp(&a.rows_saved))
1382 .then_with(|| b.rows_scanned.cmp(&a.rows_scanned))
1383 .then_with(|| b.rows_deleted.cmp(&a.rows_deleted))
1384 .then_with(|| a.path.cmp(&b.path))
1385 });
1386
1387 EventReport::new(
1388 Some(EventCounters::new(
1389 snap.ops.clone(),
1390 snap.perf.clone(),
1391 snap.window_start_ms,
1392 now_millis(),
1393 )),
1394 entity_counters,
1395 true,
1396 window_start_ms,
1397 snap.window_start_ms,
1398 )
1399}