use getopts::Options;
#[cfg(not(target_os = "linux"))]
use libc::*;
use std::collections::BTreeMap;
#[cfg(not(target_os = "linux"))]
use std::ffi;
use std::thread;
use chrono::{DateTime, Local, Timelike};
use netr::*;
fn print_line_stats(
i: &str,
in_total: i64,
out_total: i64,
isec: i64,
osec: i64,
imin: i64,
omin: i64,
) {
println!(
"{:17} {:8} {:8} | {:8} | {:8} | {:8} | {:8}",
i,
human_unit(in_total),
human_unit(out_total),
human_unit(isec),
human_unit(osec),
human_unit(imin),
human_unit(omin),
);
}
fn print_counters(config: &Config, _total: &str, h: &BTreeMap<String, DeviceHist>) {
for hist in h {
if hist.0 == "total" && config.print_total.is_none() {
continue;
}
let hist = hist.1.history.last();
if hist.is_none() {
continue;
}
let hist = hist.unwrap();
if let Some(format_str) = &config.format {
print!("{}", format_output(format_str, hist));
continue;
}
match config.counters {
Some(StatType::Bytes) => {
println!("{}\n{}", hist.ibytes, hist.obytes);
}
Some(StatType::Packets) => {
println!("{}\n{}", hist.ipackets, hist.opackets);
}
Some(StatType::All) => {
println!("{:?}", hist);
}
None => {
return;
}
}
}
}
fn print_stats(total: &str, h: &BTreeMap<String, DeviceHist>) {
let cls = "\x1Bc";
println!(
"{}{:17} {:8} {:8} | {:8} | {:8} | {:8} | {:8} ",
cls, "interface", "in", "out", "in/sec", "out/sec", "in/min", "out/min"
);
let mut list: Vec<String> = vec![];
for k in h.keys() {
if k == total {
continue;
}
list.push(k.to_string());
}
list.push(total.to_string());
for k in &list {
let dl = h.get(k).unwrap();
let history_len = dl.history.len();
let in_total = dl.history[history_len - 1].ibytes;
let out_total = dl.history[history_len - 1].obytes;
let isec = if history_len > 1 {
dl.history[history_len - 1].ibytes - dl.history[history_len - 2].ibytes
} else {
0
};
let osec = if history_len > 1 {
dl.history[history_len - 1].obytes - dl.history[history_len - 2].obytes
} else {
0
};
let imin = if history_len > 1 {
dl.history[history_len - 1].ibytes - dl.history[0].ibytes
} else {
0_i64
};
let omin = if history_len > 1 {
dl.history[history_len - 1].obytes - dl.history[0].obytes
} else {
0_i64
};
print_line_stats(k, in_total, out_total, isec, osec, imin, omin);
}
println!();
}
fn print_graph(
total: &str,
h: &BTreeMap<String, DeviceHist>,
height: i32,
indent: usize,
print_max: bool,
) {
if !h.contains_key(total) {
return;
}
let mut max_delta: i64 = 0;
let history_list = &h.get(total).unwrap().history;
if history_list.len() < 2 {
return;
}
for hist in 1..history_list.len() {
let cur_total = history_list[hist].ibytes + history_list[hist].obytes;
let last_total = history_list[hist - 1].ibytes + history_list[hist - 1].obytes;
if cur_total - last_total > max_delta {
max_delta = cur_total - last_total;
}
}
for line in 1..height {
print!("{:indent$}", "", indent = indent);
for hist in 1..history_list.len() {
let last_total = history_list[hist - 1].ibytes + history_list[hist - 1].obytes;
let last_in_diff = history_list[hist].ibytes - history_list[hist - 1].ibytes;
let total = history_list[hist].ibytes + history_list[hist].obytes;
let diff = total - last_total;
print!(
"{}",
if diff != 0 && diff >= (max_delta / height as i64) * (height as i64 - line as i64)
{
if last_in_diff >= (max_delta / height as i64) * (height as i64 - line as i64) {
"I"
} else {
"O"
}
} else {
" "
}
);
}
if line == 1 && print_max {
print!(
"{:padding$} {:>9}",
"",
human_unit(max_delta),
padding = 61 - history_list.len()
);
}
println!();
}
}
fn print_minute_marker(
total: &str,
h: &BTreeMap<String, DeviceHist>,
time: chrono::DateTime<Local>,
indent: usize,
) {
let padding = h.get(total).unwrap().history.len() as u32;
if padding > time.second() {
let padding = padding - time.second() - 2;
println!(
"{:indent$}{:padding$}^ {:02}:{:02}",
"",
"",
time.hour(),
time.minute(),
padding = padding as usize
);
}
}
fn run_loops(config: &Config) {
let total = "total";
let indent = 10;
let mut ifs: BTreeMap<String, DeviceHist> = BTreeMap::new();
loop {
let now = std::time::Instant::now();
let mut dh: BTreeMap<String, bool> = BTreeMap::new();
let devs = read_net();
if !ifs.contains_key(total) {
ifs.insert(
total.to_string(),
DeviceHist {
history: Vec::new(),
},
);
}
let mut ibytes: i64 = 0;
let mut obytes: i64 = 0;
let mut ipackets: i64 = 0;
let mut opackets: i64 = 0;
for dev in devs.iter() {
if config.filter.is_some() {
let c = config.clone().filter.unwrap();
if !c.matches(&dev.name) {
continue;
}
}
if !ifs.contains_key(&dev.name) {
ifs.insert(
dev.name.to_string(),
DeviceHist {
history: Vec::new(),
},
);
}
let h = ifs.get_mut(&dev.name).unwrap();
let hh = &mut h.history;
hh.push(Device {
name: dev.name.to_string(),
ibytes: dev.ibytes,
obytes: dev.obytes,
ipackets: dev.ipackets,
opackets: dev.opackets,
});
dh.insert(dev.name.to_string(), true);
while hh.len() > 61 {
hh.remove(0);
}
ibytes += dev.ibytes;
obytes += dev.obytes;
ipackets += dev.ipackets;
opackets += dev.opackets;
}
let total_if = ifs.get_mut(total).unwrap();
let total_history = &mut total_if.history;
total_history.push(Device {
name: total.to_string(),
ibytes,
obytes,
ipackets,
opackets,
});
while total_history.len() > 61 {
total_history.remove(0);
}
dh.insert(total.to_string(), true);
let mut remove_v: Vec<String> = Vec::new();
for ifp in ifs.keys() {
if !dh.contains_key(ifp) {
remove_v.push(ifp.to_string());
}
}
for v in remove_v {
ifs.remove(&v);
}
if config.counters.is_some() {
print_counters(config, total, &ifs);
break;
}
print_stats(total, &ifs);
print_graph(total, &ifs, 10, indent, true);
let time: DateTime<Local> = Local::now();
print_minute_marker(total, &ifs, time, indent);
loop {
thread::sleep(std::time::Duration::from_millis(100));
if now.elapsed() >= std::time::Duration::from_millis(1000) {
break;
}
}
}
}
fn banner() -> String {
format!(
"{} version {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)
}
fn print_version() {
println!("{}", &banner());
}
fn help(opts: &getopts::Options) {
println!("{}", opts.usage(&banner()));
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let mut config = Config::new();
let mut opts = Options::new();
opts.parsing_style(getopts::ParsingStyle::FloatingFrees);
opts.optopt("e", "exclude", "exclude this pattern", "REGEX");
opts.optopt("i", "include", "include this pattern", "REGEX");
opts.optopt(
"f",
"format",
"format output using %{if}, %{bytes}, %{packets}",
"FORMAT",
);
opts.optflag("c", "counters", "print counters and exit");
opts.optflag(
"",
"printtotal",
"enable print total in counter mode (default off)",
);
opts.optflag(
"",
"packets",
"print packets instead of bytes in counter mode",
);
opts.optflag("h", "help", "display help");
opts.optflag("v", "version", "display version");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
println!("{}", f);
std::process::exit(1);
}
};
if matches.opt_present("version") {
print_version();
std::process::exit(0);
}
if matches.opt_present("help") {
help(&opts);
std::process::exit(0);
}
if matches.opt_present("exclude") {
let pattern = matches.opt_str("exclude").unwrap();
let filter = match FilterOpts::create(&pattern, !matches.opt_present("exclude")) {
Some(x) => x,
None => {
eprintln!("Cannot create regex from {}", pattern);
help(&opts);
std::process::exit(1);
}
};
config.filter = Some(filter);
}
if matches.opt_present("include") {
if matches.opt_present("exclude") {
eprintln!("Cannot --exclude and --include at the same time");
help(&opts);
std::process::exit(1);
}
let pattern = matches.opt_str("include").unwrap();
let filter = match FilterOpts::create(&pattern, matches.opt_present("include")) {
Some(x) => x,
None => {
eprintln!("Cannot create regex from {}", matches.free[0].clone());
help(&opts);
std::process::exit(1);
}
};
config.filter = Some(filter);
}
if matches.opt_present("printtotal") {
config.print_total = Some(true);
}
if matches.opt_present("counters") {
config.counters = Some(StatType::Bytes);
}
if matches.opt_present("packets") {
config.counters = Some(StatType::Packets);
}
if matches.opt_present("format") {
let format_str = matches.opt_str("format").unwrap();
config.format = Some(format_str);
if config.counters.is_none() {
config.counters = Some(StatType::Bytes);
}
}
run_loops(&config);
}