canic_core/model/
metrics.rs

1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::HashMap};
4
5thread_local! {
6    static METRIC_COUNTS: RefCell<HashMap<MetricKind, u64>> = RefCell::new(HashMap::new());
7}
8
9// -----------------------------------------------------------------------------
10// Types
11// -----------------------------------------------------------------------------
12
13///
14/// MetricsSnapshot
15///
16
17pub type MetricsSnapshot = Vec<MetricEntry>;
18
19///
20/// MetricKind
21/// Enumerates the resource-heavy actions we track.
22///
23#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
24pub enum MetricKind {
25    CreateCanister,
26    InstallCode,
27    ReinstallCode,
28    UpgradeCode,
29    UninstallCode,
30    DeleteCanister,
31    DepositCycles,
32    CanisterStatus,
33    CanisterCall,
34}
35
36///
37/// MetricEntry
38/// Snapshot entry pairing a metric kind with its count.
39///
40
41#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
42pub struct MetricEntry {
43    pub kind: MetricKind,
44    pub count: u64,
45}
46
47// -----------------------------------------------------------------------------
48// State
49// -----------------------------------------------------------------------------
50
51///
52/// MetricsState
53/// Volatile counters for resource-using IC actions.
54///
55pub struct MetricsState;
56
57impl MetricsState {
58    /// Increment a counter and return the new value.
59    pub fn increment(kind: MetricKind) {
60        METRIC_COUNTS.with_borrow_mut(|counts| {
61            let entry = counts.entry(kind).or_insert(0);
62            *entry = entry.saturating_add(1);
63        });
64    }
65
66    /// Return a snapshot of all counters.
67    #[must_use]
68    pub fn snapshot() -> Vec<MetricEntry> {
69        METRIC_COUNTS.with_borrow(|counts| {
70            counts
71                .iter()
72                .map(|(kind, count)| MetricEntry {
73                    kind: *kind,
74                    count: *count,
75                })
76                .collect()
77        })
78    }
79
80    #[cfg(test)]
81    pub fn reset() {
82        METRIC_COUNTS.with_borrow_mut(HashMap::clear);
83    }
84}
85
86///
87/// TESTS
88///
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use std::collections::HashMap;
94
95    #[test]
96    fn increments_and_snapshots() {
97        MetricsState::reset();
98
99        MetricsState::increment(MetricKind::CreateCanister);
100        MetricsState::increment(MetricKind::CreateCanister);
101        MetricsState::increment(MetricKind::InstallCode);
102
103        let snapshot = MetricsState::snapshot();
104        let as_map: HashMap<MetricKind, u64> = snapshot
105            .into_iter()
106            .map(|entry| (entry.kind, entry.count))
107            .collect();
108
109        assert_eq!(as_map.get(&MetricKind::CreateCanister), Some(&2));
110        assert_eq!(as_map.get(&MetricKind::InstallCode), Some(&1));
111        assert!(!as_map.contains_key(&MetricKind::CanisterCall));
112    }
113}