Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b20158820b |
|||
|
afa6104c28 |
|||
|
a894a02e66 |
|||
|
86f7fc12b4 |
|||
|
5acc09b471 |
|||
|
cb2461b64f |
|||
|
d3f94b4acc |
|||
|
d1fb0ca78a |
|||
|
c39d48fd07 |
|||
|
1546c67fa3 |
|||
|
e310ca570b |
|||
|
a689366324 |
|||
|
171194e99f |
|||
|
534b6cde99 |
|||
|
79aff51856 |
|||
|
865fecd694 |
|||
|
8589c0c666 |
|||
|
d068d110e2 |
|||
|
374cbd546d |
|||
|
9d0a6fdd07 |
|||
|
fa0284f165 |
|||
|
96a972f9f0 |
|||
|
93de64f4b9 |
|||
|
7264d127d9 |
|||
|
98b7a8d201 |
11 changed files with 463 additions and 45 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -292,6 +292,9 @@ name = "bitflags"
|
|||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
|
|
@ -1380,6 +1383,8 @@ name = "itop"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"iced",
|
||||
"ron",
|
||||
"serde",
|
||||
"smol",
|
||||
"sysinfo",
|
||||
]
|
||||
|
|
@ -2482,6 +2487,20 @@ 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"
|
||||
|
|
@ -3091,6 +3110,12 @@ 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"
|
||||
|
|
|
|||
15
Cargo.toml
15
Cargo.toml
|
|
@ -5,10 +5,23 @@ license = "AGPL-3.0-or-later"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = [ "app" ]
|
||||
app = [ "dep:iced", "dep:smol" ]
|
||||
|
||||
[dependencies]
|
||||
smol = "2.0.2"
|
||||
ron = "0.12.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" ]
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@
|
|||
desktopItem = pkgs.makeDesktopItem {
|
||||
inherit name;
|
||||
desktopName = name;
|
||||
icon = name;
|
||||
exec = name;
|
||||
};
|
||||
in
|
||||
|
|
@ -89,6 +88,13 @@
|
|||
{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -6,18 +6,42 @@ use crate::app;
|
|||
|
||||
const CYAN: Color = Color::from_rgb8(0, 255, 255);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Graph {
|
||||
pub cache: canvas::Cache,
|
||||
timeline: VecDeque<f32>,
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() > 1000 {
|
||||
if self.timeline.len() > 500 {
|
||||
self.timeline.pop_front();
|
||||
}
|
||||
|
||||
self.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32,11 +56,19 @@ impl canvas::Program<app::Message, Theme, Renderer> for Graph {
|
|||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry<Renderer>> {
|
||||
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;
|
||||
let y = (bounds.height - value / 100. * bounds.height)
|
||||
.clamp(1., bounds.height - 1.);
|
||||
builder.line_to(Point::new(x, y))
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
64
src/app/info_stream.rs
Normal file
64
src/app/info_stream.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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<app::Message> {
|
||||
if env::args()
|
||||
.nth(1)
|
||||
.is_some_and(|command| command == "client")
|
||||
{
|
||||
Task::run(
|
||||
stream::channel(
|
||||
1,
|
||||
async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| {
|
||||
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<SysinfoSnapshot>| {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
208
src/app/mod.rs
208
src/app/mod.rs
|
|
@ -1,66 +1,198 @@
|
|||
use iced::{
|
||||
Color, Element, Length, Renderer, Task, Theme, application, futures::SinkExt, stream, theme,
|
||||
widget,
|
||||
};
|
||||
use smol::Timer;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::app::graph::Graph;
|
||||
use iced::{
|
||||
Color, Error, Event, Length, Renderer, Subscription, Task, Theme, application, event,
|
||||
keyboard::{self, Key},
|
||||
theme, widget,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{graph::Graph, utils::maybe_widget},
|
||||
sysinfo_snapshot::{ProcessSnapshot, SysinfoSnapshot},
|
||||
utils::Toggle,
|
||||
};
|
||||
|
||||
mod graph;
|
||||
mod info_stream;
|
||||
mod utils;
|
||||
|
||||
type Element<'a> = iced::Element<'a, Message, Theme, Renderer>;
|
||||
|
||||
enum Message {
|
||||
Cpu(f32),
|
||||
ToggleMain,
|
||||
ToggleCpu,
|
||||
ToggleMemory,
|
||||
ToggleProcesses,
|
||||
|
||||
Info(SysinfoSnapshot),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
cpu_graph: Graph,
|
||||
uptime: Duration,
|
||||
|
||||
is_main_section_shown: bool,
|
||||
total_cpu_graph: Graph,
|
||||
|
||||
is_cpu_section_shown: bool,
|
||||
cpu_graphs: Vec<Graph>,
|
||||
|
||||
is_memory_section_shown: bool,
|
||||
memory_graph: Graph,
|
||||
swap_graph: Graph,
|
||||
|
||||
is_processes_section_shown: bool,
|
||||
processes: Vec<ProcessSnapshot>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
(
|
||||
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());
|
||||
let mut state = Self {
|
||||
is_main_section_shown: true,
|
||||
..Default::default()
|
||||
};
|
||||
state.total_cpu_graph.show_borders = false;
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
(state, info_stream::create())
|
||||
}
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Cpu(value) => {
|
||||
self.cpu_graph.update(value);
|
||||
self.cpu_graph.cache.clear();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
fn view(&self) -> Element<'_, Message, Theme, Renderer> {
|
||||
widget::Canvas::new(&self.cpu_graph)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
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<Message> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), impl std::error::Error> {
|
||||
pub fn run() -> Result<(), Error> {
|
||||
application(State::new, State::update, State::view)
|
||||
.subscription(State::subscription)
|
||||
.title(State::title)
|
||||
.theme(theme::Theme::custom(
|
||||
"high-contrast-dark",
|
||||
theme::Palette {
|
||||
|
|
|
|||
14
src/app/utils.rs
Normal file
14
src/app/utils.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
20
src/main.rs
20
src/main.rs
|
|
@ -1,5 +1,19 @@
|
|||
mod app;
|
||||
use std::env;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
app::run()
|
||||
#[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}"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
src/server/mod.rs
Normal file
21
src/server/mod.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
87
src/sysinfo_snapshot.rs
Normal file
87
src/sysinfo_snapshot.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
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<f32>,
|
||||
|
||||
pub memory: u64,
|
||||
pub memory_total: u64,
|
||||
pub swap: u64,
|
||||
pub swap_total: u64,
|
||||
|
||||
pub processes: Vec<ProcessSnapshot>,
|
||||
}
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/utils.rs
Normal file
10
src/utils.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#[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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue