canic_core/model/metrics/
endpoint.rs1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::HashMap};
4
5thread_local! {
6 static ENDPOINT_ATTEMPT_METRICS: RefCell<HashMap<&'static str, EndpointAttemptCounts>> =
7 RefCell::new(HashMap::new());
8
9 static ENDPOINT_RESULT_METRICS: RefCell<HashMap<&'static str, EndpointResultCounts>> =
10 RefCell::new(HashMap::new());
11}
12
13#[derive(Default)]
23struct EndpointAttemptCounts {
24 attempted: u64,
25 completed: u64,
26}
27
28#[derive(Default)]
34struct EndpointResultCounts {
35 ok: u64,
36 err: u64,
37}
38
39#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
48pub struct EndpointAttemptMetricEntry {
49 pub endpoint: String,
50 pub attempted: u64,
51 pub completed: u64,
52}
53
54pub type EndpointAttemptMetricsSnapshot = Vec<EndpointAttemptMetricEntry>;
59
60#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
66pub struct EndpointResultMetricEntry {
67 pub endpoint: String,
68 pub ok: u64,
69 pub err: u64,
70}
71
72pub type EndpointResultMetricsSnapshot = Vec<EndpointResultMetricEntry>;
77
78pub struct EndpointAttemptMetrics;
92
93impl EndpointAttemptMetrics {
94 pub fn increment_attempted(endpoint: &'static str) {
95 ENDPOINT_ATTEMPT_METRICS.with_borrow_mut(|counts| {
96 let entry = counts.entry(endpoint).or_default();
97 entry.attempted = entry.attempted.saturating_add(1);
98 });
99 }
100
101 pub fn increment_completed(endpoint: &'static str) {
102 ENDPOINT_ATTEMPT_METRICS.with_borrow_mut(|counts| {
103 let entry = counts.entry(endpoint).or_default();
104 entry.completed = entry.completed.saturating_add(1);
105 });
106 }
107
108 #[must_use]
109 pub fn snapshot() -> EndpointAttemptMetricsSnapshot {
110 ENDPOINT_ATTEMPT_METRICS.with_borrow(|counts| {
111 counts
112 .iter()
113 .map(|(endpoint, c)| EndpointAttemptMetricEntry {
114 endpoint: (*endpoint).to_string(),
115 attempted: c.attempted,
116 completed: c.completed,
117 })
118 .collect()
119 })
120 }
121
122 #[cfg(test)]
123 pub fn reset() {
124 ENDPOINT_ATTEMPT_METRICS.with_borrow_mut(HashMap::clear);
125 }
126}
127
128pub struct EndpointResultMetrics;
137
138impl EndpointResultMetrics {
139 pub fn increment_ok(endpoint: &'static str) {
140 ENDPOINT_RESULT_METRICS.with_borrow_mut(|counts| {
141 let entry = counts.entry(endpoint).or_default();
142 entry.ok = entry.ok.saturating_add(1);
143 });
144 }
145
146 pub fn increment_err(endpoint: &'static str) {
147 ENDPOINT_RESULT_METRICS.with_borrow_mut(|counts| {
148 let entry = counts.entry(endpoint).or_default();
149 entry.err = entry.err.saturating_add(1);
150 });
151 }
152
153 #[must_use]
154 pub fn snapshot() -> EndpointResultMetricsSnapshot {
155 ENDPOINT_RESULT_METRICS.with_borrow(|counts| {
156 counts
157 .iter()
158 .map(|(endpoint, c)| EndpointResultMetricEntry {
159 endpoint: (*endpoint).to_string(),
160 ok: c.ok,
161 err: c.err,
162 })
163 .collect()
164 })
165 }
166
167 #[cfg(test)]
168 pub fn reset() {
169 ENDPOINT_RESULT_METRICS.with_borrow_mut(HashMap::clear);
170 }
171}
172
173#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::collections::HashMap;
181
182 #[test]
183 fn endpoint_attempt_metrics_track_attempted_and_completed() {
184 EndpointAttemptMetrics::reset();
185
186 EndpointAttemptMetrics::increment_attempted("a");
187 EndpointAttemptMetrics::increment_attempted("a");
188 EndpointAttemptMetrics::increment_attempted("b");
189 EndpointAttemptMetrics::increment_completed("a");
190
191 let snapshot = EndpointAttemptMetrics::snapshot();
192 let mut map: HashMap<String, (u64, u64)> = snapshot
193 .into_iter()
194 .map(|e| (e.endpoint, (e.attempted, e.completed)))
195 .collect();
196
197 assert_eq!(map.remove("a"), Some((2, 1)));
198 assert_eq!(map.remove("b"), Some((1, 0)));
199 assert!(map.is_empty());
200 }
201
202 #[test]
203 fn endpoint_result_metrics_track_ok_and_err() {
204 EndpointResultMetrics::reset();
205
206 EndpointResultMetrics::increment_ok("a");
207 EndpointResultMetrics::increment_ok("a");
208 EndpointResultMetrics::increment_err("a");
209 EndpointResultMetrics::increment_err("b");
210
211 let snapshot = EndpointResultMetrics::snapshot();
212 let mut map: HashMap<String, (u64, u64)> = snapshot
213 .into_iter()
214 .map(|e| (e.endpoint, (e.ok, e.err)))
215 .collect();
216
217 assert_eq!(map.remove("a"), Some((2, 1)));
218 assert_eq!(map.remove("b"), Some((0, 1)));
219 assert!(map.is_empty());
220 }
221}