use std::collections::HashSet; use std::convert::TryFrom; use std::fs::OpenOptions; use std::io::{BufRead, BufReader}; use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use std::sync::RwLock; use std::time::Instant; use failure::*; use lazy_static::lazy_static; use libc; use nix::unistd::Pid; use proxmox_tools::fs::file_read_firstline; use proxmox_tools::parse::hex_nibble; pub mod mountinfo; #[doc(inline)] pub use mountinfo::MountInfo; /// POSIX sysconf call pub fn sysconf(name: i32) -> i64 { extern "C" { fn sysconf(name: i32) -> i64; } unsafe { sysconf(name) } } lazy_static! { static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64; } /// Selected contents of the `/proc/PID/stat` file. pub struct PidStat { pub pid: Pid, pub ppid: Pid, pub status: u8, pub utime: u64, pub stime: u64, pub num_threads: u64, pub starttime: u64, pub vsize: u64, pub rss: i64, } impl PidStat { /// Retrieve the `stat` file contents of a process. pub fn read_for_pid(pid: Pid) -> Result { let stat = Self::parse(std::str::from_utf8(&std::fs::read(format!( "/proc/{}/stat", pid ))?)?)?; if stat.pid != pid { bail!( "unexpected pid for process: found pid {} in /proc/{}/stat", stat.pid.as_raw(), pid ); } Ok(stat) } /// Parse the contents of a `/proc/PID/stat` file. pub fn parse(statstr: &str) -> Result { // It starts with the pid followed by a '('. let cmdbeg = statstr .find('(') .ok_or_else(|| format_err!("missing '(' in /proc/PID/stat"))?; if !statstr[..=cmdbeg].ends_with(" (") { bail!("bad /proc/PID/stat line before the '('"); } let pid: u32 = statstr[..(cmdbeg - 1)] .parse() .map_err(|e| format_err!("bad pid in /proc/PID/stat: {}", e))?; let pid = Pid::from_raw(pid as i32); // After the '(' we have an arbitrary command name, then ')' and the remaining values let cmdend = statstr .rfind(')') .ok_or_else(|| format_err!("missing ')' in /proc/PID/stat"))?; let mut parts = statstr[cmdend + 1..].trim_start().split_ascii_whitespace(); // helpers: fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> { value.ok_or_else(|| format_err!("missing '{}' in /proc/PID/stat", what)) } fn req_num(value: Option<&str>, what: &'static str) -> Result where T: FromStr, ::Err: Into, { required(value, what)?.parse::().map_err(|e| e.into()) } fn req_byte(value: Option<&str>, what: &'static str) -> Result { let value = required(value, what)?; if value.len() != 1 { bail!("invalid '{}' in /proc/PID/stat", what); } Ok(value.as_bytes()[0]) } let out = PidStat { pid, status: req_byte(parts.next(), "status")?, ppid: Pid::from_raw(req_num::(parts.next(), "ppid")? as i32), utime: req_num::(parts.nth(9), "utime")?, stime: req_num::(parts.next(), "stime")?, num_threads: req_num::(parts.nth(4), "num_threads")?, starttime: req_num::(parts.nth(1), "start_time")?, vsize: req_num::(parts.next(), "vsize")?, rss: req_num::(parts.next(), "rss")? * 4096, }; let _ = req_num::(parts.next(), "it_real_value")?; // and more... Ok(out) } } impl TryFrom for PidStat { type Error = Error; fn try_from(pid: Pid) -> Result { Self::read_for_pid(pid) } } /// Read `/proc/PID/stat` for a pid. #[deprecated(note = "use `PidStat::read_for_pid`")] pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result { PidStat::read_for_pid(Pid::from_raw(pid)) } #[test] fn test_read_proc_pid_stat() { let stat = PidStat::parse( "28900 (zsh) S 22489 28900 28900 34826 10252 4194304 6851 5946551 0 2344 6 3 25205 1413 \ 20 0 1 0 287592 12496896 1910 18446744073709551615 93999319244800 93999319938061 \ 140722897984224 0 0 0 2 3686404 134295555 1 0 0 17 10 0 0 0 0 0 93999320079088 \ 93999320108360 93999343271936 140722897992565 140722897992570 140722897992570 \ 140722897993707 0", ) .expect("successful parsing of a sample /proc/PID/stat entry"); assert_eq!(stat.pid, Pid::from_raw(28900)); assert_eq!(stat.ppid, Pid::from_raw(22489)); assert_eq!(stat.status, b'S'); assert_eq!(stat.utime, 6); assert_eq!(stat.stime, 3); assert_eq!(stat.num_threads, 1); assert_eq!(stat.starttime, 287592); assert_eq!(stat.vsize, 12496896); assert_eq!(stat.rss, 1910 * 4096); } #[deprecated(note = "use `PidStat`")] pub fn read_proc_starttime(pid: libc::pid_t) -> Result { PidStat::read_for_pid(Pid::from_raw(pid)).map(|stat| stat.starttime) } pub fn check_process_running(pid: libc::pid_t) -> Option { PidStat::read_for_pid(Pid::from_raw(pid)) .ok() .filter(|stat| stat.status != b'Z') } pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option { if let Some(info) = check_process_running(pid) { if info.starttime == pstart { return Some(info); } } None } pub fn read_proc_uptime() -> Result<(f64, f64), Error> { let path = "/proc/uptime"; let line = file_read_firstline(&path)?; let mut values = line.split_whitespace().map(|v| v.parse::()); match (values.next(), values.next()) { (Some(Ok(up)), Some(Ok(idle))) => Ok((up, idle)), _ => bail!("Error while parsing '{}'", path), } } pub fn read_proc_uptime_ticks() -> Result<(u64, u64), Error> { let (mut up, mut idle) = read_proc_uptime()?; up *= *CLOCK_TICKS; idle *= *CLOCK_TICKS; Ok((up as u64, idle as u64)) } #[derive(Debug, Default)] /// The CPU fields from `/proc/stat` with their native time value. Multiply /// with CLOCK_TICKS to get the real value. pub struct ProcFsStat { /// Time spent in user mode. pub user: u64, /// Time spent in user mode with low priority (nice). pub nice: u64, /// Time spent in system mode. pub system: u64, /// Time spent in the idle task. pub idle: u64, /// Time waiting for I/O to complete. This value is not reiable, see `man 5 proc` pub iowait: u64, /// Time servicing interrupts. pub irq: u64, /// Time servicing softirqs. pub softirq: u64, /// Stolen time, which is the time spent in other operating systems when running /// in a virtualized environment. pub steal: u64, /// Time spent running a virtual CPU for guest operating systems under the control of the /// Linux kernel. pub guest: u64, /// Time spent running a niced guest (virtual CPU for guest operating systems under the control /// of the Linux kernel). pub guest_nice: u64, /// The sum of all other u64 fields pub total: u64, /// The percentage (0 - 1.0) of cpu utilization from the whole system, basica underlying calculation /// `1 - (idle / total)` but with delta values between now and the last call to `read_proc_stat` (min. 1s interval) pub cpu: f64, } lazy_static! { static ref PROC_LAST_STAT: RwLock<(ProcFsStat, Instant, bool)> = RwLock::new((ProcFsStat::default(), Instant::now(), true)); } /// reads `/proc/stat`. For now only total host CPU usage is handled as the /// other metrics are not really interesting pub fn read_proc_stat() -> Result { let sample_time = Instant::now(); let mut stat = parse_proc_stat(unsafe { std::str::from_utf8_unchecked(&std::fs::read("/proc/stat")?) }) .unwrap(); { // read lock scope let prev_read_guarded = PROC_LAST_STAT.read().unwrap(); let (prev_stat, prev_time, first_time) = &*prev_read_guarded; let last_update = sample_time .saturating_duration_since(*prev_time) .as_millis(); // only update if data is old if last_update < 1000 && !first_time { stat.cpu = prev_stat.cpu; return Ok(stat); } } { // write lock scope let mut prev_write_guarded = PROC_LAST_STAT.write().unwrap(); // we do not expect much lock contention, so sample_time should be // recent. Else, we'd need to reread & parse here to get current data let (prev_stat, prev_time, first_time) = &mut *prev_write_guarded; let delta_total = stat.total - prev_stat.total; let delta_idle = stat.idle - prev_stat.idle; stat.cpu = 1. - (delta_idle as f64) / (delta_total as f64); *prev_stat = ProcFsStat { ..stat }; *prev_time = sample_time; *first_time = false; } Ok(stat) } fn parse_proc_stat(statstr: &str) -> Result { // for now we just use the first accumulated "cpu" line let mut parts = statstr.trim_start().split_ascii_whitespace(); parts.next(); // swallow initial cpu string // helpers: fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> { value.ok_or_else(|| format_err!("missing '{}' in /proc/stat", what)) } fn req_num(value: Option<&str>, what: &'static str) -> Result where T: FromStr, ::Err: std::fmt::Display, { required(value, what)? .parse::() .map_err(|e| format_err!("error parsing {}: {}", what, e)) } let mut stat = ProcFsStat { user: req_num::(parts.next(), "user")?, nice: req_num::(parts.next(), "nice")?, system: req_num::(parts.next(), "system")?, idle: req_num::(parts.next(), "idle")?, iowait: req_num::(parts.next(), "iowait")?, irq: req_num::(parts.next(), "irq")?, softirq: req_num::(parts.next(), "softirq")?, steal: req_num::(parts.next(), "steal")?, guest: req_num::(parts.next(), "guest")?, guest_nice: req_num::(parts.next(), "guest_nice")?, total: 0, cpu: 0.0, }; stat.total = stat.user + stat.nice + stat.system + stat.iowait + stat.irq + stat.softirq + stat.steal + stat.guest + stat.guest_nice + stat.idle; // returns avg. heuristic for the first request stat.cpu = 1. - (stat.idle as f64) / (stat.total as f64); Ok(stat) } #[test] fn test_read_proc_stat() { let stat = parse_proc_stat( "cpu 2845612 241 173179 264715515 93366 0 7925 141017 0 0\n\ cpu0 174375 9 11367 16548335 5741 0 2394 8500 0 0\n\ cpu1 183367 11 11423 16540656 4644 0 1235 8888 0 0\n\ cpu2 179636 21 20463 16534540 4802 0 456 9270 0 0\n\ cpu3 184560 9 11379 16532136 7113 0 225 8967 0 0\n\ cpu4 182341 17 10277 16542865 3274 0 181 8461 0 0\n\ cpu5 179771 22 9910 16548859 2259 0 112 8328 0 0\n\ cpu6 181185 14 8933 16548550 2057 0 78 8313 0 0\n\ cpu7 176326 12 8514 16553428 2246 0 76 8812 0 0\n\ cpu8 177341 13 7942 16553880 1576 0 56 8565 0 0\n\ cpu9 176883 10 8648 16547886 3067 0 103 8788 0 0\n\ cpu10 169446 4 7993 16561700 1584 0 39 8797 0 0\n\ cpu11 170878 7 7783 16560870 1526 0 23 8444 0 0\n\ cpu12 164062 12 7839 16567155 1686 0 43 8794 0 0\n\ cpu13 164303 4 7661 16567497 1528 0 41 8525 0 0\n\ cpu14 173709 2 11478 16546352 3965 0 571 9414 0 0\n\ cpu15 207422 67 21561 16460798 46292 0 2283 10142 0 0\n\ intr 29200426 1 9 0 0 0 0 3 0 1 0 275744 40 16 0 0 166292 0 0 0 0 0 0 0 0 0 0 \ 0 1463843 0 1048751 328317 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n\ ctxt 27543372\n\ btime 1576502436\n\ processes 701089\n\ procs_running 2\n\ procs_blocked 0\n\ softirq 36227960 0 16653965 39 1305264 1500573 0 38330 5024204 356 11705229", ) .expect("successful parsed a sample /proc/stat entry"); assert_eq!(stat.user, 2845612); assert_eq!(stat.nice, 241); assert_eq!(stat.system, 173179); assert_eq!(stat.idle, 264715515); assert_eq!(stat.iowait, 93366); assert_eq!(stat.irq, 0); assert_eq!(stat.softirq, 7925); assert_eq!(stat.steal, 141017); assert_eq!(stat.guest, 0); assert_eq!(stat.guest_nice, 0); assert_eq!(stat.total, 267976855); assert_eq!(stat.cpu, 0.012170230149167183); } #[derive(Debug)] pub struct ProcFsMemInfo { pub memtotal: u64, pub memfree: u64, pub memused: u64, pub memshared: u64, pub swaptotal: u64, pub swapfree: u64, pub swapused: u64, } pub fn read_meminfo() -> Result { let path = "/proc/meminfo"; let file = OpenOptions::new().read(true).open(&path)?; let mut meminfo = ProcFsMemInfo { memtotal: 0, memfree: 0, memused: 0, memshared: 0, swaptotal: 0, swapfree: 0, swapused: 0, }; let (mut buffers, mut cached) = (0, 0); for line in BufReader::new(&file).lines() { let content = line?; let mut content_iter = content.split_whitespace(); if let (Some(key), Some(value)) = (content_iter.next(), content_iter.next()) { match key { "MemTotal:" => meminfo.memtotal = value.parse::()? * 1024, "MemFree:" => meminfo.memfree = value.parse::()? * 1024, "SwapTotal:" => meminfo.swaptotal = value.parse::()? * 1024, "SwapFree:" => meminfo.swapfree = value.parse::()? * 1024, "Buffers:" => buffers = value.parse::()? * 1024, "Cached:" => cached = value.parse::()? * 1024, _ => continue, } } } meminfo.memfree += buffers + cached; meminfo.memused = meminfo.memtotal - meminfo.memfree; meminfo.swapused = meminfo.swaptotal - meminfo.swapfree; let spages_line = file_read_firstline("/sys/kernel/mm/ksm/pages_sharing")?; meminfo.memshared = spages_line.trim_end().parse::()? * 4096; Ok(meminfo) } #[derive(Clone, Debug)] pub struct ProcFsCPUInfo { pub user_hz: f64, pub mhz: f64, pub model: String, pub hvm: bool, pub sockets: usize, pub cpus: usize, } static CPU_INFO: Option = None; pub fn read_cpuinfo() -> Result { if let Some(cpu_info) = &CPU_INFO { return Ok(cpu_info.clone()); } let path = "/proc/cpuinfo"; let file = OpenOptions::new().read(true).open(&path)?; let mut cpuinfo = ProcFsCPUInfo { user_hz: *CLOCK_TICKS, mhz: 0.0, model: String::new(), hvm: false, sockets: 0, cpus: 0, }; let mut socket_ids = HashSet::new(); for line in BufReader::new(&file).lines() { let content = line?; if content.is_empty() { continue; } let mut content_iter = content.split(':'); match (content_iter.next(), content_iter.next()) { (Some(key), Some(value)) => match key.trim_end() { "processor" => cpuinfo.cpus += 1, "model name" => cpuinfo.model = value.trim().to_string(), "cpu MHz" => cpuinfo.mhz = value.trim().parse::()?, "flags" => cpuinfo.hvm = value.contains(" vmx ") || value.contains(" svm "), "physical id" => { let id = value.trim().parse::()?; socket_ids.insert(id); } _ => continue, }, _ => bail!("Error while parsing '{}'", path), } } cpuinfo.sockets = socket_ids.len(); Ok(cpuinfo) } #[derive(Debug)] pub struct ProcFsMemUsage { pub size: u64, pub resident: u64, pub shared: u64, } pub fn read_memory_usage() -> Result { let path = format!("/proc/{}/statm", std::process::id()); let line = file_read_firstline(&path)?; let mut values = line.split_whitespace().map(|v| v.parse::()); let ps = 4096; match (values.next(), values.next(), values.next()) { (Some(Ok(size)), Some(Ok(resident)), Some(Ok(shared))) => Ok(ProcFsMemUsage { size: size * ps, resident: resident * ps, shared: shared * ps, }), _ => bail!("Error while parsing '{}'", path), } } #[derive(Debug)] pub struct ProcFsNetDev { pub device: String, pub receive: u64, pub send: u64, } pub fn read_proc_net_dev() -> Result, Error> { let path = "/proc/net/dev"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines().skip(2) { let content = line?; let mut iter = content.split_whitespace(); match (iter.next(), iter.next(), iter.nth(7)) { (Some(device), Some(receive), Some(send)) => { result.push(ProcFsNetDev { device: device[..device.len() - 1].to_string(), receive: receive.parse::()?, send: send.parse::()?, }); } _ => bail!("Error while parsing '{}'", path), } } Ok(result) } fn hexstr_to_ipv4addr>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 8 { bail!("Error while converting hex string to IPv4 address: unexpected string length"); } let mut addr = [0u8; 4]; for i in 0..4 { addr[3 - i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } Ok(Ipv4Addr::from(addr)) } #[derive(Debug)] pub struct ProcFsNetRoute { pub dest: Ipv4Addr, pub gateway: Ipv4Addr, pub mask: Ipv4Addr, pub metric: u32, pub mtu: u32, pub iface: String, } pub fn read_proc_net_route() -> Result, Error> { let path = "/proc/net/route"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines().skip(1) { let content = line?; if content.is_empty() { continue; } let mut iter = content.split_whitespace(); let mut next = || { iter.next() .ok_or_else(|| format_err!("Error while parsing '{}'", path)) }; let (iface, dest, gateway) = (next()?, next()?, next()?); for _ in 0..3 { next()?; } let (metric, mask, mtu) = (next()?, next()?, next()?); result.push(ProcFsNetRoute { dest: hexstr_to_ipv4addr(dest)?, gateway: hexstr_to_ipv4addr(gateway)?, mask: hexstr_to_ipv4addr(mask)?, metric: metric.parse()?, mtu: mtu.parse()?, iface: iface.to_string(), }); } Ok(result) } fn hexstr_to_ipv6addr>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 32 { bail!("Error while converting hex string to IPv6 address: unexpected string length"); } let mut addr = std::mem::MaybeUninit::<[u8; 16]>::uninit(); let addr = unsafe { let ap = &mut *addr.as_mut_ptr(); for i in 0..16 { ap[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } addr.assume_init() }; Ok(Ipv6Addr::from(addr)) } fn hexstr_to_u8>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 2 { bail!("Error while converting hex string to u8: unexpected string length"); } Ok((hex_nibble(hex[0])? << 4) + hex_nibble(hex[1])?) } fn hexstr_to_u32>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 8 { bail!("Error while converting hex string to u32: unexpected string length"); } let mut bytes = [0u8; 4]; for i in 0..4 { bytes[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } Ok(u32::from_be_bytes(bytes)) } #[derive(Debug)] pub struct ProcFsNetIPv6Route { pub dest: Ipv6Addr, pub prefix: u8, pub gateway: Ipv6Addr, pub metric: u32, pub iface: String, } pub fn read_proc_net_ipv6_route() -> Result, Error> { let path = "/proc/net/ipv6_route"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines() { let content = line?; if content.is_empty() { continue; } let mut iter = content.split_whitespace(); let mut next = || { iter.next() .ok_or_else(|| format_err!("Error while parsing '{}'", path)) }; let (dest, prefix) = (next()?, next()?); for _ in 0..2 { next()?; } let (nexthop, metric) = (next()?, next()?); for _ in 0..3 { next()?; } let iface = next()?; result.push(ProcFsNetIPv6Route { dest: hexstr_to_ipv6addr(dest)?, prefix: hexstr_to_u8(prefix)?, gateway: hexstr_to_ipv6addr(nexthop)?, metric: hexstr_to_u32(metric)?, iface: iface.to_string(), }); } Ok(result) } #[cfg(test)] mod tests { use super::*; #[test] fn test_read_proc_net_route() { read_proc_net_route().unwrap(); } #[test] fn test_read_proc_net_ipv6_route() { read_proc_net_ipv6_route().unwrap(); } }