sysutils: add cpu/memory information to top
This commit is contained in:
@@ -0,0 +1,206 @@
|
|||||||
|
use core::{any::Any, marker::PhantomData, sync::atomic::AtomicBool};
|
||||||
|
|
||||||
|
use alloc::{sync::Arc, vec::Vec};
|
||||||
|
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
|
||||||
|
use yggdrasil_abi::{
|
||||||
|
error::Error,
|
||||||
|
io::{FileMode, OpenOptions},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
fs::sysfs::{attribute::Attribute, object::KObject},
|
||||||
|
vfs::{CommonImpl, Filename, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait IntegerAttributeValue: Sized + Send + Sync + 'static {
|
||||||
|
fn from_raw(data: &[u8]) -> Result<Self, Error>;
|
||||||
|
fn into_raw(&self, format: IntegerAttributeFormat) -> Result<Vec<u8>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IntegerAttributeFormat {
|
||||||
|
Decimal,
|
||||||
|
Hex,
|
||||||
|
Octal,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntegerAttributeOps<N: IntegerAttributeValue>: Sync + Send + 'static {
|
||||||
|
type Data: Send + 'static = ();
|
||||||
|
|
||||||
|
const WRITEABLE: bool = false;
|
||||||
|
const NAME: &'static str;
|
||||||
|
const FORMAT: IntegerAttributeFormat = IntegerAttributeFormat::Decimal;
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<N, Error> {
|
||||||
|
let _ = state;
|
||||||
|
Err(Error::NotImplemented)
|
||||||
|
}
|
||||||
|
fn write(state: &Self::Data, value: N) -> Result<(), Error> {
|
||||||
|
let _ = state;
|
||||||
|
let _ = value;
|
||||||
|
Err(Error::ReadOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IntegerAttribute<N: IntegerAttributeValue, V: IntegerAttributeOps<N>>(
|
||||||
|
PhantomData<(V, N)>,
|
||||||
|
);
|
||||||
|
|
||||||
|
struct IntegerAttributeNode<N: IntegerAttributeValue, V: IntegerAttributeOps<N>> {
|
||||||
|
object: Arc<KObject<V::Data>>,
|
||||||
|
_pd: PhantomData<(V, N)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IntegerAttributeState {
|
||||||
|
value: IrqSafeRwLock<Vec<u8>>,
|
||||||
|
#[allow(unused)]
|
||||||
|
modified: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_integer_value {
|
||||||
|
($($ty:ty),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
impl IntegerAttributeValue for $ty {
|
||||||
|
fn from_raw(_data: &[u8]) -> Result<Self, Error> {
|
||||||
|
todo!("IntegerAttributeValue::from_raw()")
|
||||||
|
}
|
||||||
|
fn into_raw(&self, format: IntegerAttributeFormat) -> Result<Vec<u8>, Error> {
|
||||||
|
let string = match format {
|
||||||
|
IntegerAttributeFormat::Decimal => alloc::format!("{self}\n"),
|
||||||
|
IntegerAttributeFormat::Hex => alloc::format!("{self:#x}\n"),
|
||||||
|
IntegerAttributeFormat::Octal => alloc::format!("{self:#o}\n")
|
||||||
|
};
|
||||||
|
Ok(string.into_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_integer_value!(u8, u16, u32, u64, i8, i16, i32, i64);
|
||||||
|
|
||||||
|
impl<N: IntegerAttributeValue, V: IntegerAttributeOps<N>> CommonImpl
|
||||||
|
for IntegerAttributeNode<N, V>
|
||||||
|
{
|
||||||
|
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self as _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: IntegerAttributeValue, V: IntegerAttributeOps<N>> RegularImpl
|
||||||
|
for IntegerAttributeNode<N, V>
|
||||||
|
{
|
||||||
|
fn open(
|
||||||
|
&self,
|
||||||
|
_node: &NodeRef,
|
||||||
|
opts: OpenOptions,
|
||||||
|
) -> Result<(u64, Option<InstanceData>), Error> {
|
||||||
|
if opts.contains(OpenOptions::WRITE) {
|
||||||
|
// && !V::WRITEABLE {
|
||||||
|
return Err(Error::ReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = V::read(self.object.data())?.into_raw(V::FORMAT)?;
|
||||||
|
|
||||||
|
let instance = IntegerAttributeState {
|
||||||
|
value: IrqSafeRwLock::new(value),
|
||||||
|
modified: AtomicBool::new(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((0, Some(Arc::new(instance))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self, _node: &NodeRef, _instance: Option<&InstanceData>) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
// if V::WRITEABLE {
|
||||||
|
// let instance = instance.ok_or(Error::InvalidFile)?;
|
||||||
|
// let instance = instance
|
||||||
|
// .downcast_ref::<StringAttributeState>()
|
||||||
|
// .ok_or(Error::InvalidFile)?;
|
||||||
|
|
||||||
|
// if instance.modified.load(Ordering::Acquire) {
|
||||||
|
// let value = instance.value.read();
|
||||||
|
// let value_str =
|
||||||
|
// core::str::from_utf8(&value[..]).map_err(|_| Error::InvalidArgument)?;
|
||||||
|
|
||||||
|
// // Trim whitespace and newlines
|
||||||
|
// V::write(&self.object.data, value_str.trim())?;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(
|
||||||
|
&self,
|
||||||
|
_node: &NodeRef,
|
||||||
|
instance: Option<&InstanceData>,
|
||||||
|
pos: u64,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
let instance = instance.ok_or(Error::InvalidFile)?;
|
||||||
|
let instance = instance
|
||||||
|
.downcast_ref::<IntegerAttributeState>()
|
||||||
|
.ok_or(Error::InvalidFile)?;
|
||||||
|
|
||||||
|
let value = instance.value.read();
|
||||||
|
let len = value.len();
|
||||||
|
if pos >= len as u64 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let pos = pos as usize;
|
||||||
|
let amount = (len - pos).min(buf.len());
|
||||||
|
buf[..amount].copy_from_slice(&value[pos..pos + amount]);
|
||||||
|
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(
|
||||||
|
&self,
|
||||||
|
_node: &NodeRef,
|
||||||
|
_instance: Option<&InstanceData>,
|
||||||
|
_pos: u64,
|
||||||
|
_buf: &[u8],
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
todo!("Integer attribute write")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: IntegerAttributeValue, V: IntegerAttributeOps<N>> From<V> for IntegerAttribute<N, V> {
|
||||||
|
fn from(_value: V) -> Self {
|
||||||
|
Self(PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: IntegerAttributeValue, V: IntegerAttributeOps<N>> Attribute<V::Data>
|
||||||
|
for IntegerAttribute<N, V>
|
||||||
|
{
|
||||||
|
fn instantiate(&self, parent: &Arc<KObject<V::Data>>) -> Result<Arc<Node>, Error> {
|
||||||
|
let mode = match V::WRITEABLE {
|
||||||
|
false => FileMode::new(0o444),
|
||||||
|
true => FileMode::new(0o644),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Node::regular(
|
||||||
|
IntegerAttributeNode {
|
||||||
|
object: parent.clone(),
|
||||||
|
_pd: PhantomData::<(V, N)>,
|
||||||
|
},
|
||||||
|
NodeFlags::IN_MEMORY_PROPS,
|
||||||
|
Some(Metadata::now_root(mode, 0)),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement this properly
|
||||||
|
fn name(&self) -> &Filename {
|
||||||
|
unsafe { Filename::from_str_unchecked(V::NAME) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use crate::vfs::{Filename, NodeRef};
|
|||||||
use super::object::KObject;
|
use super::object::KObject;
|
||||||
|
|
||||||
mod bytes;
|
mod bytes;
|
||||||
|
mod integer;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
||||||
pub trait Attribute<D>: Sync + Send {
|
pub trait Attribute<D>: Sync + Send {
|
||||||
@@ -14,4 +15,7 @@ pub trait Attribute<D>: Sync + Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use bytes::{BytesAttribute, BytesAttributeOps};
|
pub use bytes::{BytesAttribute, BytesAttributeOps};
|
||||||
|
pub use integer::{
|
||||||
|
IntegerAttribute, IntegerAttributeFormat, IntegerAttributeOps, IntegerAttributeValue,
|
||||||
|
};
|
||||||
pub use string::{StringAttribute, StringAttributeOps};
|
pub use string::{StringAttribute, StringAttributeOps};
|
||||||
|
|||||||
@@ -11,9 +11,14 @@ use kernel_arch::{
|
|||||||
task::{Scheduler, TaskContext},
|
task::{Scheduler, TaskContext},
|
||||||
};
|
};
|
||||||
use libk_util::{OneTimeInit, sync::IrqGuard};
|
use libk_util::{OneTimeInit, sync::IrqGuard};
|
||||||
use yggdrasil_abi::time::SystemTime;
|
use yggdrasil_abi::{error::Error, time::SystemTime};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
fs::sysfs::{
|
||||||
|
self,
|
||||||
|
attribute::{IntegerAttribute, IntegerAttributeOps},
|
||||||
|
object::KObject,
|
||||||
|
},
|
||||||
task::{TaskContextImpl, ThreadId, ThreadState, thread::Thread},
|
task::{TaskContextImpl, ThreadId, ThreadState, thread::Thread},
|
||||||
time::monotonic_time,
|
time::monotonic_time,
|
||||||
};
|
};
|
||||||
@@ -131,7 +136,7 @@ impl CpuQueue {
|
|||||||
&self.stats.idle
|
&self.stats.idle
|
||||||
};
|
};
|
||||||
|
|
||||||
let t = delta.as_millis() as u64;
|
let t = delta.as_nanos() as u64;
|
||||||
|
|
||||||
self.stats.total.fetch_add(t, Ordering::Relaxed);
|
self.stats.total.fetch_add(t, Ordering::Relaxed);
|
||||||
counter.fetch_add(t, Ordering::Relaxed);
|
counter.fetch_add(t, Ordering::Relaxed);
|
||||||
@@ -254,6 +259,74 @@ impl Scheduler for CpuQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setup_sysfs() {
|
||||||
|
fn register_cpu_node(cpu: usize, node: Arc<KObject<usize>>) {
|
||||||
|
static KERNEL_SCHED: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
|
||||||
|
|
||||||
|
let Ok(kernel_sched) = KERNEL_SCHED.or_try_init_with(|| {
|
||||||
|
let kernel = sysfs::kernel().ok_or(Error::DoesNotExist)?;
|
||||||
|
let node = KObject::new(());
|
||||||
|
kernel.add_object("sched", node.clone())?;
|
||||||
|
Ok(node)
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = alloc::format!("{cpu}");
|
||||||
|
kernel_sched.add_object(name, node).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CpuIdle;
|
||||||
|
struct CpuKernel;
|
||||||
|
struct CpuUser;
|
||||||
|
struct CpuTotal;
|
||||||
|
|
||||||
|
impl IntegerAttributeOps<u64> for CpuIdle {
|
||||||
|
type Data = usize;
|
||||||
|
const NAME: &'static str = "idle";
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<u64, Error> {
|
||||||
|
Ok(stats(*state).idle.load(Ordering::Acquire))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntegerAttributeOps<u64> for CpuTotal {
|
||||||
|
type Data = usize;
|
||||||
|
const NAME: &'static str = "total";
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<u64, Error> {
|
||||||
|
Ok(stats(*state).total.load(Ordering::Acquire))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntegerAttributeOps<u64> for CpuUser {
|
||||||
|
type Data = usize;
|
||||||
|
const NAME: &'static str = "user";
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<u64, Error> {
|
||||||
|
Ok(stats(*state).user.load(Ordering::Acquire))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntegerAttributeOps<u64> for CpuKernel {
|
||||||
|
type Data = usize;
|
||||||
|
const NAME: &'static str = "kernel";
|
||||||
|
|
||||||
|
fn read(state: &Self::Data) -> Result<u64, Error> {
|
||||||
|
Ok(stats(*state).kernel.load(Ordering::Acquire))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for cpu in 0..ArchitectureImpl::cpu_count() {
|
||||||
|
let node = KObject::new(cpu);
|
||||||
|
node.add_attribute(IntegerAttribute::from(CpuIdle)).ok();
|
||||||
|
node.add_attribute(IntegerAttribute::from(CpuTotal)).ok();
|
||||||
|
node.add_attribute(IntegerAttribute::from(CpuUser)).ok();
|
||||||
|
node.add_attribute(IntegerAttribute::from(CpuKernel)).ok();
|
||||||
|
register_cpu_node(cpu, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stats(index: usize) -> &'static SchedulerStats {
|
pub fn stats(index: usize) -> &'static SchedulerStats {
|
||||||
&QUEUES.get()[index].stats
|
&QUEUES.get()[index].stats
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -6,7 +6,7 @@ use libk::{
|
|||||||
device::display::console,
|
device::display::console,
|
||||||
fs::devfs,
|
fs::devfs,
|
||||||
random,
|
random,
|
||||||
task::{binary::LoadOptions, process::Process, runtime, thread::Thread},
|
task::{binary::LoadOptions, process::Process, runtime, sched, thread::Thread},
|
||||||
vfs::{IoContext, NodeRef, OwnedFilename, impls::fn_hardlink},
|
vfs::{IoContext, NodeRef, OwnedFilename, impls::fn_hardlink},
|
||||||
};
|
};
|
||||||
use memfs::MemoryFilesystem;
|
use memfs::MemoryFilesystem;
|
||||||
@@ -69,6 +69,8 @@ pub fn kinit() -> Result<(), Error> {
|
|||||||
runtime::spawn(ygg_driver_usb::bus::bus_handler())?;
|
runtime::spawn(ygg_driver_usb::bus::bus_handler())?;
|
||||||
runtime::spawn(console::flush_consoles_task()).ok();
|
runtime::spawn(console::flush_consoles_task()).ok();
|
||||||
|
|
||||||
|
sched::setup_sysfs();
|
||||||
|
|
||||||
devfs::root()
|
devfs::root()
|
||||||
.add_child(
|
.add_child(
|
||||||
OwnedFilename::new("tty").unwrap(),
|
OwnedFilename::new("tty").unwrap(),
|
||||||
|
|||||||
+198
-24
@@ -1,7 +1,7 @@
|
|||||||
#![feature(yggdrasil_os)]
|
#![feature(yggdrasil_os, rustc_private)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::{HashMap, HashSet},
|
||||||
fs, io,
|
fs, io,
|
||||||
os::fd::AsRawFd,
|
os::fd::AsRawFd,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@@ -10,13 +10,16 @@ use std::{
|
|||||||
|
|
||||||
use cross::io::{Poll, PollEvent, TimerFd};
|
use cross::io::{Poll, PollEvent, TimerFd};
|
||||||
use libterm::{Term, TermKey};
|
use libterm::{Term, TermKey};
|
||||||
|
use libutil::fmt::FormatSize;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
layout::{Constraint, Layout},
|
layout::{Constraint, Layout, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
|
symbols,
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Cell, Paragraph, Row, Table},
|
widgets::{Cell, LineGauge, Paragraph, Row, Table},
|
||||||
};
|
};
|
||||||
|
use runtime::{abi::system, rt::system::SystemMemoryStats};
|
||||||
|
|
||||||
struct ProcessInfo {
|
struct ProcessInfo {
|
||||||
pid: u32,
|
pid: u32,
|
||||||
@@ -31,10 +34,64 @@ struct ThreadInfo {
|
|||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
processes: Vec<ProcessInfo>,
|
processes: Vec<ProcessInfo>,
|
||||||
|
memory: Option<SystemMemoryStats>,
|
||||||
expanded: HashSet<u32>,
|
expanded: HashSet<u32>,
|
||||||
|
cpu_load: HashMap<usize, CpuStats>,
|
||||||
|
cpu_stats: HashMap<usize, CpuStats>,
|
||||||
selection: usize,
|
selection: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CpuStats {
|
||||||
|
total_ns: u64,
|
||||||
|
idle_ns: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpuStats {
|
||||||
|
fn read_ns<P: AsRef<Path>>(path: P) -> Option<u64> {
|
||||||
|
fs::read_to_string(path).ok()?.trim().parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(index: usize) -> Self {
|
||||||
|
let path = PathBuf::from("/sys/kernel/sched").join(format!("{index}"));
|
||||||
|
let total_ns = Self::read_ns(path.join("total")).unwrap_or_default();
|
||||||
|
let idle_ns = Self::read_ns(path.join("idle")).unwrap_or_default();
|
||||||
|
Self { total_ns, idle_ns }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_info() -> io::Result<SystemMemoryStats> {
|
||||||
|
runtime::rt::system::get_system_info!(system::MemoryUsage).map_err(io::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpu_stats(stats: &mut HashMap<usize, CpuStats>, load: &mut HashMap<usize, CpuStats>) {
|
||||||
|
if stats.is_empty() {
|
||||||
|
let Ok(dir) = fs::read_dir("/sys/kernel/sched") else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for entry in dir {
|
||||||
|
let Ok(entry) = entry else { continue };
|
||||||
|
let filename = entry.file_name();
|
||||||
|
let Some(filename) = filename.to_str() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(index) = filename.parse() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let data = CpuStats::read(index);
|
||||||
|
stats.insert(index, data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cpu, stats) in stats.iter_mut() {
|
||||||
|
let new_stats = CpuStats::read(*cpu);
|
||||||
|
let idle_ns = new_stats.idle_ns.wrapping_sub(stats.idle_ns);
|
||||||
|
let total_ns = new_stats.total_ns.wrapping_sub(stats.total_ns).max(idle_ns);
|
||||||
|
load.insert(*cpu, CpuStats { total_ns, idle_ns });
|
||||||
|
*stats = new_stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn list_ids<P: AsRef<Path>>(path: P) -> io::Result<Vec<u32>> {
|
fn list_ids<P: AsRef<Path>>(path: P) -> io::Result<Vec<u32>> {
|
||||||
let mut list = vec![];
|
let mut list = vec![];
|
||||||
for entry in fs::read_dir(path)? {
|
for entry in fs::read_dir(path)? {
|
||||||
@@ -88,11 +145,16 @@ fn threads<P: AsRef<Path>>(pid_path: P) -> io::Result<Vec<ThreadInfo>> {
|
|||||||
impl State {
|
impl State {
|
||||||
fn new() -> io::Result<Self> {
|
fn new() -> io::Result<Self> {
|
||||||
let processes = processes()?;
|
let processes = processes()?;
|
||||||
Ok(Self {
|
let mut this = Self {
|
||||||
processes,
|
processes,
|
||||||
|
memory: None,
|
||||||
|
cpu_load: HashMap::new(),
|
||||||
|
cpu_stats: HashMap::new(),
|
||||||
expanded: HashSet::new(),
|
expanded: HashSet::new(),
|
||||||
selection: 0,
|
selection: 0,
|
||||||
})
|
};
|
||||||
|
this.refresh()?;
|
||||||
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self) {
|
fn next(&mut self) {
|
||||||
@@ -134,6 +196,8 @@ impl State {
|
|||||||
.processes
|
.processes
|
||||||
.get(self.selection)
|
.get(self.selection)
|
||||||
.map(|process| process.pid);
|
.map(|process| process.pid);
|
||||||
|
cpu_stats(&mut self.cpu_stats, &mut self.cpu_load);
|
||||||
|
self.memory = memory_info().ok();
|
||||||
self.processes = processes()?;
|
self.processes = processes()?;
|
||||||
let new_selection = selected_pid
|
let new_selection = selected_pid
|
||||||
.and_then(|pid| self.processes.iter().position(|process| process.pid == pid));
|
.and_then(|pid| self.processes.iter().position(|process| process.pid == pid));
|
||||||
@@ -142,22 +206,10 @@ impl State {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, f: &mut Frame) {
|
fn render_processes(&self, frame: &mut Frame, area: Rect) {
|
||||||
const KEYS: &[(&str, &str)] = &[
|
|
||||||
(" j ", "Down"),
|
|
||||||
(" k ", "Up"),
|
|
||||||
(" Enter ", "Toggle"),
|
|
||||||
(" K ", "Kill"),
|
|
||||||
];
|
|
||||||
|
|
||||||
let rects = Layout::default()
|
|
||||||
.constraints([Constraint::Percentage(99), Constraint::Min(1)].as_ref())
|
|
||||||
.split(f.area());
|
|
||||||
|
|
||||||
let normal_style = Style::default();
|
let normal_style = Style::default();
|
||||||
let selected_style = Style::default().bg(Color::Cyan).fg(Color::Black);
|
let selected_style = Style::default().bg(Color::Cyan).fg(Color::Black);
|
||||||
let hint_style = Style::default().bg(Color::Blue).fg(Color::White);
|
let hint_style = Style::default().bg(Color::Blue).fg(Color::White);
|
||||||
let key_style = Style::default().bg(Color::Cyan).fg(Color::Black);
|
|
||||||
|
|
||||||
let rows = self.processes.iter().enumerate().flat_map(|(i, process)| {
|
let rows = self.processes.iter().enumerate().flat_map(|(i, process)| {
|
||||||
let style = if i == self.selection {
|
let style = if i == self.selection {
|
||||||
@@ -213,6 +265,20 @@ impl State {
|
|||||||
.highlight_symbol(">>")
|
.highlight_symbol(">>")
|
||||||
.header(header);
|
.header(header);
|
||||||
|
|
||||||
|
frame.render_widget(table, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_keybar(&self, frame: &mut Frame, area: Rect) {
|
||||||
|
const KEYS: &[(&str, &str)] = &[
|
||||||
|
(" j ", "Down"),
|
||||||
|
(" k ", "Up"),
|
||||||
|
(" Enter ", "Toggle"),
|
||||||
|
(" K ", "Kill"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let hint_style = Style::default().bg(Color::Blue).fg(Color::White);
|
||||||
|
let key_style = Style::default().bg(Color::Cyan).fg(Color::Black);
|
||||||
|
|
||||||
let mut status_spans = vec![];
|
let mut status_spans = vec![];
|
||||||
for (i, &(key, hint)) in KEYS.iter().enumerate() {
|
for (i, &(key, hint)) in KEYS.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
@@ -221,14 +287,122 @@ impl State {
|
|||||||
status_spans.push(Span::styled(key, key_style));
|
status_spans.push(Span::styled(key, key_style));
|
||||||
status_spans.push(Span::styled(hint, hint_style));
|
status_spans.push(Span::styled(hint, hint_style));
|
||||||
}
|
}
|
||||||
let status = Paragraph::new(vec![Line::from(status_spans)]);
|
|
||||||
|
|
||||||
f.render_widget(table, rects[0]);
|
let keybar = Paragraph::new(vec![Line::from(status_spans)]);
|
||||||
f.render_widget(status, rects[1]);
|
|
||||||
|
frame.render_widget(keybar, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_stats_header(&self, frame: &mut Frame, area: Rect) {
|
||||||
|
let cpu_count = self.cpu_stats.len().max(self.cpu_load.len());
|
||||||
|
let cpu_col_count = frame.area().width as usize / 32;
|
||||||
|
let cpu_row_count = cpu_count.div_ceil(cpu_col_count);
|
||||||
|
|
||||||
|
let row_layout = vec![Constraint::Min(1); 1 + cpu_row_count];
|
||||||
|
let row_rects = Layout::vertical(&row_layout).split(area);
|
||||||
|
let mut cpu_constraints = vec![];
|
||||||
|
let mut cpu_row_rects = vec![];
|
||||||
|
for _ in 0..cpu_col_count {
|
||||||
|
cpu_constraints.push(Constraint::Fill(1));
|
||||||
|
cpu_constraints.push(Constraint::Min(1));
|
||||||
|
}
|
||||||
|
for row_rect in &row_rects[1..] {
|
||||||
|
cpu_row_rects.push(
|
||||||
|
Layout::horizontal(&cpu_constraints)
|
||||||
|
.spacing(1)
|
||||||
|
.split(*row_rect),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let memory_rects = Layout::horizontal(&[Constraint::Fill(1), Constraint::Min(16)])
|
||||||
|
.spacing(1)
|
||||||
|
.split(row_rects[0]);
|
||||||
|
// let rects = Layout::horizontal(&[Constraint::Fill(1), Constraint::Min(16)])
|
||||||
|
// .spacing(1)
|
||||||
|
// .split(area);
|
||||||
|
// let row_layout = vec![Constraint::Min(1); 1 + cpu_count];
|
||||||
|
// let bar_rects = Layout::vertical(&row_layout).split(rects[0]);
|
||||||
|
// let label_rects = Layout::vertical(&row_layout).split(rects[1]);
|
||||||
|
|
||||||
|
// Memory gauge
|
||||||
|
let (memory_total, memory_used) = match self.memory.as_ref() {
|
||||||
|
Some(memory) => (memory.total_usable_pages, memory.allocated_pages),
|
||||||
|
None => (1, 0),
|
||||||
|
};
|
||||||
|
let usage = memory_used as f64 / memory_total as f64;
|
||||||
|
let fill_style = match usage {
|
||||||
|
u if u >= 0.85 => Color::from_u32(0x00FF0000),
|
||||||
|
u if u >= 0.65 => Color::from_u32(0x00FF8800),
|
||||||
|
u if u >= 0.45 => Color::from_u32(0x00FFFF00),
|
||||||
|
_ => Color::from_u32(0x0000FF00),
|
||||||
|
};
|
||||||
|
let memory_used = memory_used * 4096;
|
||||||
|
let memory_total = memory_total * 4096;
|
||||||
|
let gauge = LineGauge::default()
|
||||||
|
.label("Mem")
|
||||||
|
.filled_style(fill_style)
|
||||||
|
.unfilled_symbol(" ")
|
||||||
|
.filled_symbol(symbols::block::FULL)
|
||||||
|
.ratio(usage);
|
||||||
|
let label = Paragraph::new(format!(
|
||||||
|
"{}/{}",
|
||||||
|
FormatSize::default(memory_used as u64),
|
||||||
|
FormatSize::default(memory_total as u64)
|
||||||
|
));
|
||||||
|
frame.render_widget(gauge, memory_rects[0]);
|
||||||
|
frame.render_widget(label, memory_rects[1]);
|
||||||
|
|
||||||
|
// CPU gauges
|
||||||
|
for cpu in 0..cpu_count {
|
||||||
|
let row = cpu / cpu_col_count;
|
||||||
|
let col = cpu % cpu_col_count;
|
||||||
|
let gauge_rect = cpu_row_rects[row][col * 2 + 0];
|
||||||
|
let label_rect = cpu_row_rects[row][col * 2 + 1];
|
||||||
|
let load = match self.cpu_load.get(&cpu) {
|
||||||
|
Some(load) => 1.0 - load.idle_ns as f64 / load.total_ns as f64,
|
||||||
|
None => 0.0,
|
||||||
|
};
|
||||||
|
let gauge = LineGauge::default()
|
||||||
|
.label(format!("{cpu}"))
|
||||||
|
.filled_style(Color::Yellow)
|
||||||
|
.unfilled_symbol(" ")
|
||||||
|
.filled_symbol(symbols::block::FULL)
|
||||||
|
.ratio(load);
|
||||||
|
let label = Paragraph::new(format!("{:<3.1}%", load * 100.0));
|
||||||
|
|
||||||
|
frame.render_widget(gauge, gauge_rect);
|
||||||
|
frame.render_widget(label, label_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, f: &mut Frame) {
|
||||||
|
let cpus_per_row = f.area().width as usize / 32;
|
||||||
|
let cpu_row_count = self
|
||||||
|
.cpu_load
|
||||||
|
.len()
|
||||||
|
.max(self.cpu_stats.len())
|
||||||
|
.div_ceil(cpus_per_row);
|
||||||
|
|
||||||
|
let rects = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Min(1 + cpu_row_count as u16),
|
||||||
|
Constraint::Percentage(99),
|
||||||
|
Constraint::Min(1),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(f.area());
|
||||||
|
|
||||||
|
self.render_stats_header(f, rects[0]);
|
||||||
|
self.render_processes(f, rects[1]);
|
||||||
|
self.render_keybar(f, rects[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> io::Result<()> {
|
fn run() -> io::Result<()> {
|
||||||
|
const INTERVAL: Duration = Duration::from_millis(250);
|
||||||
|
|
||||||
let mut state = State::new()?;
|
let mut state = State::new()?;
|
||||||
|
|
||||||
let mut poll = Poll::new()?;
|
let mut poll = Poll::new()?;
|
||||||
@@ -243,14 +417,14 @@ fn run() -> io::Result<()> {
|
|||||||
|
|
||||||
let mut term = Terminal::new(term)?;
|
let mut term = Terminal::new(term)?;
|
||||||
|
|
||||||
timer.start(Duration::from_secs(1))?;
|
timer.start(INTERVAL)?;
|
||||||
|
|
||||||
while running {
|
while running {
|
||||||
term.draw(|f| state.render(f))?;
|
term.draw(|f| state.render(f))?;
|
||||||
|
|
||||||
match poll.wait(None)? {
|
match poll.wait(None)? {
|
||||||
PollEvent::Ready(fd) if fd == timer.as_raw_fd() => {
|
PollEvent::Ready(fd) if fd == timer.as_raw_fd() => {
|
||||||
timer.start(Duration::from_secs(1))?;
|
timer.start(INTERVAL)?;
|
||||||
state.refresh()?;
|
state.refresh()?;
|
||||||
}
|
}
|
||||||
PollEvent::Ready(fd) if fd == term_fd => {
|
PollEvent::Ready(fd) if fd == term_fd => {
|
||||||
|
|||||||
Reference in New Issue
Block a user