diff --git a/Cargo.toml b/Cargo.toml index dfdf9be6b..ec55ebd93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ doc-comment = "0.3" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["fileapi", "handleapi", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "objbase"] } ntapi = "0.3" +winrt = { version = "0.6.0", features = ["windows-system"]} [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies] libc = "0.2" diff --git a/src/linux/process.rs b/src/linux/process.rs index c15543e41..27167e7ed 100644 --- a/src/linux/process.rs +++ b/src/linux/process.rs @@ -124,6 +124,8 @@ pub struct Process { /// Tasks run by this process. pub tasks: HashMap, pub(crate) stat_file: Option, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, } impl ProcessExt for Process { @@ -155,6 +157,8 @@ impl ProcessExt for Process { HashMap::new() }, stat_file: None, + read_bytes: 0, + written_bytes: 0, } } @@ -215,6 +219,14 @@ impl ProcessExt for Process { fn cpu_usage(&self) -> f32 { self.cpu_usage } + + fn read_bytes(&self) -> u64 { + self.read_bytes + } + + fn written_bytes(&self) -> u64 { + self.written_bytes + } } impl Drop for Process { diff --git a/src/linux/system.rs b/src/linux/system.rs index 9002ed24e..4b548476d 100644 --- a/src/linux/system.rs +++ b/src/linux/system.rs @@ -607,6 +607,7 @@ fn _get_process_data( uptime, now, ); + update_process_disk_activity(entry); return Ok(None); } @@ -690,9 +691,26 @@ fn _get_process_data( uptime, now, ); + update_process_disk_activity(&mut p); Ok(Some(p)) } +fn update_process_disk_activity(p: &mut Process) { + let path = PathBuf::from(format!("/proc/{}/io", p.pid)); + let data = match get_all_data(&path, 16_384) { + Ok(d) => d, + Err(_) => return, + }; + let data: Vec> = data.split("\n").map(|l| l.split(": ").collect()).collect(); + for d in data.iter() { + if d[0] == "read_bytes" { + p.read_bytes = d[1].parse::().unwrap_or(0); + } else if d[0] == "write_bytes" { + p.written_bytes = d[1].parse::().unwrap_or(0); + } + } +} + fn copy_from_file(entry: &Path) -> Vec { match File::open(entry.to_str().unwrap_or("/")) { Ok(mut f) => { diff --git a/src/mac/ffi.rs b/src/mac/ffi.rs index 73a82df26..f6c735b7f 100644 --- a/src/mac/ffi.rs +++ b/src/mac/ffi.rs @@ -87,6 +87,11 @@ extern "C" { // pub fn proc_name(pid: i32, buf: *mut i8, bufsize: u32) -> i32; } +#[link(name = "proc", kind = "dylib")] +extern "C" { + pub fn proc_pid_rusage(pid: c_int, flavor: c_int, buffer: *mut c_void) -> c_int; +} + // TODO: waiting for https://github.com/rust-lang/libc/pull/678 macro_rules! cfg_if { ($( @@ -342,6 +347,31 @@ pub struct xsw_usage { pub xsu_encrypted: boolean_t, } +//https://github.com/andrewdavidmackenzie/libproc-rs/blob/master/src/libproc/pid_rusage.rs +#[repr(C)] +#[derive(Debug, Default)] +pub struct RUsageInfoV2 { + pub ri_uuid: [u8; 16], + pub ri_user_time: u64, + pub ri_system_time: u64, + pub ri_pkg_idle_wkups: u64, + pub ri_interrupt_wkups: u64, + pub ri_pageins: u64, + pub ri_wired_size: u64, + pub ri_resident_size: u64, + pub ri_phys_footprint: u64, + pub ri_proc_start_abstime: u64, + pub ri_proc_exit_abstime: u64, + pub ri_child_user_time: u64, + pub ri_child_system_time: u64, + pub ri_child_pkg_idle_wkups: u64, + pub ri_child_interrupt_wkups: u64, + pub ri_child_pageins: u64, + pub ri_child_elapsed_abstime: u64, + pub ri_diskio_bytesread: u64, + pub ri_diskio_byteswritten: u64, +} + //pub const HOST_CPU_LOAD_INFO_COUNT: usize = 4; //pub const HOST_CPU_LOAD_INFO: u32 = 3; pub const KERN_SUCCESS: kern_return_t = 0; diff --git a/src/mac/process.rs b/src/mac/process.rs index b36796ad6..862c4a1c3 100644 --- a/src/mac/process.rs +++ b/src/mac/process.rs @@ -149,14 +149,12 @@ pub struct Process { /// /// This is very likely this one that you want instead of `process_status`. pub status: Option, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, } impl Process { - pub(crate) fn new_empty( - pid: Pid, - exe: PathBuf, - name: String, - ) -> Process { + pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String) -> Process { Process { name, pid, @@ -179,6 +177,8 @@ impl Process { gid: 0, process_status: ProcessStatus::Unknown(0), status: None, + read_bytes: 0, + written_bytes: 0, } } @@ -214,6 +214,8 @@ impl Process { gid: 0, process_status: ProcessStatus::Unknown(0), status: None, + read_bytes: 0, + written_bytes: 0, } } } @@ -242,6 +244,8 @@ impl ProcessExt for Process { gid: 0, process_status: ProcessStatus::Unknown(0), status: None, + read_bytes: 0, + written_bytes: 0, } } @@ -300,6 +304,14 @@ impl ProcessExt for Process { fn cpu_usage(&self) -> f32 { self.cpu_usage } + + fn read_bytes(&self) -> u64 { + self.read_bytes + } + + fn written_bytes(&self) -> u64 { + self.written_bytes + } } #[allow(unused_must_use)] @@ -422,6 +434,7 @@ pub(crate) fn update_process( p.memory = task_info.pti_resident_size >> 10; // divide by 1024 p.virtual_memory = task_info.pti_virtual_size >> 10; // divide by 1024 + update_proc_disk_activity(p); return Ok(None); } @@ -435,7 +448,11 @@ pub(crate) fn update_process( ) != mem::size_of::() as _ { let mut buffer: Vec = Vec::with_capacity(ffi::PROC_PIDPATHINFO_MAXSIZE as _); - match ffi::proc_pidpath(pid, buffer.as_mut_ptr() as *mut _, ffi::PROC_PIDPATHINFO_MAXSIZE) { + match ffi::proc_pidpath( + pid, + buffer.as_mut_ptr() as *mut _, + ffi::PROC_PIDPATHINFO_MAXSIZE, + ) { x if x > 0 => { buffer.set_len(x as _); let tmp = String::from_utf8_unchecked(buffer); @@ -606,11 +623,27 @@ pub(crate) fn update_process( p.uid = info.pbi_uid; p.gid = info.pbi_gid; p.process_status = ProcessStatus::from(info.pbi_status); - + update_proc_disk_activity(&mut p); Ok(Some(p)) } } +fn update_proc_disk_activity(p: &mut Process) { + let mut pidrusage = ffi::RUsageInfoV2::default(); + let ptr = &mut pidrusage as *mut _ as *mut c_void; + let retval: i32; + unsafe { + retval = ffi::proc_pid_rusage(p.pid() as c_int, 2, ptr); + } + + if retval < 0 { + panic!("proc_pid_rusage failed: {:?}", retval); + } else { + p.read_bytes = pidrusage.ri_diskio_bytesread; + p.written_bytes = pidrusage.ri_diskio_byteswritten; + } +} + pub(crate) fn get_proc_list() -> Option> { let count = unsafe { ffi::proc_listallpids(::std::ptr::null_mut(), 0) }; if count < 1 { diff --git a/src/mac/system.rs b/src/mac/system.rs index 2e5610fe9..1903cf742 100644 --- a/src/mac/system.rs +++ b/src/mac/system.rs @@ -288,15 +288,9 @@ impl SystemExt for System { let entries: Vec = { let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); pids.par_iter() - .flat_map(|pid| { - match update_process( - wrap, - *pid, - arg_max as size_t, - ) { - Ok(x) => x, - Err(_) => None, - } + .flat_map(|pid| match update_process(wrap, *pid, arg_max as size_t) { + Ok(x) => x, + Err(_) => None, }) .collect() }; @@ -311,11 +305,7 @@ impl SystemExt for System { let arg_max = get_arg_max(); match { let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); - update_process( - &wrap, - pid, - arg_max as size_t, - ) + update_process(&wrap, pid, arg_max as size_t) } { Ok(Some(p)) => { self.process_list.insert(p.pid(), p); diff --git a/src/sysinfo.rs b/src/sysinfo.rs index 6d6d5d299..6a6e013ea 100644 --- a/src/sysinfo.rs +++ b/src/sysinfo.rs @@ -69,6 +69,7 @@ cfg_if! { use windows as sys; extern crate winapi; extern crate ntapi; + extern crate winrt; } else if #[cfg(unix)] { mod linux; use linux as sys; diff --git a/src/traits.rs b/src/traits.rs index b65fbf8ee..03917e8c9 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -93,6 +93,12 @@ pub trait ProcessExt { /// Returns the total CPU usage. fn cpu_usage(&self) -> f32; + + /// Returns number of bytes read from disk + fn read_bytes(&self) -> u64; + + /// Returns number of bytes written to disk + fn written_bytes(&self) -> u64; } /// Contains all the methods of the `Processor` struct. diff --git a/src/windows/process.rs b/src/windows/process.rs index fbb0b02ab..1bde7ef18 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -27,6 +27,8 @@ use winapi::um::winnt::{ HANDLE, /*, PWSTR*/ PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ, ULARGE_INTEGER, /*THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION, THREAD_SUSPEND_RESUME,*/ }; +use winrt::windows::system::diagnostics::*; +use winrt::*; /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug)] @@ -104,6 +106,8 @@ pub struct Process { start_time: u64, cpu_usage: f32, pub(crate) updated: bool, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, } unsafe fn get_process_name(process_handler: HANDLE, h_mod: *mut c_void) -> String { @@ -192,6 +196,8 @@ impl Process { old_user_cpu: 0, start_time: get_start_time(process_handler), updated: true, + read_bytes: 0, + written_bytes: 0, } } } else { @@ -214,6 +220,8 @@ impl Process { old_user_cpu: 0, start_time: 0, updated: true, + read_bytes: 0, + written_bytes: 0, } } } @@ -259,6 +267,8 @@ impl ProcessExt for Process { old_user_cpu: 0, start_time: get_start_time(process_handler), updated: true, + read_bytes: 0, + written_bytes: 0, } } } else { @@ -281,6 +291,8 @@ impl ProcessExt for Process { old_user_cpu: 0, start_time: 0, updated: true, + read_bytes: 0, + written_bytes: 0, } } } @@ -344,6 +356,14 @@ impl ProcessExt for Process { fn cpu_usage(&self) -> f32 { self.cpu_usage } + + fn read_bytes(&self) -> u64 { + self.read_bytes + } + + fn written_bytes(&self) -> u64 { + self.written_bytes + } } impl Drop for Process { @@ -538,6 +558,39 @@ pub(crate) fn get_system_computation_time() -> ULARGE_INTEGER { } } +macro_rules! safe_unwrap { + ($x:expr) => { + match $x { + Some(x) => x, + None => return, + } + }; +} + +macro_rules! safe_unwrap_to_inner { + ($x:expr) => { + match $x { + Some(x) => safe_unwrap!(x), + None => return, + } + }; +} + +pub(crate) fn get_disk_usage(p: &mut Process) { + let diag_info = + safe_unwrap_to_inner!(ProcessDiagnosticInfo::try_get_for_process_id(p.pid as u32).ok()); + let disk_usage = safe_unwrap_to_inner!(diag_info.get_disk_usage().ok()); + let report = safe_unwrap_to_inner!(disk_usage.get_report().ok()); + let read_bytes = report.get_bytes_read_count().ok(); + let write_bytes = report.get_bytes_written_count().ok(); + if let Some(rb) = read_bytes { + p.read_bytes = rb as u64; + } + if let Some(wb) = write_bytes { + p.written_bytes = wb as u64; + } +} + pub(crate) fn compute_cpu_usage(p: &mut Process, nb_processors: u64, now: ULARGE_INTEGER) { unsafe { let mut sys: ULARGE_INTEGER = ::std::mem::zeroed(); @@ -580,6 +633,7 @@ pub fn get_handle(p: &Process) -> HANDLE { pub fn update_proc_info(p: &mut Process) { update_memory(p); + get_disk_usage(p); } pub fn update_memory(p: &mut Process) { diff --git a/src/windows/system.rs b/src/windows/system.rs index 40dc3f961..3bc4488fd 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -20,7 +20,8 @@ use SystemExt; use windows::network::{self, NetworkData}; use windows::process::{ - compute_cpu_usage, get_handle, get_system_computation_time, update_proc_info, Process, + compute_cpu_usage, get_disk_usage, get_handle, get_system_computation_time, update_proc_info, + Process, }; use windows::processor::CounterValue; use windows::tools::*; @@ -331,6 +332,7 @@ impl SystemExt for System { proc_.memory = (pi.WorkingSetSize as u64) >> 10u64; proc_.virtual_memory = (pi.VirtualSize as u64) >> 10u64; compute_cpu_usage(proc_, nb_processors, system_time); + get_disk_usage(proc_); proc_.updated = true; return None; } @@ -346,6 +348,7 @@ impl SystemExt for System { (pi.VirtualSize as u64) >> 10u64, name, ); + get_disk_usage(&mut p); compute_cpu_usage(&mut p, nb_processors, system_time); Some(p) }) diff --git a/tests/process.rs b/tests/process.rs index 75fe8a6b8..70f636e71 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -19,3 +19,23 @@ fn test_process() { .values() .any(|p| p.exe().to_str().unwrap_or_else(|| "").len() != 0)); } + +#[test] +fn test_process_disk_usage() { + use std::fs; + use std::fs::File; + use std::io::prelude::*; + use sysinfo::{get_current_pid, ProcessExt, SystemExt}; + { + let mut file = File::create("test.txt").unwrap(); + file.write_all(b"This is a test file\nwith test data.\n") + .unwrap(); + } + fs::remove_file("test.txt").ok(); + let system = sysinfo::System::new(); + let p = system + .get_process(get_current_pid().expect("Failed retrieving current pid.")) + .expect("failed to get process"); + + assert!(p.written_bytes() > 0); +}