use regex::Regex;
use std::{collections::HashMap, fmt};
use ansi_term::Color;
use std::error::Error;
use std::process::Command;
pub fn get_all_process() -> Result<String, Box<dyn Error>> {
let output = Command::new("ps")
.arg("axo")
.arg("pid,%cpu,%mem,command")
.output()?;
let result = String::from_utf8(output.stdout)?;
return Ok(result);
}
pub fn get_jps_pid_name_map() -> Result<HashMap<String, String>, Box<dyn Error>> {
let output = Command::new("jps").output()?;
let str_out = String::from_utf8(output.stdout)?;
let re = Regex::new(r"(\d+)\s*(.*)")?;
let result: HashMap<String, String> = str_out
.lines()
.filter_map(|line| {
let captures: Vec<regex::Captures> = re.captures_iter(line).collect();
if captures.len() != 1 {
return None;
}
captures
.get(0)
.map(|capture| (capture[1].to_owned(), capture[2].to_owned()))
})
.collect();
Ok(result)
}
pub fn filter_java_process(ps_output: String) -> Result<Vec<Process>, Box<dyn Error>> {
let pid_to_name = get_jps_pid_name_map()?;
let java_processes: Vec<Process> = ps_output
.lines()
.filter_map(|line| {
if line.contains("java") {
let process = match Process::new(line) {
Ok(p) => match pid_to_name.get(&p.pid) {
Some(jps_name) => Some(p.with_name(jps_name)),
None => None,
},
Err(_) => None,
};
process
} else {
None
}
})
.collect();
return Ok(java_processes);
}
pub fn print_processes(processes: &Vec<Process>, highlight_idx: usize) -> String {
let mut res = String::from("");
res = res + &(format!("{:>8}{:>6}%{:>6}% {}\r\n", "PID", "CPU", "RAM", "Comm"));
for (idx, p) in processes.iter().enumerate() {
if idx == highlight_idx {
res = res
+ &(format!(
"{}\r\n",
Color::Black.on(Color::White).paint(format!("{}", p))
))
} else {
res = res + &(format!("{}\r\n", p))
}
}
return res;
}
pub fn kill_process(process: &Process) -> Result<(), Box<dyn Error>> {
Command::new("kill")
.arg("-9")
.arg(format!("{}", process.pid))
.output()?;
return Ok(());
}
#[derive(Debug, Clone)]
enum Errors {
ParseInputError = 1,
}
impl fmt::Display for Errors {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let error_text = match self {
Errors::ParseInputError => "fail to parse input",
};
write!(f, "{}", error_text)
}
}
impl Error for Errors {}
#[derive(Debug)]
pub struct Process {
pid: String,
cpu: String,
mem: String,
name: String,
}
impl Process {
pub fn new(s: &str) -> Result<Process, Box<dyn Error>> {
let re = Regex::new(r"\s*(\d*)\s*([\d\.]*)\s*([\d\.]*)\s*(.*)\s*")?;
let captures: Vec<regex::Captures> = re.captures_iter(s).collect();
if captures.len() != 1 {
return Err(Errors::ParseInputError.into());
}
let capture = captures.get(0).ok_or(Errors::ParseInputError)?;
Ok(Process {
pid: String::from(&capture[1]),
cpu: String::from(&capture[2]),
mem: String::from(&capture[3]),
name: String::from(&capture[4]),
})
}
fn shorten_name(&self) -> Result<String, Box<dyn Error>> {
let re = Regex::new(r"-jar \S*/(\S*)")?;
let captures: Vec<regex::Captures> = re.captures_iter(&self.name).collect();
if captures.len() != 1 {
return Ok(self.name.clone());
}
let capture = captures.get(0).ok_or(Errors::ParseInputError)?;
return Ok(String::from(&capture[1]));
}
fn with_name(&self, new_name: &String) -> Process {
Process {
pid: self.pid.clone(),
cpu: self.cpu.clone(),
mem: self.mem.clone(),
name: new_name.clone(),
}
}
}
impl fmt::Display for Process {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let shorten_name = self.shorten_name().map_err(|_| fmt::Error)?;
write!(
f,
"{:>8}{:>6}%{:>6}% {}",
self.pid, self.cpu, self.mem, shorten_name
)
}
}