use crate::hist::History;
use crate::ps::Overlay;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::{Duration, Instant};
use sysinfo::{
CpuRefreshKind, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, Signal, System,
Uid, UpdateKind,
};
pub struct Proc {
pub pid: u32,
pub name: String,
pub user: Arc<str>,
pub cpu: f32,
pub mem: u64,
pub command: String,
pub start: u64,
pub search: String,
}
struct Users {
names: HashMap<Uid, Arc<str>>,
asked: HashSet<Uid>,
wait: bool,
tx: Sender<(Uid, Arc<str>)>,
rx: Receiver<(Uid, Arc<str>)>,
}
impl Users {
fn new() -> Self {
let (tx, rx) = channel();
Users {
names: sysinfo::Users::new_with_refreshed_list()
.iter()
.map(|u| (u.id().clone(), u.name().into()))
.collect(),
asked: HashSet::new(),
wait: true,
tx,
rx,
}
}
fn receive(&mut self) {
self.names.extend(self.rx.try_iter());
}
fn name(&mut self, uid: &Uid) -> Arc<str> {
if let Some(n) = self.names.get(uid) {
return n.clone();
}
if self.wait {
let n = user_name(uid);
self.names.insert(uid.clone(), n.clone());
return n;
}
if self.asked.insert(uid.clone()) {
let (uid, tx) = (uid.clone(), self.tx.clone());
std::thread::spawn(move || {
let n = user_name(&uid);
let _ = tx.send((uid, n));
});
}
uid.to_string().into()
}
}
pub struct Sys {
sys: System,
users: Users,
reread: Instant,
ps: Overlay,
pub host: String,
pub cpu_brand: String,
pub cores: Vec<f32>,
pub cpu: f32,
pub mem_used: u64,
pub mem_total: u64,
pub swap_used: u64,
pub swap_total: u64,
pub hist_cpu: History,
pub hist_mem: History,
pub procs: Vec<Proc>,
}
const REREAD: Duration = Duration::from_secs(5);
fn proc_refresh(names: UpdateKind) -> ProcessRefreshKind {
ProcessRefreshKind::nothing()
.with_cpu()
.with_memory()
.with_cmd(names)
.with_user(names)
.without_tasks()
}
impl Sys {
pub fn new() -> Self {
let mut sys = System::new();
sys.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage());
sys.refresh_processes_specifics(
ProcessesToUpdate::All,
true,
proc_refresh(UpdateKind::OnlyIfNotSet),
);
let cpu_brand = sys
.cpus()
.first()
.map(|c| c.brand().trim().to_string())
.unwrap_or_default();
let mut s = Sys {
sys,
users: Users::new(),
reread: Instant::now(),
ps: Overlay::new(),
host: System::host_name().unwrap_or_default(),
cpu_brand,
cores: Vec::new(),
cpu: 0.0,
mem_used: 0,
mem_total: 0,
swap_used: 0,
swap_total: 0,
hist_cpu: History::default(),
hist_mem: History::default(),
procs: Vec::new(),
};
std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
s.sample();
s.users.wait = false;
s
}
pub fn sample(&mut self) {
let sys = &mut self.sys;
sys.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage());
sys.refresh_memory_specifics(MemoryRefreshKind::everything());
let names = if self.reread.elapsed() >= REREAD {
self.reread = Instant::now();
UpdateKind::Always
} else {
UpdateKind::OnlyIfNotSet
};
sys.refresh_processes_specifics(ProcessesToUpdate::All, true, proc_refresh(names));
self.cores = sys.cpus().iter().map(|c| c.cpu_usage()).collect();
self.cpu = sys.global_cpu_usage();
self.mem_used = sys.used_memory();
self.mem_total = sys.total_memory();
self.swap_used = sys.used_swap();
self.swap_total = sys.total_swap();
self.hist_cpu.push(self.cpu as f64);
self.hist_mem
.push(100.0 * frac(self.mem_used, self.mem_total));
let users = &mut self.users;
users.receive();
self.ps.refresh();
let ps = &self.ps;
self.procs = sys
.processes()
.values()
.filter(|p| p.thread_kind().is_none())
.map(|p| {
let pid = p.pid().as_u32();
let name = p.name().to_string_lossy().into_owned();
let mut command = String::new();
for a in p.cmd() {
if !command.is_empty() {
command.push(' ');
}
command.push_str(&a.to_string_lossy());
}
let mut user = p.effective_user_id().or(p.user_id()).map(|u| users.name(u));
let mut mem = p.memory();
ps.fill(
pid,
&name,
p.start_time(),
&mut user,
&mut mem,
&mut command,
);
let command = if command.is_empty() {
format!("[{name}]")
} else {
command
};
let user = user.unwrap_or_else(|| "?".into());
let search = format!("{pid} {user} {name} {command}").to_lowercase();
Proc {
pid,
name,
user,
cpu: p.cpu_usage(),
mem,
command,
start: p.start_time(),
search,
}
})
.collect();
}
pub fn signal(&mut self, pid: u32, start: u64, signal: Signal) -> Result<(), String> {
if !signalable(pid) {
return Err(format!("process {pid} cannot be signalled"));
}
let id = Pid::from_u32(pid);
self.sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[id]),
true,
ProcessRefreshKind::nothing(),
);
let p = self
.sys
.process(id)
.filter(|p| p.start_time() == start)
.ok_or_else(|| format!("process {pid} has ended"))?;
match p.kill_with(signal) {
Some(true) => Ok(()),
Some(false) => Err(format!("cannot signal {pid}: not permitted, or gone")),
None => Err(format!("{signal:?} is not supported here")),
}
}
}
pub fn signalable(pid: u32) -> bool {
pid != 0
}
fn user_name(uid: &Uid) -> Arc<str> {
let uid = uid.to_string();
std::process::Command::new("id")
.args(["-nu", &uid])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.filter(|n| !n.is_empty())
.unwrap_or(uid)
.into()
}
pub fn frac(used: u64, total: u64) -> f64 {
if total == 0 {
0.0
} else {
used as f64 / total as f64
}
}
#[cfg(test)]
mod tests {
use super::signalable;
#[test]
fn pid_0_is_never_signalled() {
assert!(!signalable(0));
assert!(signalable(1));
}
}