2022-02-07 08:54:58 +00:00
|
|
|
use std::env::var;
|
2022-02-03 13:19:34 +00:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{Read, Write};
|
|
|
|
use std::ops::Sub;
|
2022-02-07 08:54:58 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-02-03 13:19:34 +00:00
|
|
|
use std::thread::sleep;
|
|
|
|
use std::time::{Duration, Instant};
|
2022-02-07 08:54:58 +00:00
|
|
|
use structopt::StructOpt;
|
|
|
|
|
|
|
|
const INTERVAL_SECONDS: u64 = 5;
|
|
|
|
|
2022-02-07 12:57:15 +00:00
|
|
|
const USE_XDG_RUNTIME_DIR: bool = true;
|
|
|
|
const ALTERNATE_PREFIX_DIR: &str = "/tmp";
|
|
|
|
|
|
|
|
const SEND_TOTAL_FILENAME: &str = "rust_send_total";
|
|
|
|
const RECV_TOTAL_FILENAME: &str = "rust_recv_total";
|
|
|
|
const SEND_INTERVAL_FILENAME: &str = "rust_send_interval";
|
|
|
|
const RECV_INTERVAL_FILENAME: &str = "rust_recv_interval";
|
|
|
|
|
2022-02-07 08:54:58 +00:00
|
|
|
#[derive(StructOpt, Debug, Clone)]
|
|
|
|
struct Opt {
|
|
|
|
net_dev: String,
|
2022-02-07 11:06:59 +00:00
|
|
|
#[structopt(
|
|
|
|
short = "s",
|
|
|
|
long = "disable-scaling",
|
|
|
|
help = "Disables byte scaling into interval files"
|
|
|
|
)]
|
|
|
|
disable_byte_scaling: bool,
|
2022-02-07 08:54:58 +00:00
|
|
|
}
|
2022-02-03 13:19:34 +00:00
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
struct ByteState {
|
|
|
|
pub recv: u64,
|
|
|
|
pub send: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sub for ByteState {
|
|
|
|
type Output = ByteState;
|
|
|
|
|
|
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
|
|
|
let mut result_byte_state = ByteState { recv: 0, send: 0 };
|
|
|
|
|
|
|
|
if self.recv >= rhs.recv {
|
|
|
|
result_byte_state.recv = self.recv - rhs.recv;
|
|
|
|
}
|
|
|
|
if self.send >= rhs.send {
|
|
|
|
result_byte_state.send = self.send - rhs.send;
|
|
|
|
}
|
|
|
|
|
|
|
|
result_byte_state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read_proc_net_dev(net_device: &str) -> Result<ByteState, String> {
|
|
|
|
let mut byte_state = ByteState { recv: 0, send: 0 };
|
|
|
|
|
|
|
|
let mut file_string = String::new();
|
|
|
|
{
|
|
|
|
let mut file = File::open("/proc/net/dev")
|
|
|
|
.map_err(|_| String::from("Failed to open \"/proc/net/dev\""))?;
|
|
|
|
file.read_to_string(&mut file_string)
|
|
|
|
.map_err(|_| String::from("Failed to read from \"/proc/net/dev\""))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for line in file_string.lines() {
|
|
|
|
if line.trim().starts_with(net_device) {
|
2022-02-07 08:58:50 +00:00
|
|
|
for (count, word) in line.split_ascii_whitespace().enumerate() {
|
2022-02-03 13:19:34 +00:00
|
|
|
if count == 1 {
|
2022-02-07 08:58:50 +00:00
|
|
|
byte_state.recv = word.parse::<u64>().map_err(|_| {
|
2022-02-03 13:19:34 +00:00
|
|
|
String::from("Failed to parse recv bytes from \"/proc/net/dev\"")
|
|
|
|
})?;
|
|
|
|
} else if count == 9 {
|
2022-02-07 08:58:50 +00:00
|
|
|
byte_state.send = word.parse::<u64>().map_err(|_| {
|
2022-02-03 13:19:34 +00:00
|
|
|
String::from("Failed to parse send bytes from \"/proc/net/dev\"")
|
|
|
|
})?;
|
|
|
|
return Ok(byte_state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Err(String::from(
|
|
|
|
"Failed to parse from \"/proc/net/dev\", too few words?",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(String::from(
|
|
|
|
"Failed to parse from \"/proc/net/dev\", missing device?",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_compare_state(
|
|
|
|
net_device: &str,
|
2022-02-07 08:54:58 +00:00
|
|
|
send_filename: &Path,
|
|
|
|
recv_filename: &Path,
|
2022-02-03 13:19:34 +00:00
|
|
|
) -> Result<ByteState, String> {
|
|
|
|
let mut prev_byte_state = ByteState { send: 0, recv: 0 };
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut temp_string: String = String::new();
|
|
|
|
let send_file_open_result = File::open(send_filename);
|
|
|
|
if let Ok(mut send_file) = send_file_open_result {
|
|
|
|
let read_result = send_file.read_to_string(&mut temp_string);
|
|
|
|
if read_result.is_ok() {
|
2022-02-07 08:58:50 +00:00
|
|
|
let int_parse_result = temp_string.trim().parse::<u64>();
|
2022-02-03 13:19:34 +00:00
|
|
|
if let Ok(i) = int_parse_result {
|
|
|
|
prev_byte_state.send = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let mut temp_string: String = String::new();
|
|
|
|
let recv_file_open_result = File::open(recv_filename);
|
|
|
|
if let Ok(mut recv_file) = recv_file_open_result {
|
|
|
|
let read_result = recv_file.read_to_string(&mut temp_string);
|
|
|
|
if read_result.is_ok() {
|
2022-02-07 08:58:50 +00:00
|
|
|
let int_parse_result = temp_string.trim().parse::<u64>();
|
2022-02-03 13:19:34 +00:00
|
|
|
if let Ok(i) = int_parse_result {
|
|
|
|
prev_byte_state.recv = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let byte_state = read_proc_net_dev(net_device)?;
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut send_file = File::create(send_filename)
|
2022-02-07 08:54:58 +00:00
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", send_filename))?;
|
2022-02-03 13:19:34 +00:00
|
|
|
send_file
|
|
|
|
.write_all(byte_state.send.to_string().as_bytes())
|
2022-02-07 08:54:58 +00:00
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", send_filename))?;
|
2022-02-03 13:19:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut recv_file = File::create(recv_filename)
|
2022-02-07 08:54:58 +00:00
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", recv_filename))?;
|
2022-02-03 13:19:34 +00:00
|
|
|
recv_file
|
|
|
|
.write_all(byte_state.recv.to_string().as_bytes())
|
2022-02-07 08:54:58 +00:00
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", recv_filename))?;
|
2022-02-03 13:19:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(byte_state - prev_byte_state)
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:54:58 +00:00
|
|
|
fn do_set_states(
|
|
|
|
net_device: &str,
|
2022-02-07 11:06:59 +00:00
|
|
|
disable_byte_scalaing: bool,
|
2022-02-07 08:54:58 +00:00
|
|
|
send_interval_filename: &Path,
|
|
|
|
recv_interval_filename: &Path,
|
|
|
|
send_total_filename: &Path,
|
|
|
|
recv_total_filename: &Path,
|
|
|
|
) -> Result<(), String> {
|
|
|
|
let state = write_compare_state(net_device, send_total_filename, recv_total_filename)?;
|
|
|
|
|
2022-02-07 11:06:59 +00:00
|
|
|
if disable_byte_scalaing {
|
|
|
|
{
|
|
|
|
let mut send_interval_file = File::create(send_interval_filename)
|
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", send_interval_filename))?;
|
|
|
|
send_interval_file
|
|
|
|
.write_all(state.send.to_string().as_bytes())
|
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", send_interval_filename))?;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let mut recv_interval_file = File::create(recv_interval_filename)
|
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", recv_interval_filename))?;
|
|
|
|
recv_interval_file
|
|
|
|
.write_all(state.recv.to_string().as_bytes())
|
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", recv_interval_filename))?;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
{
|
|
|
|
let mut send_string = String::new();
|
|
|
|
if state.send > 1024 * 1024 {
|
|
|
|
send_string.push_str(&(state.send as f64 / 1024.0 / 1024.0).to_string());
|
|
|
|
let decimal_location_opt = send_string.find('.');
|
|
|
|
if let Some(location) = decimal_location_opt {
|
|
|
|
send_string.truncate(location + 2);
|
|
|
|
}
|
|
|
|
send_string.push_str("MB");
|
|
|
|
} else if state.send > 1024 {
|
|
|
|
send_string.push_str(&(state.send as f64 / 1024.0).to_string());
|
|
|
|
let decimal_location_opt = send_string.find('.');
|
|
|
|
if let Some(location) = decimal_location_opt {
|
|
|
|
send_string.truncate(location + 2);
|
|
|
|
}
|
|
|
|
send_string.push_str("KB");
|
|
|
|
} else {
|
|
|
|
send_string.push_str(&state.send.to_string());
|
2022-02-07 12:57:15 +00:00
|
|
|
send_string.push('B');
|
2022-02-07 11:06:59 +00:00
|
|
|
}
|
|
|
|
let mut send_interval_file = File::create(send_interval_filename)
|
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", send_interval_filename))?;
|
|
|
|
send_interval_file
|
|
|
|
.write_all(send_string.as_bytes())
|
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", send_interval_filename))?;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let mut recv_string = String::new();
|
|
|
|
if state.recv > 1024 * 1024 {
|
|
|
|
recv_string.push_str(&(state.recv as f64 / 1024.0 / 1024.0).to_string());
|
|
|
|
let decimal_location_opt = recv_string.find('.');
|
|
|
|
if let Some(location) = decimal_location_opt {
|
|
|
|
recv_string.truncate(location + 2);
|
|
|
|
}
|
|
|
|
recv_string.push_str("MB");
|
|
|
|
} else if state.recv > 1024 {
|
|
|
|
recv_string.push_str(&(state.recv as f64 / 1024.0).to_string());
|
|
|
|
let decimal_location_opt = recv_string.find('.');
|
|
|
|
if let Some(location) = decimal_location_opt {
|
|
|
|
recv_string.truncate(location + 2);
|
|
|
|
}
|
|
|
|
recv_string.push_str("KB");
|
|
|
|
} else {
|
|
|
|
recv_string.push_str(&state.recv.to_string());
|
2022-02-07 12:57:15 +00:00
|
|
|
recv_string.push('B');
|
2022-02-07 11:06:59 +00:00
|
|
|
}
|
|
|
|
let mut recv_interval_file = File::create(recv_interval_filename)
|
|
|
|
.map_err(|_| format!("Failed to create \"{:?}\"", recv_interval_filename))?;
|
|
|
|
recv_interval_file
|
|
|
|
.write_all(recv_string.as_bytes())
|
|
|
|
.map_err(|_| format!("Failed to write into \"{:?}\"", recv_interval_filename))?;
|
|
|
|
}
|
2022-02-07 08:54:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-02-03 13:19:34 +00:00
|
|
|
fn timer_execute<F>(func: F, sleep_seconds: u64) -> Result<(), String>
|
|
|
|
where
|
|
|
|
F: std::ops::Fn() -> Result<(), String>,
|
|
|
|
{
|
2022-02-06 05:35:16 +00:00
|
|
|
let mut instant = Instant::now() - Duration::from_secs(sleep_seconds);
|
|
|
|
let diff_duration = Duration::from_secs(sleep_seconds * 2);
|
2022-02-03 13:19:34 +00:00
|
|
|
let mut duration: Duration;
|
|
|
|
loop {
|
|
|
|
func()?;
|
|
|
|
let newer_instant = Instant::now();
|
2022-02-06 05:35:16 +00:00
|
|
|
duration = diff_duration - (newer_instant - instant);
|
2022-02-03 13:19:34 +00:00
|
|
|
instant = newer_instant;
|
|
|
|
sleep(duration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:54:58 +00:00
|
|
|
fn main() -> Result<(), String> {
|
|
|
|
let opt = Opt::from_args();
|
|
|
|
|
|
|
|
println!("Using net_dev == \"{}\"", opt.net_dev);
|
|
|
|
|
2022-02-07 12:57:15 +00:00
|
|
|
let prefix_dir: String;
|
|
|
|
if USE_XDG_RUNTIME_DIR {
|
|
|
|
prefix_dir = var("XDG_RUNTIME_DIR").map_err(|e| format!("{}", e))?;
|
|
|
|
} else {
|
|
|
|
prefix_dir = ALTERNATE_PREFIX_DIR.to_string();
|
|
|
|
}
|
2022-02-07 08:54:58 +00:00
|
|
|
|
|
|
|
let mut send_total_path = PathBuf::new();
|
2022-02-07 12:57:15 +00:00
|
|
|
send_total_path.push(&prefix_dir);
|
|
|
|
send_total_path.push(SEND_TOTAL_FILENAME);
|
2022-02-07 08:54:58 +00:00
|
|
|
let send_total_path = send_total_path;
|
|
|
|
|
|
|
|
let mut recv_total_path = PathBuf::new();
|
2022-02-07 12:57:15 +00:00
|
|
|
recv_total_path.push(&prefix_dir);
|
|
|
|
recv_total_path.push(RECV_TOTAL_FILENAME);
|
2022-02-07 08:54:58 +00:00
|
|
|
let recv_total_path = recv_total_path;
|
|
|
|
|
|
|
|
let mut send_interval_path = PathBuf::new();
|
2022-02-07 12:57:15 +00:00
|
|
|
send_interval_path.push(&prefix_dir);
|
|
|
|
send_interval_path.push(SEND_INTERVAL_FILENAME);
|
2022-02-07 08:54:58 +00:00
|
|
|
let send_interval_path = send_interval_path;
|
|
|
|
|
|
|
|
let mut recv_interval_path = PathBuf::new();
|
2022-02-07 12:57:15 +00:00
|
|
|
recv_interval_path.push(&prefix_dir);
|
|
|
|
recv_interval_path.push(RECV_INTERVAL_FILENAME);
|
2022-02-07 08:54:58 +00:00
|
|
|
let recv_interval_path = recv_interval_path;
|
|
|
|
|
|
|
|
timer_execute(
|
|
|
|
move || {
|
|
|
|
do_set_states(
|
|
|
|
&opt.net_dev,
|
2022-02-07 11:06:59 +00:00
|
|
|
opt.disable_byte_scaling,
|
2022-02-07 08:54:58 +00:00
|
|
|
&send_interval_path,
|
|
|
|
&recv_interval_path,
|
|
|
|
&send_total_path,
|
|
|
|
&recv_total_path,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
INTERVAL_SECONDS,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
2022-02-03 12:28:57 +00:00
|
|
|
}
|