Skip to main content

icydb_core/metrics/state/
compact.rs

1//! Module: metrics::state::compact
2//! Responsibility: compact metrics DTOs and sparse compact report building.
3//! Does not own: rich metrics report DTOs or mutable metrics state updates.
4//! Boundary: keeps numeric compact metrics encoding separate from rich reports.
5
6use candid::CandidType;
7use serde::Deserialize;
8
9use crate::runtime::now_millis;
10
11use super::{EntityCounters, EventOps, with_state};
12
13/// Numeric codes used by compact metrics reports.
14///
15/// The default metrics endpoint returns these codes instead of the full
16/// `EventOps` field graph so live canisters do not retain the rich report's
17/// Candid schema unless the extended metrics endpoint is explicitly enabled.
18pub mod compact_metric_code {
19    /// Load entrypoint calls.
20    pub const LOAD_CALLS: u16 = 1;
21    /// Save entrypoint calls.
22    pub const SAVE_CALLS: u16 = 2;
23    /// Delete entrypoint calls.
24    pub const DELETE_CALLS: u16 = 3;
25    /// Successful executions.
26    pub const EXEC_SUCCESS: u16 = 4;
27    /// Execution errors collapsed across error classes.
28    pub const EXEC_ERRORS: u16 = 5;
29    /// Aborted executions.
30    pub const EXEC_ABORTED: u16 = 6;
31    /// Rows loaded.
32    pub const ROWS_LOADED: u16 = 7;
33    /// Rows saved.
34    pub const ROWS_SAVED: u16 = 8;
35    /// Rows deleted.
36    pub const ROWS_DELETED: u16 = 9;
37    /// Rows scanned.
38    pub const ROWS_SCANNED: u16 = 10;
39    /// Rows filtered.
40    pub const ROWS_FILTERED: u16 = 11;
41    /// Rows emitted.
42    pub const ROWS_EMITTED: u16 = 12;
43    /// SQL INSERT calls.
44    pub const SQL_INSERT_CALLS: u16 = 13;
45    /// SQL INSERT SELECT calls.
46    pub const SQL_INSERT_SELECT_CALLS: u16 = 14;
47    /// SQL UPDATE calls.
48    pub const SQL_UPDATE_CALLS: u16 = 15;
49    /// SQL DELETE calls.
50    pub const SQL_DELETE_CALLS: u16 = 16;
51    /// SQL write matched rows.
52    pub const SQL_WRITE_MATCHED_ROWS: u16 = 17;
53    /// SQL write mutated rows.
54    pub const SQL_WRITE_MUTATED_ROWS: u16 = 18;
55    /// SQL write RETURNING rows.
56    pub const SQL_WRITE_RETURNING_ROWS: u16 = 19;
57    /// Shared query-plan cache hits.
58    pub const CACHE_SHARED_QUERY_PLAN_HITS: u16 = 20;
59    /// Shared query-plan cache misses.
60    pub const CACHE_SHARED_QUERY_PLAN_MISSES: u16 = 21;
61    /// SQL compiled-command cache hits.
62    pub const CACHE_SQL_COMPILED_COMMAND_HITS: u16 = 22;
63    /// SQL compiled-command cache misses.
64    pub const CACHE_SQL_COMPILED_COMMAND_MISSES: u16 = 23;
65    /// SQL write rows staged before mutation.
66    pub const SQL_WRITE_STAGED_ROWS: u16 = 24;
67}
68
69#[cfg_attr(doc, doc = "CompactMetric\n\nCompact metrics counter.")]
70#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
71pub struct CompactMetric(u16, u64);
72
73impl CompactMetric {
74    #[must_use]
75    const fn new(code: u16, value: u64) -> Self {
76        Self(code, value)
77    }
78
79    /// Return the numeric metric code.
80    #[must_use]
81    pub const fn code(&self) -> u16 {
82        self.0
83    }
84
85    /// Return the metric value.
86    #[must_use]
87    pub const fn value(&self) -> u64 {
88        self.1
89    }
90
91    /// Return the metric as a numeric code/value pair.
92    #[must_use]
93    pub const fn into_code_and_value(self) -> (u16, u64) {
94        (self.0, self.1)
95    }
96}
97
98#[cfg_attr(doc, doc = "CompactEventCounters\n\nCompact global metrics counters.")]
99#[derive(CandidType, Clone, Debug, Default, Deserialize, Eq, PartialEq)]
100pub struct CompactEventCounters(Vec<CompactMetric>, u64, u64);
101
102impl CompactEventCounters {
103    #[must_use]
104    const fn new(metrics: Vec<CompactMetric>, window_start_ms: u64, window_end_ms: u64) -> Self {
105        Self(metrics, window_start_ms, window_end_ms)
106    }
107
108    /// Borrow the sparse global metrics vector.
109    #[must_use]
110    pub fn metrics(&self) -> &[CompactMetric] {
111        &self.0
112    }
113
114    /// Return the active window start timestamp in milliseconds.
115    #[must_use]
116    pub const fn window_start_ms(&self) -> u64 {
117        self.1
118    }
119
120    /// Return the report window end timestamp in milliseconds.
121    #[must_use]
122    pub const fn window_end_ms(&self) -> u64 {
123        self.2
124    }
125
126    /// Return the report window duration in milliseconds.
127    #[must_use]
128    pub const fn window_duration_ms(&self) -> u64 {
129        self.2.saturating_sub(self.1)
130    }
131}
132
133#[cfg_attr(
134    doc,
135    doc = "CompactEntityMetrics\n\nCompact per-entity metrics counters."
136)]
137#[derive(CandidType, Clone, Debug, Default, Deserialize, Eq, PartialEq)]
138pub struct CompactEntityMetrics(String, Vec<CompactMetric>);
139
140impl CompactEntityMetrics {
141    #[must_use]
142    const fn new(path: String, metrics: Vec<CompactMetric>) -> Self {
143        Self(path, metrics)
144    }
145
146    /// Return the entity schema path.
147    #[must_use]
148    pub const fn path(&self) -> &str {
149        self.0.as_str()
150    }
151
152    /// Borrow the sparse entity metrics vector.
153    #[must_use]
154    pub fn metrics(&self) -> &[CompactMetric] {
155        &self.1
156    }
157}
158
159#[cfg_attr(doc, doc = "CompactMetricsReport\n\nCompact metrics query payload.")]
160#[derive(CandidType, Clone, Debug, Default, Deserialize, Eq, PartialEq)]
161pub struct CompactMetricsReport(
162    Option<CompactEventCounters>,
163    Vec<CompactEntityMetrics>,
164    Option<u64>,
165    u64,
166);
167
168impl CompactMetricsReport {
169    #[must_use]
170    const fn new(
171        counters: Option<CompactEventCounters>,
172        entity_counters: Vec<CompactEntityMetrics>,
173        requested_window_start_ms: Option<u64>,
174        active_window_start_ms: u64,
175    ) -> Self {
176        Self(
177            counters,
178            entity_counters,
179            requested_window_start_ms,
180            active_window_start_ms,
181        )
182    }
183
184    /// Borrow the compact global counters when the requested window matched.
185    #[must_use]
186    pub const fn counters(&self) -> Option<&CompactEventCounters> {
187        self.0.as_ref()
188    }
189
190    /// Borrow compact per-entity counters.
191    #[must_use]
192    pub fn entity_counters(&self) -> &[CompactEntityMetrics] {
193        &self.1
194    }
195
196    /// Return whether the requested window matched the active window.
197    #[must_use]
198    pub const fn window_filter_matched(&self) -> bool {
199        self.0.is_some()
200    }
201
202    /// Return the requested window start timestamp, if supplied.
203    #[must_use]
204    pub const fn requested_window_start_ms(&self) -> Option<u64> {
205        self.2
206    }
207
208    /// Return the active metrics window start timestamp.
209    #[must_use]
210    pub const fn active_window_start_ms(&self) -> u64 {
211        self.3
212    }
213}
214
215fn push_compact_metric(metrics: &mut Vec<CompactMetric>, code: u16, value: u64) {
216    if value != 0 {
217        metrics.push(CompactMetric::new(code, value));
218    }
219}
220
221const fn event_ops_exec_errors(ops: &EventOps) -> u64 {
222    ops.exec_error_corruption
223        .saturating_add(ops.exec_error_incompatible_persisted_format)
224        .saturating_add(ops.exec_error_not_found)
225        .saturating_add(ops.exec_error_internal)
226        .saturating_add(ops.exec_error_conflict)
227        .saturating_add(ops.exec_error_unsupported)
228        .saturating_add(ops.exec_error_invariant_violation)
229}
230
231const fn entity_ops_exec_errors(ops: &EntityCounters) -> u64 {
232    ops.exec_error_corruption
233        .saturating_add(ops.exec_error_incompatible_persisted_format)
234        .saturating_add(ops.exec_error_not_found)
235        .saturating_add(ops.exec_error_internal)
236        .saturating_add(ops.exec_error_conflict)
237        .saturating_add(ops.exec_error_unsupported)
238        .saturating_add(ops.exec_error_invariant_violation)
239}
240
241fn compact_event_metrics(ops: &EventOps) -> Vec<CompactMetric> {
242    use compact_metric_code::{
243        CACHE_SHARED_QUERY_PLAN_HITS, CACHE_SHARED_QUERY_PLAN_MISSES,
244        CACHE_SQL_COMPILED_COMMAND_HITS, CACHE_SQL_COMPILED_COMMAND_MISSES, DELETE_CALLS,
245        EXEC_ABORTED, EXEC_ERRORS, EXEC_SUCCESS, LOAD_CALLS, ROWS_DELETED, ROWS_EMITTED,
246        ROWS_FILTERED, ROWS_LOADED, ROWS_SAVED, ROWS_SCANNED, SAVE_CALLS, SQL_DELETE_CALLS,
247        SQL_INSERT_CALLS, SQL_INSERT_SELECT_CALLS, SQL_UPDATE_CALLS, SQL_WRITE_MATCHED_ROWS,
248        SQL_WRITE_MUTATED_ROWS, SQL_WRITE_RETURNING_ROWS, SQL_WRITE_STAGED_ROWS,
249    };
250
251    let mut metrics = Vec::new();
252    push_compact_metric(&mut metrics, LOAD_CALLS, ops.load_calls);
253    push_compact_metric(&mut metrics, SAVE_CALLS, ops.save_calls);
254    push_compact_metric(&mut metrics, DELETE_CALLS, ops.delete_calls);
255    push_compact_metric(&mut metrics, EXEC_SUCCESS, ops.exec_success);
256    push_compact_metric(&mut metrics, EXEC_ERRORS, event_ops_exec_errors(ops));
257    push_compact_metric(&mut metrics, EXEC_ABORTED, ops.exec_aborted);
258    push_compact_metric(&mut metrics, ROWS_LOADED, ops.rows_loaded);
259    push_compact_metric(&mut metrics, ROWS_SAVED, ops.rows_saved);
260    push_compact_metric(&mut metrics, ROWS_DELETED, ops.rows_deleted);
261    push_compact_metric(&mut metrics, ROWS_SCANNED, ops.rows_scanned);
262    push_compact_metric(&mut metrics, ROWS_FILTERED, ops.rows_filtered);
263    push_compact_metric(&mut metrics, ROWS_EMITTED, ops.rows_emitted);
264    push_compact_metric(&mut metrics, SQL_INSERT_CALLS, ops.sql_insert_calls);
265    push_compact_metric(
266        &mut metrics,
267        SQL_INSERT_SELECT_CALLS,
268        ops.sql_insert_select_calls,
269    );
270    push_compact_metric(&mut metrics, SQL_UPDATE_CALLS, ops.sql_update_calls);
271    push_compact_metric(&mut metrics, SQL_DELETE_CALLS, ops.sql_delete_calls);
272    push_compact_metric(
273        &mut metrics,
274        SQL_WRITE_MATCHED_ROWS,
275        ops.sql_write_matched_rows,
276    );
277    push_compact_metric(
278        &mut metrics,
279        SQL_WRITE_MUTATED_ROWS,
280        ops.sql_write_mutated_rows,
281    );
282    push_compact_metric(
283        &mut metrics,
284        SQL_WRITE_RETURNING_ROWS,
285        ops.sql_write_returning_rows,
286    );
287    push_compact_metric(
288        &mut metrics,
289        SQL_WRITE_STAGED_ROWS,
290        ops.sql_write_staged_rows,
291    );
292    push_compact_metric(
293        &mut metrics,
294        CACHE_SHARED_QUERY_PLAN_HITS,
295        ops.cache_shared_query_plan_hits,
296    );
297    push_compact_metric(
298        &mut metrics,
299        CACHE_SHARED_QUERY_PLAN_MISSES,
300        ops.cache_shared_query_plan_misses,
301    );
302    push_compact_metric(
303        &mut metrics,
304        CACHE_SQL_COMPILED_COMMAND_HITS,
305        ops.cache_sql_compiled_command_hits,
306    );
307    push_compact_metric(
308        &mut metrics,
309        CACHE_SQL_COMPILED_COMMAND_MISSES,
310        ops.cache_sql_compiled_command_misses,
311    );
312
313    metrics
314}
315
316fn compact_entity_metrics(ops: &EntityCounters) -> Vec<CompactMetric> {
317    use compact_metric_code::{
318        CACHE_SHARED_QUERY_PLAN_HITS, CACHE_SHARED_QUERY_PLAN_MISSES,
319        CACHE_SQL_COMPILED_COMMAND_HITS, CACHE_SQL_COMPILED_COMMAND_MISSES, DELETE_CALLS,
320        EXEC_ABORTED, EXEC_ERRORS, EXEC_SUCCESS, LOAD_CALLS, ROWS_DELETED, ROWS_EMITTED,
321        ROWS_FILTERED, ROWS_LOADED, ROWS_SAVED, ROWS_SCANNED, SAVE_CALLS, SQL_DELETE_CALLS,
322        SQL_INSERT_CALLS, SQL_INSERT_SELECT_CALLS, SQL_UPDATE_CALLS, SQL_WRITE_MATCHED_ROWS,
323        SQL_WRITE_MUTATED_ROWS, SQL_WRITE_RETURNING_ROWS, SQL_WRITE_STAGED_ROWS,
324    };
325
326    let mut metrics = Vec::new();
327    push_compact_metric(&mut metrics, LOAD_CALLS, ops.load_calls);
328    push_compact_metric(&mut metrics, SAVE_CALLS, ops.save_calls);
329    push_compact_metric(&mut metrics, DELETE_CALLS, ops.delete_calls);
330    push_compact_metric(&mut metrics, EXEC_SUCCESS, ops.exec_success);
331    push_compact_metric(&mut metrics, EXEC_ERRORS, entity_ops_exec_errors(ops));
332    push_compact_metric(&mut metrics, EXEC_ABORTED, ops.exec_aborted);
333    push_compact_metric(&mut metrics, ROWS_LOADED, ops.rows_loaded);
334    push_compact_metric(&mut metrics, ROWS_SAVED, ops.rows_saved);
335    push_compact_metric(&mut metrics, ROWS_DELETED, ops.rows_deleted);
336    push_compact_metric(&mut metrics, ROWS_SCANNED, ops.rows_scanned);
337    push_compact_metric(&mut metrics, ROWS_FILTERED, ops.rows_filtered);
338    push_compact_metric(&mut metrics, ROWS_EMITTED, ops.rows_emitted);
339    push_compact_metric(&mut metrics, SQL_INSERT_CALLS, ops.sql_insert_calls);
340    push_compact_metric(
341        &mut metrics,
342        SQL_INSERT_SELECT_CALLS,
343        ops.sql_insert_select_calls,
344    );
345    push_compact_metric(&mut metrics, SQL_UPDATE_CALLS, ops.sql_update_calls);
346    push_compact_metric(&mut metrics, SQL_DELETE_CALLS, ops.sql_delete_calls);
347    push_compact_metric(
348        &mut metrics,
349        SQL_WRITE_MATCHED_ROWS,
350        ops.sql_write_matched_rows,
351    );
352    push_compact_metric(
353        &mut metrics,
354        SQL_WRITE_MUTATED_ROWS,
355        ops.sql_write_mutated_rows,
356    );
357    push_compact_metric(
358        &mut metrics,
359        SQL_WRITE_RETURNING_ROWS,
360        ops.sql_write_returning_rows,
361    );
362    push_compact_metric(
363        &mut metrics,
364        SQL_WRITE_STAGED_ROWS,
365        ops.sql_write_staged_rows,
366    );
367    push_compact_metric(
368        &mut metrics,
369        CACHE_SHARED_QUERY_PLAN_HITS,
370        ops.cache_shared_query_plan_hits,
371    );
372    push_compact_metric(
373        &mut metrics,
374        CACHE_SHARED_QUERY_PLAN_MISSES,
375        ops.cache_shared_query_plan_misses,
376    );
377    push_compact_metric(
378        &mut metrics,
379        CACHE_SQL_COMPILED_COMMAND_HITS,
380        ops.cache_sql_compiled_command_hits,
381    );
382    push_compact_metric(
383        &mut metrics,
384        CACHE_SQL_COMPILED_COMMAND_MISSES,
385        ops.cache_sql_compiled_command_misses,
386    );
387
388    metrics
389}
390
391/// Build a compact metrics report gated by `window_start_ms`.
392#[must_use]
393pub(in crate::metrics) fn compact_report_window_start(
394    window_start_ms: Option<u64>,
395) -> CompactMetricsReport {
396    let snap = with_state(Clone::clone);
397    if let Some(requested_window_start_ms) = window_start_ms
398        && requested_window_start_ms > snap.window_start_ms
399    {
400        return CompactMetricsReport::new(None, Vec::new(), window_start_ms, snap.window_start_ms);
401    }
402
403    let mut entity_counters: Vec<CompactEntityMetrics> = Vec::new();
404    for (path, ops) in &snap.entities {
405        let metrics = compact_entity_metrics(ops);
406        if !metrics.is_empty() {
407            entity_counters.push(CompactEntityMetrics::new(path.clone(), metrics));
408        }
409    }
410
411    entity_counters.sort_by(|a, b| {
412        let a_activity = compact_metrics_activity(a.metrics());
413        let b_activity = compact_metrics_activity(b.metrics());
414        b_activity
415            .cmp(&a_activity)
416            .then_with(|| a.path().cmp(b.path()))
417    });
418
419    CompactMetricsReport::new(
420        Some(CompactEventCounters::new(
421            compact_event_metrics(&snap.ops),
422            snap.window_start_ms,
423            now_millis(),
424        )),
425        entity_counters,
426        window_start_ms,
427        snap.window_start_ms,
428    )
429}
430
431fn compact_metrics_activity(metrics: &[CompactMetric]) -> u64 {
432    metrics
433        .iter()
434        .fold(0, |total, metric| total.saturating_add(metric.value()))
435}