canic_core/model/metrics/
icc.rs

1use candid::{CandidType, Principal};
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::HashMap};
4
5thread_local! {
6    static ICC_METRICS: RefCell<HashMap<IccMetricKey, u64>> = RefCell::new(HashMap::new());
7}
8
9///
10/// IccMetricKey
11/// Uniquely identifies an inter-canister call by target + method.
12///
13
14#[derive(CandidType, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
15pub struct IccMetricKey {
16    pub target: Principal,
17    pub method: String,
18}
19
20///
21/// IccMetricEntry
22/// Snapshot entry pairing a target/method with its count.
23///
24
25#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
26pub struct IccMetricEntry {
27    pub target: Principal,
28    pub method: String,
29    pub count: u64,
30}
31
32///
33/// IccMetricsSnapshot
34///
35
36pub type IccMetricsSnapshot = Vec<IccMetricEntry>;
37
38///
39/// IccMetrics
40/// Volatile counters for inter-canister calls keyed by target + method.
41///
42
43pub struct IccMetrics;
44
45impl IccMetrics {
46    /// Increment the ICC counter for a target/method pair.
47    pub fn increment(target: Principal, method: &str) {
48        ICC_METRICS.with_borrow_mut(|counts| {
49            let key = IccMetricKey {
50                target,
51                method: method.to_string(),
52            };
53            let entry = counts.entry(key).or_insert(0);
54            *entry = entry.saturating_add(1);
55        });
56    }
57
58    /// Snapshot all ICC counters.
59    #[must_use]
60    pub fn snapshot() -> IccMetricsSnapshot {
61        ICC_METRICS.with_borrow(|counts| {
62            counts
63                .iter()
64                .map(|(key, count)| IccMetricEntry {
65                    target: key.target,
66                    method: key.method.clone(),
67                    count: *count,
68                })
69                .collect()
70        })
71    }
72
73    #[cfg(test)]
74    pub fn reset() {
75        ICC_METRICS.with_borrow_mut(HashMap::clear);
76    }
77}
78
79///
80/// TESTS
81///
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn icc_metrics_track_target_and_method() {
89        IccMetrics::reset();
90
91        let t1 = Principal::from_slice(&[1; 29]);
92        let t2 = Principal::from_slice(&[2; 29]);
93
94        IccMetrics::increment(t1, "foo");
95        IccMetrics::increment(t1, "foo");
96        IccMetrics::increment(t1, "bar");
97        IccMetrics::increment(t2, "foo");
98
99        let snapshot = IccMetrics::snapshot();
100        let mut map: HashMap<(Principal, String), u64> = snapshot
101            .into_iter()
102            .map(|entry| ((entry.target, entry.method), entry.count))
103            .collect();
104
105        assert_eq!(map.remove(&(t1, "foo".to_string())), Some(2));
106        assert_eq!(map.remove(&(t1, "bar".to_string())), Some(1));
107        assert_eq!(map.remove(&(t2, "foo".to_string())), Some(1));
108        assert!(map.is_empty());
109    }
110}