linescroll-0.2.0/.cargo_vcs_info.json0000644000000001360000000000100132010ustar { "git": { "sha1": "dc646bccd060c516f74bccd60caaa1d5fd626f13" }, "path_in_vcs": "" }linescroll-0.2.0/.gitlab-ci.yml000064400000000000000000000004761046102023000144340ustar 00000000000000image: "rust:latest" before_script: - apt-get update -yqq - apt-get install -yqq --no-install-recommends build-essential pandoc # Use cargo to test the project test:cargo: script: | rustc --version && cargo --version # Print version info for debugging cargo test --workspace --verbose make all linescroll-0.2.0/Cargo.lock0000644000000101300000000000100111470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", "num-traits", "time", "winapi", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "libc" version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "linescroll" version = "0.2.0" dependencies = [ "chrono", "getopts", "nix", ] [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", "libc", "memoffset", "pin-utils", "static_assertions", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "time" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi", "winapi", ] [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" linescroll-0.2.0/Cargo.toml0000644000000022030000000000100111740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "linescroll" version = "0.2.0" authors = ["ed neville "] description = "report the number of lines input per time ranges and present rate as statistics and graph" homepage = "https://www.usenix.org.uk/content/linescroll.html" documentation = "https://www.usenix.org.uk/content/linescroll.html" readme = "README.md" keywords = [ "lines", "reporting", "graph", "files", "logwatch", ] categories = ["command-line-utilities"] license = "GPL-3.0-or-later" repository = "https://gitlab.com/edneville/linescroll" [dependencies.chrono] version = "0.4" [dependencies.getopts] version = "0.2" [dependencies.nix] version = "0.26" linescroll-0.2.0/Cargo.toml.orig000064400000000000000000000011351046102023000146600ustar 00000000000000[package] name = "linescroll" version = "0.2.0" authors = ["ed neville "] edition = "2018" description = "report the number of lines input per time ranges and present rate as statistics and graph" license = "GPL-3.0-or-later" homepage = "https://www.usenix.org.uk/content/linescroll.html" repository = "https://gitlab.com/edneville/linescroll" readme = "README.md" categories = ["command-line-utilities"] documentation = "https://www.usenix.org.uk/content/linescroll.html" keywords = ["lines","reporting","graph","files","logwatch"] [dependencies] chrono = "0.4" nix = "0.26" getopts = "0.2" linescroll-0.2.0/Makefile000064400000000000000000000013661046102023000134370ustar 00000000000000VERSION := $(shell grep version Cargo.toml | sed -e 's/.* = "//g;s/"$$//g') NAME := linescroll DOC := $(NAME) MDDATE := $(shell find $(DOC).md -printf "%Td %TB %TY\n") RELEASE := target/release/$(NAME) all: build test bintest doc build: cargo build --release doc: ( cat $(NAME).md | sed -e 's/^footer: \(\S\+\) \S\+$$/footer: \1 $(VERSION)/g' -e 's/^date:.*/date: $(MDDATE)/g' ) > $(NAME).md.tmp && mv $(NAME).md.tmp $(NAME).md cat $(NAME).md | sed -e 's,\([^ `-]\)--\([a-zA-Z]\),\1\\--\2,g' -e '/^|/s/\\n/\\\\n/g' -e '/^|/s/\\t/\\\\t/g' > $(NAME).man.md pandoc --standalone --ascii --to man $(NAME).man.md -o $(NAME).1 rm $(NAME).man.md test: cargo test bintest: install: all strip $(RELEASE) please install -m 0755 $(RELEASE) /usr/local/bin linescroll-0.2.0/README.md000064400000000000000000000055051046102023000132550ustar 00000000000000# linescroll Installation: ``` git clone https://gitlab.com/edneville/linescroll.git cd linescroll cargo test && cargo build --release \ && please install -m755 target/release/linescroll /usr/local/bin ``` # running tail a log file to linescroll to report how many lines are flowing on average over a time period: ``` $ tail -F /var/service/tinydns/log/main/current | linescroll --noclear --speedonly 125/sec 125/min 125/5min 125/15min 123/sec 124/min 124/5min 124/15min 113/sec 120/min 120/5min 120/15min 136/sec 124/min 124/5min 124/15min 121/sec 123/min 123/5min 123/15min 124/sec 123/min 123/5min 123/15min 112/sec 122/min 122/5min 122/15min 111/sec 120/min 120/5min 120/15min 165/sec 125/min 125/5min 125/15min 132/sec 126/min 126/5min 126/15min 110/sec 124/min 124/5min 124/15min 99/sec 122/min 122/5min 122/15min 107/sec 121/min 121/5min 121/15min ``` linescroll can read from multiple files provided as filename arguments, optionally combining their stats. ``` $ linescroll \ /var/log/apache/access.log \ /var/service/tinydns/log/main/current 0/sec 5/min 1/5min 0/15min # 128 # # # # # # # # # # # # # # # #### 0 171/sec 179/min 188/5min 196/15min # 259 # ## # ### ### # ### # ### # ## ### # # # # # # ####### ################## ### ######### ### # ### ## ##### ############################################################ ############################################################ ############################################################ ############################################################ ############################################################ 0 ``` Bash sub-shell redirection allows a simple way to graph the number of processes. Below we use two while loops to show the number of processes that `ed` started and the number of processes on the system. ``` $ linescroll <( while true; do pgrep -u ed; sleep 1; done) <(while true; do pgrep -f .; sleep 1; done) ``` linescroll-0.2.0/linescroll.1000064400000000000000000000062431046102023000142260ustar 00000000000000.\" Automatically generated by Pandoc 2.17.1.1 .\" .\" Define V font for inline verbatim, using C font in formats .\" that render this, and otherwise B font. .ie "\f[CB]x\f[]"x" \{\ . ftr V B . ftr VI BI . ftr VB B . ftr VBI BI .\} .el \{\ . ftr V CR . ftr VI CI . ftr VB CB . ftr VBI CBI .\} .TH "linescroll" "1" "04 September 2023" "linescroll 0.2.0" "User Manual" .hy .SH NAME .PP linescroll - a tool to monitor log files .SH SYNOPSIS .PP linescroll [OPTION] [FILE]\&... .SH DESCRIPTION .PP shows line flow rate in \f[B]file(s)\f[R] or \f[B]stdin\f[R]. .PP \f[B]-c\f[R]/\f[B]--combine\f[R] all input sources into a single source .PP \f[B]-l\f[R]/\f[B]--limit\f[R] \f[B]ITERATIONS\f[R] exit after \f[B]ITERATIONS\f[R] cycles .PP \f[B]-s\f[R]/\f[B]--speedonly\f[R] don\[cq]t print graphs .PP \f[B]-p\f[R]/\f[B]--print\f[R] \f[B]ITERATIONS\f[R] print only every \f[B]ITERATIONS\f[R]\[cq]th stats .PP \f[B]-f\f[R]/\f[B]--filename\f[R] print filename headings .PP \f[B]-a\f[R]/\f[B]--noaxislimit\f[R] do not print the axis limits .PP \f[B]-n\f[R]/\f[B]--noclear\f[R] do not clear the screen between prints .PP \f[B]-r\f[R]/\f[B]--raw\f[R] treat input lines as values to add to counter .PP \f[B]-h\f[R]/\f[B]--help\f[R] print help .SH USAGE .PP linescroll can be used to show how much activity happens in log files or program output. .PP Bash can be used in conjunction to capture several programs as arguments for linescroll. .PP \f[B]stdin\f[R] can be used as a pipe if no other files are given as arguments, or if the special filename \f[B]-\f[R] is used. .SH EXAMPLE .PP Show relative difference of (un-)cached traffic: .IP .nf \f[C] linescroll \[rs] <( varnishncsa -F \[aq]\[dq]%{Varnish:hitmiss}x\[dq]\[aq] | grep \[aq]\[dq]hit\[dq]$\[aq] ) \[rs] <( varnishncsa -F \[aq]\[dq]%{Varnish:hitmiss}x\[dq]\[aq] | grep -v \[aq]\[dq]hit\[dq]$\[aq] ) \f[R] .fi .PP Or just rate of traffic through the cache via stdin: .IP .nf \f[C] varnishncsa -F \[aq]%h %l %u %t \[dq]%r\[dq] %s %b \[dq]%{Referer}i\[dq] \[dq]%{User-agent}i\[dq] \[dq]%{Varnish:hitmiss}x\[dq]\[aq] | linescroll \f[R] .fi .PP Build a graph of stats for email: .IP .nf \f[C] ( printf \[dq]From: cache\[at]darkstar\[rs]nTo: super\[at]webmasters\[rs]nSubject: cache effectiveness snapshot\[rs]n\[rs]n\[dq]; linescroll --limit 60 --print 60 \[rs] <( varnishncsa -F \[aq]\[dq]%{Varnish:hitmiss}x\[dq]\[aq] | grep \[aq]\[dq]hit\[dq]$\[aq] ) \[rs] <( varnishncsa -F \[aq]\[dq]%{Varnish:hitmiss}x\[dq]\[aq] | grep -v \[aq]\[dq]hit\[dq]$\[aq] ) ) | /usr/sbin/sendmail -fcache super\[at]webmasters \f[R] .fi .SH OPTIONS .IP .nf \f[C] -c, --combine combine all file source metrics -l, --limit ITERATIONS stop after number of iterations (default never) -s, --speedonly no graph, speed only -p, --print ITERATIONS print only every Nth iteration -f, --filename print filename headings -h, --help print usage help -n, --noclear do not clear screen between draws --nofollow do not follow file renames -v, --version display version number -r, --raw input is metric \f[R] .fi .SH AUTHORS Ed Neville (ed-linescroll\[at]s5h.net). linescroll-0.2.0/linescroll.md000064400000000000000000000046311046102023000144650ustar 00000000000000--- title: linescroll section: 1 header: User Manual footer: linescroll 0.2.0 author: Ed Neville (ed-linescroll@s5h.net) date: 04 September 2023 --- # NAME linescroll - a tool to monitor log files # SYNOPSIS linescroll [OPTION] [FILE]... # DESCRIPTION shows line flow rate in **file(s)** or **stdin**. **-c**/**--combine** all input sources into a single source **-l**/**--limit** **ITERATIONS** exit after **ITERATIONS** cycles **-s**/**--speedonly** don't print graphs **-p**/**--print** **ITERATIONS** print only every **ITERATIONS**'th stats **-f**/**--filename** print filename headings **-a**/**--noaxislimit** do not print the axis limits **-n**/**--noclear** do not clear the screen between prints **-r**/**--raw** treat input lines as values to add to counter **-h**/**--help** print help # USAGE linescroll can be used to show how much activity happens in log files or program output. Bash can be used in conjunction to capture several programs as arguments for linescroll. **stdin** can be used as a pipe if no other files are given as arguments, or if the special filename **-** is used. # EXAMPLE Show relative difference of (un-)cached traffic: ``` linescroll \ <( varnishncsa -F '"%{Varnish:hitmiss}x"' | grep '"hit"$' ) \ <( varnishncsa -F '"%{Varnish:hitmiss}x"' | grep -v '"hit"$' ) ``` Or just rate of traffic through the cache via stdin: ``` varnishncsa -F '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i" "%{Varnish:hitmiss}x"' | linescroll ``` Build a graph of stats for email: ``` ( printf "From: cache@darkstar\nTo: super@webmasters\nSubject: cache effectiveness snapshot\n\n"; linescroll --limit 60 --print 60 \ <( varnishncsa -F '"%{Varnish:hitmiss}x"' | grep '"hit"$' ) \ <( varnishncsa -F '"%{Varnish:hitmiss}x"' | grep -v '"hit"$' ) ) | /usr/sbin/sendmail -fcache super@webmasters ``` # OPTIONS ``` -c, --combine combine all file source metrics -l, --limit ITERATIONS stop after number of iterations (default never) -s, --speedonly no graph, speed only -p, --print ITERATIONS print only every Nth iteration -f, --filename print filename headings -h, --help print usage help -n, --noclear do not clear screen between draws --nofollow do not follow file renames -v, --version display version number -r, --raw input is metric ``` linescroll-0.2.0/src/lib.rs000064400000000000000000000050731046102023000137010ustar 00000000000000use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] pub struct FileWatch { pub count: Arc>, pub minute: Vec, pub five_minute: Vec, pub fifteen_minute: Vec, pub name: String, } #[derive(Clone)] pub struct Settings { pub combine: bool, pub speedonly: bool, pub print_every: usize, pub limit: usize, pub filenames: bool, pub noclear: bool, pub nofollow: bool, pub raw: bool, pub exit: bool, pub axislimit: bool, } pub fn average_set(set: &[u64]) -> u64 { let mut avg: u64 = 0; for x in set.iter() { avg += x; } avg / set.len() as u64 } pub fn graph(minute: &[u64], axislimit: bool) -> String { let height: u64 = 10; let mut max_delta: u64 = 0; let mut s = String::new(); if minute.len() < 2 { for _ in 1..10 { s.push('\n'); } return s; } // get the limit for the graph for val in minute.iter().skip(1) { if val > &max_delta { max_delta = *val; } } for line in 1..height { s.push_str(&format!("{:10}", "")); for hist in minute.iter().skip(1) { s.push_str( if *hist as f64 > (max_delta as f64 / height as f64) * (height as f64 - line as f64) { "#" } else { " " }, ); } if line == 1 && axislimit { for _ in minute.len()..=60 { s.push(' '); } s.push_str(&format!(" {}", max_delta)); } if line == height - 1 && axislimit { for _ in minute.len()..=60 { s.push(' '); } s.push_str(" 0"); } s.push('\n'); } s } pub fn filename_print(settings: &Settings, item: &FileWatch) -> String { if settings.filenames { format!("{}\n", item.name) } else { "".to_string() } } pub fn trim_item(item: &mut FileWatch, size: usize) { while item.minute.len() > size + 1 { item.minute.remove(0); } while item.five_minute.len() > (size + 1) * 5 { item.five_minute.remove(0); } while item.fifteen_minute.len() > (size + 1) * 15 { item.fifteen_minute.remove(0); } } pub fn should_clear(settings: &Settings) -> bool { !settings.noclear } pub fn should_print(settings: &Settings, iterations: usize) -> bool { if settings.print_every != 1 && (iterations % settings.print_every != 0 || iterations == 0) { return false; } true } linescroll-0.2.0/src/main.rs000064400000000000000000000326351046102023000140630ustar 00000000000000use 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, fw_list: &mut Vec) -> 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::() { 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::() { 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 = vec![]; let args: Vec = 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::() { Ok(n) => n, Err(_x) => { // eprintln!("couldn't read {}: {}", u, x); // return 0; 0 } } } fn spawn_reader(count: Arc>, path: Option, settings: Settings) { thread::spawn(move || { let settings = settings; let mut inode_changed = false; loop { fn update_counters( fd: RawFd, count: &Arc>, path: &Option, 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::>(), ) .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) => { // might be /dev/fd // eprintln!("Cannot seek {}: {}", p, x); // std::process::exit(1); } } 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); } linescroll-0.2.0/tests/funcs.rs000064400000000000000000000030101046102023000146110ustar 00000000000000use linescroll::*; #[cfg(test)] mod test { use super::*; #[test] fn test_should_clear() { let settings = Settings { combine: false, speedonly: false, print_every: 3, limit: 3, filenames: false, noclear: false, nofollow: false, raw: false, exit: false, axislimit: true, }; assert_eq!(should_clear(&settings), true); } #[test] fn test_should_not_clear() { let settings = Settings { combine: false, speedonly: false, print_every: 3, limit: 3, filenames: false, noclear: true, nofollow: false, raw: false, exit: false, axislimit: true, }; assert_eq!(should_clear(&settings), false); } #[test] fn test_should_print() { let settings = Settings { combine: false, speedonly: false, print_every: 3, limit: 3, filenames: false, noclear: true, nofollow: false, raw: false, exit: false, axislimit: true, }; assert_eq!(should_print(&settings, 0), false); assert_eq!(should_print(&settings, 1), false); assert_eq!(should_print(&settings, 2), false); assert_eq!(should_print(&settings, 3), true); assert_eq!(should_print(&settings, 4), false); } }