use candid::CandidType;
use canic_cdk::utils::time::now_millis;
use serde::Deserialize;
use std::{cell::RefCell, collections::BTreeMap};
#[derive(Clone, Debug)]
pub(crate) struct EventState {
pub(crate) ops: EventOps,
pub(crate) perf: EventPerf,
pub(crate) entities: BTreeMap<String, EntityCounters>,
pub(crate) window_start_ms: u64,
}
impl Default for EventState {
fn default() -> Self {
Self {
ops: EventOps::default(),
perf: EventPerf::default(),
entities: BTreeMap::new(),
window_start_ms: now_millis(),
}
}
}
#[cfg_attr(doc, doc = "EventOps\n\nOperation counters.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EventOps {
pub(crate) load_calls: u64,
pub(crate) save_calls: u64,
pub(crate) delete_calls: u64,
pub(crate) plan_index: u64,
pub(crate) plan_keys: u64,
pub(crate) plan_range: u64,
pub(crate) plan_full_scan: u64,
pub(crate) plan_grouped_hash_materialized: u64,
pub(crate) plan_grouped_ordered_materialized: u64,
pub(crate) rows_loaded: u64,
pub(crate) rows_scanned: u64,
pub(crate) rows_filtered: u64,
pub(crate) rows_aggregated: u64,
pub(crate) rows_emitted: u64,
pub(crate) rows_deleted: u64,
pub(crate) index_inserts: u64,
pub(crate) index_removes: u64,
pub(crate) reverse_index_inserts: u64,
pub(crate) reverse_index_removes: u64,
pub(crate) relation_reverse_lookups: u64,
pub(crate) relation_delete_blocks: u64,
pub(crate) unique_violations: u64,
pub(crate) non_atomic_partial_commits: u64,
pub(crate) non_atomic_partial_rows_committed: u64,
}
impl EventOps {
#[must_use]
pub const fn load_calls(&self) -> u64 {
self.load_calls
}
#[must_use]
pub const fn save_calls(&self) -> u64 {
self.save_calls
}
#[must_use]
pub const fn delete_calls(&self) -> u64 {
self.delete_calls
}
#[must_use]
pub const fn plan_index(&self) -> u64 {
self.plan_index
}
#[must_use]
pub const fn plan_keys(&self) -> u64 {
self.plan_keys
}
#[must_use]
pub const fn plan_range(&self) -> u64 {
self.plan_range
}
#[must_use]
pub const fn plan_full_scan(&self) -> u64 {
self.plan_full_scan
}
#[must_use]
pub const fn plan_grouped_hash_materialized(&self) -> u64 {
self.plan_grouped_hash_materialized
}
#[must_use]
pub const fn plan_grouped_ordered_materialized(&self) -> u64 {
self.plan_grouped_ordered_materialized
}
#[must_use]
pub const fn rows_loaded(&self) -> u64 {
self.rows_loaded
}
#[must_use]
pub const fn rows_scanned(&self) -> u64 {
self.rows_scanned
}
#[must_use]
pub const fn rows_filtered(&self) -> u64 {
self.rows_filtered
}
#[must_use]
pub const fn rows_aggregated(&self) -> u64 {
self.rows_aggregated
}
#[must_use]
pub const fn rows_emitted(&self) -> u64 {
self.rows_emitted
}
#[must_use]
pub const fn rows_deleted(&self) -> u64 {
self.rows_deleted
}
#[must_use]
pub const fn index_inserts(&self) -> u64 {
self.index_inserts
}
#[must_use]
pub const fn index_removes(&self) -> u64 {
self.index_removes
}
#[must_use]
pub const fn reverse_index_inserts(&self) -> u64 {
self.reverse_index_inserts
}
#[must_use]
pub const fn reverse_index_removes(&self) -> u64 {
self.reverse_index_removes
}
#[must_use]
pub const fn relation_reverse_lookups(&self) -> u64 {
self.relation_reverse_lookups
}
#[must_use]
pub const fn relation_delete_blocks(&self) -> u64 {
self.relation_delete_blocks
}
#[must_use]
pub const fn unique_violations(&self) -> u64 {
self.unique_violations
}
#[must_use]
pub const fn non_atomic_partial_commits(&self) -> u64 {
self.non_atomic_partial_commits
}
#[must_use]
pub const fn non_atomic_partial_rows_committed(&self) -> u64 {
self.non_atomic_partial_rows_committed
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct EntityCounters {
pub(crate) load_calls: u64,
pub(crate) save_calls: u64,
pub(crate) delete_calls: u64,
pub(crate) rows_loaded: u64,
pub(crate) rows_scanned: u64,
pub(crate) rows_filtered: u64,
pub(crate) rows_aggregated: u64,
pub(crate) rows_emitted: u64,
pub(crate) rows_deleted: u64,
pub(crate) index_inserts: u64,
pub(crate) index_removes: u64,
pub(crate) reverse_index_inserts: u64,
pub(crate) reverse_index_removes: u64,
pub(crate) relation_reverse_lookups: u64,
pub(crate) relation_delete_blocks: u64,
pub(crate) unique_violations: u64,
pub(crate) non_atomic_partial_commits: u64,
pub(crate) non_atomic_partial_rows_committed: u64,
}
#[cfg_attr(doc, doc = "EventPerf\n\nInstruction totals and maxima.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EventPerf {
pub(crate) load_inst_total: u128,
pub(crate) save_inst_total: u128,
pub(crate) delete_inst_total: u128,
pub(crate) load_inst_max: u64,
pub(crate) save_inst_max: u64,
pub(crate) delete_inst_max: u64,
}
impl EventPerf {
#[must_use]
pub const fn new(
load_inst_total: u128,
save_inst_total: u128,
delete_inst_total: u128,
load_inst_max: u64,
save_inst_max: u64,
delete_inst_max: u64,
) -> Self {
Self {
load_inst_total,
save_inst_total,
delete_inst_total,
load_inst_max,
save_inst_max,
delete_inst_max,
}
}
#[must_use]
pub const fn load_inst_total(&self) -> u128 {
self.load_inst_total
}
#[must_use]
pub const fn save_inst_total(&self) -> u128 {
self.save_inst_total
}
#[must_use]
pub const fn delete_inst_total(&self) -> u128 {
self.delete_inst_total
}
#[must_use]
pub const fn load_inst_max(&self) -> u64 {
self.load_inst_max
}
#[must_use]
pub const fn save_inst_max(&self) -> u64 {
self.save_inst_max
}
#[must_use]
pub const fn delete_inst_max(&self) -> u64 {
self.delete_inst_max
}
}
thread_local! {
static EVENT_STATE: RefCell<EventState> = RefCell::new(EventState::default());
}
pub(crate) fn with_state<R>(f: impl FnOnce(&EventState) -> R) -> R {
EVENT_STATE.with(|m| f(&m.borrow()))
}
pub(crate) fn with_state_mut<R>(f: impl FnOnce(&mut EventState) -> R) -> R {
EVENT_STATE.with(|m| f(&mut m.borrow_mut()))
}
pub(super) fn reset() {
with_state_mut(|m| *m = EventState::default());
}
pub(crate) fn reset_all() {
reset();
}
pub(super) fn add_instructions(total: &mut u128, max: &mut u64, delta_inst: u64) {
*total = total.saturating_add(u128::from(delta_inst));
if delta_inst > *max {
*max = delta_inst;
}
}
#[cfg_attr(doc, doc = "EventReport\n\nMetrics query payload.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EventReport {
counters: Option<EventCounters>,
entity_counters: Vec<EntitySummary>,
}
impl EventReport {
#[must_use]
pub(crate) const fn new(
counters: Option<EventCounters>,
entity_counters: Vec<EntitySummary>,
) -> Self {
Self {
counters,
entity_counters,
}
}
#[must_use]
pub const fn counters(&self) -> Option<&EventCounters> {
self.counters.as_ref()
}
#[must_use]
pub fn entity_counters(&self) -> &[EntitySummary] {
&self.entity_counters
}
#[must_use]
pub fn into_counters(self) -> Option<EventCounters> {
self.counters
}
#[must_use]
pub fn into_entity_counters(self) -> Vec<EntitySummary> {
self.entity_counters
}
}
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EventCounters {
pub(crate) ops: EventOps,
pub(crate) perf: EventPerf,
pub(crate) window_start_ms: u64,
}
impl EventCounters {
#[must_use]
pub(crate) const fn new(ops: EventOps, perf: EventPerf, window_start_ms: u64) -> Self {
Self {
ops,
perf,
window_start_ms,
}
}
#[must_use]
pub const fn ops(&self) -> &EventOps {
&self.ops
}
#[must_use]
pub const fn perf(&self) -> &EventPerf {
&self.perf
}
#[must_use]
pub const fn window_start_ms(&self) -> u64 {
self.window_start_ms
}
}
#[cfg_attr(doc, doc = "EntitySummary\n\nPer-entity metrics summary.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EntitySummary {
path: String,
load_calls: u64,
delete_calls: u64,
rows_loaded: u64,
rows_scanned: u64,
rows_deleted: u64,
}
impl EntitySummary {
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn load_calls(&self) -> u64 {
self.load_calls
}
#[must_use]
pub const fn delete_calls(&self) -> u64 {
self.delete_calls
}
#[must_use]
pub const fn rows_loaded(&self) -> u64 {
self.rows_loaded
}
#[must_use]
pub const fn rows_scanned(&self) -> u64 {
self.rows_scanned
}
#[must_use]
pub const fn rows_deleted(&self) -> u64 {
self.rows_deleted
}
}
#[must_use]
pub(super) fn report_window_start(window_start_ms: Option<u64>) -> EventReport {
let snap = with_state(Clone::clone);
if let Some(requested_window_start_ms) = window_start_ms
&& requested_window_start_ms > snap.window_start_ms
{
return EventReport::default();
}
let mut entity_counters: Vec<EntitySummary> = Vec::new();
for (path, ops) in &snap.entities {
entity_counters.push(EntitySummary {
path: path.clone(),
load_calls: ops.load_calls,
delete_calls: ops.delete_calls,
rows_loaded: ops.rows_loaded,
rows_scanned: ops.rows_scanned,
rows_deleted: ops.rows_deleted,
});
}
entity_counters.sort_by(|a, b| {
b.rows_loaded
.cmp(&a.rows_loaded)
.then_with(|| b.rows_scanned.cmp(&a.rows_scanned))
.then_with(|| b.rows_deleted.cmp(&a.rows_deleted))
.then_with(|| a.path.cmp(&b.path))
});
EventReport::new(
Some(EventCounters::new(
snap.ops.clone(),
snap.perf.clone(),
snap.window_start_ms,
)),
entity_counters,
)
}