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 available_memory = AVAILABLE_MEMORY.get();
409 assert!(available_memory >= 0.0);
410
411 let disk_usage = DISK_USAGE.get();
412 assert!(disk_usage >= 0.0);
413
414 let disk_percent = DISK_USAGE_PERCENT.get();
415 assert!((0.0..=100.0).contains(&disk_percent));
416
417 assert!(memory_usage <= total_memory);
419
420 assert!(available_memory <= total_memory);
422 }
423
424 #[test]
425 fn test_monitoring_metrics_update() {
426 let _lock = TEST_MUTEX.lock().unwrap();
427 reset_all_metrics();
428
429 let mut monitors = HashMap::new();
431 let mut networks = HashMap::new();
432 let triggers = HashMap::new();
433
434 networks.insert(
436 "ethereum".to_string(),
437 create_test_network("ethereum", "Ethereum", 1),
438 );
439 networks.insert(
440 "polygon".to_string(),
441 create_test_network("polygon", "Polygon", 137),
442 );
443 networks.insert(
444 "arbitrum".to_string(),
445 create_test_network("arbitrum", "Arbitrum", 42161),
446 );
447
448 monitors.insert(
450 "monitor1".to_string(),
451 create_test_monitor(
452 "Test Monitor 1",
453 vec!["ethereum".to_string()],
454 vec!["0x1234567890123456789012345678901234567890".to_string()],
455 false,
456 ),
457 );
458
459 monitors.insert(
460 "monitor2".to_string(),
461 create_test_monitor(
462 "Test Monitor 2",
463 vec!["polygon".to_string(), "ethereum".to_string()],
464 vec!["0x0987654321098765432109876543210987654321".to_string()],
465 true,
466 ),
467 );
468
469 monitors.insert(
470 "monitor3".to_string(),
471 create_test_monitor(
472 "Test Monitor 3",
473 vec!["arbitrum".to_string()],
474 vec![
475 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
476 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
477 ],
478 false,
479 ),
480 );
481
482 update_monitoring_metrics(&monitors, &triggers, &networks);
484
485 assert_eq!(MONITORS_TOTAL.get(), 3.0);
487 assert_eq!(MONITORS_ACTIVE.get(), 2.0);
488 assert_eq!(TRIGGERS_TOTAL.get(), 0.0);
489 assert_eq!(CONTRACTS_MONITORED.get(), 5.0);
490 assert_eq!(NETWORKS_MONITORED.get(), 2.0);
491
492 let ethereum_monitors = NETWORK_MONITORS
494 .get_metric_with_label_values(&["ethereum"])
495 .unwrap();
496 assert_eq!(ethereum_monitors.get(), 1.0);
497
498 let polygon_monitors = NETWORK_MONITORS
499 .get_metric_with_label_values(&["polygon"])
500 .unwrap();
501 assert_eq!(polygon_monitors.get(), 0.0);
502
503 let arbitrum_monitors = NETWORK_MONITORS
504 .get_metric_with_label_values(&["arbitrum"])
505 .unwrap();
506 assert_eq!(arbitrum_monitors.get(), 1.0);
507 }
508
509 #[test]
510 fn test_nonexistent_networks_are_ignored() {
511 let _lock = TEST_MUTEX.lock().unwrap();
512 reset_all_metrics();
513
514 let mut monitors = HashMap::new();
516 let mut networks = HashMap::new();
517 let triggers = HashMap::new();
518
519 networks.insert(
520 "ethereum".to_string(),
521 create_test_network("ethereum", "Ethereum", 1),
522 );
523
524 monitors.insert(
525 "monitor1".to_string(),
526 create_test_monitor(
527 "Test Monitor 1",
528 vec!["ethereum".to_string(), "nonexistent_network".to_string()],
529 vec!["0x1234567890123456789012345678901234567890".to_string()],
530 false,
531 ),
532 );
533
534 update_monitoring_metrics(&monitors, &triggers, &networks);
536
537 assert_eq!(NETWORKS_MONITORED.get(), 1.0);
539 assert_eq!(CONTRACTS_MONITORED.get(), 1.0);
540
541 let nonexistent = NETWORK_MONITORS.get_metric_with_label_values(&["nonexistent_network"]);
543 assert!(nonexistent.is_err() || nonexistent.unwrap().get() == 0.0);
544 }
545
546 #[test]
547 fn test_multiple_monitors_same_network() {
548 let _lock = TEST_MUTEX.lock().unwrap();
549 reset_all_metrics();
550
551 let mut monitors = HashMap::new();
553 let mut networks = HashMap::new();
554 let triggers = HashMap::new();
555
556 networks.insert(
557 "ethereum".to_string(),
558 create_test_network("ethereum", "Ethereum", 1),
559 );
560
561 monitors.insert(
563 "monitor1".to_string(),
564 create_test_monitor(
565 "Test Monitor 1",
566 vec!["ethereum".to_string()],
567 vec!["0x1111111111111111111111111111111111111111".to_string()],
568 false,
569 ),
570 );
571
572 monitors.insert(
573 "monitor2".to_string(),
574 create_test_monitor(
575 "Test Monitor 2",
576 vec!["ethereum".to_string()],
577 vec!["0x2222222222222222222222222222222222222222".to_string()],
578 false,
579 ),
580 );
581
582 monitors.insert(
583 "monitor3".to_string(),
584 create_test_monitor(
585 "Test Monitor 3",
586 vec!["ethereum".to_string()],
587 vec!["0x3333333333333333333333333333333333333333".to_string()],
588 true, ),
590 );
591
592 update_monitoring_metrics(&monitors, &triggers, &networks);
594
595 assert_eq!(MONITORS_TOTAL.get(), 3.0);
597 assert_eq!(MONITORS_ACTIVE.get(), 2.0);
598 assert_eq!(NETWORKS_MONITORED.get(), 1.0);
599
600 let ethereum_monitors = NETWORK_MONITORS
602 .get_metric_with_label_values(&["ethereum"])
603 .unwrap();
604 assert_eq!(ethereum_monitors.get(), 2.0);
605 }
606
607 #[test]
608 fn test_multiple_contracts_per_monitor() {
609 let _lock = TEST_MUTEX.lock().unwrap();
610 reset_all_metrics();
611
612 let mut monitors = HashMap::new();
614 let mut networks = HashMap::new();
615 let triggers = HashMap::new();
616
617 networks.insert(
618 "ethereum".to_string(),
619 create_test_network("ethereum", "Ethereum", 1),
620 );
621
622 monitors.insert(
624 "monitor1".to_string(),
625 create_test_monitor(
626 "Test Monitor 1",
627 vec!["ethereum".to_string()],
628 vec![
629 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
630 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
631 "0xcccccccccccccccccccccccccccccccccccccccc".to_string(),
632 ],
633 false,
634 ),
635 );
636
637 update_monitoring_metrics(&monitors, &triggers, &networks);
639
640 assert_eq!(CONTRACTS_MONITORED.get(), 3.0);
642 }
643
644 #[test]
645 fn test_triggers_count() {
646 let _lock = TEST_MUTEX
647 .lock()
648 .unwrap_or_else(|poisoned| poisoned.into_inner());
649 reset_all_metrics();
650
651 let monitors = HashMap::new();
653 let networks = HashMap::new();
654 let mut triggers = HashMap::new();
655
656 triggers.insert("trigger1".to_string(), create_test_trigger("trigger1"));
658 triggers.insert("trigger2".to_string(), create_test_trigger("trigger2"));
659 triggers.insert("trigger3".to_string(), create_test_trigger("trigger3"));
660
661 update_monitoring_metrics(&monitors, &triggers, &networks);
663
664 let total_triggers = TRIGGERS_TOTAL.get();
666 assert_eq!(total_triggers, 3.0);
667
668 assert_eq!(MONITORS_TOTAL.get(), 0.0);
670 assert_eq!(MONITORS_ACTIVE.get(), 0.0);
671 assert_eq!(CONTRACTS_MONITORED.get(), 0.0);
672 assert_eq!(NETWORKS_MONITORED.get(), 0.0);
673 }
674
675 #[test]
676 fn test_empty_collections() {
677 let _lock = TEST_MUTEX.lock().unwrap();
678
679 let monitors = HashMap::new();
681 let networks = HashMap::new();
682 let triggers = HashMap::new();
683
684 MONITORS_TOTAL.set(10.0);
686 MONITORS_ACTIVE.set(5.0);
687 TRIGGERS_TOTAL.set(3.0);
688 CONTRACTS_MONITORED.set(7.0);
689 NETWORKS_MONITORED.set(2.0);
690 NETWORK_MONITORS.reset();
691
692 NETWORK_MONITORS.with_label_values(&["test"]).set(3.0);
694
695 update_monitoring_metrics(&monitors, &triggers, &networks);
697
698 assert_eq!(MONITORS_TOTAL.get(), 0.0);
700 assert_eq!(MONITORS_ACTIVE.get(), 0.0);
701 assert_eq!(TRIGGERS_TOTAL.get(), 0.0);
702 assert_eq!(CONTRACTS_MONITORED.get(), 0.0);
703 assert_eq!(NETWORKS_MONITORED.get(), 0.0);
704
705 let test_network = NETWORK_MONITORS
707 .get_metric_with_label_values(&["test"])
708 .unwrap();
709 assert_eq!(test_network.get(), 0.0);
710 }
711}