Skip to content

Commit 2f5b617

Browse files
committed
rPing: enhance device monitoring by adding status updates and new devices; streamline ping logic and improve error handling
1 parent 97088b5 commit 2f5b617

File tree

4 files changed

+227
-114
lines changed

4 files changed

+227
-114
lines changed

devices.json

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
[
2-
{
3-
"name": "Firewall",
4-
"ip": "192.168.1.1",
5-
"category": "Firewall",
6-
"sensors": [
7-
"Ping"
8-
]
9-
},
102
{
113
"name": "Google DNS",
124
"ip": "8.8.8.8",
@@ -29,14 +21,6 @@
2921
],
3022
"http_path": "http://google.com"
3123
},
32-
{
33-
"name": "Network Switch",
34-
"ip": "192.168.0.100",
35-
"category": "Network Equipment",
36-
"sensors": [
37-
"Ping"
38-
]
39-
},
4024
{
4125
"name": "Antivirus Server",
4226
"ip": "8.8.4.4",
@@ -47,5 +31,59 @@
4731
"Https"
4832
],
4933
"http_path": "https://google.in"
34+
},
35+
{
36+
"name": "Google DNS [1]",
37+
"ip": "4.2.2.2",
38+
"category": "DNS Server",
39+
"sensors": [
40+
"Ping",
41+
"Http"
42+
],
43+
"http_path": "https://google.com"
44+
},
45+
{
46+
"name": "Adguard Server [1]",
47+
"ip": "94.140.15.16",
48+
"category": "DNS Server",
49+
"sensors": [
50+
"Ping",
51+
"Http"
52+
],
53+
"http_path": "https://google.com"
54+
},
55+
{
56+
"name": "Adguard Server [2]",
57+
"ip": "94.140.14.15",
58+
"category": "DNS Server",
59+
"sensors": [
60+
"Ping",
61+
"Http"
62+
],
63+
"http_path": "https://google.com"
64+
},
65+
{
66+
"name": "Cisco Virtual Controller",
67+
"ip": "8.8.8.8",
68+
"category": "Access Point",
69+
"sensors": [
70+
"Ping"
71+
]
72+
},
73+
{
74+
"name": "NAS",
75+
"ip": "8.8.4.4",
76+
"category": "Storage Server",
77+
"sensors": [
78+
"Ping"
79+
]
80+
},
81+
{
82+
"name": "CCTV Server",
83+
"ip": "8.8.8.8",
84+
"category": "CCTV",
85+
"sensors": [
86+
"Ping"
87+
]
5088
}
5189
]

src/main.rs

Lines changed: 105 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rocket::{get, post, delete, routes, State, response::Redirect, catch, catche
88
use rocket::serde::json::Json;
99
use rocket::fs::{NamedFile, FileServer, relative};
1010
use models::{Device as ModelDevice, SensorType};
11-
use log::{info, error, debug};
11+
use log::{info, error};
1212
use sensors::{monitor_ping, monitor_http};
1313
use std::fs::{self, OpenOptions};
1414
use std::io::{Read, Write};
@@ -60,17 +60,34 @@ async fn process_logs(start_date_parsed: Option<NaiveDate>, end_date_parsed: Opt
6060
// Define a struct to track device status.
6161
#[derive(Debug, Clone)]
6262
struct DeviceStatus {
63-
failed_attempts: u32,
64-
last_status: bool,
63+
ping_status: Option<bool>,
64+
http_status: Option<bool>,
65+
bandwidth_usage: Option<f64>,
66+
last_update: DateTime<Local>,
67+
changed_at: DateTime<Local>,
6568
}
6669

6770
impl DeviceStatus {
6871
fn new() -> Self {
69-
DeviceStatus {
70-
failed_attempts: 0,
71-
last_status: true, // Assume initially up
72+
let now = Local::now();
73+
Self {
74+
ping_status: None,
75+
http_status: None,
76+
bandwidth_usage: None,
77+
last_update: now,
78+
changed_at: now,
7279
}
7380
}
81+
82+
fn update_ping(&mut self, new_status: bool) -> bool {
83+
let changed = self.ping_status != Some(new_status);
84+
if changed {
85+
self.ping_status = Some(new_status);
86+
self.changed_at = Local::now();
87+
}
88+
self.last_update = Local::now();
89+
changed
90+
}
7491
}
7592

7693
// Use the new DeviceStatus struct in the type alias.
@@ -567,82 +584,107 @@ async fn main() {
567584
let status_map_clone = device_status_map.clone();
568585

569586
tokio::spawn(async move {
587+
let mut device_statuses: HashMap<String, DeviceStatus> = HashMap::new();
588+
570589
loop {
571590
let devices_to_monitor: Vec<ModelDevice> = {
572591
let locked = devices_clone.lock().await;
573592
locked.clone()
574593
};
575594

595+
let mut status_changed = false;
596+
576597
for dev in devices_to_monitor {
577-
let mut updated_dev = dev.clone();
598+
let status = device_statuses.entry(dev.ip.clone())
599+
.or_insert_with(DeviceStatus::new);
578600

579-
// Always perform ping check regardless of sensor configuration
580-
match monitor_ping(&updated_dev.ip).await {
581-
true => {
582-
debug!("Ping successful for {} ({})", updated_dev.name, updated_dev.ip);
583-
updated_dev.ping_status = Some(true);
584-
},
585-
false => {
586-
error!("Ping failed for {} ({})", updated_dev.name, updated_dev.ip);
587-
updated_dev.ping_status = Some(false);
588-
}
589-
}
590-
591-
// HTTP and bandwidth checks only if configured
592-
if updated_dev.sensors.contains(&SensorType::Http) || updated_dev.sensors.contains(&SensorType::Https) {
593-
if let Some(ref url) = updated_dev.http_path {
594-
updated_dev.http_status = Some(monitor_http(url).await);
595-
updated_dev.bandwidth_usage = Some(rand::thread_rng().gen_range(10.0..1000.0));
596-
}
601+
// First check ping
602+
let ping_result = monitor_ping(&dev.ip).await;
603+
if status.update_ping(ping_result) {
604+
status_changed = true;
597605
}
598606

599607
// Update device in shared state
600608
let mut devices_locked = devices_clone.lock().await;
601-
if let Some(existing_dev) = devices_locked.iter_mut().find(|d| d.ip == updated_dev.ip) {
602-
existing_dev.ping_status = updated_dev.ping_status;
603-
existing_dev.http_status = updated_dev.http_status;
604-
existing_dev.bandwidth_usage = updated_dev.bandwidth_usage;
605-
}
606-
}
607-
608-
// Write to log file
609-
if let Ok(mut file) = OpenOptions::new().append(true).create(true).open(LOG_FILE) {
610-
let now: DateTime<Local> = Local::now();
611-
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
612-
let today = now.format("%Y-%m-%d").to_string();
613-
614-
// Write header if needed
615-
if last_log_date != today {
616-
if let Err(e) = writeln!(file, "// {}", today) {
617-
error!("Failed to write log header: {}", e);
609+
if let Some(device) = devices_locked.iter_mut().find(|d| d.ip == dev.ip) {
610+
device.ping_status = status.ping_status;
611+
612+
// Check HTTP and bandwidth if configured and ping is successful
613+
if device.ping_status == Some(true) {
614+
if device.sensors.contains(&SensorType::Http) ||
615+
device.sensors.contains(&SensorType::Https) {
616+
if let Some(ref url) = device.http_path {
617+
match monitor_http(url).await {
618+
true => {
619+
device.http_status = Some(true);
620+
// Simulate bandwidth measurement only for successful HTTP connections
621+
device.bandwidth_usage = Some(rand::thread_rng().gen_range(10.0..1000.0));
622+
status_changed = true;
623+
},
624+
false => {
625+
device.http_status = Some(false);
626+
device.bandwidth_usage = None;
627+
status_changed = true;
628+
}
629+
}
630+
}
631+
}
632+
} else {
633+
// If ping fails, mark HTTP as down and clear bandwidth
634+
if device.sensors.contains(&SensorType::Http) ||
635+
device.sensors.contains(&SensorType::Https) {
636+
device.http_status = Some(false);
637+
device.bandwidth_usage = None;
638+
status_changed = true;
639+
}
618640
}
619-
last_log_date = today;
620641
}
642+
}
621643

622-
// Log each device status
623-
let devices_locked = devices_clone.lock().await;
624-
for dev in devices_locked.iter() {
625-
let log_entry = format!(
626-
"{} - {} ({}): Ping: {}, HTTP: {}, Bandwidth: {}\n",
627-
timestamp,
628-
dev.name,
629-
dev.ip,
630-
dev.ping_status.map_or("N/A", |s| if s { "OK" } else { "FAIL" }),
631-
dev.http_status.map_or("N/A", |s| if s { "OK" } else { "FAIL" }),
632-
dev.bandwidth_usage.map_or("N/A".to_string(), |b| format!("{:.2} Mbps", b))
633-
);
634-
635-
if let Err(e) = file.write_all(log_entry.as_bytes()) {
636-
error!("Failed to write log entry: {}", e);
644+
// Write to log file when status changes
645+
if status_changed {
646+
if let Ok(mut file) = OpenOptions::new().append(true).create(true).open(LOG_FILE) {
647+
let now = Local::now();
648+
let devices_locked = devices_clone.lock().await;
649+
650+
for dev in devices_locked.iter() {
651+
let status = device_statuses.get(&dev.ip)
652+
.cloned()
653+
.unwrap_or_else(DeviceStatus::new);
654+
655+
// Format HTTP status and bandwidth based on sensor configuration
656+
let http_status = if dev.sensors.contains(&SensorType::Http) ||
657+
dev.sensors.contains(&SensorType::Https) {
658+
dev.http_status.map_or("FAIL", |s| if s { "OK" } else { "FAIL" })
659+
} else {
660+
"N/A"
661+
};
662+
663+
let bandwidth = if (dev.sensors.contains(&SensorType::Http) ||
664+
dev.sensors.contains(&SensorType::Https)) &&
665+
dev.http_status == Some(true) {
666+
dev.bandwidth_usage.map_or("N/A".to_string(), |b| format!("{:.2} Mbps", b))
667+
} else {
668+
"N/A".to_string()
669+
};
670+
671+
let log_entry = format!(
672+
"{} - {} ({}): Ping: {}, HTTP: {}, Bandwidth: {}\n",
673+
now.format("%Y-%m-%d %H:%M:%S"),
674+
dev.name,
675+
dev.ip,
676+
status.ping_status.map_or("N/A", |s| if s { "OK" } else { "FAIL" }),
677+
http_status,
678+
bandwidth
679+
);
680+
681+
if let Err(e) = file.write_all(log_entry.as_bytes()) {
682+
error!("Failed to write log entry: {}", e);
683+
}
637684
}
638685
}
639-
640-
if let Err(e) = file.flush() {
641-
error!("Failed to flush log file: {}", e);
642-
}
643686
}
644687

645-
// Wait before next monitoring cycle
646688
sleep(Duration::from_secs(5)).await;
647689
}
648690
});

src/sensors.rs

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,57 @@ use reqwest;
44
use tokio::time::{sleep, Duration};
55

66
pub async fn monitor_ping(ip: &str) -> bool {
7-
debug!("Attempting to ping {} (10 requests)", ip);
7+
debug!("Pinging {}", ip);
88

99
let mut success_count = 0;
10-
11-
// Send 10 ping requests
12-
for attempt in 1..=10 {
13-
debug!("Ping attempt {} of 10 for {}", attempt, ip);
14-
10+
let attempts = 3; // Try 3 times before deciding status
11+
12+
for i in 1..=attempts {
1513
let output = if cfg!(target_os = "macos") || cfg!(target_os = "linux") {
1614
Command::new("ping")
1715
.arg("-c")
1816
.arg("1")
19-
.arg("-W")
20-
.arg("2")
17+
.arg("-t") // Use -t for macOS timeout
18+
.arg("2") // 2 second timeout
2119
.arg(ip)
2220
.output()
2321
} else {
2422
Command::new("ping")
2523
.arg("-n")
2624
.arg("1")
25+
.arg("-w")
26+
.arg("2000")
2727
.arg(ip)
2828
.output()
2929
};
3030

3131
match output {
3232
Ok(output) => {
33-
let stdout = String::from_utf8_lossy(&output.stdout);
34-
let stderr = String::from_utf8_lossy(&output.stderr);
35-
36-
debug!("Ping stdout: {}", stdout);
37-
if !stderr.is_empty() {
38-
debug!("Ping stderr: {}", stderr);
39-
}
40-
41-
let ping_success = if cfg!(target_os = "macos") || cfg!(target_os = "linux") {
42-
output.status.success() && !stdout.contains("100.0% packet loss")
43-
} else {
44-
output.status.success() && stdout.contains("bytes=")
45-
};
46-
47-
if ping_success {
33+
if output.status.success() {
4834
success_count += 1;
35+
debug!("Ping attempt {} successful for {}", i, ip);
36+
} else {
37+
debug!("Ping attempt {} failed for {}", i, ip);
4938
}
5039
}
5140
Err(e) => {
52-
error!("Error executing ping command for {}: {}", ip, e);
41+
error!("Ping attempt {} error for {}: {}", i, ip, e);
5342
}
5443
}
5544

56-
// Add a small delay between pings to prevent flooding
45+
// Short delay between attempts
5746
sleep(Duration::from_millis(200)).await;
5847
}
5948

60-
// Calculate success rate (consider up if 70% or more pings successful)
61-
let success_rate = (success_count as f32 / 10.0) * 100.0;
62-
let is_up = success_rate >= 70.0;
63-
64-
if is_up {
65-
info!("Device {} is UP ({}% success rate)", ip, success_rate);
49+
// Consider it up if more than 50% attempts succeeded
50+
let status = success_count > attempts / 2;
51+
if status {
52+
info!("Ping UP for {} ({}/{} successful)", ip, success_count, attempts);
6653
} else {
67-
error!("Device {} is DOWN ({}% success rate)", ip, success_rate);
54+
error!("Ping DOWN for {} ({}/{} failed)", ip, attempts - success_count, attempts);
6855
}
69-
70-
is_up
56+
57+
status
7158
}
7259

7360
pub async fn monitor_http(url: &str) -> bool {

0 commit comments

Comments
 (0)