1pub mod server;
7use lazy_static::lazy_static;
8use prometheus::{Encoder, Gauge, GaugeVec, Opts, Registry, TextEncoder};
9use sysinfo::{Disks, System};
10
11lazy_static! {
12 pub static ref REGISTRY: Registry = Registry::new();
17
18 pub static ref CPU_USAGE: Gauge = {
22 let gauge = Gauge::new("cpu_usage_percentage", "Current CPU usage percentage").unwrap();
23 REGISTRY.register(Box::new(gauge.clone())).unwrap();
24 gauge
25 };
26
27 pub static ref MEMORY_USAGE_PERCENT: Gauge = {
31 let gauge = Gauge::new("memory_usage_percentage", "Memory usage percentage").unwrap();
32 REGISTRY.register(Box::new(gauge.clone())).unwrap();
33 gauge
34 };
35
36 pub static ref MEMORY_USAGE: Gauge = {
40 let gauge = Gauge::new("memory_usage_bytes", "Memory usage in bytes").unwrap();
41 REGISTRY.register(Box::new(gauge.clone())).unwrap();
42 gauge
43 };
44
45 pub static ref TOTAL_MEMORY: Gauge = {
49 let gauge = Gauge::new("total_memory_bytes", "Total memory in bytes").unwrap();
50 REGISTRY.register(Box::new(gauge.clone())).unwrap();
51 gauge
52 };
53
54 pub static ref AVAILABLE_MEMORY: Gauge = {
58 let gauge = Gauge::new("available_memory_bytes", "Available memory in bytes").unwrap();
59 REGISTRY.register(Box::new(gauge.clone())).unwrap();
60 gauge
61 };
62
63 pub static ref DISK_USAGE: Gauge = {
67 let gauge = Gauge::new("disk_usage_bytes", "Used disk space in bytes").unwrap();
68 REGISTRY.register(Box::new(gauge.clone())).unwrap();
69 gauge
70 };
71
72 pub static ref DISK_USAGE_PERCENT: Gauge = {
76 let gauge = Gauge::new("disk_usage_percentage", "Disk usage percentage").unwrap();
77 REGISTRY.register(Box::new(gauge.clone())).unwrap();
78 gauge
79 };
80
81 pub static ref MONITORS_TOTAL: Gauge = {
85 let gauge = Gauge::new("monitors_total", "Total number of configured monitors").unwrap();
86 REGISTRY.register(Box::new(gauge.clone())).unwrap();
87 gauge
88 };
89
90 pub static ref MONITORS_ACTIVE: Gauge = {
94 let gauge = Gauge::new("monitors_active", "Number of active monitors").unwrap();
95 REGISTRY.register(Box::new(gauge.clone())).unwrap();
96 gauge
97 };
98
99 pub static ref TRIGGERS_TOTAL: Gauge = {
103 let gauge = Gauge::new("triggers_total", "Total number of configured triggers").unwrap();
104 REGISTRY.register(Box::new(gauge.clone())).unwrap();
105 gauge
106 };
107
108 pub static ref CONTRACTS_MONITORED: Gauge = {
112 let gauge = Gauge::new("contracts_monitored", "Total number of contracts being monitored").unwrap();
113 REGISTRY.register(Box::new(gauge.clone())).unwrap();
114 gauge
115 };
116
117 pub static ref NETWORKS_MONITORED: Gauge = {
121 let gauge = Gauge::new("networks_monitored", "Total number of networks being monitored").unwrap();
122 REGISTRY.register(Box::new(gauge.clone())).unwrap();
123 gauge
124 };
125
126 pub static ref NETWORK_MONITORS: GaugeVec = {
130 let gauge = GaugeVec::new(
131 Opts::new("network_monitors", "Number of monitors per network"),
132 &["network"]
133 ).unwrap();
134 REGISTRY.register(Box::new(gauge.clone())).unwrap();
135 gauge
136 };
137}
138
139pub fn gather_metrics() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
141 let encoder = TextEncoder::new();
142 let metric_families = REGISTRY.gather();
143 let mut buffer = Vec::new();
144 encoder.encode(&metric_families, &mut buffer)?;
145 Ok(buffer)
146}
147
148pub fn update_system_metrics() {
150 let mut sys = System::new_all();
151 sys.refresh_all();
152
153 let cpu_usage = sys.global_cpu_usage();
155 CPU_USAGE.set(cpu_usage as f64);
156
157 let total_memory = sys.total_memory();
159 TOTAL_MEMORY.set(total_memory as f64);
160
161 let available_memory = sys.available_memory();
163 AVAILABLE_MEMORY.set(available_memory as f64);
164
165 let memory_usage = sys.used_memory();
167 MEMORY_USAGE.set(memory_usage as f64);
168
169 let memory_percentage = if total_memory > 0 {
171 (memory_usage as f64 / total_memory as f64) * 100.0
172 } else {
173 0.0
174 };
175 MEMORY_USAGE_PERCENT.set(memory_percentage);
176
177 let disks = Disks::new_with_refreshed_list();
180 let mut total_disk_space: u64 = 0;
181 let mut total_disk_available: u64 = 0;
182 for disk in disks.list() {
183 total_disk_space += disk.total_space();
184 total_disk_available += disk.available_space();
185 }
186 let used_disk_space = total_disk_space.saturating_sub(total_disk_available);
188 DISK_USAGE.set(used_disk_space as f64);
189
190 let disk_percentage = if total_disk_space > 0 {
192 (used_disk_space as f64 / total_disk_space as f64) * 100.0
193 } else {
194 0.0
195 };
196 DISK_USAGE_PERCENT.set(disk_percentage);
197}
198
199pub fn update_monitoring_metrics(
201 monitors: &std::collections::HashMap<String, crate::models::Monitor>,
202 triggers: &std::collections::HashMap<String, crate::models::Trigger>,
203 networks: &std::collections::HashMap<String, crate::models::Network>,
204) {
205 let total_monitors = monitors.len();
207 let active_monitors = monitors.values().filter(|m| !m.paused).count();
208
209 MONITORS_TOTAL.set(total_monitors as f64);
210 MONITORS_ACTIVE.set(active_monitors as f64);
211
212 TRIGGERS_TOTAL.set(triggers.len() as f64);
214
215 let mut unique_contracts = std::collections::HashSet::new();
217 for monitor in monitors.values() {
218 for address in &monitor.addresses {
219 for network in &monitor.networks {
221 if networks.contains_key(network) {
223 unique_contracts.insert(format!("{}:{}", network, address.address));
224 }
225 }
226 }
227 }
228 CONTRACTS_MONITORED.set(unique_contracts.len() as f64);
229
230 let mut networks_with_monitors = std::collections::HashSet::new();
232 for monitor in monitors.values().filter(|m| !m.paused) {
233 for network in &monitor.networks {
234 if networks.contains_key(network) {
236 networks_with_monitors.insert(network.clone());
237 }
238 }
239 }
240 NETWORKS_MONITORED.set(networks_with_monitors.len() as f64);
241
242 NETWORK_MONITORS.reset();
244
245 let mut network_monitor_counts = std::collections::HashMap::<String, usize>::new();
247 for monitor in monitors.values().filter(|m| !m.paused) {
248 for network in &monitor.networks {
249 if networks.contains_key(network) {
250 *network_monitor_counts.entry(network.clone()).or_insert(0) += 1;
251 }
252 }
253 }
254
255 for (network, count) in network_monitor_counts {
256 NETWORK_MONITORS
257 .with_label_values(&[&network])
258 .set(count as f64);
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::{
266 models::{BlockChainType, Monitor, Network, TransactionStatus, Trigger},
267 utils::tests::builders::{
268 evm::monitor::MonitorBuilder, network::NetworkBuilder, trigger::TriggerBuilder,
269 },
270 };
271 use std::collections::HashMap;
272 use std::sync::Mutex;
273
274 lazy_static! {
276 static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
277 }
278
279 fn reset_all_metrics() {
281 CPU_USAGE.set(0.0);
283 MEMORY_USAGE.set(0.0);
284 MEMORY_USAGE_PERCENT.set(0.0);
285 TOTAL_MEMORY.set(0.0);
286 AVAILABLE_MEMORY.set(0.0);
287 DISK_USAGE.set(0.0);
288 DISK_USAGE_PERCENT.set(0.0);
289
290 MONITORS_TOTAL.set(0.0);
292 MONITORS_ACTIVE.set(0.0);
293 TRIGGERS_TOTAL.set(0.0);
294 CONTRACTS_MONITORED.set(0.0);
295 NETWORKS_MONITORED.set(0.0);
296 NETWORK_MONITORS.reset();
297 }
298
299 fn create_test_network(slug: &str, name: &str, chain_id: u64) -> Network {
301 NetworkBuilder::new()
302 .name(name)
303 .slug(slug)
304 .network_type(BlockChainType::EVM)
305 .chain_id(chain_id)
306 .rpc_url(&format!("https://{}.example.com", slug))
307 .block_time_ms(15000)
308 .confirmation_blocks(12)
309 .cron_schedule("*/15 * * * * *")
310 .max_past_blocks(1000)
311 .store_blocks(true)
312 .build()
313 }
314
315 fn create_test_monitor(
317 name: &str,
318 networks: Vec<String>,
319 addresses: Vec<String>,
320 paused: bool,
321 ) -> Monitor {
322 MonitorBuilder::new()
323 .name(name)
324 .networks(networks)
325 .paused(paused)
326 .addresses(addresses)
327 .function("transfer(address,uint256)", None)
328 .transaction(TransactionStatus::Success, None)
329 .build()
330 }
331
332 fn create_test_trigger(name: &str) -> Trigger {
333 TriggerBuilder::new()
334 .name(name)
335 .email(
336 "smtp.example.com",
337 "user@example.com",
338 "password123",
339 "alerts@example.com",
340 vec!["user@example.com"],
341 )
342 .message("Alert", "Something happened!")
343 .build()
344 }
345
346 #[test]
347 fn test_gather_metrics_contains_expected_names() {
348 let _lock = TEST_MUTEX.lock().unwrap();
349 reset_all_metrics();
350
351 CPU_USAGE.set(50.0);
353 MEMORY_USAGE_PERCENT.set(60.0);
354 MEMORY_USAGE.set(1024.0);
355 TOTAL_MEMORY.set(2048.0);
356 AVAILABLE_MEMORY.set(1024.0);
357 DISK_USAGE.set(512.0);
358 DISK_USAGE_PERCENT.set(25.0);
359 MONITORS_TOTAL.set(5.0);
360 MONITORS_ACTIVE.set(3.0);
361 TRIGGERS_TOTAL.set(2.0);
362 CONTRACTS_MONITORED.set(4.0);
363 NETWORKS_MONITORED.set(2.0);
364 NETWORK_MONITORS.with_label_values(&["test"]).set(1.0);
365
366 let metrics = gather_metrics().expect("failed to gather metrics");
367 let output = String::from_utf8(metrics).expect("metrics output is not valid UTF-8");
368
369 assert!(output.contains("cpu_usage_percentage"));
371 assert!(output.contains("memory_usage_percentage"));
372 assert!(output.contains("memory_usage_bytes"));
373 assert!(output.contains("total_memory_bytes"));
374 assert!(output.contains("available_memory_bytes"));
375 assert!(output.contains("disk_usage_bytes"));
376 assert!(output.contains("disk_usage_percentage"));
377
378 assert!(output.contains("monitors_total"));
380 assert!(output.contains("monitors_active"));
381 assert!(output.contains("triggers_total"));
382 assert!(output.contains("contracts_monitored"));
383 assert!(output.contains("networks_monitored"));
384 assert!(output.contains("network_monitors"));
385 }
386
387 #[test]
388 fn test_system_metrics_update() {
389 let _lock = TEST_MUTEX.lock().unwrap();
390 reset_all_metrics();
391
392 update_system_metrics();
394
395 let cpu_usage = CPU_USAGE.get();
397 assert!((0.0..=100.0).contains(&cpu_usage));
398
399 let memory_usage = MEMORY_USAGE.get();
400 assert!(memory_usage >= 0.0);
401
402 let memory_percent = MEMORY_USAGE_PERCENT.get();
403 assert!((0.0..=100.0).contains(&memory_percent));
404
405 let total_memory = TOTAL_MEMORY.get();
406 assert!(total_memory > 0.0);
407
408 let expected_percentage = if total_memory > 0.0 {
409 (memory_usage / total_memory) * 100.0
410 } else {
411 0.0
412 };
413 assert_eq!(memory_percent, expected_percentage);
414
415 let available_memory = AVAILABLE_MEMORY.get();
416 assert!(available_memory >= 0.0);
417
418 let disk_usage = DISK_USAGE.get();
419 assert!(disk_usage >= 0.0);
420
421 let disk_percent = DISK_USAGE_PERCENT.get();
422 assert!((0.0..=100.0).contains(&disk_percent));
423
424 assert!(memory_usage <= total_memory);
426
427 assert!(available_memory <= total_memory);
429 }
430
431 #[test]
432 fn test_monitoring_metrics_update() {
433 let _lock = TEST_MUTEX.lock().unwrap();
434 reset_all_metrics();
435
436 let mut monitors = HashMap::new();
438 let mut networks = HashMap::new();
439 let triggers = HashMap::new();
440
441 networks.insert(
443 "ethereum".to_string(),
444 create_test_network("ethereum", "Ethereum", 1),
445 );
446 networks.insert(
447 "polygon".to_string(),
448 create_test_network("polygon", "Polygon", 137),
449 );
450 networks.insert(
451 "arbitrum".to_string(),
452 create_test_network("arbitrum", "Arbitrum", 42161),
453 );
454
455 monitors.insert(
457 "monitor1".to_string(),
458 create_test_monitor(
459 "Test Monitor 1",
460 vec!["ethereum".to_string()],
461 vec!["0x1234567890123456789012345678901234567890".to_string()],
462 false,
463 ),
464 );
465
466 monitors.insert(
467 "monitor2".to_string(),
468 create_test_monitor(
469 "Test Monitor 2",
470 vec!["polygon".to_string(), "ethereum".to_string()],
471 vec!["0x0987654321098765432109876543210987654321".to_string()],
472 true,
473 ),
474 );
475
476 monitors.insert(
477 "monitor3".to_string(),
478 create_test_monitor(
479 "Test Monitor 3",
480 vec!["arbitrum".to_string()],
481 vec![
482 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
483 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
484 ],
485 false,
486 ),
487 );
488
489 update_monitoring_metrics(&monitors, &triggers, &networks);
491
492 assert_eq!(MONITORS_TOTAL.get(), 3.0);
494 assert_eq!(MONITORS_ACTIVE.get(), 2.0);
495 assert_eq!(TRIGGERS_TOTAL.get(), 0.0);
496 assert_eq!(CONTRACTS_MONITORED.get(), 5.0);
497 assert_eq!(NETWORKS_MONITORED.get(), 2.0);
498
499 let ethereum_monitors = NETWORK_MONITORS
501 .get_metric_with_label_values(&["ethereum"])
502 .unwrap();
503 assert_eq!(ethereum_monitors.get(), 1.0);
504
505 let polygon_monitors = NETWORK_MONITORS
506 .get_metric_with_label_values(&["polygon"])
507 .unwrap();
508 assert_eq!(polygon_monitors.get(), 0.0);
509
510 let arbitrum_monitors = NETWORK_MONITORS
511 .get_metric_with_label_values(&["arbitrum"])
512 .unwrap();
513 assert_eq!(arbitrum_monitors.get(), 1.0);
514 }
515
516 #[test]
517 fn test_nonexistent_networks_are_ignored() {
518 let _lock = TEST_MUTEX.lock().unwrap();
519 reset_all_metrics();
520
521 let mut monitors = HashMap::new();
523 let mut networks = HashMap::new();
524 let triggers = HashMap::new();
525
526 networks.insert(
527 "ethereum".to_string(),
528 create_test_network("ethereum", "Ethereum", 1),
529 );
530
531 monitors.insert(
532 "monitor1".to_string(),
533 create_test_monitor(
534 "Test Monitor 1",
535 vec!["ethereum".to_string(), "nonexistent_network".to_string()],
536 vec!["0x1234567890123456789012345678901234567890".to_string()],
537 false,
538 ),
539 );
540
541 update_monitoring_metrics(&monitors, &triggers, &networks);
543
544 assert_eq!(NETWORKS_MONITORED.get(), 1.0);
546 assert_eq!(CONTRACTS_MONITORED.get(), 1.0);
547
548 let nonexistent = NETWORK_MONITORS.get_metric_with_label_values(&["nonexistent_network"]);
550 assert!(nonexistent.is_err() || nonexistent.unwrap().get() == 0.0);
551 }
552
553 #[test]
554 fn test_multiple_monitors_same_network() {
555 let _lock = TEST_MUTEX.lock().unwrap();
556 reset_all_metrics();
557
558 let mut monitors = HashMap::new();
560 let mut networks = HashMap::new();
561 let triggers = HashMap::new();
562
563 networks.insert(
564 "ethereum".to_string(),
565 create_test_network("ethereum", "Ethereum", 1),
566 );
567
568 monitors.insert(
570 "monitor1".to_string(),
571 create_test_monitor(
572 "Test Monitor 1",
573 vec!["ethereum".to_string()],
574 vec!["0x1111111111111111111111111111111111111111".to_string()],
575 false,
576 ),
577 );
578
579 monitors.insert(
580 "monitor2".to_string(),
581 create_test_monitor(
582 "Test Monitor 2",
583 vec!["ethereum".to_string()],
584 vec!["0x2222222222222222222222222222222222222222".to_string()],
585 false,
586 ),
587 );
588
589 monitors.insert(
590 "monitor3".to_string(),
591 create_test_monitor(
592 "Test Monitor 3",
593 vec!["ethereum".to_string()],
594 vec!["0x3333333333333333333333333333333333333333".to_string()],
595 true, ),
597 );
598
599 update_monitoring_metrics(&monitors, &triggers, &networks);
601
602 assert_eq!(MONITORS_TOTAL.get(), 3.0);
604 assert_eq!(MONITORS_ACTIVE.get(), 2.0);
605 assert_eq!(NETWORKS_MONITORED.get(), 1.0);
606
607 let ethereum_monitors = NETWORK_MONITORS
609 .get_metric_with_label_values(&["ethereum"])
610 .unwrap();
611 assert_eq!(ethereum_monitors.get(), 2.0);
612 }
613
614 #[test]
615 fn test_multiple_contracts_per_monitor() {
616 let _lock = TEST_MUTEX.lock().unwrap();
617 reset_all_metrics();
618
619 let mut monitors = HashMap::new();
621 let mut networks = HashMap::new();
622 let triggers = HashMap::new();
623
624 networks.insert(
625 "ethereum".to_string(),
626 create_test_network("ethereum", "Ethereum", 1),
627 );
628
629 monitors.insert(
631 "monitor1".to_string(),
632 create_test_monitor(
633 "Test Monitor 1",
634 vec!["ethereum".to_string()],
635 vec![
636 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
637 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
638 "0xcccccccccccccccccccccccccccccccccccccccc".to_string(),
639 ],
640 false,
641 ),
642 );
643
644 update_monitoring_metrics(&monitors, &triggers, &networks);
646
647 assert_eq!(CONTRACTS_MONITORED.get(), 3.0);
649 }
650
651 #[test]
652 fn test_triggers_count() {
653 let _lock = TEST_MUTEX
654 .lock()
655 .unwrap_or_else(|poisoned| poisoned.into_inner());
656 reset_all_metrics();
657
658 let monitors = HashMap::new();
660 let networks = HashMap::new();
661 let mut triggers = HashMap::new();
662
663 triggers.insert("trigger1".to_string(), create_test_trigger("trigger1"));
665 triggers.insert("trigger2".to_string(), create_test_trigger("trigger2"));
666 triggers.insert("trigger3".to_string(), create_test_trigger("trigger3"));
667
668 update_monitoring_metrics(&monitors, &triggers, &networks);
670
671 let total_triggers = TRIGGERS_TOTAL.get();
673 assert_eq!(total_triggers, 3.0);
674
675 assert_eq!(MONITORS_TOTAL.get(), 0.0);
677 assert_eq!(MONITORS_ACTIVE.get(), 0.0);
678 assert_eq!(CONTRACTS_MONITORED.get(), 0.0);
679 assert_eq!(NETWORKS_MONITORED.get(), 0.0);
680 }
681
682 #[test]
683 fn test_empty_collections() {
684 let _lock = TEST_MUTEX.lock().unwrap();
685
686 let monitors = HashMap::new();
688 let networks = HashMap::new();
689 let triggers = HashMap::new();
690
691 MONITORS_TOTAL.set(10.0);
693 MONITORS_ACTIVE.set(5.0);
694 TRIGGERS_TOTAL.set(3.0);
695 CONTRACTS_MONITORED.set(7.0);
696 NETWORKS_MONITORED.set(2.0);
697 NETWORK_MONITORS.reset();
698
699 NETWORK_MONITORS.with_label_values(&["test"]).set(3.0);
701
702 update_monitoring_metrics(&monitors, &triggers, &networks);
704
705 assert_eq!(MONITORS_TOTAL.get(), 0.0);
707 assert_eq!(MONITORS_ACTIVE.get(), 0.0);
708 assert_eq!(TRIGGERS_TOTAL.get(), 0.0);
709 assert_eq!(CONTRACTS_MONITORED.get(), 0.0);
710 assert_eq!(NETWORKS_MONITORED.get(), 0.0);
711
712 let test_network = NETWORK_MONITORS
714 .get_metric_with_label_values(&["test"])
715 .unwrap();
716 assert_eq!(test_network.get(), 0.0);
717 }
718}