confium_coordinator/coordinator/
connection_stats.rs1use chrono::{DateTime, Utc};
8use std::collections::HashMap;
9use std::sync::Mutex;
10
11#[derive(Debug, Clone)]
13pub struct ConnectionStats {
14 pub signer_id: String,
16 pub connected_at: DateTime<Utc>,
18 pub messages_received: u64,
20 pub messages_sent: u64,
22 pub bytes_received: u64,
24 pub bytes_sent: u64,
26 pub error_count: u64,
28 pub last_activity: DateTime<Utc>,
30}
31
32impl ConnectionStats {
33 pub fn new(signer_id: &str) -> Self {
35 let now = Utc::now();
36 Self {
37 signer_id: signer_id.into(),
38 connected_at: now,
39 messages_received: 0,
40 messages_sent: 0,
41 bytes_received: 0,
42 bytes_sent: 0,
43 error_count: 0,
44 last_activity: now,
45 }
46 }
47
48 pub fn record_received(&mut self, bytes: u64) {
50 self.messages_received += 1;
51 self.bytes_received += bytes;
52 self.last_activity = Utc::now();
53 }
54
55 pub fn record_sent(&mut self, bytes: u64) {
57 self.messages_sent += 1;
58 self.bytes_sent += bytes;
59 self.last_activity = Utc::now();
60 }
61
62 pub fn record_error(&mut self) {
64 self.error_count += 1;
65 self.last_activity = Utc::now();
66 }
67
68 pub fn connection_duration_secs(&self) -> i64 {
70 (Utc::now() - self.connected_at).num_seconds()
71 }
72
73 pub fn total_messages(&self) -> u64 {
75 self.messages_received + self.messages_sent
76 }
77
78 pub fn total_bytes(&self) -> u64 {
80 self.bytes_received + self.bytes_sent
81 }
82}
83
84#[derive(Default)]
86pub struct ConnectionStatsRegistry {
87 entries: Mutex<HashMap<String, ConnectionStats>>,
88}
89
90impl ConnectionStatsRegistry {
91 pub fn new() -> Self {
93 Self::default()
94 }
95
96 pub fn register(&self, signer_id: &str) {
99 self.entries
100 .lock()
101 .unwrap()
102 .insert(signer_id.into(), ConnectionStats::new(signer_id));
103 }
104
105 pub fn record_received(&self, signer_id: &str, bytes: u64) {
107 if let Some(stats) = self.entries.lock().unwrap().get_mut(signer_id) {
108 stats.record_received(bytes);
109 }
110 }
111
112 pub fn record_sent(&self, signer_id: &str, bytes: u64) {
114 if let Some(stats) = self.entries.lock().unwrap().get_mut(signer_id) {
115 stats.record_sent(bytes);
116 }
117 }
118
119 pub fn record_error(&self, signer_id: &str) {
121 if let Some(stats) = self.entries.lock().unwrap().get_mut(signer_id) {
122 stats.record_error();
123 }
124 }
125
126 pub fn get(&self, signer_id: &str) -> Option<ConnectionStats> {
128 self.entries.lock().unwrap().get(signer_id).cloned()
129 }
130
131 pub fn all(&self) -> Vec<ConnectionStats> {
133 self.entries.lock().unwrap().values().cloned().collect()
134 }
135
136 pub fn remove(&self, signer_id: &str) {
138 self.entries.lock().unwrap().remove(signer_id);
139 }
140
141 pub fn count(&self) -> usize {
143 self.entries.lock().unwrap().len()
144 }
145
146 pub fn clear(&self) {
148 self.entries.lock().unwrap().clear();
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn new_stats_starts_at_zero() {
158 let stats = ConnectionStats::new("s1");
159 assert_eq!(stats.signer_id, "s1");
160 assert_eq!(stats.messages_received, 0);
161 assert_eq!(stats.messages_sent, 0);
162 assert_eq!(stats.bytes_received, 0);
163 assert_eq!(stats.bytes_sent, 0);
164 assert_eq!(stats.error_count, 0);
165 }
166
167 #[test]
168 fn record_received_increments() {
169 let mut stats = ConnectionStats::new("s1");
170 stats.record_received(100);
171 stats.record_received(50);
172 assert_eq!(stats.messages_received, 2);
173 assert_eq!(stats.bytes_received, 150);
174 }
175
176 #[test]
177 fn record_sent_increments() {
178 let mut stats = ConnectionStats::new("s1");
179 stats.record_sent(200);
180 assert_eq!(stats.messages_sent, 1);
181 assert_eq!(stats.bytes_sent, 200);
182 }
183
184 #[test]
185 fn record_error_increments() {
186 let mut stats = ConnectionStats::new("s1");
187 stats.record_error();
188 stats.record_error();
189 assert_eq!(stats.error_count, 2);
190 }
191
192 #[test]
193 fn total_messages_and_bytes() {
194 let mut stats = ConnectionStats::new("s1");
195 stats.record_received(100);
196 stats.record_sent(200);
197 assert_eq!(stats.total_messages(), 2);
198 assert_eq!(stats.total_bytes(), 300);
199 }
200
201 #[test]
202 fn connection_duration_positive() {
203 let stats = ConnectionStats::new("s1");
204 std::thread::sleep(std::time::Duration::from_millis(100));
205 assert!(stats.connection_duration_secs() >= 0);
206 }
207
208 #[test]
209 fn registry_register_and_get() {
210 let reg = ConnectionStatsRegistry::new();
211 reg.register("s1");
212 let stats = reg.get("s1").unwrap();
213 assert_eq!(stats.signer_id, "s1");
214 assert_eq!(stats.messages_received, 0);
215 }
216
217 #[test]
218 fn registry_record_received() {
219 let reg = ConnectionStatsRegistry::new();
220 reg.register("s1");
221 reg.record_received("s1", 500);
222 let stats = reg.get("s1").unwrap();
223 assert_eq!(stats.messages_received, 1);
224 assert_eq!(stats.bytes_received, 500);
225 }
226
227 #[test]
228 fn registry_record_sent() {
229 let reg = ConnectionStatsRegistry::new();
230 reg.register("s1");
231 reg.record_sent("s1", 1000);
232 assert_eq!(reg.get("s1").unwrap().bytes_sent, 1000);
233 }
234
235 #[test]
236 fn registry_record_error() {
237 let reg = ConnectionStatsRegistry::new();
238 reg.register("s1");
239 reg.record_error("s1");
240 assert_eq!(reg.get("s1").unwrap().error_count, 1);
241 }
242
243 #[test]
244 fn registry_all_returns_all_signers() {
245 let reg = ConnectionStatsRegistry::new();
246 reg.register("s1");
247 reg.register("s2");
248 reg.register("s3");
249 assert_eq!(reg.all().len(), 3);
250 }
251
252 #[test]
253 fn registry_remove() {
254 let reg = ConnectionStatsRegistry::new();
255 reg.register("s1");
256 reg.remove("s1");
257 assert!(reg.get("s1").is_none());
258 assert_eq!(reg.count(), 0);
259 }
260
261 #[test]
262 fn registry_unknown_signer_no_op() {
263 let reg = ConnectionStatsRegistry::new();
264 reg.record_received("unknown", 100);
265 assert_eq!(reg.count(), 0);
266 }
267}