canic_core/model/
metrics.rs1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::HashMap};
4
5use crate::types::Principal;
6
7thread_local! {
8 static METRIC_COUNTS: RefCell<HashMap<MetricKind, u64>> = RefCell::new(HashMap::new());
9 static ICC_METRICS: RefCell<HashMap<IccMetricKey, u64>> = RefCell::new(HashMap::new());
10}
11
12pub type MetricsSnapshot = Vec<MetricEntry>;
21
22#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
27pub enum MetricKind {
28 CreateCanister,
29 InstallCode,
30 ReinstallCode,
31 UpgradeCode,
32 UninstallCode,
33 DeleteCanister,
34 DepositCycles,
35 CanisterStatus,
36 CanisterCall,
37}
38
39#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
45pub struct MetricEntry {
46 pub kind: MetricKind,
47 pub count: u64,
48}
49
50#[derive(CandidType, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
55pub struct IccMetricKey {
56 pub target: Principal,
57 pub method: String,
58}
59
60#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
65pub struct IccMetricEntry {
66 pub target: Principal,
67 pub method: String,
68 pub count: u64,
69}
70
71pub type IccMetricsSnapshot = Vec<IccMetricEntry>;
75
76#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
81pub struct MetricsReport {
82 pub system: MetricsSnapshot,
83 pub icc: IccMetricsSnapshot,
84}
85
86pub struct MetricsState;
95
96impl MetricsState {
97 pub fn increment(kind: MetricKind) {
99 METRIC_COUNTS.with_borrow_mut(|counts| {
100 let entry = counts.entry(kind).or_insert(0);
101 *entry = entry.saturating_add(1);
102 });
103 }
104
105 #[must_use]
107 pub fn snapshot() -> Vec<MetricEntry> {
108 METRIC_COUNTS.with_borrow(|counts| {
109 counts
110 .iter()
111 .map(|(kind, count)| MetricEntry {
112 kind: *kind,
113 count: *count,
114 })
115 .collect()
116 })
117 }
118
119 #[cfg(test)]
120 pub fn reset() {
121 METRIC_COUNTS.with_borrow_mut(HashMap::clear);
122 }
123}
124
125pub struct SystemMetrics;
130
131impl SystemMetrics {
132 pub fn record(kind: MetricKind) {
133 MetricsState::increment(kind);
134 }
135
136 #[must_use]
137 pub fn snapshot() -> MetricsSnapshot {
138 MetricsState::snapshot()
139 }
140}
141
142pub struct IccMetrics;
147
148impl IccMetrics {
149 pub fn increment(target: Principal, method: &str) {
151 ICC_METRICS.with_borrow_mut(|counts| {
152 let key = IccMetricKey {
153 target,
154 method: method.to_string(),
155 };
156 let entry = counts.entry(key).or_insert(0);
157 *entry = entry.saturating_add(1);
158 });
159 }
160
161 #[must_use]
163 pub fn snapshot() -> IccMetricsSnapshot {
164 ICC_METRICS.with_borrow(|counts| {
165 counts
166 .iter()
167 .map(|(key, count)| IccMetricEntry {
168 target: key.target,
169 method: key.method.clone(),
170 count: *count,
171 })
172 .collect()
173 })
174 }
175
176 #[cfg(test)]
177 pub fn reset() {
178 ICC_METRICS.with_borrow_mut(HashMap::clear);
179 }
180}
181
182#[cfg(test)]
187mod tests {
188 use super::*;
189 use std::collections::HashMap;
190
191 #[test]
192 fn increments_and_snapshots() {
193 MetricsState::reset();
194
195 MetricsState::increment(MetricKind::CreateCanister);
196 MetricsState::increment(MetricKind::CreateCanister);
197 MetricsState::increment(MetricKind::InstallCode);
198
199 let snapshot = MetricsState::snapshot();
200 let as_map: HashMap<MetricKind, u64> = snapshot
201 .into_iter()
202 .map(|entry| (entry.kind, entry.count))
203 .collect();
204
205 assert_eq!(as_map.get(&MetricKind::CreateCanister), Some(&2));
206 assert_eq!(as_map.get(&MetricKind::InstallCode), Some(&1));
207 assert!(!as_map.contains_key(&MetricKind::CanisterCall));
208 }
209
210 #[test]
211 fn icc_metrics_track_target_and_method() {
212 IccMetrics::reset();
213
214 let t1 = Principal::from_slice(&[1; 29]);
215 let t2 = Principal::from_slice(&[2; 29]);
216
217 IccMetrics::increment(t1, "foo");
218 IccMetrics::increment(t1, "foo");
219 IccMetrics::increment(t1, "bar");
220 IccMetrics::increment(t2, "foo");
221
222 let snapshot = IccMetrics::snapshot();
223 let mut map: HashMap<(Principal, String), u64> = snapshot
224 .into_iter()
225 .map(|entry| ((entry.target, entry.method), entry.count))
226 .collect();
227
228 assert_eq!(map.remove(&(t1, "foo".to_string())), Some(2));
229 assert_eq!(map.remove(&(t1, "bar".to_string())), Some(1));
230 assert_eq!(map.remove(&(t2, "foo".to_string())), Some(1));
231 assert!(map.is_empty());
232 }
233}