use super::*;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::File;
use std::io::{self, Read};
#[cfg(unix)]
use std::os::linux::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
#[cfg(windows)]
trait FakeMedatadataExt {
fn st_uid(&self) -> u32;
}
#[cfg(windows)]
impl FakeMedatadataExt for std::fs::Metadata {
fn st_uid(&self) -> u32 {
panic!()
}
}
bitflags! {
pub struct StatFlags: u32 {
const PF_IDLE = 0x0000_0002;
const PF_EXITING = 0x0000_0004;
const PF_EXITPIDONE = 0x0000_0008;
const PF_VCPU = 0x0000_0010;
const PF_WQ_WORKER = 0x0000_0020;
const PF_FORKNOEXEC = 0x0000_0040;
const PF_MCE_PROCESS = 0x0000_0080;
const PF_SUPERPRIV = 0x0000_0100;
const PF_DUMPCORE = 0x0000_0200;
const PF_SIGNALED = 0x0000_0400;
const PF_MEMALLOC = 0x0000_0800;
const PF_NPROC_EXCEEDED = 0x0000_1000;
const PF_USED_MATH = 0x0000_2000;
const PF_USED_ASYNC = 0x0000_4000;
const PF_NOFREEZE = 0x0000_8000;
const PF_FROZEN = 0x0001_0000;
const PF_KSWAPD = 0x0002_0000;
const PF_MEMALLOC_NOFS = 0x0004_0000;
const PF_MEMALLOC_NOIO = 0x0008_0000;
const PF_LESS_THROTTLE = 0x0010_0000;
const PF_KTHREAD = 0x0020_0000;
const PF_RANDOMIZE = 0x0040_0000;
const PF_SWAPWRITE = 0x0080_0000;
const PF_NO_SETAFFINITY = 0x0400_0000;
const PF_MCE_EARLY = 0x0800_0000;
const PF_MUTEX_TESTER = 0x2000_0000;
const PF_FREEZER_SKIP = 0x4000_0000;
const PF_SUSPEND_TASK = 0x8000_0000;
}
}
bitflags! {
pub struct CoredumpFlags: u32 {
const ANONYMOUS_PRIVATE_MAPPINGS = 0x01;
const ANONYMOUS_SHARED_MAPPINGS = 0x02;
const FILEBACKED_PRIVATE_MAPPINGS = 0x04;
const FILEBACKED_SHARED_MAPPINGS = 0x08;
const ELF_HEADERS = 0x10;
const PROVATE_HUGEPAGES = 0x20;
const SHARED_HUGEPAGES = 0x40;
const PRIVATE_DAX_PAGES = 0x80;
const SHARED_DAX_PAGES = 0x100;
}
}
bitflags! {
pub struct NFSServerCaps: u32 {
const NFS_CAP_READDIRPLUS = (1 << 0);
const NFS_CAP_HARDLINKS = (1 << 1);
const NFS_CAP_SYMLINKS = (1 << 2);
const NFS_CAP_ACLS = (1 << 3);
const NFS_CAP_ATOMIC_OPEN = (1 << 4);
const NFS_CAP_LGOPEN = (1 << 5);
const NFS_CAP_FILEID = (1 << 6);
const NFS_CAP_MODE = (1 << 7);
const NFS_CAP_NLINK = (1 << 8);
const NFS_CAP_OWNER = (1 << 9);
const NFS_CAP_OWNER_GROUP = (1 << 10);
const NFS_CAP_ATIME = (1 << 11);
const NFS_CAP_CTIME = (1 << 12);
const NFS_CAP_MTIME = (1 << 13);
const NFS_CAP_POSIX_LOCK = (1 << 14);
const NFS_CAP_UIDGID_NOMAP = (1 << 15);
const NFS_CAP_STATEID_NFSV41 = (1 << 16);
const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17);
const NFS_CAP_SECURITY_LABEL = (1 << 18);
const NFS_CAP_SEEK = (1 << 19);
const NFS_CAP_ALLOCATE = (1 << 20);
const NFS_CAP_DEALLOCATE = (1 << 21);
const NFS_CAP_LAYOUTSTATS = (1 << 22);
const NFS_CAP_CLONE = (1 << 23);
const NFS_CAP_COPY = (1 << 24);
const NFS_CAP_OFFLOAD_CANCEL = (1 << 25);
}
}
fn from_iter<'a, I, U>(i: I) -> U
where
I: IntoIterator<Item = &'a str>,
U: FromStr,
{
let mut iter = i.into_iter();
let val = expect!(iter.next(), "Missing iterator next item");
match FromStr::from_str(val) {
Ok(u) => u,
Err(..) => panic!("Failed to convert".to_string()),
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ProcState {
Running,
Sleeping,
Waiting,
Zombie,
Stopped,
Tracing,
Dead,
Wakekill,
Waking,
Parked,
}
impl ProcState {
pub fn from_char(c: char) -> Option<ProcState> {
match c {
'R' => Some(ProcState::Running),
'S' => Some(ProcState::Sleeping),
'D' => Some(ProcState::Waiting),
'Z' => Some(ProcState::Zombie),
'T' => Some(ProcState::Stopped),
't' => Some(ProcState::Tracing),
'X' | 'x' => Some(ProcState::Dead),
'K' => Some(ProcState::Wakekill),
'W' => Some(ProcState::Waking),
'P' => Some(ProcState::Parked),
_ => None,
}
}
}
impl FromStr for ProcState {
type Err = &'static str;
fn from_str(s: &str) -> Result<ProcState, &'static str> {
ProcState::from_char(expect!(s.chars().next(), "empty string")).ok_or("Failed to convert")
}
}
#[derive(Debug, Clone)]
pub struct Stat {
pub pid: i32,
pub comm: String,
pub state: char,
pub ppid: i32,
pub pgrp: i32,
pub session: i32,
pub tty_nr: i32,
pub tpgid: i32,
pub flags: u32,
pub minflt: u64,
pub cminflt: u64,
pub majflt: u64,
pub cmajflt: u64,
pub utime: u64,
pub stime: u64,
pub cutime: i64,
pub cstime: i64,
pub priority: i64,
pub nice: i64,
pub num_threads: i64,
pub itrealvalue: i64,
pub starttime: i64,
pub vsize: u64,
pub rss: i64,
pub rsslim: u64,
pub startcode: u64,
pub endcode: u64,
pub startstack: u64,
pub kstkesp: u64,
pub kstkeip: u64,
pub signal: u64,
pub blocked: u64,
pub sigignore: u64,
pub sigcatch: u64,
pub wchan: u64,
pub nswap: u64,
pub cnswap: u64,
pub exit_signal: Option<i32>,
pub processor: Option<i32>,
pub rt_priority: Option<u32>,
pub policy: Option<u32>,
pub delayacct_blkio_ticks: Option<u64>,
pub guest_time: Option<u32>,
pub cguest_time: Option<u32>,
pub start_data: Option<usize>,
pub end_data: Option<usize>,
pub start_brk: Option<usize>,
pub arg_start: Option<usize>,
pub arg_end: Option<usize>,
pub env_start: Option<usize>,
pub env_end: Option<usize>,
pub exit_code: Option<i32>,
}
#[derive(Debug)]
pub struct Io {
pub rchar: u64,
pub wchar: u64,
pub syscr: u64,
pub syscw: u64,
pub read_bytes: u64,
pub write_bytes: u64,
pub cancelled_write_bytes: u64,
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MountStat {
pub device: Option<String>,
pub mount_point: PathBuf,
pub fs: String,
pub statistics: Option<MountNFSStatistics>,
}
impl MountStat {
pub fn from_reader<R: io::Read>(r: R) -> Vec<MountStat> {
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
let mut v = Vec::new();
let bufread = BufReader::new(r);
let mut lines = bufread.lines();
while let Some(Ok(line)) = lines.next() {
if line.starts_with("device ") {
let mut s = line.split_whitespace();
let device = Some(s.nth(1).unwrap().to_owned());
let mount_point = PathBuf::from(s.nth(2).unwrap());
let fs = s.nth(2).unwrap().to_owned();
let statistics = match s.next() {
Some(stats) if stats.starts_with("statvers=") => {
Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..]))
}
_ => None,
};
v.push(MountStat {
device,
mount_point,
fs,
statistics,
});
}
}
v
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MountNFSStatistics {
pub version: String,
pub opts: Vec<String>,
pub age: Duration,
pub caps: Vec<String>,
pub sec: Vec<String>,
pub events: NFSEventCounter,
pub bytes: NFSByteCounter,
pub per_op_stats: NFSPerOpStats,
}
impl MountNFSStatistics {
fn from_lines<B: io::BufRead>(r: &mut io::Lines<B>, statsver: &str) -> MountNFSStatistics {
use std::iter::Iterator;
let mut parsing_per_op = false;
let mut opts: Option<Vec<String>> = None;
let mut age = None;
let mut caps = None;
let mut sec = None;
let mut bytes = None;
let mut events = None;
let mut per_op = HashMap::new();
while let Some(Ok(line)) = r.next() {
let line = line.trim();
if line.trim() == "" {
break;
}
if !parsing_per_op {
if line.starts_with("opts:") {
opts = Some(line[5..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("age:") {
age = Some(Duration::from_secs(from_str!(u64, &line[4..].trim())));
} else if line.starts_with("caps:") {
caps = Some(line[5..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("sec:") {
sec = Some(line[4..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("bytes:") {
bytes = Some(NFSByteCounter::from_str(&line[6..].trim()));
} else if line.starts_with("events:") {
events = Some(NFSEventCounter::from_str(&line[7..].trim()));
}
if line == "per-op statistics" {
parsing_per_op = true;
}
} else {
let mut split = line.split(':');
let name = expect!(split.next()).to_string();
let stats = NFSOperationStat::from_str(expect!(split.next()));
per_op.insert(name, stats);
}
}
MountNFSStatistics {
version: statsver.to_string(),
opts: expect!(opts, "Failed to find opts field in nfs stats"),
age: expect!(age, "Failed to find age field in nfs stats"),
caps: expect!(caps, "Failed to find caps field in nfs stats"),
sec: expect!(sec, "Failed to find sec field in nfs stats"),
events: expect!(events, "Failed to find events section in nfs stats"),
bytes: expect!(bytes, "Failed to find bytes section in nfs stats"),
per_op_stats: per_op,
}
}
pub fn server_caps(&self) -> Option<NFSServerCaps> {
for data in &self.caps {
if data.starts_with("caps=0x") {
let val = from_str!(u32, &data[7..], 16);
return NFSServerCaps::from_bits(val);
}
}
None
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSEventCounter {
inode_revalidate: libc::c_ulong,
deny_try_revalidate: libc::c_ulong,
data_invalidate: libc::c_ulong,
attr_invalidate: libc::c_ulong,
vfs_open: libc::c_ulong,
vfs_lookup: libc::c_ulong,
vfs_access: libc::c_ulong,
vfs_update_page: libc::c_ulong,
vfs_read_page: libc::c_ulong,
vfs_read_pages: libc::c_ulong,
vfs_write_page: libc::c_ulong,
vfs_write_pages: libc::c_ulong,
vfs_get_dents: libc::c_ulong,
vfs_set_attr: libc::c_ulong,
vfs_flush: libc::c_ulong,
vfs_fs_sync: libc::c_ulong,
vfs_lock: libc::c_ulong,
vfs_release: libc::c_ulong,
congestion_wait: libc::c_ulong,
set_attr_trunc: libc::c_ulong,
extend_write: libc::c_ulong,
silly_rename: libc::c_ulong,
short_read: libc::c_ulong,
short_write: libc::c_ulong,
delay: libc::c_ulong,
pnfs_read: libc::c_ulong,
pnfs_write: libc::c_ulong,
}
impl NFSEventCounter {
fn from_str(s: &str) -> NFSEventCounter {
use libc::c_ulong;
let mut s = s.split_whitespace();
NFSEventCounter {
inode_revalidate: from_str!(c_ulong, expect!(s.next())),
deny_try_revalidate: from_str!(c_ulong, expect!(s.next())),
data_invalidate: from_str!(c_ulong, expect!(s.next())),
attr_invalidate: from_str!(c_ulong, expect!(s.next())),
vfs_open: from_str!(c_ulong, expect!(s.next())),
vfs_lookup: from_str!(c_ulong, expect!(s.next())),
vfs_access: from_str!(c_ulong, expect!(s.next())),
vfs_update_page: from_str!(c_ulong, expect!(s.next())),
vfs_read_page: from_str!(c_ulong, expect!(s.next())),
vfs_read_pages: from_str!(c_ulong, expect!(s.next())),
vfs_write_page: from_str!(c_ulong, expect!(s.next())),
vfs_write_pages: from_str!(c_ulong, expect!(s.next())),
vfs_get_dents: from_str!(c_ulong, expect!(s.next())),
vfs_set_attr: from_str!(c_ulong, expect!(s.next())),
vfs_flush: from_str!(c_ulong, expect!(s.next())),
vfs_fs_sync: from_str!(c_ulong, expect!(s.next())),
vfs_lock: from_str!(c_ulong, expect!(s.next())),
vfs_release: from_str!(c_ulong, expect!(s.next())),
congestion_wait: from_str!(c_ulong, expect!(s.next())),
set_attr_trunc: from_str!(c_ulong, expect!(s.next())),
extend_write: from_str!(c_ulong, expect!(s.next())),
silly_rename: from_str!(c_ulong, expect!(s.next())),
short_read: from_str!(c_ulong, expect!(s.next())),
short_write: from_str!(c_ulong, expect!(s.next())),
delay: from_str!(c_ulong, expect!(s.next())),
pnfs_read: from_str!(c_ulong, expect!(s.next())),
pnfs_write: from_str!(c_ulong, expect!(s.next())),
}
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSByteCounter {
pub normal_read: libc::c_ulonglong,
pub normal_write: libc::c_ulonglong,
pub direct_read: libc::c_ulonglong,
pub direct_write: libc::c_ulonglong,
pub server_read: libc::c_ulonglong,
pub server_write: libc::c_ulonglong,
pub pages_read: libc::c_ulonglong,
pub pages_write: libc::c_ulonglong,
}
impl NFSByteCounter {
fn from_str(s: &str) -> NFSByteCounter {
use libc::c_ulonglong;
let mut s = s.split_whitespace();
NFSByteCounter {
normal_read: from_str!(c_ulonglong, expect!(s.next())),
normal_write: from_str!(c_ulonglong, expect!(s.next())),
direct_read: from_str!(c_ulonglong, expect!(s.next())),
direct_write: from_str!(c_ulonglong, expect!(s.next())),
server_read: from_str!(c_ulonglong, expect!(s.next())),
server_write: from_str!(c_ulonglong, expect!(s.next())),
pages_read: from_str!(c_ulonglong, expect!(s.next())),
pages_write: from_str!(c_ulonglong, expect!(s.next())),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSOperationStat {
pub operations: libc::c_ulong,
pub transmissions: libc::c_ulong,
pub major_timeouts: libc::c_ulong,
pub bytes_sent: libc::c_ulonglong,
pub bytes_recv: libc::c_ulonglong,
pub cum_queue_time: Duration,
pub cum_resp_time: Duration,
pub cum_total_req_time: Duration,
}
impl NFSOperationStat {
fn from_str(s: &str) -> NFSOperationStat {
use libc::{c_ulong, c_ulonglong};
let mut s = s.split_whitespace();
let operations = from_str!(c_ulong, expect!(s.next()));
let transmissions = from_str!(c_ulong, expect!(s.next()));
let major_timeouts = from_str!(c_ulong, expect!(s.next()));
let bytes_sent = from_str!(c_ulonglong, expect!(s.next()));
let bytes_recv = from_str!(c_ulonglong, expect!(s.next()));
let cum_queue_time_ms = from_str!(u64, expect!(s.next()));
let cum_resp_time_ms = from_str!(u64, expect!(s.next()));
let cum_total_req_time_ms = from_str!(u64, expect!(s.next()));
NFSOperationStat {
operations,
transmissions,
major_timeouts,
bytes_sent,
bytes_recv,
cum_queue_time: Duration::from_millis(cum_queue_time_ms),
cum_resp_time: Duration::from_millis(cum_resp_time_ms),
cum_total_req_time: Duration::from_millis(cum_total_req_time_ms),
}
}
}
pub type NFSPerOpStats = HashMap<String, NFSOperationStat>;
#[derive(Debug, PartialEq)]
pub enum MMapPath {
Path(PathBuf),
Heap,
Stack,
TStack(u32),
Vdso,
Anonymous,
Other(String),
}
impl MMapPath {
fn from(path: &str) -> MMapPath {
match path.trim() {
"" => MMapPath::Anonymous,
"[heap]" => MMapPath::Heap,
"[stack]" => MMapPath::Stack,
"[vdso]" => MMapPath::Vdso,
x if x.starts_with("[stack:") => {
let mut s = x[1..x.len() - 1].split(':');
let tid = from_str!(u32, s.nth(1).unwrap());
MMapPath::TStack(tid)
}
x if x.starts_with('[') && x.ends_with(']') => {
MMapPath::Other(x[1..x.len() - 1].to_string())
}
x => MMapPath::Path(PathBuf::from(x)),
}
}
}
#[derive(Debug)]
pub struct MemoryMap {
pub address: (u64, u64),
pub perms: String,
pub offset: u64,
pub dev: (i32, i32),
pub inode: u32,
pub pathname: MMapPath,
}
impl Io {
pub fn from_reader<R: io::Read>(r: R) -> Option<Io> {
use std::collections::HashMap;
use std::io::{BufRead, BufReader};
let mut map = HashMap::new();
let reader = BufReader::new(r);
for line in reader.lines() {
let line = expect!(line, "Failed to read line");
if line.is_empty() || !line.contains(' ') {
continue;
}
let mut s = line.split_whitespace();
let field = expect!(s.next(), "no field");
let value = expect!(s.next(), "no value");
let value = from_str!(u64, value);
map.insert(field[..field.len() - 1].to_string(), value);
}
let io = Io {
rchar: expect!(map.remove("rchar")),
wchar: expect!(map.remove("wchar")),
syscr: expect!(map.remove("syscr")),
syscw: expect!(map.remove("syscw")),
read_bytes: expect!(map.remove("read_bytes")),
write_bytes: expect!(map.remove("write_bytes")),
cancelled_write_bytes: expect!(map.remove("cancelled_write_bytes")),
};
if cfg!(test) {
if !map.is_empty() {
panic!("io map is not empty: {:#?}", map);
}
}
Some(io)
}
}
#[derive(Debug)]
pub enum FDTarget {
Path(PathBuf),
Socket(u32),
Net(u32),
Pipe(u32),
AnonInode(String),
MemFD(String),
Other(String, u32),
}
impl FromStr for FDTarget {
type Err = String;
fn from_str(s: &str) -> Result<FDTarget, String> {
if !s.starts_with('/') && s.contains(':') {
let mut s = s.split(':');
let fd_type = s.next().unwrap();
match fd_type {
"socket" => {
let inode = expect!(s.next(), "socket inode");
let inode = u32::from_str_radix(&inode[1..inode.len() - 1], 10).unwrap();
Ok(FDTarget::Socket(inode))
}
"net" => {
let inode = expect!(s.next(), "net inode");
let inode = u32::from_str_radix(&inode[1..inode.len() - 1], 10).unwrap();
Ok(FDTarget::Net(inode))
}
"pipe" => {
let inode = expect!(s.next(), "pipe inode");
let inode = u32::from_str_radix(&inode[1..inode.len() - 1], 10).unwrap();
Ok(FDTarget::Pipe(inode))
}
"anon_inode" => Ok(FDTarget::AnonInode(
expect!(s.next(), "anon inode").to_string(),
)),
"/memfd" => Ok(FDTarget::MemFD(expect!(s.next(), "memfd name").to_string())),
x => {
let inode = expect!(s.next(), "other inode");
let inode = u32::from_str_radix(&inode[1..inode.len() - 1], 10).unwrap();
Ok(FDTarget::Other(x.to_string(), inode))
}
}
} else {
Ok(FDTarget::Path(PathBuf::from(s)))
}
}
}
#[derive(Debug)]
pub struct FDInfo {
pub fd: u32,
pub target: FDTarget,
}
macro_rules! since_kernel {
($a:tt, $b:tt, $c:tt, $e:expr) => {
if *KERNEL >= KernelVersion::new($a, $b, $c) {
Some($e)
} else {
None
}
};
}
impl Stat {
pub fn from_reader<R: io::Read>(mut r: R) -> Option<Stat> {
let mut buf = String::new();
r.read_to_string(&mut buf).ok()?;
let buf = buf.trim();
let start_paren = buf.find('(')?;
let end_paren = buf.rfind(')')?;
let pid_s = &buf[..start_paren - 1];
let comm = buf[start_paren + 1..end_paren].to_string();
let rest = &buf[end_paren + 2..];
let pid = FromStr::from_str(pid_s).unwrap();
let mut rest = rest.split(' ');
let state = rest.next().unwrap().chars().next().unwrap();
let ppid = from_iter(&mut rest);
let pgrp = from_iter(&mut rest);
let session = from_iter(&mut rest);
let tty_nr = from_iter(&mut rest);
let tpgid = from_iter(&mut rest);
let flags = from_iter(&mut rest);
let minflt = from_iter(&mut rest);
let cminflt = from_iter(&mut rest);
let majflt = from_iter(&mut rest);
let cmajflt = from_iter(&mut rest);
let utime = from_iter(&mut rest);
let stime = from_iter(&mut rest);
let cutime = from_iter(&mut rest);
let cstime = from_iter(&mut rest);
let priority = from_iter(&mut rest);
let nice = from_iter(&mut rest);
let num_threads = from_iter(&mut rest);
let itrealvalue = from_iter(&mut rest);
let starttime = from_iter(&mut rest);
let vsize = from_iter(&mut rest);
let rss = from_iter(&mut rest);
let rsslim = from_iter(&mut rest);
let startcode = from_iter(&mut rest);
let endcode = from_iter(&mut rest);
let startstack = from_iter(&mut rest);
let kstkesp = from_iter(&mut rest);
let kstkeip = from_iter(&mut rest);
let signal = from_iter(&mut rest);
let blocked = from_iter(&mut rest);
let sigignore = from_iter(&mut rest);
let sigcatch = from_iter(&mut rest);
let wchan = from_iter(&mut rest);
let nswap = from_iter(&mut rest);
let cnswap = from_iter(&mut rest);
let exit_signal = since_kernel!(2, 1, 22, from_iter(&mut rest));
let processor = since_kernel!(2, 2, 8, from_iter(&mut rest));
let rt_priority = since_kernel!(2, 5, 19, from_iter(&mut rest));
let policy = since_kernel!(2, 5, 19, from_iter(&mut rest));
let delayacct_blkio_ticks = since_kernel!(2, 6, 18, from_iter(&mut rest));
let guest_time = since_kernel!(2, 6, 24, from_iter(&mut rest));
let cguest_time = since_kernel!(2, 6, 24, from_iter(&mut rest));
let start_data = since_kernel!(3, 3, 0, from_iter(&mut rest));
let end_data = since_kernel!(3, 3, 0, from_iter(&mut rest));
let start_brk = since_kernel!(3, 3, 0, from_iter(&mut rest));
let arg_start = since_kernel!(3, 5, 0, from_iter(&mut rest));
let arg_end = since_kernel!(3, 5, 0, from_iter(&mut rest));
let env_start = since_kernel!(3, 5, 0, from_iter(&mut rest));
let env_end = since_kernel!(3, 5, 0, from_iter(&mut rest));
let exit_code = since_kernel!(3, 5, 0, from_iter(&mut rest));
Some(Stat {
pid,
comm,
state,
ppid,
pgrp,
session,
tty_nr,
tpgid,
flags,
minflt,
cminflt,
majflt,
cmajflt,
utime,
stime,
cutime,
cstime,
priority,
nice,
num_threads,
itrealvalue,
starttime,
vsize,
rss,
rsslim,
startcode,
endcode,
startstack,
kstkesp,
kstkeip,
signal,
blocked,
sigignore,
sigcatch,
wchan,
nswap,
cnswap,
exit_signal,
processor,
rt_priority,
policy,
delayacct_blkio_ticks,
guest_time,
cguest_time,
start_data,
end_data,
start_brk,
arg_start,
arg_end,
env_start,
env_end,
exit_code,
})
}
pub fn state(&self) -> ProcState {
ProcState::from_char(self.state).unwrap()
}
pub fn tty_nr(&self) -> (i32, i32) {
let major = (self.tty_nr & 0xfff00) >> 8;
let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00);
(major, minor)
}
pub fn flags(&self) -> StatFlags {
StatFlags::from_bits(self.flags).unwrap_or_else(|| {
panic!(format!(
"Can't construct flags bitfield from {:?}",
self.flags
))
})
}
pub fn starttime(&self) -> DateTime<Local> {
let seconds_since_boot = self.starttime as f32 / *TICKS_PER_SECOND as f32;
*BOOTTIME + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)
}
pub fn rss_bytes(&self) -> i64 {
self.rss * *PAGESIZE
}
}
#[derive(Debug, Clone)]
pub struct Status {
pub name: String,
pub umask: Option<u32>,
pub state: String,
pub tgid: i32,
pub ngid: Option<i32>,
pub pid: i32,
pub ppid: i32,
pub tracerpid: i32,
pub ruid: i32,
pub euid: i32,
pub suid: i32,
pub fuid: i32,
pub rgid: i32,
pub egid: i32,
pub sgid: i32,
pub fgid: i32,
pub fdsize: u32,
pub groups: Vec<i32>,
pub nstgid: Option<Vec<i32>>,
pub nspid: Option<Vec<i32>>,
pub nspgid: Option<Vec<i32>>,
pub nssid: Option<Vec<i32>>,
pub vmpeak: u64,
pub vmsize: u64,
pub vmlck: u64,
pub vmpin: Option<u64>,
pub vmhwm: u64,
pub vmrss: u64,
pub rssanon: Option<u64>,
pub rssfile: Option<u64>,
pub rssshmem: Option<u64>,
pub vmdata: u64,
pub vmstk: u64,
pub vmexe: u64,
pub vmlib: u64,
pub vmpte: Option<u64>,
pub vmswap: Option<u64>,
pub hugetblpages: Option<u64>,
pub threads: u64,
pub sigq: (u64, u64),
pub sigpnd: u64,
pub shdpnd: u64,
pub sigblk: u64,
pub sigign: u64,
pub sigcgt: u64,
pub capinh: u64,
pub capprm: u64,
pub capeff: u64,
pub capbnd: Option<u64>,
pub capamb: Option<u64>,
pub nonewprivs: Option<u64>,
pub seccomp: Option<u32>,
pub cpus_allowed: Option<Vec<u32>>,
pub cpus_allowed_list: Option<Vec<(u32, u32)>>,
pub mems_allowed: Option<Vec<u32>>,
pub mems_allowed_list: Option<Vec<(u32, u32)>>,
pub voluntary_ctxt_switches: Option<u64>,
pub nonvoluntary_ctxt_switches: Option<u64>,
}
impl Status {
pub fn from_reader<R: io::Read>(r: R) -> Option<Status> {
use std::collections::HashMap;
use std::io::{BufRead, BufReader};
let mut map = HashMap::new();
let reader = BufReader::new(r);
for line in reader.lines() {
let line = expect!(line, "Failed to read line");
if line.is_empty() {
continue;
}
let mut s = line.split(':');
let field = expect!(s.next(), "no field");
let value = expect!(s.next(), "no value").trim();
map.insert(field.to_string(), value.to_string());
}
let status = Status {
name: expect!(map.remove("Name")),
umask: since_kernel!(4, 7, 0, from_str!(u32, &expect!(map.remove("Umask")), 8)),
state: expect!(map.remove("State")),
tgid: from_str!(i32, &expect!(map.remove("Tgid"))),
ngid: since_kernel!(3, 13, 0, from_str!(i32, &expect!(map.remove("Ngid")))),
pid: from_str!(i32, &expect!(map.remove("Pid"))),
ppid: from_str!(i32, &expect!(map.remove("PPid"))),
tracerpid: from_str!(i32, &expect!(map.remove("TracerPid"))),
ruid: Status::parse_uid_gid(&expect!(map.get("Uid")), 0),
euid: Status::parse_uid_gid(&expect!(map.get("Uid")), 1),
suid: Status::parse_uid_gid(&expect!(map.get("Uid")), 2),
fuid: Status::parse_uid_gid(&expect!(map.remove("Uid")), 3),
rgid: Status::parse_uid_gid(&expect!(map.get("Gid")), 0),
egid: Status::parse_uid_gid(&expect!(map.get("Gid")), 1),
sgid: Status::parse_uid_gid(&expect!(map.get("Gid")), 2),
fgid: Status::parse_uid_gid(&expect!(map.remove("Gid")), 3),
fdsize: from_str!(u32, &expect!(map.remove("FDSize"))),
groups: Status::parse_list(&expect!(map.remove("Groups"))),
nstgid: since_kernel!(4, 1, 0, Status::parse_list(&expect!(map.remove("NStgid")))),
nspid: since_kernel!(4, 1, 0, Status::parse_list(&expect!(map.remove("NSpid")))),
nspgid: since_kernel!(4, 1, 0, Status::parse_list(&expect!(map.remove("NSpgid")))),
nssid: since_kernel!(4, 1, 0, Status::parse_list(&expect!(map.remove("NSsid")))),
vmpeak: Status::parse_with_kb(&expect!(map.remove("VmPeak"))),
vmsize: Status::parse_with_kb(&expect!(map.remove("VmSize"))),
vmlck: Status::parse_with_kb(&expect!(map.remove("VmLck"))),
vmpin: since_kernel!(
3,
2,
0,
Status::parse_with_kb(&expect!(map.remove("VmPin")))
),
vmhwm: Status::parse_with_kb(&expect!(map.remove("VmHWM"))),
vmrss: Status::parse_with_kb(&expect!(map.remove("VmRSS"))),
rssanon: since_kernel!(
4,
5,
0,
Status::parse_with_kb(&expect!(map.remove("RssAnon")))
),
rssfile: since_kernel!(
4,
5,
0,
Status::parse_with_kb(&expect!(map.remove("RssFile")))
),
rssshmem: since_kernel!(
4,
5,
0,
Status::parse_with_kb(&expect!(map.remove("RssShmem")))
),
vmdata: Status::parse_with_kb(&expect!(map.remove("VmData"))),
vmstk: Status::parse_with_kb(&expect!(map.remove("VmStk"))),
vmexe: Status::parse_with_kb(&expect!(map.remove("VmExe"))),
vmlib: Status::parse_with_kb(&expect!(map.remove("VmLib"))),
vmpte: since_kernel!(
2,
6,
10,
Status::parse_with_kb(&expect!(map.remove("VmPTE")))
),
vmswap: since_kernel!(
2,
6,
34,
Status::parse_with_kb(&expect!(map.remove("VmSwap")))
),
hugetblpages: since_kernel!(
4,
4,
0,
Status::parse_with_kb(&expect!(map.remove("HugetlbPages")))
),
threads: from_str!(u64, &expect!(map.remove("Threads"))),
sigq: Status::parse_sigq(&expect!(map.remove("SigQ"))),
sigpnd: from_str!(u64, &expect!(map.remove("SigPnd")), 16),
shdpnd: from_str!(u64, &expect!(map.remove("ShdPnd")), 16),
sigblk: from_str!(u64, &expect!(map.remove("SigBlk")), 16),
sigign: from_str!(u64, &expect!(map.remove("SigIgn")), 16),
sigcgt: from_str!(u64, &expect!(map.remove("SigCgt")), 16),
capinh: from_str!(u64, &expect!(map.remove("CapInh")), 16),
capprm: from_str!(u64, &expect!(map.remove("CapPrm")), 16),
capeff: from_str!(u64, &expect!(map.remove("CapEff")), 16),
capbnd: since_kernel!(2, 6, 26, from_str!(u64, &expect!(map.remove("CapBnd")), 16)),
capamb: since_kernel!(4, 3, 0, from_str!(u64, &expect!(map.remove("CapAmb")), 16)),
nonewprivs: since_kernel!(4, 10, 0, from_str!(u64, &expect!(map.remove("NoNewPrivs")))),
seccomp: since_kernel!(3, 8, 0, from_str!(u32, &expect!(map.remove("Seccomp")))),
cpus_allowed: since_kernel!(
2,
6,
24,
Status::parse_allowed(&expect!(map.remove("Cpus_allowed")))
),
cpus_allowed_list: since_kernel!(
2,
6,
26,
Status::parse_allowed_list(&expect!(map.remove("Cpus_allowed_list")))
),
mems_allowed: since_kernel!(
2,
6,
24,
Status::parse_allowed(&expect!(map.remove("Mems_allowed")))
),
mems_allowed_list: since_kernel!(
2,
6,
26,
Status::parse_allowed_list(&expect!(map.remove("Mems_allowed_list")))
),
voluntary_ctxt_switches: since_kernel!(
2,
6,
23,
from_str!(u64, &expect!(map.remove("voluntary_ctxt_switches")))
),
nonvoluntary_ctxt_switches: since_kernel!(
2,
6,
23,
from_str!(u64, &expect!(map.remove("nonvoluntary_ctxt_switches")))
),
};
if cfg!(test) {
if !map.is_empty() {
eprintln!("Warning: status map is not empty: {:#?}", map);
}
}
Some(status)
}
fn parse_with_kb<T: FromStrRadix>(s: &str) -> T {
from_str!(T, &s.replace(" kB", ""))
}
fn parse_uid_gid(s: &str, i: usize) -> i32 {
from_str!(i32, expect!(s.split_whitespace().nth(i)))
}
fn parse_sigq(s: &str) -> (u64, u64) {
let mut iter = s.split('/');
let first = from_str!(u64, expect!(iter.next()));
let second = from_str!(u64, expect!(iter.next()));
(first, second)
}
fn parse_list<T: FromStrRadix>(s: &str) -> Vec<T> {
let mut ret = Vec::new();
for i in s.split_whitespace() {
ret.push(from_str!(T, i));
}
ret
}
fn parse_allowed(s: &str) -> Vec<u32> {
let mut ret = Vec::new();
for i in s.split(',') {
ret.push(from_str!(u32, i, 16));
}
ret
}
fn parse_allowed_list(s: &str) -> Vec<(u32, u32)> {
let mut ret = Vec::new();
for s in s.split(',') {
if s.contains('-') {
let mut s = s.split('-');
let beg = from_str!(u32, expect!(s.next()));
let end = from_str!(u32, expect!(s.next()));
ret.push((beg, end));
} else {
let beg = from_str!(u32, s);
let end = from_str!(u32, s);
ret.push((beg, end));
}
}
ret
}
}
#[derive(Debug, Clone)]
pub struct Process {
pub stat: Stat,
pub owner: u32,
pub(crate) root: PathBuf,
}
impl Process {
pub fn new(pid: pid_t) -> ProcResult<Process> {
let root = PathBuf::from("/proc").join(format!("{}", pid));
let stat = Stat::from_reader(File::open(root.join("stat"))?).unwrap();
let md = std::fs::metadata(&root)?;
Ok(Process {
root,
stat,
owner: md.st_uid(),
})
}
pub fn myself() -> ProcResult<Process> {
let root = PathBuf::from("/proc/self");
let stat = Stat::from_reader(File::open(root.join("stat"))?).unwrap();
let md = std::fs::metadata(&root)?;
Ok(Process {
root,
stat,
owner: md.st_uid(),
})
}
pub fn cmdline(&self) -> ProcResult<Vec<String>> {
let mut buf = String::new();
let mut f = File::open(self.root.join("cmdline"))?;
f.read_to_string(&mut buf)?;
Ok(buf
.split('\0')
.filter_map(|s| {
if !s.is_empty() {
Some(s.to_string())
} else {
None
}
})
.collect())
}
pub fn pid(&self) -> pid_t {
self.stat.pid
}
pub fn is_alive(&self) -> bool {
match Process::new(self.pid()) {
Ok(prc) => {
prc.stat.comm == self.stat.comm
&& prc.owner == self.owner
&& prc.stat.starttime == self.stat.starttime
&& prc.stat.state() != ProcState::Zombie
&& self.stat.state() != ProcState::Zombie
}
_ => false,
}
}
pub fn cwd(&self) -> ProcResult<PathBuf> {
Ok(std::fs::read_link(self.root.join("cwd"))?)
}
pub fn environ(&self) -> ProcResult<HashMap<OsString, OsString>> {
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
let mut map = HashMap::new();
let mut file = File::open(self.root.join("environ"))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
for slice in buf.split(|b| *b == 0) {
let mut split = slice.splitn(2, |b| *b == b'=');
if let (Some(k), Some(v)) = (split.next(), split.next()) {
map.insert(
OsStr::from_bytes(k).to_os_string(),
OsStr::from_bytes(v).to_os_string(),
);
};
}
Ok(map)
}
pub fn exe(&self) -> ProcResult<PathBuf> {
Ok(std::fs::read_link(self.root.join("exe"))?)
}
pub fn io(&self) -> ProcResult<Io> {
let file = File::open(self.root.join("io"))?;
Ok(Io::from_reader(file).unwrap())
}
pub fn maps(&self) -> ProcResult<Vec<MemoryMap>> {
use std::io::{BufRead, BufReader};
let file = File::open(self.root.join("maps"))?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.map(|line| {
let line = line.unwrap();
let mut s = line.splitn(6, ' ');
let address = expect!(s.next(), "maps::address");
let perms = expect!(s.next(), "maps::perms");
let offset = expect!(s.next(), "maps::offset");
let dev = expect!(s.next(), "maps::dev");
let inode = expect!(s.next(), "maps::inode");
let path = expect!(s.next(), "maps::path");
MemoryMap {
address: split_into_num(address, '-', 16),
perms: perms.to_string(),
offset: from_str!(u64, offset, 16),
dev: split_into_num(dev, ':', 16),
inode: from_str!(u32, inode),
pathname: MMapPath::from(path),
}
})
.collect())
}
pub fn fd(&self) -> ProcResult<Vec<FDInfo>> {
use std::ffi::OsStr;
use std::fs::read_link;
let mut vec = Vec::new();
for dir in self.root.join("fd").read_dir()? {
let entry = dir?;
let fd = u32::from_str_radix(entry.file_name().to_str().unwrap(), 10).unwrap();
if let Ok(link) = read_link(entry.path()) {
let link_os: &OsStr = link.as_ref();
vec.push(FDInfo {
fd,
target: FDTarget::from_str(link_os.to_str().unwrap()).unwrap(),
});
}
}
Ok(vec)
}
pub fn coredump_filter(&self) -> ProcResult<Option<CoredumpFlags>> {
use std::fs::File;
let mut file = File::open(self.root.join("coredump_filter"))?;
let mut s = String::new();
file.read_to_string(&mut s)?;
if s.trim().is_empty() {
return Ok(None);
}
let flags = from_str!(u32, &s.trim(), 16, pid:self.stat.pid);
Ok(Some(expect!(CoredumpFlags::from_bits(flags))))
}
pub fn autogroup(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = File::open(self.root.join("autogroup"))?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn auxv(&self) -> ProcResult<HashMap<u32, u32>> {
use byteorder::{NativeEndian, ReadBytesExt};
let mut file = File::open(self.root.join("auxv"))?;
let mut map = HashMap::new();
loop {
let key = file.read_u32::<NativeEndian>()?;
let value = file.read_u32::<NativeEndian>()?;
if key == 0 && value == 0 {
break;
}
map.insert(key, value);
}
Ok(map)
}
pub fn mountstats(&self) -> ProcResult<Vec<MountStat>> {
let file = File::open(self.root.join("mountstats"))?;
Ok(MountStat::from_reader(file))
}
pub fn wchan(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = File::open(self.root.join("wchan"))?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn status(&self) -> ProcResult<Status> {
let file = File::open(self.root.join("status"))?;
Ok(Status::from_reader(file).unwrap())
}
}
pub fn all_processes() -> Vec<Process> {
let mut v = Vec::new();
for dir in expect!(std::fs::read_dir("/proc/"), "No /proc/ directory") {
if let Ok(entry) = dir {
if let Ok(pid) = i32::from_str(&entry.file_name().to_string_lossy()) {
if let Ok(prc) = Process::new(pid) {
v.push(prc);
}
}
}
}
v
}
#[cfg(test)]
mod tests {
use super::*;
fn check_unwrap<T>(val: ProcResult<T>) {
match val {
Ok(t) => {}
Err(ProcError::PermissionDenied) if unsafe { libc::geteuid() } != 0 => {
}
Err(ProcError::NotFound) => {}
Err(err) => panic!("{:?}", err),
}
}
#[test]
fn test_self_proc() {
let myself = Process::myself().unwrap();
println!("{:#?}", myself);
println!("state: {:?}", myself.stat.state());
println!("tty: {:?}", myself.stat.tty_nr());
println!("flags: {:?}", myself.stat.flags());
println!("starttime: {:#?}", myself.stat.starttime());
let kernel = KernelVersion::current().unwrap();
if kernel >= KernelVersion::new(2, 1, 22) {
assert!(myself.stat.exit_signal.is_some());
} else {
assert!(myself.stat.exit_signal.is_none());
}
if kernel >= KernelVersion::new(2, 2, 8) {
assert!(myself.stat.processor.is_some());
} else {
assert!(myself.stat.processor.is_none());
}
if kernel >= KernelVersion::new(2, 5, 19) {
assert!(myself.stat.rt_priority.is_some());
} else {
assert!(myself.stat.rt_priority.is_none());
}
if kernel >= KernelVersion::new(2, 5, 19) {
assert!(myself.stat.rt_priority.is_some());
assert!(myself.stat.policy.is_some());
} else {
assert!(myself.stat.rt_priority.is_none());
assert!(myself.stat.policy.is_none());
}
if kernel >= KernelVersion::new(2, 6, 18) {
assert!(myself.stat.delayacct_blkio_ticks.is_some());
} else {
assert!(myself.stat.delayacct_blkio_ticks.is_none());
}
if kernel >= KernelVersion::new(2, 6, 24) {
assert!(myself.stat.guest_time.is_some());
assert!(myself.stat.cguest_time.is_some());
} else {
assert!(myself.stat.guest_time.is_none());
assert!(myself.stat.cguest_time.is_none());
}
if kernel >= KernelVersion::new(3, 3, 0) {
assert!(myself.stat.start_data.is_some());
assert!(myself.stat.end_data.is_some());
assert!(myself.stat.start_brk.is_some());
} else {
assert!(myself.stat.start_data.is_none());
assert!(myself.stat.end_data.is_none());
assert!(myself.stat.start_brk.is_none());
}
if kernel >= KernelVersion::new(3, 5, 0) {
assert!(myself.stat.arg_start.is_some());
assert!(myself.stat.arg_end.is_some());
assert!(myself.stat.env_start.is_some());
assert!(myself.stat.env_end.is_some());
assert!(myself.stat.exit_code.is_some());
} else {
assert!(myself.stat.arg_start.is_none());
assert!(myself.stat.arg_end.is_none());
assert!(myself.stat.env_start.is_none());
assert!(myself.stat.env_end.is_none());
assert!(myself.stat.exit_code.is_none());
}
}
#[test]
fn test_all() {
for prc in all_processes() {
prc.stat.flags();
prc.stat.starttime();
check_unwrap(prc.cmdline());
check_unwrap(prc.environ());
check_unwrap(prc.fd());
check_unwrap(prc.io());
check_unwrap(prc.maps());
check_unwrap(prc.coredump_filter());
check_unwrap(prc.autogroup());
check_unwrap(prc.auxv());
check_unwrap(prc.cgroups());
}
}
#[test]
fn test_proc_alive() {
let myself = Process::myself().unwrap();
assert!(myself.is_alive());
}
#[test]
fn test_proc_environ() {
let myself = Process::myself().unwrap();
let proc_environ = myself.environ().unwrap();
let std_environ: HashMap<_, _> = std::env::vars_os().collect();
assert_eq!(proc_environ, std_environ);
}
#[test]
fn test_error_handling() {
let init = Process::new(1).unwrap();
assert!(!init.cwd().is_ok());
assert!(!init.environ().is_ok());
}
#[test]
fn test_proc_exe() {
let myself = Process::myself().unwrap();
let proc_exe = myself.exe().unwrap();
let std_exe = std::env::current_exe().unwrap();
assert_eq!(proc_exe, std_exe);
}
#[test]
fn test_proc_io() {
let myself = Process::myself().unwrap();
let kernel = KernelVersion::current().unwrap();
let io = myself.io();
println!("{:?}", io);
if let Ok(_) = io {
assert!(kernel >= KernelVersion::new(2, 6, 20));
}
}
#[test]
fn test_proc_maps() {
let myself = Process::myself().unwrap();
let maps = myself.maps().unwrap();
for map in maps {
println!("{:?}", map);
}
}
#[test]
fn test_mmap_path() {
assert_eq!(MMapPath::from("[stack]"), MMapPath::Stack);
assert_eq!(MMapPath::from("[foo]"), MMapPath::Other("foo".to_owned()));
assert_eq!(MMapPath::from(""), MMapPath::Anonymous);
assert_eq!(MMapPath::from("[stack:154]"), MMapPath::TStack(154));
assert_eq!(
MMapPath::from("/lib/libfoo.so"),
MMapPath::Path(PathBuf::from("/lib/libfoo.so"))
);
}
#[test]
fn test_proc_fd() {
let myself = Process::myself().unwrap();
for fd in myself.fd().unwrap() {
println!("{:?}", fd);
}
}
#[test]
fn test_proc_coredump() {
let myself = Process::myself().unwrap();
let flags = myself.coredump_filter();
println!("{:?}", flags);
}
#[test]
fn test_proc_auxv() {
let myself = Process::myself().unwrap();
let auxv = myself.auxv().unwrap();
println!("{:?}", auxv);
}
#[test]
fn test_proc_mountstats() {
let simple = MountStat::from_reader(
"device /dev/md127 mounted on /boot with fstype ext2
device /dev/md124 mounted on /home with fstype ext4
device tmpfs mounted on /run/user/0 with fstype tmpfs
"
.as_bytes(),
);
let simple_parsed = vec![
MountStat {
device: Some("/dev/md127".to_string()),
mount_point: PathBuf::from("/boot"),
fs: "ext2".to_string(),
statistics: None,
},
MountStat {
device: Some("/dev/md124".to_string()),
mount_point: PathBuf::from("/home"),
fs: "ext4".to_string(),
statistics: None,
},
MountStat {
device: Some("tmpfs".to_string()),
mount_point: PathBuf::from("/run/user/0"),
fs: "tmpfs".to_string(),
statistics: None,
},
];
assert_eq!(simple, simple_parsed);
let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1
opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none
age: 3542
impl_id: name='',domain='',date='0,0'
caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255
nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured
sec: flavor=6,pseudoflavor=390003
events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1
bytes: 1 2 3 4 5 6 7 8
RPC iostats version: 1.0 p/v: 100003/4 (nfs)
xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0
per-op statistics
NULL: 0 0 0 0 0 0 0 0
READ: 1 2 3 4 5 6 7 8
WRITE: 0 0 0 0 0 0 0 0
COMMIT: 0 0 0 0 0 0 0 0
OPEN: 1 1 0 320 420 0 124 124
".as_bytes());
let nfs_v4 = &mountstats[0];
match &nfs_v4.statistics {
Some(stats) => {
assert_eq!(
"1.1".to_string(),
stats.version,
"mountstats version wrongly parsed."
);
assert_eq!(Duration::from_secs(3542), stats.age);
assert_eq!(1, stats.bytes.normal_read);
assert_eq!(114, stats.events.inode_revalidate);
assert!(stats.server_caps().is_some());
}
None => {
assert!(false, "Failed to retrieve nfs statistics");
}
}
}
#[test]
fn test_proc_mountstats_live() {
let stats = MountStat::from_reader(File::open("/proc/self/mountstats").unwrap());
for stat in stats {
println!("{:#?}", stat);
if let Some(nfs) = stat.statistics {
println!(" {:?}", nfs.server_caps().unwrap());
}
}
}
#[test]
fn test_proc_wchan() {
let myself = Process::myself().unwrap();
let wchan = myself.wchan().unwrap();
println!("{:?}", wchan);
}
#[test]
fn test_proc_status() {
let myself = Process::myself().unwrap();
let status = myself.status().unwrap();
println!("{:?}", status);
assert_eq!(status.name, myself.stat.comm);
assert_eq!(status.pid, myself.stat.pid);
assert_eq!(status.ppid, myself.stat.ppid);
}
}