diff --git a/FEATURES.md b/FEATURES.md index f92028a..2f1b244 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,8 +1,11 @@ Supported Features * ✅ `/proc/` + * cmdline * comm - * exe * cwd + * environ + * exe + * io * root * ✅ `/proc/buddyinfo` diff --git a/examples/proc_io.rs b/examples/proc_io.rs new file mode 100644 index 0000000..4e29a46 --- /dev/null +++ b/examples/proc_io.rs @@ -0,0 +1,14 @@ +use procsys::proc; + +fn main() { + let sys_proc = proc::collect(2).expect("system proc 2"); + let sys_proc_io = sys_proc.io().expect("system proc 2 io"); + + match serde_json::to_string_pretty(&sys_proc_io) { + Ok(output) => println!("{}", output), + Err(err) => { + log::error!("{}", err); + std::process::exit(1); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ad199e6..3b6a602 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod meminfo; pub mod net_dev; pub mod net_protocols; pub mod proc; +pub mod proc_io; pub mod swaps; pub mod sysfs; pub mod utils; diff --git a/src/proc.rs b/src/proc.rs index 9ad09df..9877e75 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -26,6 +26,10 @@ impl Proc { self.id } + pub fn path(&self) -> PathBuf { + self.path.clone() + } + /// returns the command name of a process pub fn comm(&self) -> CollectResult { match utils::collect_info_string("comm", &self.path) { @@ -51,6 +55,40 @@ impl Proc { } } + /// returns the command line of a process + pub fn cmdline(&self) -> CollectResult> { + match utils::collect_info_string("cmdline", &self.path) { + Ok(c) => { + let proc_cmdline = c + .unwrap_or_default() + .trim_end_matches("\x00") + .split("\x00") + .map(|v| v.to_string()) + .collect::>(); + + Ok(proc_cmdline) + } + Err(err) => Err(err), + } + } + + /// returns the process environments from `/proc//environ` + pub fn environ(&self) -> CollectResult> { + match utils::collect_info_string("environ", &self.path) { + Ok(c) => { + let proc_cmdline = c + .unwrap_or_default() + .trim_end_matches("\x00") + .split("\x00") + .map(|v| v.to_string()) + .collect::>(); + + Ok(proc_cmdline) + } + Err(err) => Err(err), + } + } + /// returns the absolute path of the executable command of a process pub fn executable(&self) -> CollectResult { let mut proc_path = self.path.clone(); @@ -194,6 +232,14 @@ mod tests { PathBuf::from("/usr/bin/vim"), ); assert_eq!(sys_single_proc.root_dir().unwrap(), PathBuf::from("/")); + assert_eq!( + sys_single_proc.cmdline().unwrap(), + ["vim", "test.go", "+10"], + ); + assert_eq!( + sys_single_proc.environ().unwrap(), + ["PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOSTNAME=cd24e11f73a5", "TERM=xterm", "GOLANG_VERSION=1.12.5", "GOPATH=/go", "HOME=/root"], + ); let sys_single_proc = collect_from(proc_path, 26232).expect("running proc 26232"); assert_eq!(sys_single_proc.cwd().is_err(), true); diff --git a/src/proc_io.rs b/src/proc_io.rs new file mode 100644 index 0000000..5ba10a1 --- /dev/null +++ b/src/proc_io.rs @@ -0,0 +1,113 @@ +use serde::Serialize; + +use crate::{ + error::{CollectResult, MetricError}, + proc::Proc, + utils, +}; + +enum ProcIOType { + RChar, + WChar, + SyscR, + SyscW, + ReadBytes, + WriteBytes, + CancelledWriteBytes, + Unknown, +} + +impl ProcIOType { + fn from(name: &str) -> ProcIOType { + match name { + "rchar" => ProcIOType::RChar, + "wchar" => ProcIOType::WChar, + "syscr" => ProcIOType::SyscR, + "syscw" => ProcIOType::SyscW, + "read_bytes" => ProcIOType::ReadBytes, + "write_bytes" => ProcIOType::WriteBytes, + "cancelled_write_bytes" => ProcIOType::CancelledWriteBytes, + _ => ProcIOType::Unknown, + } + } +} + +/// ProcIO models the content of /proc/\/io +#[derive(Debug, Serialize, Clone, Default)] +pub struct ProcIO { + pub rchar: u64, + pub wchar: u64, + pub syscr: u64, + pub syscw: u64, + pub read_bytes: u64, + pub write_bytes: u64, + pub cancelled_write_bytes: i64, +} + +impl ProcIO { + fn new() -> Self { + Default::default() + } +} + +impl Proc { + /// returns proc IO stats + pub fn io(&self) -> CollectResult { + let mut proc_io = ProcIO::new(); + let proc_io_path_str = format!("{:?}", self.path()); + let proc_io_file = format!("{}/io", proc_io_path_str.replace("\"", "")); + for line in utils::read_file_lines(&proc_io_file)? { + let item_fields: Vec<&str> = line.trim().split(':').filter(|s| !s.is_empty()).collect(); + + if item_fields.len() != 2 { + return Err(MetricError::InvalidFieldNumberError( + "proc io".to_string(), + item_fields.len(), + line, + )); + } + + let item_value = item_fields[1].trim(); + match ProcIOType::from(item_fields[0]) { + ProcIOType::RChar => proc_io.rchar = item_value.parse::().unwrap_or_default(), + ProcIOType::WChar => proc_io.wchar = item_value.parse::().unwrap_or_default(), + ProcIOType::SyscR => proc_io.syscr = item_value.parse::().unwrap_or_default(), + ProcIOType::SyscW => proc_io.syscw = item_value.parse::().unwrap_or_default(), + ProcIOType::ReadBytes => { + proc_io.read_bytes = item_value.parse::().unwrap_or_default() + } + ProcIOType::WriteBytes => { + proc_io.write_bytes = item_value.parse::().unwrap_or_default() + } + ProcIOType::CancelledWriteBytes => { + proc_io.cancelled_write_bytes = item_value.parse::().unwrap_or_default() + } + ProcIOType::Unknown => {} + } + } + + Ok(proc_io) + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use crate::proc::*; + + #[test] + fn proc_io() { + let proc_path = Path::new("test_data/fixtures/proc"); + let sys_proc = collect_from(proc_path, 26231).expect("running proc 26231"); + let sys_proc_io = sys_proc.io().expect("running proc 26231 io stat"); + + assert_eq!(sys_proc_io.rchar, 750339); + assert_eq!(sys_proc_io.wchar, 818609); + assert_eq!(sys_proc_io.syscr, 7405); + assert_eq!(sys_proc_io.syscw, 5245); + assert_eq!(sys_proc_io.read_bytes, 1024); + assert_eq!(sys_proc_io.write_bytes, 2048); + assert_eq!(sys_proc_io.cancelled_write_bytes, -1024); + } +}