use core::time::Duration;
use getopts::Options;
use linescroll::*;
use nix::fcntl::*;
use nix::poll::*;
use nix::sys::time::*;
use std::fs::File;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use std::{str, thread, time};
fn banner() -> String {
format!(
"{} version {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)
}
fn print_version() {
println!("{}", &banner());
}
fn general_options(args: Vec<String>, fw_list: &mut Vec<FileWatch>) -> Settings {
let mut opts = Options::new();
opts.parsing_style(getopts::ParsingStyle::FloatingFrees);
opts.optflag("c", "combine", "combine all file source metrics");
opts.optopt(
"l",
"limit",
"stop after number of iterations (default never)",
"ITERATIONS",
);
opts.optflag("s", "speedonly", "no graph, speed only");
opts.optopt("p", "print", "print only every Nth iteration", "ITERATIONS");
opts.optflag("f", "filename", "print filename headings");
opts.optflag("h", "help", "print usage help");
opts.optflag("n", "noclear", "do not clear screen between draws");
opts.optflag("a", "noaxislimit", "do not print axis limit");
opts.optflag("", "nofollow", "do not follow file renames");
opts.optflag("v", "version", "display version number");
opts.optflag("r", "raw", "input is metric");
opts.optflag("e", "exit", "exit on failure");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
println!("{}", f);
std::process::exit(1);
}
};
if matches.opt_present("help") {
println!("{}", opts.usage(&banner()));
std::process::exit(0);
}
if matches.opt_present("version") {
print_version();
std::process::exit(0);
}
let mut settings = Settings {
combine: false,
speedonly: false,
print_every: 1,
limit: 0,
filenames: false,
noclear: false,
nofollow: false,
raw: false,
exit: false,
axislimit: true,
};
if matches.opt_present("print") {
settings.print_every = match matches.opt_str("print").unwrap().parse::<usize>() {
Ok(n) => n,
Err(x) => {
eprintln!("can't convert to numeric: {}", x);
std::process::exit(1);
}
};
};
settings.filenames = matches.opt_present("f");
settings.combine = matches.opt_present("c");
settings.noclear = matches.opt_present("n");
settings.nofollow = matches.opt_present("nofollow");
settings.raw = matches.opt_present("raw");
settings.exit = matches.opt_present("exit");
settings.axislimit = !matches.opt_present("noaxislimit");
if matches.opt_present("limit") {
settings.limit = match matches.opt_str("limit").unwrap().parse::<usize>() {
Ok(n) => n,
Err(x) => {
eprintln!("can't convert to numeric: {}", x);
std::process::exit(1);
}
};
};
if matches.opt_present("speedonly") {
settings.speedonly = true;
}
let free_args = matches.free;
if !free_args.is_empty() {
for free in free_args {
let f = FileWatch {
count: Arc::new(Mutex::new(0_u64)),
minute: vec![],
five_minute: vec![],
fifteen_minute: vec![],
name: free.clone(),
};
if f.name == "-" {
spawn_reader(Arc::clone(&f.count), None, settings.clone());
} else {
spawn_reader(Arc::clone(&f.count), Some(free), settings.clone());
}
fw_list.push(f);
}
} else {
let f = FileWatch {
count: Arc::new(Mutex::new(0_u64)),
minute: vec![],
five_minute: vec![],
fifteen_minute: vec![],
name: "stdin".to_string(),
};
spawn_reader(Arc::clone(&f.count), None, settings.clone());
fw_list.push(f);
}
settings
}
fn print_stats(
settings: &Settings,
item: &FileWatch,
counter: u64,
iterations: usize,
axislimit: bool,
) {
if !should_print(settings, iterations) {
return;
}
println!(
"{}{:width$}/sec {:width$}/min {:width$}/5min {:width$}/15min",
filename_print(settings, item),
counter,
average_set(&item.minute),
average_set(&item.five_minute),
average_set(&item.fifteen_minute),
width = 6
);
if !settings.speedonly {
print!("{}", graph(&item.minute, axislimit));
}
}
fn main() {
let mut fw_list: Vec<FileWatch> = vec![];
let args: Vec<String> = std::env::args().collect();
let settings = general_options(args, &mut fw_list);
let mut iterations: usize = 0;
let clear_and_top = "\x1Bc".to_string();
let mut combine: u64 = 0;
let mut combine_item = FileWatch {
count: Arc::new(Mutex::new(0_u64)),
minute: vec![],
five_minute: vec![],
fifteen_minute: vec![],
name: "[combined input]".to_string(),
};
loop {
sleep(1000);
iterations += 1;
if should_print(&settings, iterations) && should_clear(&settings) {
println!("{}", clear_and_top);
}
let mut counter;
if settings.combine {
combine = 0;
}
for item in fw_list.iter_mut() {
trim_item(item, 59);
let mut counter_lock = item.count.lock().unwrap();
counter = *counter_lock;
*counter_lock = 0;
item.minute.push(counter);
item.five_minute.push(counter);
item.fifteen_minute.push(counter);
if !settings.combine {
print_stats(&settings, item, counter, iterations, settings.axislimit)
} else {
combine += counter;
}
}
if settings.combine {
trim_item(&mut combine_item, 59);
combine_item.minute.push(combine);
combine_item.five_minute.push(combine);
combine_item.fifteen_minute.push(combine);
print_stats(
&settings,
&combine_item,
combine,
iterations,
settings.axislimit,
)
}
if settings.limit > 0 && iterations == settings.limit {
break;
}
}
}
fn line_val(u: &str) -> u64 {
match u.trim().parse::<u64>() {
Ok(n) => n,
Err(_x) => {
0
}
}
}
fn spawn_reader(count: Arc<Mutex<u64>>, path: Option<String>, settings: Settings) {
thread::spawn(move || {
let settings = settings;
let mut inode_changed = false;
loop {
fn update_counters(
fd: RawFd,
count: &Arc<Mutex<u64>>,
path: &Option<String>,
nofollow: bool,
raw: bool,
fail_exit: bool,
) {
let mut counter: u64 = 0;
let original_inode = match nix::sys::stat::fstat(fd) {
Ok(i) => Some(i.st_ino),
Err(_) => None,
};
if fcntl(fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).is_err() {
println!("could not set fd to non-blocking");
}
let pfds_orig = vec![PollFd::new(fd, PollFlags::POLLIN)];
loop {
let now = SystemTime::now();
let mut buffer = [0; 4096];
'reader: loop {
if now.elapsed().unwrap().as_millis() >= 100 {
break;
}
let mut pfds = pfds_orig.clone();
let timespec = Some(TimeSpec::from(Duration::new(0, 500000000)));
match ppoll(&mut pfds, timespec, None) {
Ok(_y) => {
if let Some(x) = pfds[0].revents() {
if x | PollFlags::POLLIN == PollFlags::POLLIN {
while let Ok(size) = nix::unistd::read(fd, &mut buffer[..])
{
if raw {
let s: String = str::from_utf8(
&buffer
.iter()
.cloned()
.take(size)
.collect::<Vec<u8>>(),
)
.unwrap()
.to_string();
for l in s.split('\n') {
if l.is_empty() {
continue;
}
counter += line_val(l);
}
} else {
for x in buffer.iter().take(size) {
if *x == b'\n' {
counter += 1;
}
}
}
if size != 4096 {
if size == 0 {
sleep(200);
}
break 'reader;
}
}
}
}
}
Err(x) => {
println!("Error polling: {}", x);
}
}
}
let mut counter_lock = count.lock().unwrap();
*counter_lock += counter;
counter = 0;
if !nofollow && path.is_some() {
if let Some(oi) = original_inode {
let path = path.clone().unwrap();
let stat_source = Path::new(&path);
match nix::sys::stat::stat(stat_source) {
Ok(r) => {
if r.st_ino != oi {
return;
}
}
Err(x) => {
if fail_exit {
eprintln!("Cannot stat {}:{}", path, x);
std::process::exit(1);
}
}
}
}
}
}
}
match path {
Some(ref p) => {
let f = File::open(p);
if f.is_err() {
if settings.exit {
eprintln!("Cannot open {}", p);
std::process::exit(1);
} else {
sleep(100);
continue;
}
}
let mut f = f.unwrap();
match f.seek(if inode_changed {
SeekFrom::Start(0)
} else {
SeekFrom::End(0)
}) {
Ok(_) => {}
Err(_x) => {
}
}
let v = f.as_raw_fd();
update_counters(
v,
&count,
&path,
settings.nofollow,
settings.raw,
settings.exit,
);
inode_changed = true;
}
None => {
let v = std::io::stdin().as_raw_fd();
update_counters(
v,
&count,
&path,
settings.nofollow,
settings.raw,
settings.exit,
);
}
}
}
});
}
fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}