diff --git a/Cargo.lock b/Cargo.lock index fe58937f00..53b194d5b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -870,6 +870,7 @@ dependencies = [ "nu-plugin", "nu-pretty-hex", "nu-protocol", + "nu-system", "nu-table", "nu-term-grid", "nu_plugin_example", @@ -903,6 +904,27 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fallible-streaming-iterator" version = "0.1.9" @@ -1225,6 +1247,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "htmlescape" version = "0.3.1" @@ -1547,6 +1575,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "libproc" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6466fc1f834276563fbbd4be1c24236ef92bb9efdbd4691e07f1cf85a0b407f0" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "libssh2-sys" version = "0.2.23" @@ -1912,6 +1950,7 @@ dependencies = [ "nu-path", "nu-pretty-hex", "nu-protocol", + "nu-system", "nu-table", "nu-term-grid", "num 0.4.0", @@ -2027,6 +2066,20 @@ dependencies = [ "typetag", ] +[[package]] +name = "nu-system" +version = "0.60.0" +dependencies = [ + "chrono", + "errno", + "libc", + "libproc", + "procfs", + "users", + "which", + "winapi", +] + [[package]] name = "nu-table" version = "0.36.0" @@ -2614,6 +2667,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "flate2", + "hex", + "lazy_static", + "libc", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3848,6 +3916,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 9bcb9e8bef..edf82e439d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/nu-cli", "crates/nu-engine", "crates/nu-parser", + "crates/nu-system", "crates/nu-command", "crates/nu-protocol", "crates/nu-plugin", @@ -32,6 +33,7 @@ nu-path = { path="./crates/nu-path" } nu-pretty-hex = { path = "./crates/nu-pretty-hex" } nu-protocol = { path = "./crates/nu-protocol" } nu-plugin = { path = "./crates/nu-plugin", optional = true } +nu-system = { path = "./crates/nu-system"} nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } # nu-ansi-term = { path = "./crates/nu-ansi-term" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 8ad34320d4..9c11c0d238 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -16,6 +16,7 @@ nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-parser = { path = "../nu-parser" } +nu-system = { path = "../nu-system" } # nu-ansi-term = { path = "../nu-ansi-term" } nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config" } diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index 0d51089aaf..ef49489e60 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -1,9 +1,10 @@ +use std::time::Duration; + use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, }; -use sysinfo::{ProcessExt, System, SystemExt}; #[derive(Clone)] pub struct Ps; @@ -49,86 +50,59 @@ impl Command for Ps { } fn run_ps(engine_state: &EngineState, call: &Call) -> Result { + let mut output = vec![]; let span = call.head; let long = call.has_flag("long"); - let mut sys = System::new_all(); - sys.refresh_all(); - let duration = std::time::Duration::from_millis(500); - std::thread::sleep(duration); + for proc in nu_system::collect_proc(Duration::from_millis(100), false) { + let mut cols = vec![]; + let mut vals = vec![]; - let mut output = vec![]; + cols.push("pid".to_string()); + vals.push(Value::Int { + val: proc.pid() as i64, + span, + }); - let result: Vec<_> = sys.processes().iter().map(|x| *x.0).collect(); + cols.push("name".to_string()); + vals.push(Value::String { + val: proc.name(), + span, + }); - for pid in result { - sys.refresh_process(pid); - if let Some(result) = sys.process(pid) { - let mut cols = vec![]; - let mut vals = vec![]; + cols.push("status".to_string()); + vals.push(Value::String { + val: proc.status(), + span, + }); - cols.push("pid".into()); - vals.push(Value::Int { - val: pid as i64, - span, - }); + cols.push("cpu".to_string()); + vals.push(Value::Float { + val: proc.cpu_usage(), + span, + }); - cols.push("name".into()); + cols.push("mem".to_string()); + vals.push(Value::Filesize { + val: proc.mem_size() as i64, + span, + }); + + cols.push("virtual".to_string()); + vals.push(Value::Filesize { + val: proc.virtual_size() as i64, + span, + }); + + if long { + cols.push("command".to_string()); vals.push(Value::String { - val: result.name().into(), + val: proc.command(), span, }); - - cols.push("status".into()); - vals.push(Value::String { - val: format!("{:?}", result.status()), - span, - }); - - cols.push("cpu".into()); - vals.push(Value::Float { - val: result.cpu_usage() as f64, - span, - }); - - cols.push("mem".into()); - vals.push(Value::Filesize { - val: result.memory() as i64 * 1000, - span, - }); - - cols.push("virtual".into()); - vals.push(Value::Filesize { - val: result.virtual_memory() as i64 * 1000, - span, - }); - - if long { - cols.push("parent".into()); - if let Some(parent) = result.parent() { - vals.push(Value::Int { - val: parent as i64, - span, - }); - } else { - vals.push(Value::Nothing { span }); - } - - cols.push("exe".into()); - vals.push(Value::String { - val: result.exe().to_string_lossy().to_string(), - span, - }); - - cols.push("command".into()); - vals.push(Value::String { - val: result.cmd().join(" "), - span, - }); - } - - output.push(Value::Record { cols, vals, span }); } + + output.push(Value::Record { cols, vals, span }); } Ok(output diff --git a/crates/nu-system/.gitignore b/crates/nu-system/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/crates/nu-system/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/nu-system/Cargo.lock b/crates/nu-system/Cargo.lock new file mode 100644 index 0000000000..47bce2f73b --- /dev/null +++ b/crates/nu-system/Cargo.lock @@ -0,0 +1,253 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "crc32fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libproc" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6466fc1f834276563fbbd4be1c24236ef92bb9efdbd4691e07f1cf85a0b407f0" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "nu-system" +version = "0.1.0" +dependencies = [ + "errno", + "libproc", + "procfs", + "users", + "which", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "flate2", + "hex", + "lazy_static", + "libc", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml new file mode 100644 index 0000000000..375e181618 --- /dev/null +++ b/crates/nu-system/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["The Nu Project Contributors", "procs creators"] +description = "Nushell system querying" +name = "nu-system" +version = "0.60.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "ps" +path = "src/main.rs" + +[dependencies] + + +[target.'cfg(target_os = "linux")'.dependencies] +procfs = "0.12.0" +users = "0.11" +which = "4" + +[target.'cfg(target_os = "macos")'.dependencies] +libproc = "0.10" +errno = "0.2" +users = "0.11" +which = "4" +libc = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3", features = ["handleapi", "minwindef", "psapi", "securitybaseapi", "tlhelp32", "winbase", "winnt"] } +chrono = "0.4" +libc = "0.2" diff --git a/crates/nu-system/LICENSE b/crates/nu-system/LICENSE new file mode 100644 index 0000000000..e0e33baa83 --- /dev/null +++ b/crates/nu-system/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 procs developers and Nushell developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/nu-system/src/lib.rs b/crates/nu-system/src/lib.rs new file mode 100644 index 0000000000..a8e9728d1f --- /dev/null +++ b/crates/nu-system/src/lib.rs @@ -0,0 +1,13 @@ +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "linux")] +pub use self::linux::*; +#[cfg(target_os = "macos")] +pub use self::macos::*; +#[cfg(target_os = "windows")] +pub use self::windows::*; diff --git a/crates/nu-system/src/linux.rs b/crates/nu-system/src/linux.rs new file mode 100644 index 0000000000..5432eed07b --- /dev/null +++ b/crates/nu-system/src/linux.rs @@ -0,0 +1,253 @@ +use procfs::process::{FDInfo, Io, Process, Stat, Status, TasksIter}; +use procfs::{ProcError, ProcessCgroup}; +use std::collections::HashMap; +use std::thread; +use std::time::{Duration, Instant}; + +pub enum ProcessTask { + Process(Process), + Task { stat: Stat, owner: u32 }, +} + +impl ProcessTask { + pub fn stat(&self) -> &Stat { + match self { + ProcessTask::Process(x) => &x.stat, + ProcessTask::Task { stat: x, owner: _ } => x, + } + } + + pub fn cmdline(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.cmdline(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn cgroups(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.cgroups(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn fd(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.fd(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn loginuid(&self) -> Result { + match self { + ProcessTask::Process(x) => x.loginuid(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn owner(&self) -> u32 { + match self { + ProcessTask::Process(x) => x.owner, + ProcessTask::Task { stat: _, owner: x } => *x, + } + } + + pub fn wchan(&self) -> Result { + match self { + ProcessTask::Process(x) => x.wchan(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } +} + +pub struct ProcessInfo { + pub pid: i32, + pub ppid: i32, + pub curr_proc: ProcessTask, + pub prev_proc: ProcessTask, + pub curr_io: Option, + pub prev_io: Option, + pub curr_status: Option, + pub interval: Duration, +} + +pub fn collect_proc(interval: Duration, with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut base_tasks = HashMap::new(); + let mut ret = Vec::new(); + + if let Ok(all_proc) = procfs::process::all_processes() { + for proc in all_proc { + let io = proc.io().ok(); + let time = Instant::now(); + if with_thread { + if let Ok(iter) = proc.tasks() { + collect_task(iter, &mut base_tasks); + } + } + base_procs.push((proc.pid(), proc, io, time)); + } + } + + thread::sleep(interval); + + for (pid, prev_proc, prev_io, prev_time) in base_procs { + let curr_proc = if let Ok(proc) = Process::new(pid) { + proc + } else { + prev_proc.clone() + }; + let curr_io = curr_proc.io().ok(); + let curr_status = curr_proc.status().ok(); + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + let ppid = curr_proc.stat.ppid; + let owner = curr_proc.owner; + + let mut curr_tasks = HashMap::new(); + if with_thread { + if let Ok(iter) = curr_proc.tasks() { + collect_task(iter, &mut curr_tasks); + } + } + + let curr_proc = ProcessTask::Process(curr_proc); + let prev_proc = ProcessTask::Process(prev_proc); + + let proc = ProcessInfo { + pid, + ppid, + curr_proc, + prev_proc, + curr_io, + prev_io, + curr_status, + interval, + }; + + ret.push(proc); + + for (tid, (pid, curr_stat, curr_status, curr_io)) in curr_tasks { + if let Some((_, prev_stat, _, prev_io)) = base_tasks.remove(&tid) { + let proc = ProcessInfo { + pid: tid, + ppid: pid, + curr_proc: ProcessTask::Task { + stat: curr_stat, + owner, + }, + prev_proc: ProcessTask::Task { + stat: prev_stat, + owner, + }, + curr_io, + prev_io, + curr_status, + interval, + }; + ret.push(proc); + } + } + } + + ret +} + +#[allow(clippy::type_complexity)] +fn collect_task(iter: TasksIter, map: &mut HashMap, Option)>) { + for task in iter { + let task = if let Ok(x) = task { + x + } else { + continue; + }; + if task.tid != task.pid { + let stat = if let Ok(x) = task.stat() { + x + } else { + continue; + }; + let status = task.status().ok(); + let io = task.io().ok(); + map.insert(task.tid, (task.pid, stat, status, io)); + } + } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + if let Ok(cmd) = &self.curr_proc.cmdline() { + if !cmd.is_empty() { + let mut cmd = cmd + .iter() + .cloned() + .map(|mut x| { + x.push(' '); + x + }) + .collect::(); + cmd.pop(); + cmd = cmd.replace("\n", " ").replace("\t", " "); + cmd + } else { + self.curr_proc.stat().comm.clone() + } + } else { + self.curr_proc.stat().comm.clone() + } + } + + /// Get the status of the process + pub fn status(&self) -> String { + match self.curr_proc.stat().state { + 'S' => "Sleeping".into(), + 'R' => "Running".into(), + 'D' => "Disk sleep".into(), + 'Z' => "Zombie".into(), + 'T' => "Stopped".into(), + 't' => "Tracing".into(), + 'X' => "Dead".into(), + 'x' => "Dead".into(), + 'K' => "Wakekill".into(), + 'W' => "Waking".into(), + 'P' => "Parked".into(), + _ => "Unknown".into(), + } + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = self.curr_proc.stat().utime + self.curr_proc.stat().stime; + let prev_time = self.prev_proc.stat().utime + self.prev_proc.stat().stime; + let usage_ms = + (curr_time - prev_time) * 1000 / procfs::ticks_per_second().unwrap_or(100) as u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.curr_proc.stat().rss_bytes().unwrap_or(0) as u64 + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.curr_proc.stat().vsize + } +} diff --git a/crates/nu-system/src/macos.rs b/crates/nu-system/src/macos.rs new file mode 100644 index 0000000000..e6ae3d9ac6 --- /dev/null +++ b/crates/nu-system/src/macos.rs @@ -0,0 +1,391 @@ +use libc::{c_int, c_void, size_t}; +use libproc::libproc::bsd_info::BSDInfo; +use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType}; +use libproc::libproc::net_info::{InSockInfo, SocketFDInfo, SocketInfoKind, TcpSockInfo}; +use libproc::libproc::pid_rusage::{pidrusage, RUsageInfoV2}; +use libproc::libproc::proc_pid::{listpidinfo, listpids, pidinfo, ListThreads, ProcType}; +use libproc::libproc::task_info::{TaskAllInfo, TaskInfo}; +use libproc::libproc::thread_info::ThreadInfo; +use std::cmp; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::thread; +use std::time::{Duration, Instant}; + +pub struct ProcessInfo { + pub pid: i32, + pub ppid: i32, + pub curr_task: TaskAllInfo, + pub prev_task: TaskAllInfo, + pub curr_path: Option, + pub curr_threads: Vec, + pub curr_udps: Vec, + pub curr_tcps: Vec, + pub curr_res: Option, + pub prev_res: Option, + pub interval: Duration, +} + +#[cfg_attr(tarpaulin, skip)] +pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut ret = Vec::new(); + let arg_max = get_arg_max(); + + if let Ok(procs) = listpids(ProcType::ProcAllPIDS) { + for p in procs { + if let Ok(task) = pidinfo::(p as i32, 0) { + let res = pidrusage::(p as i32).ok(); + let time = Instant::now(); + base_procs.push((p as i32, task, res, time)); + } + } + } + + thread::sleep(interval); + + for (pid, prev_task, prev_res, prev_time) in base_procs { + let curr_task = if let Ok(task) = pidinfo::(pid, 0) { + task + } else { + clone_task_all_info(&prev_task) + }; + + let curr_path = get_path_info(pid, arg_max); + + let threadids = listpidinfo::(pid, curr_task.ptinfo.pti_threadnum as usize); + let mut curr_threads = Vec::new(); + if let Ok(threadids) = threadids { + for t in threadids { + if let Ok(thread) = pidinfo::(pid, t) { + curr_threads.push(thread); + } + } + } + + let mut curr_tcps = Vec::new(); + let mut curr_udps = Vec::new(); + + let fds = listpidinfo::(pid, curr_task.pbsd.pbi_nfiles as usize); + if let Ok(fds) = fds { + for fd in fds { + if let ProcFDType::Socket = fd.proc_fdtype.into() { + if let Ok(socket) = pidfdinfo::(pid, fd.proc_fd) { + match socket.psi.soi_kind.into() { + SocketInfoKind::In => { + if socket.psi.soi_protocol == libc::IPPROTO_UDP { + let info = unsafe { socket.psi.soi_proto.pri_in }; + curr_udps.push(info); + } + } + SocketInfoKind::Tcp => { + let info = unsafe { socket.psi.soi_proto.pri_tcp }; + curr_tcps.push(info); + } + _ => (), + } + } + } + } + } + + let curr_res = pidrusage::(pid).ok(); + + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + let ppid = curr_task.pbsd.pbi_ppid as i32; + + let proc = ProcessInfo { + pid, + ppid, + curr_task, + prev_task, + curr_path, + curr_threads, + curr_udps, + curr_tcps, + curr_res, + prev_res, + interval, + }; + + ret.push(proc); + } + + ret +} + +#[cfg_attr(tarpaulin, skip)] +fn get_arg_max() -> size_t { + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_ARGMAX]; + let mut arg_max = 0i32; + let mut size = ::std::mem::size_of::(); + unsafe { + while libc::sysctl( + mib.as_mut_ptr(), + 2, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ) == -1 + {} + } + arg_max as size_t +} + +pub struct PathInfo { + pub name: String, + pub exe: PathBuf, + pub root: PathBuf, + pub cmd: Vec, + pub env: Vec, +} + +#[cfg_attr(tarpaulin, skip)] +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + ::std::mem::forget(part); + tmp +} + +#[cfg_attr(tarpaulin, skip)] +fn get_path_info(pid: i32, mut size: size_t) -> Option { + let mut proc_args = Vec::with_capacity(size as usize); + let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); + + let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as c_int]; + + unsafe { + let ret = libc::sysctl( + mib.as_mut_ptr(), + 3, + ptr as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ); + if ret != -1 { + let mut n_args: c_int = 0; + libc::memcpy( + (&mut n_args) as *mut c_int as *mut c_void, + ptr as *const c_void, + ::std::mem::size_of::(), + ); + let mut cp = ptr.add(::std::mem::size_of::()); + let mut start = cp; + if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_str() + .unwrap_or("") + .to_owned(); + let mut need_root = true; + let mut root = Default::default(); + if exe.is_absolute() { + if let Some(parent) = exe.parent() { + root = parent.to_path_buf(); + need_root = false; + } + } + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::new(); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + start = cp; + let mut env = Vec::new(); + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + env.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + if need_root { + for env in env.iter() { + if env.starts_with("PATH=") { + root = Path::new(&env[6..]).to_path_buf(); + break; + } + } + } + + Some(PathInfo { + exe, + name, + root, + cmd, + env, + }) + } else { + None + } + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn clone_task_all_info(src: &TaskAllInfo) -> TaskAllInfo { + let pbsd = BSDInfo { + pbi_flags: src.pbsd.pbi_flags, + pbi_status: src.pbsd.pbi_status, + pbi_xstatus: src.pbsd.pbi_xstatus, + pbi_pid: src.pbsd.pbi_pid, + pbi_ppid: src.pbsd.pbi_ppid, + pbi_uid: src.pbsd.pbi_uid, + pbi_gid: src.pbsd.pbi_gid, + pbi_ruid: src.pbsd.pbi_ruid, + pbi_rgid: src.pbsd.pbi_rgid, + pbi_svuid: src.pbsd.pbi_svuid, + pbi_svgid: src.pbsd.pbi_svgid, + rfu_1: src.pbsd.rfu_1, + pbi_comm: src.pbsd.pbi_comm, + pbi_name: src.pbsd.pbi_name, + pbi_nfiles: src.pbsd.pbi_nfiles, + pbi_pgid: src.pbsd.pbi_pgid, + pbi_pjobc: src.pbsd.pbi_pjobc, + e_tdev: src.pbsd.e_tdev, + e_tpgid: src.pbsd.e_tpgid, + pbi_nice: src.pbsd.pbi_nice, + pbi_start_tvsec: src.pbsd.pbi_start_tvsec, + pbi_start_tvusec: src.pbsd.pbi_start_tvusec, + }; + let ptinfo = TaskInfo { + pti_virtual_size: src.ptinfo.pti_virtual_size, + pti_resident_size: src.ptinfo.pti_resident_size, + pti_total_user: src.ptinfo.pti_total_user, + pti_total_system: src.ptinfo.pti_total_system, + pti_threads_user: src.ptinfo.pti_threads_user, + pti_threads_system: src.ptinfo.pti_threads_system, + pti_policy: src.ptinfo.pti_policy, + pti_faults: src.ptinfo.pti_faults, + pti_pageins: src.ptinfo.pti_pageins, + pti_cow_faults: src.ptinfo.pti_cow_faults, + pti_messages_sent: src.ptinfo.pti_messages_sent, + pti_messages_received: src.ptinfo.pti_messages_received, + pti_syscalls_mach: src.ptinfo.pti_syscalls_mach, + pti_syscalls_unix: src.ptinfo.pti_syscalls_unix, + pti_csw: src.ptinfo.pti_csw, + pti_threadnum: src.ptinfo.pti_threadnum, + pti_numrunning: src.ptinfo.pti_numrunning, + pti_priority: src.ptinfo.pti_priority, + }; + TaskAllInfo { pbsd, ptinfo } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + if let Some(path) = &self.curr_path { + if !path.cmd.is_empty() { + let mut cmd = path + .cmd + .iter() + .cloned() + .map(|mut x| { + x.push(' '); + x + }) + .collect::(); + cmd.pop(); + cmd = cmd.replace("\n", " ").replace("\t", " "); + cmd + } else { + String::from("") + } + } else { + String::from("") + } + } + + /// Get the status of the process + pub fn status(&self) -> String { + let mut state = 7; + for t in &self.curr_threads { + let s = match t.pth_run_state { + 1 => 1, // TH_STATE_RUNNING + 2 => 5, // TH_STATE_STOPPED + 3 => { + if t.pth_sleep_time > 20 { + 4 + } else { + 3 + } + } // TH_STATE_WAITING + 4 => 2, // TH_STATE_UNINTERRUPTIBLE + 5 => 6, // TH_STATE_HALTED + _ => 7, + }; + state = cmp::min(s, state); + } + let state = match state { + 0 => "", + 1 => "Running", + 2 => "Uninterruptible", + 3 => "Sleep", + 4 => "Waiting", + 5 => "Stopped", + 6 => "Halted", + _ => "?", + }; + state.to_string() + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = + self.curr_task.ptinfo.pti_total_user + self.curr_task.ptinfo.pti_total_system; + let prev_time = + self.prev_task.ptinfo.pti_total_user + self.prev_task.ptinfo.pti_total_system; + let usage_ms = (curr_time - prev_time) / 1000000u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.curr_task.ptinfo.pti_resident_size + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.curr_task.ptinfo.pti_virtual_size + } +} diff --git a/crates/nu-system/src/main.rs b/crates/nu-system/src/main.rs new file mode 100644 index 0000000000..f0ad96ee68 --- /dev/null +++ b/crates/nu-system/src/main.rs @@ -0,0 +1,17 @@ +use std::time::Duration; + +fn main() { + for proc in nu_system::collect_proc(Duration::from_millis(100), false) { + // if proc.cpu_usage() > 0.1 { + println!( + "{} - {} - {} - {:.1} - {}M - {}M", + proc.pid(), + proc.name(), + proc.status(), + proc.cpu_usage(), + proc.mem_size() / (1024 * 1024), + proc.virtual_size() / (1024 * 1024), + ) + // } + } +} diff --git a/crates/nu-system/src/windows.rs b/crates/nu-system/src/windows.rs new file mode 100644 index 0000000000..7cca387898 --- /dev/null +++ b/crates/nu-system/src/windows.rs @@ -0,0 +1,689 @@ +use chrono::offset::TimeZone; +use chrono::{Local, NaiveDate}; +use libc::c_void; +use std::cell::RefCell; +use std::collections::HashMap; +use std::mem::{size_of, zeroed}; +use std::ptr; +use std::thread; +use std::time::{Duration, Instant}; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, MAX_PATH}; +use winapi::um::handleapi::CloseHandle; +use winapi::um::processthreadsapi::{ + GetCurrentProcess, GetPriorityClass, GetProcessTimes, OpenProcess, OpenProcessToken, +}; +use winapi::um::psapi::{ + EnumProcessModulesEx, GetModuleBaseNameW, GetProcessMemoryInfo, K32EnumProcesses, + LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, +}; +use winapi::um::securitybaseapi::{AdjustTokenPrivileges, GetTokenInformation}; +use winapi::um::tlhelp32::{ + CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, +}; +use winapi::um::winbase::{GetProcessIoCounters, LookupAccountSidW, LookupPrivilegeValueW}; +use winapi::um::winnt::{ + TokenGroups, TokenUser, HANDLE, IO_COUNTERS, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PSID, + SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED, SID, TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, + TOKEN_PRIVILEGES, TOKEN_QUERY, TOKEN_USER, +}; + +pub struct ProcessInfo { + pub pid: i32, + pub command: String, + pub ppid: i32, + pub start_time: chrono::DateTime, + pub cpu_info: CpuInfo, + pub memory_info: MemoryInfo, + pub disk_info: DiskInfo, + pub user: SidName, + pub groups: Vec, + pub priority: u32, + pub thread: i32, + pub interval: Duration, +} + +#[derive(Default)] +pub struct MemoryInfo { + pub page_fault_count: u64, + pub peak_working_set_size: u64, + pub working_set_size: u64, + pub quota_peak_paged_pool_usage: u64, + pub quota_paged_pool_usage: u64, + pub quota_peak_non_paged_pool_usage: u64, + pub quota_non_paged_pool_usage: u64, + pub page_file_usage: u64, + pub peak_page_file_usage: u64, + pub private_usage: u64, +} + +#[derive(Default)] +pub struct DiskInfo { + pub prev_read: u64, + pub prev_write: u64, + pub curr_read: u64, + pub curr_write: u64, +} + +#[derive(Default)] +pub struct CpuInfo { + pub prev_sys: u64, + pub prev_user: u64, + pub curr_sys: u64, + pub curr_user: u64, +} + +#[cfg_attr(tarpaulin, skip)] +pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut ret = Vec::new(); + + let _ = set_privilege(); + + for pid in get_pids() { + let handle = get_handle(pid); + + if let Some(handle) = handle { + let times = get_times(handle); + let io = get_io(handle); + + let time = Instant::now(); + + if let (Some((_, _, sys, user)), Some((read, write))) = (times, io) { + base_procs.push((pid, sys, user, read, write, time)); + } + } + } + + thread::sleep(interval); + + let (mut ppids, mut threads) = get_ppid_threads(); + + for (pid, prev_sys, prev_user, prev_read, prev_write, prev_time) in base_procs { + let ppid = ppids.remove(&pid); + let thread = threads.remove(&pid); + let handle = get_handle(pid); + + if let Some(handle) = handle { + let command = get_command(handle); + let memory_info = get_memory_info(handle); + let times = get_times(handle); + let io = get_io(handle); + + let start_time = if let Some((start, _, _, _)) = times { + let time = chrono::Duration::seconds(start as i64 / 10_000_000); + let base = NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0); + let time = base + time; + Local.from_utc_datetime(&time) + } else { + Local.from_utc_datetime(&NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0)) + }; + + let cpu_info = if let Some((_, _, curr_sys, curr_user)) = times { + Some(CpuInfo { + prev_sys, + prev_user, + curr_sys, + curr_user, + }) + } else { + None + }; + + let disk_info = if let Some((curr_read, curr_write)) = io { + Some(DiskInfo { + prev_read, + prev_write, + curr_read, + curr_write, + }) + } else { + None + }; + + let user = get_user(handle); + let groups = get_groups(handle); + + let priority = get_priority(handle); + + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + + let mut all_ok = true; + all_ok &= command.is_some(); + all_ok &= cpu_info.is_some(); + all_ok &= memory_info.is_some(); + all_ok &= disk_info.is_some(); + all_ok &= user.is_some(); + all_ok &= groups.is_some(); + all_ok &= thread.is_some(); + + if all_ok { + let command = command.unwrap_or_default(); + let ppid = ppid.unwrap_or(0); + let cpu_info = cpu_info.unwrap_or_default(); + let memory_info = memory_info.unwrap_or_default(); + let disk_info = disk_info.unwrap_or_default(); + let user = user.unwrap_or_else(|| SidName { + sid: vec![], + name: None, + domainname: None, + }); + let groups = groups.unwrap_or_else(Vec::new); + let thread = thread.unwrap_or_default(); + + let proc = ProcessInfo { + pid, + command, + ppid, + start_time, + cpu_info, + memory_info, + disk_info, + user, + groups, + priority, + thread, + interval, + }; + + ret.push(proc); + } + + unsafe { + CloseHandle(handle); + } + } + } + + ret +} + +#[cfg_attr(tarpaulin, skip)] +fn set_privilege() -> bool { + unsafe { + let handle = GetCurrentProcess(); + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, &mut token); + if ret == 0 { + return false; + } + + let mut tps: TOKEN_PRIVILEGES = zeroed(); + let se_debug_name: Vec = format!("{}\0", SE_DEBUG_NAME).encode_utf16().collect(); + tps.PrivilegeCount = 1; + let ret = LookupPrivilegeValueW( + ptr::null(), + se_debug_name.as_ptr(), + &mut tps.Privileges[0].Luid, + ); + if ret == 0 { + return false; + } + + tps.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + let ret = AdjustTokenPrivileges( + token, + FALSE, + &mut tps, + 0, + ptr::null::() as *mut TOKEN_PRIVILEGES, + ptr::null::() as *mut u32, + ); + if ret == 0 { + return false; + } + + true + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_pids() -> Vec { + let dword_size = size_of::(); + let mut pids: Vec = Vec::with_capacity(10192); + let mut cb_needed = 0; + + unsafe { + pids.set_len(10192); + let result = K32EnumProcesses( + pids.as_mut_ptr(), + (dword_size * pids.len()) as DWORD, + &mut cb_needed, + ); + if result == 0 { + return Vec::new(); + } + let pids_len = cb_needed / dword_size as DWORD; + pids.set_len(pids_len as usize); + } + + pids.iter().map(|x| *x as i32).collect() +} + +#[cfg_attr(tarpaulin, skip)] +fn get_ppid_threads() -> (HashMap, HashMap) { + let mut ppids = HashMap::new(); + let mut threads = HashMap::new(); + + unsafe { + let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + let mut entry: PROCESSENTRY32 = zeroed(); + entry.dwSize = size_of::() as u32; + let mut not_the_end = Process32First(snapshot, &mut entry); + + while not_the_end != 0 { + ppids.insert(entry.th32ProcessID as i32, entry.th32ParentProcessID as i32); + threads.insert(entry.th32ProcessID as i32, entry.cntThreads as i32); + not_the_end = Process32Next(snapshot, &mut entry); + } + + CloseHandle(snapshot); + } + + (ppids, threads) +} + +#[cfg_attr(tarpaulin, skip)] +fn get_handle(pid: i32) -> Option { + if pid == 0 { + return None; + } + + let handle = unsafe { + OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, + pid as DWORD, + ) + }; + + if handle.is_null() { + None + } else { + Some(handle) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_times(handle: HANDLE) -> Option<(u64, u64, u64, u64)> { + unsafe { + let mut start: FILETIME = zeroed(); + let mut exit: FILETIME = zeroed(); + let mut sys: FILETIME = zeroed(); + let mut user: FILETIME = zeroed(); + + let ret = GetProcessTimes( + handle, + &mut start as *mut FILETIME, + &mut exit as *mut FILETIME, + &mut sys as *mut FILETIME, + &mut user as *mut FILETIME, + ); + + let start = u64::from(start.dwHighDateTime) << 32 | u64::from(start.dwLowDateTime); + let exit = u64::from(exit.dwHighDateTime) << 32 | u64::from(exit.dwLowDateTime); + let sys = u64::from(sys.dwHighDateTime) << 32 | u64::from(sys.dwLowDateTime); + let user = u64::from(user.dwHighDateTime) << 32 | u64::from(user.dwLowDateTime); + + if ret != 0 { + Some((start, exit, sys, user)) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_memory_info(handle: HANDLE) -> Option { + unsafe { + let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); + let ret = GetProcessMemoryInfo( + handle, + &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void + as *mut PROCESS_MEMORY_COUNTERS, + size_of::() as DWORD, + ); + + if ret != 0 { + let info = MemoryInfo { + page_fault_count: u64::from(pmc.PageFaultCount), + peak_working_set_size: pmc.PeakWorkingSetSize as u64, + working_set_size: pmc.WorkingSetSize as u64, + quota_peak_paged_pool_usage: pmc.QuotaPeakPagedPoolUsage as u64, + quota_paged_pool_usage: pmc.QuotaPagedPoolUsage as u64, + quota_peak_non_paged_pool_usage: pmc.QuotaPeakNonPagedPoolUsage as u64, + quota_non_paged_pool_usage: pmc.QuotaNonPagedPoolUsage as u64, + page_file_usage: pmc.PagefileUsage as u64, + peak_page_file_usage: pmc.PeakPagefileUsage as u64, + private_usage: pmc.PrivateUsage as u64, + }; + Some(info) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_command(handle: HANDLE) -> Option { + unsafe { + let mut exe_buf = [0u16; MAX_PATH + 1]; + let mut h_mod = std::ptr::null_mut(); + let mut cb_needed = 0; + + let ret = EnumProcessModulesEx( + handle, + &mut h_mod, + size_of::() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ); + if ret == 0 { + return None; + } + + let ret = GetModuleBaseNameW(handle, h_mod, exe_buf.as_mut_ptr(), MAX_PATH as DWORD + 1); + + let mut pos = 0; + for x in exe_buf.iter() { + if *x == 0 { + break; + } + pos += 1; + } + + if ret != 0 { + Some(String::from_utf16_lossy(&exe_buf[..pos])) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_io(handle: HANDLE) -> Option<(u64, u64)> { + unsafe { + let mut io: IO_COUNTERS = zeroed(); + let ret = GetProcessIoCounters(handle, &mut io); + + if ret != 0 { + Some((io.ReadTransferCount, io.WriteTransferCount)) + } else { + None + } + } +} + +pub struct SidName { + pub sid: Vec, + pub name: Option, + pub domainname: Option, +} + +#[cfg_attr(tarpaulin, skip)] +fn get_user(handle: HANDLE) -> Option { + unsafe { + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token); + + if ret == 0 { + return None; + } + + let mut cb_needed = 0; + let _ = GetTokenInformation( + token, + TokenUser, + ptr::null::() as *mut c_void, + 0, + &mut cb_needed, + ); + + let mut buf: Vec = Vec::with_capacity(cb_needed as usize); + + let ret = GetTokenInformation( + token, + TokenUser, + buf.as_mut_ptr() as *mut c_void, + cb_needed, + &mut cb_needed, + ); + buf.set_len(cb_needed as usize); + + if ret == 0 { + return None; + } + + #[allow(clippy::cast_ptr_alignment)] + let token_user = buf.as_ptr() as *const TOKEN_USER; + let psid = (*token_user).User.Sid; + + let sid = get_sid(psid); + let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) { + (Some(x), Some(y)) + } else { + (None, None) + }; + + Some(SidName { + sid, + name, + domainname, + }) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_groups(handle: HANDLE) -> Option> { + unsafe { + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token); + + if ret == 0 { + return None; + } + + let mut cb_needed = 0; + let _ = GetTokenInformation( + token, + TokenGroups, + ptr::null::() as *mut c_void, + 0, + &mut cb_needed, + ); + + let mut buf: Vec = Vec::with_capacity(cb_needed as usize); + + let ret = GetTokenInformation( + token, + TokenGroups, + buf.as_mut_ptr() as *mut c_void, + cb_needed, + &mut cb_needed, + ); + buf.set_len(cb_needed as usize); + + if ret == 0 { + return None; + } + + #[allow(clippy::cast_ptr_alignment)] + let token_groups = buf.as_ptr() as *const TOKEN_GROUPS; + + let mut ret = Vec::new(); + let sa = (*token_groups).Groups.as_ptr(); + for i in 0..(*token_groups).GroupCount { + let psid = (*sa.offset(i as isize)).Sid; + let sid = get_sid(psid); + let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) { + (Some(x), Some(y)) + } else { + (None, None) + }; + + let sid_name = SidName { + sid, + name, + domainname, + }; + ret.push(sid_name); + } + + Some(ret) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_sid(psid: PSID) -> Vec { + unsafe { + let mut ret = Vec::new(); + let psid = psid as *const SID; + + let mut ia = 0; + ia |= u64::from((*psid).IdentifierAuthority.Value[0]) << 40; + ia |= u64::from((*psid).IdentifierAuthority.Value[1]) << 32; + ia |= u64::from((*psid).IdentifierAuthority.Value[2]) << 24; + ia |= u64::from((*psid).IdentifierAuthority.Value[3]) << 16; + ia |= u64::from((*psid).IdentifierAuthority.Value[4]) << 8; + ia |= u64::from((*psid).IdentifierAuthority.Value[5]); + + ret.push(u64::from((*psid).Revision)); + ret.push(ia); + let cnt = (*psid).SubAuthorityCount; + let sa = (*psid).SubAuthority.as_ptr(); + for i in 0..cnt { + ret.push(u64::from(*sa.offset(i as isize))); + } + + ret + } +} + +thread_local!( + pub static NAME_CACHE: RefCell>> = + RefCell::new(HashMap::new()); +); + +#[cfg_attr(tarpaulin, skip)] +fn get_name_cached(psid: PSID) -> Option<(String, String)> { + NAME_CACHE.with(|c| { + let mut c = c.borrow_mut(); + if let Some(x) = c.get(&psid) { + x.clone() + } else { + let x = get_name(psid); + c.insert(psid, x.clone()); + x + } + }) +} + +#[cfg_attr(tarpaulin, skip)] +fn get_name(psid: PSID) -> Option<(String, String)> { + unsafe { + let mut cc_name = 0; + let mut cc_domainname = 0; + let mut pe_use = 0; + let _ = LookupAccountSidW( + ptr::null::() as *mut u16, + psid, + ptr::null::() as *mut u16, + &mut cc_name, + ptr::null::() as *mut u16, + &mut cc_domainname, + &mut pe_use, + ); + + if cc_name == 0 || cc_domainname == 0 { + return None; + } + + let mut name: Vec = Vec::with_capacity(cc_name as usize); + let mut domainname: Vec = Vec::with_capacity(cc_domainname as usize); + name.set_len(cc_name as usize); + domainname.set_len(cc_domainname as usize); + let ret = LookupAccountSidW( + ptr::null::() as *mut u16, + psid, + name.as_mut_ptr() as *mut u16, + &mut cc_name, + domainname.as_mut_ptr() as *mut u16, + &mut cc_domainname, + &mut pe_use, + ); + + if ret == 0 { + return None; + } + + let name = from_wide_ptr(name.as_ptr()); + let domainname = from_wide_ptr(domainname.as_ptr()); + Some((name, domainname)) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn from_wide_ptr(ptr: *const u16) -> String { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + unsafe { + assert!(!ptr.is_null()); + let len = (0..std::isize::MAX) + .position(|i| *ptr.offset(i) == 0) + .unwrap_or_default(); + let slice = std::slice::from_raw_parts(ptr, len); + OsString::from_wide(slice).to_string_lossy().into_owned() + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_priority(handle: HANDLE) -> u32 { + unsafe { GetPriorityClass(handle) } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + self.command.clone() + } + + /// Get the status of the process + pub fn status(&self) -> String { + "unknown".to_string() + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = self.cpu_info.curr_sys + self.cpu_info.curr_user; + let prev_time = self.cpu_info.prev_sys + self.cpu_info.prev_user; + + let usage_ms = (curr_time - prev_time) / 10000u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.memory_info.working_set_size + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.memory_info.private_usage + } +}