feat: process list

this is unnacceptably unperformant;
I think I've reached the point at which sysinfo doesn't do what I want,
which is keep track of a table of processes efficiently enough.
This commit is contained in:
electria 2026-08-18 14:05:36 -07:00
commit b20158820b
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
4 changed files with 105 additions and 22 deletions

View file

@ -8,7 +8,10 @@ use iced::{
use ron::de; use ron::de;
use smol::{Timer, unblock}; use smol::{Timer, unblock};
use crate::{app, sysinfo_snapshot::SysinfoSnapshot}; use crate::{
app,
sysinfo_snapshot::{self, SysinfoSnapshot},
};
pub fn create() -> Task<app::Message> { pub fn create() -> Task<app::Message> {
if env::args() if env::args()
@ -43,9 +46,7 @@ pub fn create() -> Task<app::Message> {
1, 1,
async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| { async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| {
let mut sys = sysinfo::System::new(); let mut sys = sysinfo::System::new();
let refreshes = sysinfo::RefreshKind::nothing() let refreshes = sysinfo_snapshot::refreshes();
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage());
sys.refresh_specifics(refreshes); sys.refresh_specifics(refreshes);
Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;

View file

@ -8,7 +8,7 @@ use iced::{
use crate::{ use crate::{
app::{graph::Graph, utils::maybe_widget}, app::{graph::Graph, utils::maybe_widget},
sysinfo_snapshot::SysinfoSnapshot, sysinfo_snapshot::{ProcessSnapshot, SysinfoSnapshot},
utils::Toggle, utils::Toggle,
}; };
@ -22,6 +22,7 @@ enum Message {
ToggleMain, ToggleMain,
ToggleCpu, ToggleCpu,
ToggleMemory, ToggleMemory,
ToggleProcesses,
Info(SysinfoSnapshot), Info(SysinfoSnapshot),
} }
@ -39,6 +40,9 @@ struct State {
is_memory_section_shown: bool, is_memory_section_shown: bool,
memory_graph: Graph, memory_graph: Graph,
swap_graph: Graph, swap_graph: Graph,
is_processes_section_shown: bool,
processes: Vec<ProcessSnapshot>,
} }
impl State { impl State {
@ -65,6 +69,10 @@ impl State {
self.is_memory_section_shown.toggle(); self.is_memory_section_shown.toggle();
self.check_borders(); self.check_borders();
} }
Message::ToggleProcesses => {
self.is_processes_section_shown.toggle();
self.check_borders();
}
Message::Info(snapshot) => { Message::Info(snapshot) => {
self.uptime = snapshot.uptime(); self.uptime = snapshot.uptime();
@ -89,6 +97,10 @@ impl State {
.zip(snapshot.cpus.into_iter()) .zip(snapshot.cpus.into_iter())
.for_each(|(graph, value)| graph.update(value)); .for_each(|(graph, value)| graph.update(value));
} }
self.processes = snapshot.processes;
self.processes
.sort_by(|a, b| b.cpu_usage.total_cmp(&a.cpu_usage));
} }
} }
@ -115,6 +127,7 @@ impl State {
}), }),
]) ])
.into(), .into(),
widget::row([
maybe_widget(self.is_memory_section_shown, || { maybe_widget(self.is_memory_section_shown, || {
widget::row([ widget::row([
widget::canvas(&self.memory_graph) widget::canvas(&self.memory_graph)
@ -128,6 +141,25 @@ impl State {
]) ])
.into() .into()
}), }),
maybe_widget(self.is_processes_section_shown, || {
widget::scrollable(
widget::grid(self.processes.iter().flat_map(|process| {
[
widget::text(&process.name).into(),
widget::text(process.cpu_usage).into(),
widget::text(format!("{:?}", process.cpu_time())).into(),
widget::text(process.pid).into(),
]
}))
.height(Length::Shrink)
.columns(4),
)
.width(Length::Fill)
.height(Length::Fill)
.into()
}),
])
.into(),
]) ])
.into() .into()
} }
@ -139,6 +171,7 @@ impl State {
Key::Character("M" | "1") => Some(Message::ToggleMain), Key::Character("M" | "1") => Some(Message::ToggleMain),
Key::Character("c" | "2") => Some(Message::ToggleCpu), Key::Character("c" | "2") => Some(Message::ToggleCpu),
Key::Character("m" | "3") => Some(Message::ToggleMemory), Key::Character("m" | "3") => Some(Message::ToggleMemory),
Key::Character("p" | "4") => Some(Message::ToggleProcesses),
_ => None, _ => None,
} }
} }

View file

@ -2,13 +2,11 @@ use std::{io::Write, thread};
use ron::{Error, ser}; use ron::{Error, ser};
use crate::sysinfo_snapshot::SysinfoSnapshot; use crate::sysinfo_snapshot::{self, SysinfoSnapshot};
pub fn run() -> Result<(), Error> { pub fn run() -> Result<(), Error> {
let mut sys = sysinfo::System::new(); let mut sys = sysinfo::System::new();
let refreshes = sysinfo::RefreshKind::nothing() let refreshes = sysinfo_snapshot::refreshes();
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage());
sys.refresh_specifics(refreshes); sys.refresh_specifics(refreshes);
thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);

View file

@ -1,7 +1,20 @@
use std::time::Duration; use std::time::Duration;
pub fn refreshes() -> sysinfo::RefreshKind {
sysinfo::RefreshKind::nothing()
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage())
.with_processes(
sysinfo::ProcessRefreshKind::nothing()
.with_cpu()
.with_cmd(sysinfo::UpdateKind::OnlyIfNotSet)
.with_exe(sysinfo::UpdateKind::OnlyIfNotSet),
)
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct SysinfoSnapshot { pub struct SysinfoSnapshot {
/// in seconds
uptime: u64, uptime: u64,
pub cpu_total: f32, pub cpu_total: f32,
@ -11,6 +24,8 @@ pub struct SysinfoSnapshot {
pub memory_total: u64, pub memory_total: u64,
pub swap: u64, pub swap: u64,
pub swap_total: u64, pub swap_total: u64,
pub processes: Vec<ProcessSnapshot>,
} }
impl SysinfoSnapshot { impl SysinfoSnapshot {
#[cfg_attr(not(feature = "app"), allow(dead_code))] #[cfg_attr(not(feature = "app"), allow(dead_code))]
@ -31,6 +46,42 @@ impl From<&sysinfo::System> for SysinfoSnapshot {
memory_total: value.total_memory(), memory_total: value.total_memory(),
swap: value.used_swap(), swap: value.used_swap(),
swap_total: value.total_swap(), swap_total: value.total_swap(),
processes: value
.processes()
.iter()
.map(|(_pid, process)| ProcessSnapshot::from(process))
.collect(),
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ProcessSnapshot {
pub pid: usize,
/// in CPU-milliseconds
accumulated_cpu_time: u64,
/// note: it's by core, and can be up to eg 1600 on a 16-core device
pub cpu_usage: f32,
pub name: String,
}
impl ProcessSnapshot {
pub fn cpu_time(&self) -> Duration {
Duration::from_millis(self.accumulated_cpu_time)
}
}
impl From<&sysinfo::Process> for ProcessSnapshot {
fn from(value: &sysinfo::Process) -> Self {
Self {
pid: value.pid().into(),
accumulated_cpu_time: value.accumulated_cpu_time(),
cpu_usage: value.cpu_usage(),
name: value.name().to_string_lossy().into_owned(),
} }
} }
} }