forked from proxmox-mirrors/proxmox
714 lines
23 KiB
Rust
714 lines
23 KiB
Rust
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<Self, Error> {
|
|
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<PidStat, Error> {
|
|
// 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<T>(value: Option<&str>, what: &'static str) -> Result<T, Error>
|
|
where
|
|
T: FromStr,
|
|
<T as FromStr>::Err: Into<Error>,
|
|
{
|
|
required(value, what)?.parse::<T>().map_err(|e| e.into())
|
|
}
|
|
|
|
fn req_byte(value: Option<&str>, what: &'static str) -> Result<u8, Error> {
|
|
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::<u32>(parts.next(), "ppid")? as i32),
|
|
utime: req_num::<u64>(parts.nth(9), "utime")?,
|
|
stime: req_num::<u64>(parts.next(), "stime")?,
|
|
num_threads: req_num::<u64>(parts.nth(4), "num_threads")?,
|
|
starttime: req_num::<u64>(parts.nth(1), "start_time")?,
|
|
vsize: req_num::<u64>(parts.next(), "vsize")?,
|
|
rss: req_num::<i64>(parts.next(), "rss")? * 4096,
|
|
};
|
|
|
|
let _ = req_num::<u64>(parts.next(), "it_real_value")?;
|
|
// and more...
|
|
|
|
Ok(out)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Pid> for PidStat {
|
|
type Error = Error;
|
|
|
|
fn try_from(pid: Pid) -> Result<Self, Error> {
|
|
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, Error> {
|
|
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<u64, Error> {
|
|
PidStat::read_for_pid(Pid::from_raw(pid)).map(|stat| stat.starttime)
|
|
}
|
|
|
|
pub fn check_process_running(pid: libc::pid_t) -> Option<PidStat> {
|
|
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<PidStat> {
|
|
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::<f64>());
|
|
|
|
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<ProcFsStat, Error> {
|
|
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<ProcFsStat, Error> {
|
|
// 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<T>(value: Option<&str>, what: &'static str) -> Result<T, Error>
|
|
where
|
|
T: FromStr,
|
|
<T as FromStr>::Err: std::fmt::Display,
|
|
{
|
|
required(value, what)?
|
|
.parse::<T>()
|
|
.map_err(|e| format_err!("error parsing {}: {}", what, e))
|
|
}
|
|
|
|
let mut stat = ProcFsStat {
|
|
user: req_num::<u64>(parts.next(), "user")?,
|
|
nice: req_num::<u64>(parts.next(), "nice")?,
|
|
system: req_num::<u64>(parts.next(), "system")?,
|
|
idle: req_num::<u64>(parts.next(), "idle")?,
|
|
iowait: req_num::<u64>(parts.next(), "iowait")?,
|
|
irq: req_num::<u64>(parts.next(), "irq")?,
|
|
softirq: req_num::<u64>(parts.next(), "softirq")?,
|
|
steal: req_num::<u64>(parts.next(), "steal")?,
|
|
guest: req_num::<u64>(parts.next(), "guest")?,
|
|
guest_nice: req_num::<u64>(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<ProcFsMemInfo, Error> {
|
|
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::<u64>()? * 1024,
|
|
"MemFree:" => meminfo.memfree = value.parse::<u64>()? * 1024,
|
|
"SwapTotal:" => meminfo.swaptotal = value.parse::<u64>()? * 1024,
|
|
"SwapFree:" => meminfo.swapfree = value.parse::<u64>()? * 1024,
|
|
"Buffers:" => buffers = value.parse::<u64>()? * 1024,
|
|
"Cached:" => cached = value.parse::<u64>()? * 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::<u64>()? * 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<ProcFsCPUInfo> = None;
|
|
|
|
pub fn read_cpuinfo() -> Result<ProcFsCPUInfo, Error> {
|
|
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::<f64>()?,
|
|
"flags" => cpuinfo.hvm = value.contains(" vmx ") || value.contains(" svm "),
|
|
"physical id" => {
|
|
let id = value.trim().parse::<u8>()?;
|
|
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<ProcFsMemUsage, Error> {
|
|
let path = format!("/proc/{}/statm", std::process::id());
|
|
let line = file_read_firstline(&path)?;
|
|
let mut values = line.split_whitespace().map(|v| v.parse::<u64>());
|
|
|
|
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<Vec<ProcFsNetDev>, 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::<u64>()?,
|
|
send: send.parse::<u64>()?,
|
|
});
|
|
}
|
|
_ => bail!("Error while parsing '{}'", path),
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
fn hexstr_to_ipv4addr<T: AsRef<[u8]>>(hex: T) -> Result<Ipv4Addr, Error> {
|
|
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<Vec<ProcFsNetRoute>, 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<T: AsRef<[u8]>>(hex: T) -> Result<Ipv6Addr, Error> {
|
|
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<T: AsRef<[u8]>>(hex: T) -> Result<u8, Error> {
|
|
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<T: AsRef<[u8]>>(hex: T) -> Result<u32, Error> {
|
|
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<Vec<ProcFsNetIPv6Route>, 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();
|
|
}
|
|
}
|