Add nu-system and rewrite ps command (#734)

* Add nu-system and rewrite ps command

* Add more deps

* Add more deps

* clippy

* clippy

* clippy

* clippy

* clippy

* clippy
This commit is contained in:
JT 2022-01-14 17:20:53 +11:00 committed by GitHub
parent 2b6ce4dfe5
commit ca215c1152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1795 additions and 69 deletions

79
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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" }

View File

@ -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<PipelineData, ShellError> {
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

1
crates/nu-system/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

253
crates/nu-system/Cargo.lock generated Normal file
View File

@ -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"

View File

@ -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"

21
crates/nu-system/LICENSE Normal file
View File

@ -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.

View File

@ -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::*;

View File

@ -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<Vec<String>, ProcError> {
match self {
ProcessTask::Process(x) => x.cmdline(),
_ => Err(ProcError::Other("not supported".to_string())),
}
}
pub fn cgroups(&self) -> Result<Vec<ProcessCgroup>, ProcError> {
match self {
ProcessTask::Process(x) => x.cgroups(),
_ => Err(ProcError::Other("not supported".to_string())),
}
}
pub fn fd(&self) -> Result<Vec<FDInfo>, ProcError> {
match self {
ProcessTask::Process(x) => x.fd(),
_ => Err(ProcError::Other("not supported".to_string())),
}
}
pub fn loginuid(&self) -> Result<u32, ProcError> {
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<String, ProcError> {
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<Io>,
pub prev_io: Option<Io>,
pub curr_status: Option<Status>,
pub interval: Duration,
}
pub fn collect_proc(interval: Duration, with_thread: bool) -> Vec<ProcessInfo> {
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<i32, (i32, Stat, Option<Status>, Option<Io>)>) {
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::<Vec<_>>()
.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::<String>();
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
}
}

View File

@ -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<PathInfo>,
pub curr_threads: Vec<ThreadInfo>,
pub curr_udps: Vec<InSockInfo>,
pub curr_tcps: Vec<TcpSockInfo>,
pub curr_res: Option<RUsageInfoV2>,
pub prev_res: Option<RUsageInfoV2>,
pub interval: Duration,
}
#[cfg_attr(tarpaulin, skip)]
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
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::<TaskAllInfo>(p as i32, 0) {
let res = pidrusage::<RUsageInfoV2>(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::<TaskAllInfo>(pid, 0) {
task
} else {
clone_task_all_info(&prev_task)
};
let curr_path = get_path_info(pid, arg_max);
let threadids = listpidinfo::<ListThreads>(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::<ThreadInfo>(pid, t) {
curr_threads.push(thread);
}
}
}
let mut curr_tcps = Vec::new();
let mut curr_udps = Vec::new();
let fds = listpidinfo::<ListFDs>(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::<SocketFDInfo>(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::<RUsageInfoV2>(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::<c_int>();
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<String>,
pub env: Vec<String>,
}
#[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<PathInfo> {
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::<c_int>(),
);
let mut cp = ptr.add(::std::mem::size_of::<c_int>());
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::<Vec<_>>()
.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::<String>();
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
}
}

View File

@ -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),
)
// }
}
}

View File

@ -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<chrono::Local>,
pub cpu_info: CpuInfo,
pub memory_info: MemoryInfo,
pub disk_info: DiskInfo,
pub user: SidName,
pub groups: Vec<SidName>,
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<ProcessInfo> {
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<u16> = 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::<TOKEN_PRIVILEGES>() as *mut TOKEN_PRIVILEGES,
ptr::null::<u32>() as *mut u32,
);
if ret == 0 {
return false;
}
true
}
}
#[cfg_attr(tarpaulin, skip)]
fn get_pids() -> Vec<i32> {
let dword_size = size_of::<DWORD>();
let mut pids: Vec<DWORD> = 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<i32, i32>, HashMap<i32, i32>) {
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::<PROCESSENTRY32>() 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<HANDLE> {
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<MemoryInfo> {
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::<PROCESS_MEMORY_COUNTERS_EX>() 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<String> {
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::<DWORD>() 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<u64>,
pub name: Option<String>,
pub domainname: Option<String>,
}
#[cfg_attr(tarpaulin, skip)]
fn get_user(handle: HANDLE) -> Option<SidName> {
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::<c_void>() as *mut c_void,
0,
&mut cb_needed,
);
let mut buf: Vec<u8> = 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<Vec<SidName>> {
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::<c_void>() as *mut c_void,
0,
&mut cb_needed,
);
let mut buf: Vec<u8> = 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<u64> {
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<HashMap<PSID, Option<(String, String)>>> =
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::<u16>() as *mut u16,
psid,
ptr::null::<u16>() as *mut u16,
&mut cc_name,
ptr::null::<u16>() as *mut u16,
&mut cc_domainname,
&mut pe_use,
);
if cc_name == 0 || cc_domainname == 0 {
return None;
}
let mut name: Vec<u16> = Vec::with_capacity(cc_name as usize);
let mut domainname: Vec<u16> = 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::<u16>() 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::<Vec<_>>()
.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
}
}