moved structs to their own files

added time since last state change
This commit is contained in:
Trevor Merritt 2025-04-20 15:56:03 -04:00
parent 7bd745635b
commit 81bf3cf61d
8 changed files with 186 additions and 356 deletions

View File

@ -7,9 +7,4 @@ edition = "2024"
env_logger = "0.11"
log = "0.4"
ansi_term = "0.12"
clap = { version = "4.5.36", features = ["derive"] }
[[bin]]
name = "threads"
clap = { version = "4.5", features = ["derive"] }

View File

@ -1,158 +0,0 @@
use std::net::Ipv4Addr;
use std::process::Command;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::{Duration, SystemTime};
const SECONDS_BETWEEN_DISPLAY: u32 = 5;
const SECONDS_BETWEEN_PING: u32 = 1;
#[derive(Clone)]
struct PingRequest {
target: Ipv4Addr,
}
#[derive(Clone)]
struct PingResult {
target: Ipv4Addr,
success: bool,
rtt: u32,
}
fn spawn_manager_thread(targets: Vec<Ipv4Addr>, sender: Sender<PingResult>) {
let local_targets = targets.clone();
thread::spawn(move || {
loop {
for target in &local_targets {
spawn_single_ping(PingRequest { target: *target }, sender.clone());
}
thread::sleep(Duration::from_secs(SECONDS_BETWEEN_PING as u64));
}
});
}
// Spawns a thread that pings a target and sends the response back to the main thread
// via the sender
fn spawn_single_ping(request: PingRequest, sender: Sender<PingResult>) {
let local_request = request.clone();
thread::spawn(move || {
let mut result = 0;
let mut success = true;
let start_time = SystemTime::now();
#[cfg(any(target_os="windows"))] {
result = Command::new("c:/windows/system32/ping.exe").arg(request.target.to_string())
.arg("-n 1")
.arg("-w 10")
.arg("-4")
.output().unwrap().status.code().unwrap_or(255);
success = result == 1;
}
#[cfg(any(target_os="linux"))] {
result = Command::new("/usr/bin/ping").arg(request.target.to_string())
.arg("-c 1")
.arg("-4")
.arg("-w 10")
.arg("-W 1")
.output().unwrap().status.code().unwrap_or(255);
success = result == 0;
}
let ping_duration = SystemTime::now().duration_since(start_time).unwrap_or(Duration::from_secs(600));
sender.send(PingResult { target: local_request.target, success: result == 0, rtt: ping_duration.as_millis() as u32 }).expect("Unable to send response");
let success = ping_duration.as_millis() >= SECONDS_BETWEEN_PING as u128 && result == 0;
// println!("Attempt for for {}", local_request.target);
});
}
#[derive(Clone)]
struct TargetState {
target: Ipv4Addr,
alive: bool,
last_rtt: u32,
}
impl Default for TargetState {
fn default() -> Self {
TargetState {
target: Ipv4Addr::new(127, 0, 0, 1),
alive: false,
last_rtt: 0,
}
}
}
#[derive(Default)]
pub struct State {
targets: Vec<TargetState>,
}
impl State {
pub fn add_target(&mut self, to_add: Ipv4Addr) {
self.targets.push(TargetState {
target: to_add,
..TargetState::default()
});
}
pub fn targets_ipv4(&self) -> Vec<Ipv4Addr> {
let mut working = vec![];
for current in &self.targets {
working.push(current.target);
}
working
}
}
fn main() {
// channel to send requests to ping
let (ping_response_sender, ping_response_listener) = mpsc::channel::<PingResult>();
println!("Taxation is Theft");
let targets = vec![Ipv4Addr::new(127, 0, 0, 1),
Ipv4Addr::new(172, 24, 10, 200),
Ipv4Addr::new(1,1,1,1)];
let mut state = State::default();
for current in &targets {
state.add_target(*current);
}
println!("Setting up requests for {} hosts.", targets.len());
spawn_manager_thread(state.targets_ipv4().clone(), ping_response_sender.clone());
let mut display_loop_start = SystemTime::now();
let mut duration_since_last_loop = SystemTime::now().duration_since(display_loop_start).unwrap();
loop {
if let Ok(response) = ping_response_listener.recv_timeout(Duration::from_millis(100)) {
// println!("Master got a ping response for {} with {}/{}", response.target, response.success, response.rtt);
for (index, current_state) in state.targets.clone().iter().enumerate() {
if current_state.target == response.target {
let new_state = TargetState {
target: current_state.target,
alive: response.success,
last_rtt: response.rtt,
};
state.targets[index] = new_state.clone();
// println!("({}) Found the target -> {}/{}/{}", state.targets.clone().len(), new_state.target, new_state.alive, new_state.last_rtt);
}
}
}
duration_since_last_loop = SystemTime::now().duration_since(display_loop_start).expect("unable to figure out how long ago we displayed stuff");
if duration_since_last_loop.as_secs() > SECONDS_BETWEEN_DISPLAY as u64 {
println!("DISPLAY LOOP");
println!("------------");
for current_result in state.targets.clone() {
println!("{} / {} -> {}", current_result.target, current_result.alive, current_result.last_rtt);
}
display_loop_start = SystemTime::now();
}
}
}

View File

@ -1,214 +1,77 @@
use std::fs::File;
use std::net::Ipv4Addr;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use std::string::ToString;
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use std::{io, thread};
use std::io::BufRead;
use std::thread;
use std::time::{Duration, SystemTime};
use ansi_term::Colour::{Green, Red};
use clap::Parser;
use log::{debug, error, log_enabled, info, Level};
use crate::manager::Manager;
use crate::ping_result::PingResult;
use crate::state::State;
use crate::target_state::TargetState;
#[derive(Clone)]
struct HistoricHost {
pub name: String,
pub address: Ipv4Addr,
pub history: Vec<u128>,
pub alive: bool,
}
mod manager;
mod state;
mod target_state;
mod ping_result;
mod ping_request;
#[derive(Clone, Debug)]
struct PingResult {
pub address: Ipv4Addr,
pub rtt: u128,
}
impl HistoricHost {
pub fn dump_state(&self) {
let working_history = self.history.clone();
if working_history.len() == 0 {
println!("No Results for {} yet.", self.name);
return;
}
let num_results = working_history.len();
let mut running_total = 0;
for result in working_history {
running_total += result;
}
let average_ping_time = running_total.div_ceil(num_results as u128);
println!("Counted {} results, total time {} with average {}",
num_results, running_total, average_ping_time
);
}
pub fn get_ping_time_for(target: Ipv4Addr) -> (i32, Duration) {
let start_time = SystemTime::now();
let result = Command::new("/usr/bin/ping").arg(target.to_string())
.arg("-c 1")
.arg("-4")
.arg("-w 10")
.arg("-W 1")
.output().unwrap().status.code().unwrap_or(255);
let ping_duration = SystemTime::now().duration_since(start_time).unwrap_or(Duration::from_secs(600));
(result, ping_duration)
}
pub fn get_ping_time(&self) -> (i32, Duration) {
HistoricHost::get_ping_time_for(self.address)
}
pub fn ping(&mut self) {
let (result, ping_duration) = self.get_ping_time();
match result {
0 => {
self.history.push(ping_duration.as_millis())
}
1 => {
println!("Error ping to {}", self.name);
}
_ => {
println!("General Ping Error.");
}
}
}
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct PPArgs {
pub targets: Option<PathBuf>,
}
// The output is wrapped in a Result to allow matching on errors.
// Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
const SECONDS_BETWEEN_DISPLAY: u32 = 1;
const SECONDS_BETWEEN_PING: u32 = 2;
fn main() {
env_logger::init();
// channel to send requests to ping
let (ping_response_sender, ping_response_listener) = mpsc::channel::<PingResult>();
let args = PPArgs::parse();
println!("Taxation is Theft");
let targets = match args.targets {
Some(file) => {
println!("Loading hosts from {}", file.to_str().unwrap_or("no file."));
let mut working = vec![];
if let Ok(lines) = read_lines(file) {
// Consumes the iterator, returns an (Optional) String
for line in lines.map_while(Result::ok) {
println!("{}", line);
working.push(line.to_string())
}
}
working.to_vec()
}
None => {
println!("Default Hosts used.");
vec!["127.0.0.1".to_string(), "1.1.1.1".to_string(), "8.8.8.8".to_string()]
}
};
let targets = vec![Ipv4Addr::new(127, 0, 0, 1),
Ipv4Addr::new(172, 24, 10, 137),
Ipv4Addr::new(1,1,1,1)];
let mut state = State::default();
let (sender, receiver) = mpsc::channel::<PingResult>();
// sender gets cloned and sent to each pinger
let mut hosts = vec![];
info!("Building hosts list...");
// build hosts list...
for target in targets {
hosts.push(
HistoricHost {
name: target.to_string(),
address: Ipv4Addr::from_str(&target).unwrap_or(Ipv4Addr::new(127, 0, 0, 1)),
history: vec![],
alive: false,
}
)
for current in &targets {
state.add_target(*current);
}
info!("Completed building with {} hosts.", hosts.len());
info!("Starting Manager Thread");
// Manager Thread
let my_hosts = hosts.clone();
let _ = thread::spawn(move || {
loop {
println!("\x1b[2J --------------");
for current_target in my_hosts.clone() {
// debug!("[MT] Preparing to send off ping request to {}", current_target.address.to_string());
let start_time = SystemTime::now();
println!("Setting up requests for {} hosts.", targets.len());
Manager::spawn_manager_thread(state.targets_ipv4().clone(), ping_response_sender.clone());
let result = Command::new("/usr/bin/ping").arg(current_target.address.to_string())
.arg("-c 1")
.arg("-4")
.arg("-w 10")
.arg("-W 1")
.output().unwrap().status.code().unwrap_or(255);
let ping_duration = SystemTime::now().duration_since(start_time).unwrap_or(Duration::from_secs(600));
let to_send = if result == 0 {
PingResult { address: current_target.address, rtt: ping_duration.as_millis() }
} else {
PingResult { address: current_target.address, rtt: u128::MAX }
};
let my_sender = sender.clone();
let _ = my_sender.send(to_send);
}
thread::sleep(Duration::from_secs(2));
}
});
let mut display_loop_start = SystemTime::now();
let mut duration_since_last_loop = SystemTime::now().duration_since(display_loop_start).unwrap();
loop {
if let Ok(response) = ping_response_listener.recv_timeout(Duration::from_millis(100)) {
// println!("[MM] Master got a ping response for {} with {}/{}", response.target, response.success, response.rtt);
for (index, current_state) in state.targets.clone().iter().enumerate() {
if current_state.target == response.target {
// print!("start with {} and {} for {}", response.success, current_state.alive, current_state.target);
let new_alive = current_state.alive != response.success;
let last_alive_change = if response.success == current_state.alive {
// println!("-- LAST ALIVE DID NOT CHANGE FOR {}", current_state.target);
current_state.last_alive_change
} else {
// println!("++ LAST ALIVE IS RESET");
SystemTime::now()
};
info!("Manager Thread Started");
info!("Starting Data Thread");
// Data Thread
let _ = thread::spawn(move || {
debug!("[DT] STARTING WITH {} HOSTS", hosts.len());
loop {
if let Ok(rx) = receiver.recv_timeout(Duration::from_micros(1000)) {
let addr = rx.clone().address;
let rtt = rx.clone().rtt;
if rtt == u128::MAX {
// error!("[DT] No reply from {}", addr);
println!("{} not responding", Red.paint(addr.to_string()));
} else {
// debug!("[DT] Recieved a ping result from {} with time of {}", addr, rtt);
println!("{} responding.", Green.paint(addr.to_string()));
let new_state = TargetState {
target: current_state.target,
alive: response.success,
last_rtt: response.rtt,
last_alive_change,
};
state.targets[index] = new_state.clone();
// println!("Found the target -> {}/{}/{}", new_state.target, new_state.alive, new_state.last_rtt);
}
}
}
});
info!("Data Thread Started");
info!("Keep Alive Thread Starting.");
let _ = thread::spawn(move || {
let mut counter = 0;
loop {
thread::sleep(Duration::from_millis(1000));
counter += 1;
if counter % 1000 == 0 {
info!("Keep Alive Counter at {}", counter);
duration_since_last_loop = SystemTime::now().duration_since(display_loop_start).expect("unable to figure out how long ago we displayed stuff");
if duration_since_last_loop.as_secs() > SECONDS_BETWEEN_DISPLAY as u64 {
println!("DISPLAY LOOP");
println!("------------");
for current_result in state.targets.clone() {
println!("{} / {} -> {} / Changed {}s ago", current_result.target, current_result.alive, current_result.last_rtt, SystemTime::now().duration_since(current_result.last_alive_change).unwrap().as_secs());
}
display_loop_start = SystemTime::now();
}
}).join();
//
// let _ = thread::spawn(move || {
// loop {
// println!("-- Status Start -- ");
// for host in &hosts.clone() {
// host.dump_state();
// }
// println!("-- Status Done -- ");
// thread::sleep(Duration::from_secs(1));
// }
// }).join();
}
}

68
src/manager.rs Normal file
View File

@ -0,0 +1,68 @@
use std::net::Ipv4Addr;
use std::process::Command;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::{Duration, SystemTime};
use crate::ping_request::PingRequest;
use crate::ping_result::PingResult;
use crate::SECONDS_BETWEEN_PING;
pub struct Manager;
const WIN_PING_SUCCESS: u8 = 1;
const LIN_PING_SUCCESS: u8 = 0;
impl Manager {
pub fn spawn_manager_thread(targets: Vec<Ipv4Addr>, sender: Sender<PingResult>) {
let local_targets = targets.clone();
thread::spawn(move || {
loop {
for target in &local_targets {
Manager::spawn_single_ping(PingRequest { target: *target }, sender.clone());
}
thread::sleep(Duration::from_secs(SECONDS_BETWEEN_PING as u64));
}
});
}
// Spawns a thread that pings a target and sends the response back to the main thread
// via the sender
fn spawn_single_ping(request: PingRequest, sender: Sender<PingResult>) {
let local_request = request.clone();
thread::spawn(move || {
let mut result = 0;
let mut success = true;
let start_time = SystemTime::now();
#[cfg(any(target_os="windows"))] {
result = Command::new("c:/windows/system32/ping.exe").arg(request.target.to_string())
.arg("-n 1")
.arg("-w 10")
.arg("-4")
.output().unwrap().status.code().unwrap_or(255);
success = result == 1;
}
#[cfg(any(target_os="linux"))] {
result = Command::new("/usr/bin/ping").arg(request.target.to_string())
.arg("-c 1")
.arg("-4")
.arg("-w 10")
.arg("-W 1")
.output().unwrap().status.code().unwrap_or(255);
success = result == 0;
}
let ping_duration = SystemTime::now()
.duration_since(start_time)
.unwrap_or(Duration::from_secs(600));
let success = ping_duration.as_millis() >= SECONDS_BETWEEN_PING as u128 && success;
sender.send(PingResult {
target: local_request.target,
success,
rtt: ping_duration.as_millis() as u32
}).expect("Unable to send response");
// println!("Attempt for for {}", local_request.target);
});
}
}

6
src/ping_request.rs Normal file
View File

@ -0,0 +1,6 @@
use std::net::Ipv4Addr;
#[derive(Clone)]
pub struct PingRequest {
pub target: Ipv4Addr,
}

9
src/ping_result.rs Normal file
View File

@ -0,0 +1,9 @@
use std::net::Ipv4Addr;
#[derive(Clone)]
pub struct PingResult {
pub target: Ipv4Addr,
pub success: bool,
pub rtt: u32,
}

26
src/state.rs Normal file
View File

@ -0,0 +1,26 @@
use std::net::Ipv4Addr;
use crate::target_state::TargetState;
#[derive(Default)]
pub struct State {
pub targets: Vec<TargetState>,
}
impl State {
pub fn add_target(&mut self, to_add: Ipv4Addr) {
self.targets.push(TargetState {
target: to_add,
..TargetState::default()
});
}
pub fn targets_ipv4(&self) -> Vec<Ipv4Addr> {
let mut working = vec![];
for current in &self.targets {
working.push(current.target);
}
working
}
}

21
src/target_state.rs Normal file
View File

@ -0,0 +1,21 @@
use std::net::Ipv4Addr;
use std::time::SystemTime;
#[derive(Clone)]
pub struct TargetState {
pub target: Ipv4Addr,
pub alive: bool,
pub last_rtt: u32,
pub last_alive_change: SystemTime
}
impl Default for TargetState {
fn default() -> Self {
TargetState {
target: Ipv4Addr::new(127, 0, 0, 1),
alive: false,
last_rtt: 0,
last_alive_change: SystemTime::now()
}
}
}