<commit_before>use super::common::*;
use super::unix;
use data::*;
use libc::statvfs;
use libc::{c_char, c_long, c_schar, c_uint, c_ulong, c_ushort};
use nom::bytes::complete::{tag, take_till, take_until};
use nom::character::complete::{digit1, multispace0, not_line_ending, space1};
use nom::character::is_space;
use nom::combinator::{complete, flat_map, map, map_res, opt};
use nom::error::ParseError;
use nom::multi::{fold_many0, many0, many1};
use nom::sequence::{delimited, preceded, tuple};
use nom::{IResult, Parser};
use std::io::Read;
use std::path::Path;
use std::str;
use std::time::Duration;
use std::{fs, io, mem, path};

fn read_file(path: &str) -> io::Result<String> {
    let mut s = String::new();
    fs::File::open(path)
        .and_then(|mut f| f.read_to_string(&mut s))
        .map(|_| s)
}

fn value_from_file<T: str::FromStr>(path: &str) -> io::Result<T> {
    read_file(path)?
        .trim_end_matches("\n")
        .parse()
        .and_then(|n| Ok(n))
        .or_else(|_| {
            Err(io::Error::new(io::ErrorKind::Other,
                               format!("File: \"{}\" doesn't contain an int value", &path)))
        })
}

fn capacity(charge_full: i32, charge_now: i32) -> f32 {
    charge_now as f32 / charge_full as f32
}

fn time(on_ac: bool, charge_full: i32, charge_now: i32, current_now: i32) -> Duration {
    if current_now != 0 {
        if on_ac {
            // Charge time
            Duration::from_secs(charge_full.saturating_sub(charge_now).abs() as u64 * 3600u64 / current_now as u64)
        }
        else {
            // Discharge time
            Duration::from_secs(charge_now as u64 * 3600u64 / current_now as u64)
        }
    } else {
        Duration::new(0, 0)
    }
}

/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and
/// trailing whitespace, returning the output of `inner`.
fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(
    inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
    F: Parser<&'a str, O, E>,
{
    delimited(multispace0, inner, multispace0)
}

/// Parse an unsigned integer out of a string, surrounded by whitespace
fn usize_s(input: &str) -> IResult<&str, usize> {
    map_res(
        map_res(map(ws(digit1), str::as_bytes), str::from_utf8),
        str::FromStr::from_str,
    )(input)
}

// Parse `cpuX`, where X is a number
fn proc_stat_cpu_prefix(input: &str) -> IResult<&str, ()> {
    map(tuple((tag("cpu"), digit1)), |_| ())(input)
}

// Parse a `/proc/stat` CPU line into a `CpuTime` struct
fn proc_stat_cpu_time(input: &str) -> IResult<&str, CpuTime> {
    map(
        preceded(
            ws(proc_stat_cpu_prefix),
            tuple((usize_s, usize_s, usize_s, usize_s, usize_s, usize_s)),
        ),
        |(user, nice, system, idle, iowait, irq)| CpuTime {
            user: user,
            nice: nice,
            system: system,
            idle: idle,
            interrupt: irq,
            other: iowait,
        },
    )(input)
}

// Parse the top CPU load aggregate line of `/proc/stat`
fn proc_stat_cpu_aggregate(input: &str) -> IResult<&str, ()> {
    map(tuple((tag("cpu"), space1)), |_| ())(input)
}

// Parse `/proc/stat` to extract per-CPU loads
fn proc_stat_cpu_times(input: &str) -> IResult<&str, Vec<CpuTime>> {
    preceded(
        map(ws(not_line_ending), proc_stat_cpu_aggregate),
        many1(flat_map(ws(not_line_ending), |_| proc_stat_cpu_time)),
    )(input)
}

/// Get the current per-CPU `CpuTime` statistics
fn cpu_time() -> io::Result<Vec<CpuTime>> {
    read_file("/proc/stat").and_then(|data| {
        proc_stat_cpu_times(&data)
            .map(|(_, res)| res)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
    })
}

// Parse a `/proc/meminfo` line into (key, ByteSize)
fn proc_meminfo_line(input: &str) -> IResult<&str, (&str, ByteSize)> {
    complete(map(
        tuple((take_until(":"), delimited(tag(":"), usize_s, ws(tag("kB"))))),
        |(key, value)| (key, ByteSize::kib(value as u64)),
    ))(input)
}

// Optionally parse a `/proc/meminfo` line`
fn proc_meminfo_line_opt(input: &str) -> IResult<&str, Option<(&str, ByteSize)>> {
    opt(proc_meminfo_line)(input)
}

// Parse `/proc/meminfo` into a hashmap
fn proc_meminfo(input: &str) -> IResult<&str, BTreeMap<String, ByteSize>> {
    fold_many0(
        ws(flat_map(not_line_ending, |_| proc_meminfo_line_opt)),
        BTreeMap::new(),
        |mut map: BTreeMap<String, ByteSize>, opt| {
            if let Some((key, val)) = opt {
                map.insert(key.to_string(), val);
            }
            map
        },
    )(input)
}

/// Get memory statistics
fn memory_stats() -> io::Result<BTreeMap<String, ByteSize>> {
    read_file("/proc/meminfo").and_then(|data| {
        proc_meminfo(&data)
            .map(|(_, res)| res)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
    })
}

// Parse a single word
fn word_s(input: &str) -> IResult<&str, &str> {
    take_till(|c| is_space(c as u8))(input)
}

/// `/proc/mounts` data
struct ProcMountsData {
    source: String,
    target: String,
    fstype: String,
}

// Parse a `/proc/mounts` line to get a mountpoint
fn proc_mounts_line(input: &str) -> IResult<&str, ProcMountsData> {
    map(
        tuple((ws(word_s), ws(word_s), ws(word_s))),
        |(source, target, fstype)| ProcMountsData {
            source: source.to_string(),
            target: target.to_string(),
            fstype: fstype.to_string(),
        },
    )(input)
}

// Parse `/proc/mounts` to get a list of mountpoints
fn proc_mounts(input: &str) -> IResult<&str, Vec<ProcMountsData>> {
    many1(flat_map(
        map_res(ws(not_line_ending), |input| {
            if input.is_empty() {
                return Err(());
            } else {
                Ok(input)
            }
        }),
        |_| proc_mounts_line,
    ))(input)
}

/// `/proc/net/sockstat` data
struct ProcNetSockStat {
    tcp_in_use: usize,
    tcp_orphaned: usize,
    udp_in_use: usize,
}

// Parse `/proc/net/sockstat` to get socket statistics
fn proc_net_sockstat(input: &str) -> IResult<&str, ProcNetSockStat> {
    map(
        ws(preceded(
            not_line_ending,
            tuple((
                preceded(tag("TCP: inuse"), usize_s),
                delimited(tag("orphan"), usize_s, not_line_ending),
                preceded(tag("UDP: inuse"), usize_s),
            )),
        )),
        |(tcp_in_use, tcp_orphaned, udp_in_use)| ProcNetSockStat {
            tcp_in_use: tcp_in_use,
            tcp_orphaned: tcp_orphaned,
            udp_in_use: udp_in_use,<commit_msg>Fix an issue where proc_mounts would skip the first line<commit_after>use super::common::*;
use super::unix;
use data::*;
use libc::statvfs;
use libc::{c_char, c_long, c_schar, c_uint, c_ulong, c_ushort};
use nom::bytes::complete::{tag, take_till, take_until};
use nom::character::complete::{digit1, multispace0, not_line_ending, space1};
use nom::character::is_space;
use nom::combinator::{complete, flat_map, map, map_res, opt};
use nom::error::ParseError;
use nom::multi::{fold_many0, many0, many1};
use nom::sequence::{delimited, preceded, tuple};
use nom::{IResult, Parser};
use std::io::Read;
use std::path::Path;
use std::str;
use std::time::Duration;
use std::{fs, io, mem, path};

fn read_file(path: &str) -> io::Result<String> {
    let mut s = String::new();
    fs::File::open(path)
        .and_then(|mut f| f.read_to_string(&mut s))
        .map(|_| s)
}

fn value_from_file<T: str::FromStr>(path: &str) -> io::Result<T> {
    read_file(path)?
        .trim_end_matches("\n")
        .parse()
        .and_then(|n| Ok(n))
        .or_else(|_| {
            Err(io::Error::new(io::ErrorKind::Other,
                               format!("File: \"{}\" doesn't contain an int value", &path)))
        })
}

fn capacity(charge_full: i32, charge_now: i32) -> f32 {
    charge_now as f32 / charge_full as f32
}

fn time(on_ac: bool, charge_full: i32, charge_now: i32, current_now: i32) -> Duration {
    if current_now != 0 {
        if on_ac {
            // Charge time
            Duration::from_secs(charge_full.saturating_sub(charge_now).abs() as u64 * 3600u64 / current_now as u64)
        }
        else {
            // Discharge time
            Duration::from_secs(charge_now as u64 * 3600u64 / current_now as u64)
        }
    } else {
        Duration::new(0, 0)
    }
}

/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and
/// trailing whitespace, returning the output of `inner`.
fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(
    inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
    F: Parser<&'a str, O, E>,
{
    delimited(multispace0, inner, multispace0)
}

/// Parse an unsigned integer out of a string, surrounded by whitespace
fn usize_s(input: &str) -> IResult<&str, usize> {
    map_res(
        map_res(map(ws(digit1), str::as_bytes), str::from_utf8),
        str::FromStr::from_str,
    )(input)
}

// Parse `cpuX`, where X is a number
fn proc_stat_cpu_prefix(input: &str) -> IResult<&str, ()> {
    map(tuple((tag("cpu"), digit1)), |_| ())(input)
}

// Parse a `/proc/stat` CPU line into a `CpuTime` struct
fn proc_stat_cpu_time(input: &str) -> IResult<&str, CpuTime> {
    map(
        preceded(
            ws(proc_stat_cpu_prefix),
            tuple((usize_s, usize_s, usize_s, usize_s, usize_s, usize_s)),
        ),
        |(user, nice, system, idle, iowait, irq)| CpuTime {
            user: user,
            nice: nice,
            system: system,
            idle: idle,
            interrupt: irq,
            other: iowait,
        },
    )(input)
}

// Parse the top CPU load aggregate line of `/proc/stat`
fn proc_stat_cpu_aggregate(input: &str) -> IResult<&str, ()> {
    map(tuple((tag("cpu"), space1)), |_| ())(input)
}

// Parse `/proc/stat` to extract per-CPU loads
fn proc_stat_cpu_times(input: &str) -> IResult<&str, Vec<CpuTime>> {
    preceded(
        map(ws(not_line_ending), proc_stat_cpu_aggregate),
        many1(flat_map(ws(not_line_ending), |_| proc_stat_cpu_time)),
    )(input)
}

/// Get the current per-CPU `CpuTime` statistics
fn cpu_time() -> io::Result<Vec<CpuTime>> {
    read_file("/proc/stat").and_then(|data| {
        proc_stat_cpu_times(&data)
            .map(|(_, res)| res)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
    })
}

// Parse a `/proc/meminfo` line into (key, ByteSize)
fn proc_meminfo_line(input: &str) -> IResult<&str, (&str, ByteSize)> {
    complete(map(
        tuple((take_until(":"), delimited(tag(":"), usize_s, ws(tag("kB"))))),
        |(key, value)| (key, ByteSize::kib(value as u64)),
    ))(input)
}

// Optionally parse a `/proc/meminfo` line`
fn proc_meminfo_line_opt(input: &str) -> IResult<&str, Option<(&str, ByteSize)>> {
    opt(proc_meminfo_line)(input)
}

// Parse `/proc/meminfo` into a hashmap
fn proc_meminfo(input: &str) -> IResult<&str, BTreeMap<String, ByteSize>> {
    fold_many0(
        ws(flat_map(not_line_ending, |_| proc_meminfo_line_opt)),
        BTreeMap::new(),
        |mut map: BTreeMap<String, ByteSize>, opt| {
            if let Some((key, val)) = opt {
                map.insert(key.to_string(), val);
            }
            map
        },
    )(input)
}

/// Get memory statistics
fn memory_stats() -> io::Result<BTreeMap<String, ByteSize>> {
    read_file("/proc/meminfo").and_then(|data| {
        proc_meminfo(&data)
            .map(|(_, res)| res)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
    })
}

// Parse a single word
fn word_s(input: &str) -> IResult<&str, &str> {
    take_till(|c| is_space(c as u8))(input)
}

/// `/proc/mounts` data
struct ProcMountsData {
    source: String,
    target: String,
    fstype: String,
}

// Parse a `/proc/mounts` line to get a mountpoint
fn proc_mounts_line(input: &str) -> IResult<&str, ProcMountsData> {
    map(
        tuple((ws(word_s), ws(word_s), ws(word_s))),
        |(source, target, fstype)| ProcMountsData {
            source: source.to_string(),
            target: target.to_string(),
            fstype: fstype.to_string(),
        },
    )(input)
}

// Parse `/proc/mounts` to get a list of mountpoints
fn proc_mounts(input: &str) -> IResult<&str, Vec<ProcMountsData>> {
    many1(map_res(ws(not_line_ending), |input| {
        if input.is_empty() {
            return Err(());
        } else {
            proc_mounts_line(input).map(|(_, res)| res).map_err(|_| ())
        }
    }))(input)
}

#[test]
fn test_proc_mounts() {
    let test_input_1 = r#"/dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0
tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0"#;
    let mounts = proc_mounts(test_input_1).unwrap().1;
    assert!(mounts.len() == 6);
    let root = mounts.iter().find(|m| m.target == "/").unwrap();
    assert!(root.source == "/dev/md0");
    assert!(root.target == "/");
    assert!(root.fstype == "btrfs");

    let test_input_2 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0
tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0
/dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0"#;
    let mounts = proc_mounts(test_input_2).unwrap().1;
    assert!(mounts.len() == 6);
    let root = mounts.iter().find(|m| m.target == "/").unwrap();
    assert!(root.source == "/dev/md0");
    assert!(root.target == "/");
    assert!(root.fstype == "btrfs");

    // On some distros, there is a blank line at the end of `/proc/mounts`,
    // so we test here to make sure we do not crash on that
    let test_input_3 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
sys /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
dev /dev devtmpfs rw,nosuid,relatime,size=16131864k,nr_inodes=4032966,mode=755,inode64 0 0
run /run tmpfs rw,nosuid,nodev,relatime,mode=755,inode64 0 0
efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0
/dev/nvme0n1p3 / btrfs rw,noatime,ssd,space_cache,subvolid=5,subvol=/ 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0
"#;
    let mounts = proc_mounts(test_input_3).unwrap().1;
    assert!(mounts.len() == 8);
    let root = mounts.iter().find(|m| m.target == "/").unwrap();
    assert!(root.source == "/dev/nvme0n1p3");
    assert!(root.target == "/");
    assert!(root.fstype == "btrfs");
}

/// `/proc/net/sockstat` data
struct ProcNetSockStat {
    tcp_in_use: usize,
    tcp_orphaned: usize,
    udp_in_use: usize,
}

// Parse `/proc/net/sockstat` to get socket statistics
fn proc_net_sockstat(input: &str) -> IResult<&str, ProcNetSockStat> {
    map(
        ws(preceded(
            not_line_ending,
            tuple((
                preceded(tag("TCP: inuse"), usize_s),
                delimited(tag("orphan"), usize_s, not_line_ending),
                preceded(tag("UDP: inuse"), usize_s),
            )),
        )),
        |(tcp_in_use, tcp_orphaned, udp_in_use)| ProcNetSockStat {
            tcp_in_use: tcp_in_use,
            tcp_orphaned: tcp_orphaned,
            udp_in_use: udp_in_use,
{'fraction_non_alphanumeric': 0.16209758551307846, 'fraction_numerical': 0.02137826961770624, 'mean_word_length': 3.0646562739585996, 'pattern_counts': {'":': 4, '<': 59, '<?xml version=': 0, '>': 99, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '881894', 'n_tokens_mistral': 6123, 'n_tokens_neox': 5893, 'n_words': 1245}