netr 0.3.0

Display network interface throughput by second and by minute along with a graph. This is quick and easy to use via a mobile handset or similar device where typing is cumbersome.
Documentation
use regex::*;

#[derive(Clone, Debug)]
pub enum StatType {
    All,
    Bytes,
    Packets,
}

#[derive(Clone)]
pub struct Config {
    pub filter: Option<FilterOpts>,
    pub counters: Option<StatType>,
    pub print_total: Option<bool>,
    pub format: Option<String>,
}

impl Config {
    pub fn new() -> Config {
        Config {
            filter: None,
            counters: None,
            print_total: None,
            format: None,
        }
    }
}

impl Default for Config {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone)]
pub struct FilterOpts {
    pub include: bool,
    pub name: Regex,
}

impl FilterOpts {
    pub fn new() -> FilterOpts {
        FilterOpts {
            include: true,
            name: Regex::new(".").expect("cannot create regex"),
        }
    }

    pub fn create(text: &str, include: bool) -> Option<FilterOpts> {
        let r = match Regex::new(text) {
            Ok(x) => x,
            Err(_) => {
                return None;
            }
        };

        let filter = FilterOpts { name: r, include };

        Some(filter)
    }

    pub fn matches(self, text: &str) -> bool {
        if self.name.is_match(text) && !self.include {
            return false;
        }

        if !self.name.is_match(text) && self.include {
            return false;
        }

        true
    }
}

impl Default for FilterOpts {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
pub struct Device {
    // TODO: this should be a structure of counters, perhaps holding all the proc data
    pub name: String,

    pub ibytes: i64,
    pub obytes: i64,
    pub ipackets: i64,
    pub opackets: i64,
}

#[derive(Debug)]
pub struct DeviceHist {
    pub history: Vec<Device>,
}

#[cfg(target_os = "linux")]
pub enum ProcNetDevIndexes {
    Ibytes = 1,
    Ipackets = 2,
    Obytes = 9,
    Opackets = 10,
}

#[allow(clippy::result_unit_err)]
#[cfg(target_os = "linux")]
pub fn line_to_device_struct(line: &str) -> Result<Device, ()> {
    let idx = line.find(':');
    if idx.is_none() {
        return Err(());
    }

    let part_split = line[idx.unwrap()..].split_ascii_whitespace();
    let vec_list = part_split.collect::<Vec<&str>>();

    let d = Device {
        name: (line[0..idx.unwrap()].trim()).to_string(),
        ibytes: vec_list[ProcNetDevIndexes::Ibytes as usize]
            .parse()
            .unwrap_or(0),
        obytes: vec_list[ProcNetDevIndexes::Obytes as usize]
            .parse()
            .unwrap_or(0),
        ipackets: vec_list[ProcNetDevIndexes::Ipackets as usize]
            .parse()
            .unwrap_or(0),
        opackets: vec_list[ProcNetDevIndexes::Opackets as usize]
            .parse()
            .unwrap_or(0),
    };
    Ok(d)
}

#[cfg(target_os = "linux")]
pub fn read_net() -> Vec<Device> {
    use std::fs::File;
    use std::io::{prelude::*, BufReader};

    let mut devs = Vec::new();
    let file = File::open("/proc/net/dev").unwrap();
    let reader = BufReader::new(file);
    for line in reader.lines() {
        let a = line.unwrap();
        let d = line_to_device_struct(&a);
        match d {
            Err(_x) => {}
            Ok(d) => {
                devs.push(d);
            }
        }
    }

    devs
}

pub type Int64T = ::std::os::raw::c_long;
pub type Uint64T = ::std::os::raw::c_ulong;
pub type UChar = ::std::os::raw::c_uchar;
pub type TimeT = Int64T;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct timespec {
    pub tv_sec: TimeT,
    pub tv_nsec: ::std::os::raw::c_long,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct rtnl_link_stats {
    pub ifi_type: UChar,
    pub ifi_addrlen: UChar,
    pub ifi_hdrlen: UChar,
    pub ifi_link_state: ::std::os::raw::c_int,
    pub ifi_mtu: Uint64T,
    pub ifi_metric: Uint64T,
    pub ifi_baudrate: Uint64T,
    pub ifi_ipackets: Uint64T,
    pub ifi_ierrors: Uint64T,
    pub ifi_opackets: Uint64T,
    pub ifi_oerrors: Uint64T,
    pub ifi_collisions: Uint64T,
    pub ifi_ibytes: Uint64T,
    pub ifi_obytes: Uint64T,
    pub ifi_imcasts: Uint64T,
    pub ifi_omcasts: Uint64T,
    pub ifi_iqdrops: Uint64T,
    pub ifi_noproto: Uint64T,
    pub ifi_lastchange: timespec,
}

#[cfg(not(target_os = "linux"))]
pub fn read_net() -> Vec<Device> {
    let mut devs = Vec::new();

    unsafe {
        let mut addrs: *mut libc::ifaddrs = std::mem::MaybeUninit::uninit().as_mut_ptr();
        let _a = libc::getifaddrs(&mut addrs);

        loop {
            if (*(*addrs).ifa_addr).sa_family as i32 == libc::AF_LINK {
                let s: *const rtnl_link_stats = ((*addrs).ifa_data) as *const rtnl_link_stats;
                let d = Device {
                    name: ffi::CStr::from_ptr((*addrs).ifa_name)
                        .to_str()
                        .unwrap()
                        .to_string(),
                    ibytes: (*s).ifi_ibytes as i64,
                    obytes: (*s).ifi_obytes as i64,
                    ipackets: (*s).ifi_ipackets as i64,
                    opackets: (*s).ifi_opackets as i64,
                };

                devs.push(d);
            }

            addrs = (*addrs).ifa_next;

            if std::ptr::null() == addrs {
                freeifaddrs(addrs);
                break;
            }
        }
    };

    devs
}

pub fn human_unit(value: i64) -> String {
    let kb = 1024;
    let mb = 1024 * 1024;
    let gb = 1024 * 1024 * 1024;
    let tb = 1024 * 1024 * 1024 * 1024;
    let pb = 1024 * 1024 * 1024 * 1024 * 1024;
    if value >= pb {
        return format!("{} PiB", value / pb);
    }
    if value >= tb {
        return format!("{} TiB", value / tb);
    }
    if value >= gb {
        return format!("{} GiB", value / gb);
    }
    if value >= mb {
        return format!("{} MiB", value / mb);
    }
    if value >= kb {
        return format!("{} KiB", value / kb);
    }
    value.to_string()
}

pub fn format_output(format_str: &str, device: &Device) -> String {
    // Replace placeholders with actual values
    format_str
        .replace("\\n", "\n")
        .replace("\\t", "\t")
        .replace("\\r", "\r")
        .replace("%{if}", &device.name)
        .replace("%{ibytes}", &device.ibytes.to_string())
        .replace("%{obytes}", &device.obytes.to_string())
        .replace("%{ipackets}", &device.ipackets.to_string())
        .replace("%{opackets}", &device.opackets.to_string())
}