use std::cmp;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::time::{Duration, Instant};
use nix::sys::signal::{kill, Signal};
use nix::unistd;
use crate::common::NetConnectionType;
use crate::memory;
use crate::process::{
psutil_error_to_process_error, MemType, MemoryInfo, OpenFile, ProcessCpuTimes, ProcessError,
ProcessResult, Status,
};
use crate::utils::duration_percent;
use crate::{Count, Percent, Pid};
#[cfg(target_os = "linux")]
use crate::process::os::linux::ProcfsStat;
#[derive(Clone, Debug)]
pub struct Process {
pub(crate) pid: Pid,
pub(crate) create_time: Duration,
pub(crate) busy: Duration,
pub(crate) instant: Instant,
#[cfg(target_os = "linux")]
pub(crate) procfs_stat: ProcfsStat,
}
impl Process {
pub fn new(pid: Pid) -> ProcessResult<Process> {
Process::sys_new(pid)
}
pub fn current() -> ProcessResult<Process> {
Process::new(std::process::id())
}
pub fn pid(&self) -> Pid {
self.pid
}
pub fn ppid(&self) -> ProcessResult<Option<Pid>> {
self.sys_ppid()
}
pub fn name(&self) -> ProcessResult<String> {
self.sys_name()
}
pub fn exe(&self) -> ProcessResult<PathBuf> {
self.sys_exe()
}
pub fn cmdline(&self) -> ProcessResult<Option<String>> {
self.sys_cmdline()
}
pub fn cmdline_vec(&self) -> ProcessResult<Option<Vec<String>>> {
self.sys_cmdline_vec()
}
pub fn create_time(&self) -> Duration {
self.create_time
}
pub fn parent(&self) -> ProcessResult<Option<Process>> {
if !self.is_running() {
return Err(ProcessError::NoSuchProcess { pid: self.pid });
}
self.ppid()?.map(Process::new).transpose()
}
pub fn parents(&self) -> Option<Vec<Process>> {
self.sys_parents()
}
pub fn status(&self) -> ProcessResult<Status> {
self.sys_status()
}
pub fn cwd(&self) -> ProcessResult<PathBuf> {
self.sys_cwd()
}
pub fn username(&self) -> String {
self.sys_username()
}
pub fn get_nice(&self) -> i32 {
self.sys_get_nice()
}
pub fn set_nice(&self, nice: i32) {
self.sys_set_nice(nice)
}
pub fn num_ctx_switches(&self) -> Count {
self.sys_num_ctx_switches()
}
pub fn num_threads(&self) -> Count {
self.sys_num_threads()
}
pub fn threads(&self) {
self.sys_threads()
}
pub fn cpu_times(&self) -> ProcessResult<ProcessCpuTimes> {
self.sys_cpu_times()
}
pub fn cpu_percent(&mut self) -> ProcessResult<Percent> {
let busy = self.cpu_times()?.busy();
let instant = Instant::now();
let percent = duration_percent(
busy.checked_sub(self.busy).unwrap_or_default(),
instant - self.instant,
);
self.busy = busy;
self.instant = instant;
Ok(percent)
}
pub fn memory_info(&self) -> ProcessResult<MemoryInfo> {
self.sys_memory_info()
}
pub fn memory_full_info(&self) {
self.sys_memory_full_info()
}
pub fn memory_percent(&self) -> ProcessResult<Percent> {
let virtual_memory =
memory::virtual_memory().map_err(|e| psutil_error_to_process_error(e, self.pid))?;
self.memory_percent_oneshot(&virtual_memory)
}
pub fn memory_percent_oneshot(
&self,
virtual_memory: &memory::VirtualMemory,
) -> ProcessResult<Percent> {
let memory_info = self.memory_info()?;
let percent = (memory_info.rss() as f64 / virtual_memory.total() as f64) * 100.0;
Ok(percent as f32)
}
pub fn memory_percent_with_type(&self, r#type: MemType) -> ProcessResult<Percent> {
self.sys_memory_percent_with_type(r#type)
}
pub fn children(&self) {
self.sys_children()
}
pub fn open_files(&self) -> ProcessResult<Vec<OpenFile>> {
self.sys_open_files()
}
pub fn connections(&self) {
self.sys_connections()
}
pub fn connections_with_type(&self, type_: NetConnectionType) {
self.sys_connections_with_type(type_)
}
pub fn is_running(&self) -> bool {
match Process::new(self.pid) {
Ok(p) => p == *self,
Err(_) => false,
}
}
pub fn is_replaced(&self) -> bool {
match Process::new(self.pid) {
Ok(p) => p != *self,
Err(_) => false,
}
}
pub fn replace(&mut self) -> bool {
match Process::new(self.pid) {
Ok(p) if p != *self => {
*self = p;
true
}
_ => false,
}
}
pub fn send_signal(&self, signal: Signal) -> ProcessResult<()> {
if !self.is_running() {
return Err(ProcessError::NoSuchProcess { pid: self.pid });
}
#[cfg(target_family = "unix")]
{
kill(unistd::Pid::from_raw(self.pid as i32), signal)
.map_err(From::from)
.map_err(|e| psutil_error_to_process_error(e, self.pid))
}
#[cfg(not(any(target_family = "unix")))]
{
todo!()
}
}
pub fn suspend(&self) -> ProcessResult<()> {
#[cfg(target_family = "unix")]
{
self.send_signal(Signal::SIGSTOP)
}
#[cfg(not(any(target_family = "unix")))]
{
todo!()
}
}
pub fn resume(&self) -> ProcessResult<()> {
#[cfg(target_family = "unix")]
{
self.send_signal(Signal::SIGCONT)
}
#[cfg(not(any(target_family = "unix")))]
{
todo!()
}
}
pub fn terminate(&self) -> ProcessResult<()> {
self.send_signal(Signal::SIGTERM)
}
pub fn kill(&self) -> ProcessResult<()> {
#[cfg(target_family = "unix")]
{
self.send_signal(Signal::SIGKILL)
}
#[cfg(not(any(target_family = "unix")))]
{
todo!()
}
}
pub fn wait(&self) {
self.sys_wait()
}
}
impl PartialEq for Process {
fn eq(&self, other: &Process) -> bool {
(self.pid() == other.pid()) && (self.create_time() == other.create_time())
}
}
impl cmp::Eq for Process {}
impl Hash for Process {
fn hash<H: Hasher>(&self, state: &mut H) {
self.pid().hash(state);
self.create_time().hash(state);
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::process::processes;
#[test]
fn test_process_exe() {
assert!(Process::current().unwrap().exe().is_ok());
}
#[test]
fn test_process_cmdline() {
assert!(Process::current().unwrap().cmdline().is_ok());
}
#[test]
fn test_process_cwd() {
assert!(Process::current().unwrap().cwd().is_ok());
}
#[test]
fn test_process_equality() {
assert_eq!(Process::current().unwrap(), Process::current().unwrap());
}
#[test]
fn test_process_inequality() {
assert_ne!(Process::current().unwrap(), Process::new(1).unwrap());
}
#[test]
fn test_processes() {
processes().unwrap();
}
}