diff --git a/Cargo.lock b/Cargo.lock index d771ddf..b9c2f0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,9 +292,6 @@ name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" -dependencies = [ - "serde_core", -] [[package]] name = "block" @@ -1383,8 +1380,6 @@ name = "itop" version = "0.1.0" dependencies = [ "iced", - "ron", - "serde", "smol", "sysinfo", ] @@ -2487,20 +2482,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "ron" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" -dependencies = [ - "bitflags 2.13.1", - "once_cell", - "serde", - "serde_derive", - "typeid", - "unicode-ident", -] - [[package]] name = "roxmltree" version = "0.20.0" @@ -3110,12 +3091,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "uds_windows" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 00abd23..5a74d3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,23 +5,10 @@ license = "AGPL-3.0-or-later" version = "0.1.0" edition = "2024" -[features] -default = [ "app" ] -app = [ "dep:iced", "dep:smol" ] - [dependencies] -ron = "0.12.2" +smol = "2.0.2" sysinfo = "0.39.6" [dependencies.iced] version = "0.14.0" features = [ "canvas", "smol" ] -optional = true - -[dependencies.smol] -version = "2.0.2" -optional = true - -[dependencies.serde] -version = "1.0.229" -features = [ "derive" ] diff --git a/flake.nix b/flake.nix index 1a338f0..827d996 100644 --- a/flake.nix +++ b/flake.nix @@ -58,6 +58,7 @@ desktopItem = pkgs.makeDesktopItem { inherit name; desktopName = name; + icon = name; exec = name; }; in @@ -88,13 +89,6 @@ { packages.default = crate; - packages."${name}-server-only" = craneLib.buildPackage { - name = "${name}-server-only"; - src = ./.; - doCheck = false; - cargoExtraArgs = "--locked --no-default-features"; - }; - checks = { crate-clippy = craneLib.cargoClippy ( commonArgs diff --git a/src/app/graph.rs b/src/app/graph.rs index 5dea431..8a3796a 100644 --- a/src/app/graph.rs +++ b/src/app/graph.rs @@ -6,42 +6,18 @@ use crate::app; const CYAN: Color = Color::from_rgb8(0, 255, 255); +#[derive(Default)] pub struct Graph { - timeline: VecDeque, - - pub show_borders: bool, pub cache: canvas::Cache, -} -impl Default for Graph { - fn default() -> Self { - Self { - show_borders: true, - - timeline: Default::default(), - cache: Default::default(), - } - } + timeline: VecDeque, } impl Graph { - pub fn new(value: f32) -> Self { - Self { - timeline: { - let mut vec = VecDeque::new(); - vec.push_front(value); - vec - }, - ..Default::default() - } - } - pub fn update(&mut self, value: f32) { self.timeline.push_back(value); - if self.timeline.len() > 500 { + if self.timeline.len() > 1000 { self.timeline.pop_front(); } - - self.cache.clear(); } } @@ -56,19 +32,11 @@ impl canvas::Program for Graph { _cursor: mouse::Cursor, ) -> Vec> { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { - if self.show_borders { - frame.stroke_rectangle( - Point::new(0., 0.), - bounds.size(), - canvas::Stroke::default().with_color(Color::from_rgb8(255, 0, 0)), - ); - } frame.stroke( &canvas::Path::new(|builder| { for (index, value) in self.timeline.iter().enumerate() { let x = index as f32 / self.timeline.len() as f32 * bounds.width; - let y = (bounds.height - value / 100. * bounds.height) - .clamp(1., bounds.height - 1.); + let y = bounds.height - value / 100. * bounds.height; builder.line_to(Point::new(x, y)) } }), diff --git a/src/app/info_stream.rs b/src/app/info_stream.rs deleted file mode 100644 index 787e459..0000000 --- a/src/app/info_stream.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::env; - -use iced::{ - Task, - futures::{self, SinkExt}, - stream, -}; -use ron::de; -use smol::{Timer, unblock}; - -use crate::{ - app, - sysinfo_snapshot::{self, SysinfoSnapshot}, -}; - -pub fn create() -> Task { - if env::args() - .nth(1) - .is_some_and(|command| command == "client") - { - Task::run( - stream::channel( - 1, - async |mut sender: futures::channel::mpsc::Sender| { - let mut buf = String::new(); - loop { - buf = unblock(move || { - std::io::stdin().read_line(&mut buf).unwrap(); - buf - }) - .await; - - match de::from_str(&buf) { - Ok(info) => sender.send(info).await.unwrap(), - Err(e) => eprintln!("{e}"), - } - buf.clear(); - } - }, - ), - app::Message::Info, - ) - } else { - Task::run( - stream::channel( - 1, - async |mut sender: futures::channel::mpsc::Sender| { - let mut sys = sysinfo::System::new(); - let refreshes = sysinfo_snapshot::refreshes(); - - sys.refresh_specifics(refreshes); - Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; - - loop { - sys.refresh_specifics(refreshes); - sender.send(SysinfoSnapshot::from(&sys)).await.unwrap(); - Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; - } - }, - ), - app::Message::Info, - ) - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs index c636868..f48d29a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,198 +1,66 @@ -use std::time::Duration; - use iced::{ - Color, Error, Event, Length, Renderer, Subscription, Task, Theme, application, event, - keyboard::{self, Key}, - theme, widget, + Color, Element, Length, Renderer, Task, Theme, application, futures::SinkExt, stream, theme, + widget, }; +use smol::Timer; -use crate::{ - app::{graph::Graph, utils::maybe_widget}, - sysinfo_snapshot::{ProcessSnapshot, SysinfoSnapshot}, - utils::Toggle, -}; +use crate::app::graph::Graph; mod graph; -mod info_stream; -mod utils; - -type Element<'a> = iced::Element<'a, Message, Theme, Renderer>; enum Message { - ToggleMain, - ToggleCpu, - ToggleMemory, - ToggleProcesses, - - Info(SysinfoSnapshot), + Cpu(f32), } -#[derive(Default)] struct State { - uptime: Duration, - - is_main_section_shown: bool, - total_cpu_graph: Graph, - - is_cpu_section_shown: bool, - cpu_graphs: Vec, - - is_memory_section_shown: bool, - memory_graph: Graph, - swap_graph: Graph, - - is_processes_section_shown: bool, - processes: Vec, + cpu_graph: Graph, } impl State { fn new() -> (Self, Task) { - let mut state = Self { - is_main_section_shown: true, - ..Default::default() - }; - state.total_cpu_graph.show_borders = false; + ( + Self { + cpu_graph: Default::default(), + }, + Task::run( + stream::channel(1, async |mut sender| { + let mut sys = sysinfo::System::new(); + let refreshes = sysinfo::RefreshKind::nothing() + .with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage()); - (state, info_stream::create()) + sys.refresh_specifics(refreshes); + Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; + + loop { + sys.refresh_specifics(refreshes); + Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; + sender.send(sys.global_cpu_usage()).await.unwrap(); + } + }), + Message::Cpu, + ), + ) } fn update(&mut self, message: Message) -> Task { match message { - Message::ToggleMain => { - self.is_main_section_shown.toggle(); - self.check_borders(); - } - Message::ToggleCpu => { - self.is_cpu_section_shown.toggle(); - self.check_borders(); - } - Message::ToggleMemory => { - self.is_memory_section_shown.toggle(); - self.check_borders(); - } - Message::ToggleProcesses => { - self.is_processes_section_shown.toggle(); - self.check_borders(); - } - - Message::Info(snapshot) => { - self.uptime = snapshot.uptime(); - - self.total_cpu_graph.update(snapshot.cpu_total); - - self.memory_graph - .update(snapshot.memory as f32 / snapshot.memory_total as f32 * 100.); - - self.swap_graph - .update(snapshot.swap as f32 / snapshot.swap_total as f32 * 100.); - - if self.cpu_graphs.len() != snapshot.cpus.len() { - self.cpu_graphs = snapshot - .cpus - .into_iter() - .map(|value| Graph::new(value)) - .collect(); - } else { - self.cpu_graphs - .iter_mut() - .zip(snapshot.cpus.into_iter()) - .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)); + Message::Cpu(value) => { + self.cpu_graph.update(value); + self.cpu_graph.cache.clear(); } } Task::none() } - fn view(&self) -> Element<'_> { - widget::column([ - widget::row([ - maybe_widget(self.is_main_section_shown, || { - widget::canvas(&self.total_cpu_graph) - .width(Length::Fill) - .height(Length::Fill) - .into() - }), - maybe_widget(self.is_cpu_section_shown, || { - widget::grid( - self.cpu_graphs - .iter() - .map(|graph| widget::Canvas::new(graph).into()), - ) - .columns(2) - .height(Length::Fill) - .into() - }), - ]) - .into(), - widget::row([ - maybe_widget(self.is_memory_section_shown, || { - widget::row([ - widget::canvas(&self.memory_graph) - .width(Length::Fill) - .height(Length::Fill) - .into(), - widget::canvas(&self.swap_graph) - .width(Length::Fill) - .height(Length::Fill) - .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() - } - - fn subscription(&self) -> Subscription { - event::listen().filter_map(|event| match event { - Event::Keyboard(keyboard::Event::KeyPressed { modified_key, .. }) => { - match modified_key.as_ref() { - Key::Character("M" | "1") => Some(Message::ToggleMain), - Key::Character("c" | "2") => Some(Message::ToggleCpu), - Key::Character("m" | "3") => Some(Message::ToggleMemory), - Key::Character("p" | "4") => Some(Message::ToggleProcesses), - _ => None, - } - } - _ => None, - }) - } - - fn title(&self) -> String { - format!("itop {:?}", self.uptime) - } - - fn check_borders(&mut self) { - self.total_cpu_graph.show_borders = - self.is_cpu_section_shown || self.is_memory_section_shown; + fn view(&self) -> Element<'_, Message, Theme, Renderer> { + widget::Canvas::new(&self.cpu_graph) + .width(Length::Fill) + .height(Length::Fill) + .into() } } -pub fn run() -> Result<(), Error> { +pub fn run() -> Result<(), impl std::error::Error> { application(State::new, State::update, State::view) - .subscription(State::subscription) - .title(State::title) .theme(theme::Theme::custom( "high-contrast-dark", theme::Palette { diff --git a/src/app/utils.rs b/src/app/utils.rs deleted file mode 100644 index ceef158..0000000 --- a/src/app/utils.rs +++ /dev/null @@ -1,14 +0,0 @@ -use iced::widget; - -use crate::app; - -pub fn maybe_widget<'a>( - condition: bool, - widget: impl FnOnce() -> app::Element<'a>, -) -> app::Element<'a> { - if condition { - widget() - } else { - widget::space().into() - } -} diff --git a/src/main.rs b/src/main.rs index 327e179..2e13470 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,5 @@ -use std::env; - -#[cfg(feature = "app")] mod app; -mod server; -mod sysinfo_snapshot; -mod utils; -fn main() { - match env::args().nth(1).as_deref() { - #[cfg(feature = "app")] - None | Some("client") => app::run().unwrap(), - - #[cfg_attr(feature = "app", allow(unreachable_patterns))] - None | Some("server") => server::run().unwrap(), - - Some(s) => panic!("invalid argument: {s}"), - } +fn main() -> Result<(), impl std::error::Error> { + app::run() } diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 5777c84..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::{io::Write, thread}; - -use ron::{Error, ser}; - -use crate::sysinfo_snapshot::{self, SysinfoSnapshot}; - -pub fn run() -> Result<(), Error> { - let mut sys = sysinfo::System::new(); - let refreshes = sysinfo_snapshot::refreshes(); - - sys.refresh_specifics(refreshes); - thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); - - let mut stdout = std::io::stdout(); - - loop { - sys.refresh_specifics(refreshes); - stdout.write_all((ser::to_string(&SysinfoSnapshot::from(&sys))? + "\n").as_bytes())?; - thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); - } -} diff --git a/src/sysinfo_snapshot.rs b/src/sysinfo_snapshot.rs deleted file mode 100644 index ef0f36e..0000000 --- a/src/sysinfo_snapshot.rs +++ /dev/null @@ -1,87 +0,0 @@ -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)] -pub struct SysinfoSnapshot { - /// in seconds - uptime: u64, - - pub cpu_total: f32, - pub cpus: Vec, - - pub memory: u64, - pub memory_total: u64, - pub swap: u64, - pub swap_total: u64, - - pub processes: Vec, -} -impl SysinfoSnapshot { - #[cfg_attr(not(feature = "app"), allow(dead_code))] - pub fn uptime(&self) -> Duration { - Duration::from_secs(self.uptime) - } -} - -impl From<&sysinfo::System> for SysinfoSnapshot { - fn from(value: &sysinfo::System) -> Self { - Self { - uptime: sysinfo::System::uptime(), - - cpu_total: value.global_cpu_usage(), - cpus: value.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(), - - memory: value.used_memory(), - memory_total: value.total_memory(), - swap: value.used_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(), - } - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 622bff1..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[cfg_attr(not(feature = "app"), allow(dead_code))] -pub trait Toggle { - fn toggle(&mut self); -} - -impl Toggle for bool { - fn toggle(&mut self) { - *self = !*self - } -}