Skip to content

Commit

Permalink
first version
Browse files Browse the repository at this point in the history
  • Loading branch information
epilys committed Sep 4, 2019
1 parent e9bab54 commit f759b54
Show file tree
Hide file tree
Showing 23 changed files with 972 additions and 859 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ termion = "1.5.3"
libc = "0.2.62"
unicode-segmentation = "1.2.1"
nix = "0.15.0"
cassowary = "^0.3.0"

[profile.release]
opt-level = 'z' # Optimize for size.
Expand Down
28 changes: 28 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
NAME
bb — your system's Big Brother

SYNOPSIS
bb

DESCRIPTION
simple process viewer made for fun
screenshot: see ./screenshot.png

BUILD
prerequisites: rustc, cargo >= 1.36, Linux

execute `cargo build --release`, resulting binary will be in
./target/release/bb

SHORTCUTS
'k' kill process under cursor
press enter to confirm kill, or esc to cancel
'f' (un)freeze process list updates
AUTHORS
Copyright 2019 Manos Pitsidianakis <[email protected]> Released
under the GPL, version 3 or greater. This software carries no warranty of
any kind. (See COPYING for full copyright and warranty notices.)

⟨https://nessuent.xyz/⟩

September 04, 2019
Binary file added screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 52 additions & 54 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ use crossbeam::channel::{bounded, tick};
use crossbeam::select;
use libc::c_int;
use std::io::Error;
use std::time::{Duration, Instant};
use std::time::Duration;

mod ui;
use ui::*;
mod thread;
use thread::start_thread;

fn notify(signals: &[c_int]) -> Result<crossbeam::channel::Receiver<c_int>, Error> {
let (s, r) = bounded(100);
Expand Down Expand Up @@ -69,66 +71,62 @@ fn main() -> Result<(), Error> {

let receiver = state.receiver();

let window = Box::new(HSplit::new(
/* Start data update thread (src/thread.rs) */
let (s, r) = start_thread();
let window = Box::new(Window::new(
Box::new(ui::components::KernelMetrics::new()),
Box::new(ui::components::ProcessList::new()),
83,
false,
Box::new(ui::components::ProcessList::new(s, r)),
));

state.register_component(window);

/* Keep track of the input mode. See ui::UIMode for details */
'main: loop {
'inner: loop {
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
select! {
recv(ticker) -> _ => {
state.redraw(true);
},
recv(signal_recvr) -> sig => {
eprintln!("got signal {:?}", sig);
match sig.unwrap() {
signal_hook::SIGWINCH => {
state.update_size();
state.render();
state.redraw(true);
},
_ => {}
}
},
recv(receiver) -> msg => {
match msg.unwrap() {
ThreadEvent::Input(Key::Ctrl('z')) => {
state.switch_to_main_screen();
//_thread_handler.join().expect("Couldn't join on the associated thread");
let self_pid = nix::unistd::Pid::this();
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
state.switch_to_alternate_screen();
state.restore_input();
// BUG: thread sends input event after one received key
state.update_size();
state.render();
state.redraw(true);
},
ThreadEvent::Input(k) => {
match k {
Key::Char('q') | Key::Char('Q') => {
drop(state);
break 'main;
},
key => {
state.rcv_event(UIEvent::Input(key));
state.redraw(false);
},
}
},
ThreadEvent::UIEvent(_) => {
},
}
},
}
} // end of 'inner
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
select! {
recv(ticker) -> _ => {
state.redraw(true);
},
recv(signal_recvr) -> sig => {
eprintln!("got signal {:?}", sig);
match sig.unwrap() {
signal_hook::SIGWINCH => {
state.update_size();
state.render();
state.redraw(true);
},
_ => {}
}
},
recv(receiver) -> msg => {
match msg.unwrap() {
ThreadEvent::Input(Key::Ctrl('z')) => {
state.switch_to_main_screen();
//_thread_handler.join().expect("Couldn't join on the associated thread");
let self_pid = nix::unistd::Pid::this();
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
state.switch_to_alternate_screen();
state.restore_input();
// BUG: thread sends input event after one received key
state.update_size();
state.render();
state.redraw(true);
},
ThreadEvent::Input(k) => {
match k {
Key::Char('q') | Key::Char('Q') => {
drop(state);
break 'main;
},
key => {
state.rcv_event(UIEvent::Input(key));
state.redraw(false);
},
}
},
}
},
}
}
Ok(())
}
221 changes: 221 additions & 0 deletions src/thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* bb
*
* Copyright 2019 Manos Pitsidianakis
*
* This file is part of bb.
*
* bb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* bb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with bb. If not, see <http://www.gnu.org/licenses/>.
*/

use crate::ui::components::processes::State;
use crate::ui::components::*;
use crossbeam::channel::bounded;
use crossbeam::{Receiver, Sender};
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::str::FromStr;

struct ProcessData {
cpu_stat: Stat,
processes_times: HashMap<Pid, usize>,
}

pub fn start_thread() -> (Sender<Vec<ProcessDisplay>>, Receiver<Vec<ProcessDisplay>>) {
let (s, r): (Sender<Vec<ProcessDisplay>>, Receiver<Vec<ProcessDisplay>>) = bounded(1); /* ours */
let (ts, tr): (Sender<Vec<ProcessDisplay>>, Receiver<Vec<ProcessDisplay>>) = bounded(1); /* theirs */
ts.send(Vec::with_capacity(1024)).unwrap();
std::thread::spawn(move || {
let mut data = ProcessData {
cpu_stat: get_stat(&mut 0).remove(0),
processes_times: Default::default(),
};
loop {
let mut processes = match tr.recv() {
Ok(n) => n,
Err(e) => panic!(e),
};
processes.clear();

let cpu_stat = get_stat(&mut 0).remove(0);
for entry in std::fs::read_dir("/proc/").unwrap() {
let dir = entry.unwrap();
if let Some(fname) = dir.file_name().to_str() {
if !fname.chars().all(|c| c.is_numeric()) {
continue;
}
} else {
continue;
}

let process = if let Ok(p) = get_pid_info(dir.path()) {
p
} else {
continue;
};

if process.cmd_line.is_empty() {
/* This is a kernel thread, skip for now */
continue;
}

let mut process_display = ProcessDisplay {
i: process.pid,
pid: PidString(process.pid.to_string()),
ppid: PpidString(process.ppid.to_string()),
vm_rss: VmRssString(Bytes(process.vm_rss * 1024).as_convenient_string()),
cpu_percent: (100.0
* ((process.utime
- data
.processes_times
.get(&process.pid)
.map(|v| *v)
.unwrap_or(process.utime)) as f64
/ ((cpu_stat.total_time() - data.cpu_stat.total_time()) as f64)))
as usize,
utime: process.utime,
state: process.state,
cmd_line: CmdLineString(process.cmd_line),
username: UserString(crate::ui::username(process.uid)),
};
if process_display.cpu_percent > 100 {
process_display.cpu_percent = 0;
}

data.processes_times
.insert(process.pid, process_display.utime);

processes.push(process_display);
}
data.cpu_stat = cpu_stat;

s.send(processes).unwrap();
}
});

(ts, r)
}

/* Might return Error if process has disappeared
* during the function's run */
fn get_pid_info(mut path: PathBuf) -> Result<Process, std::io::Error> {
/* proc file structure can be found in man 5 proc.*/
path.push("status");
let mut file: File = File::open(&path)?;
let mut res = String::with_capacity(2048);
file.read_to_string(&mut res)?;
let mut lines_iter = res.lines();
let mut ret = Process {
pid: 0,
ppid: 0,
vm_rss: 0,
uid: 0,
utime: 0,
state: State::Waiting,
cmd_line: String::new(),
};
let mut line;

macro_rules! err {
($res:expr) => {
match $res {
Ok(v) => v,
Err(_) => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, ""));
}
}
};
}

macro_rules! none_err {
($res:expr) => {
if let Some(v) = $res {
v
} else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, ""));
}
};
}

let mut b = 0;
while b < 5 {
let line_opt = lines_iter.next();
if line_opt.is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{} returned malformed input", path.display()),
));
}
line = none_err!(line_opt);
let mut mut_value_iter = line.split_whitespace();
match mut_value_iter.next() {
Some("VmRSS:") => {
ret.vm_rss = err!(usize::from_str(none_err!(mut_value_iter.next())));
b += 1;
}
Some("State:") => {
ret.state = State::from(none_err!(none_err!(mut_value_iter.next()).chars().next()));
b += 1;
}
Some("Pid:") => {
ret.pid = err!(i32::from_str(none_err!(mut_value_iter.next())));
b += 1;
}
Some("PPid:") => {
ret.ppid = err!(i32::from_str(none_err!(mut_value_iter.next())));
b += 1;
}
Some("Uid:") => {
ret.uid = err!(u32::from_str(none_err!(mut_value_iter.next())));
b += 1;
}
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"{} returned malformed input. Original error was while parsing file",
path.display(),
),
));
}
_ => {}
}
}

path.pop();
path.push("cmdline");
let mut file: File = File::open(&path)?;
res.clear();
file.read_to_string(&mut res)?;
if !res.is_empty() {
/* values are separated by null bytes */
ret.cmd_line = format!("{}", res.split('\0').collect::<Vec<&str>>().join(" "));
}
path.pop();
path.push("stat");
let mut file: File = File::open(&path)?;
res.clear();
file.read_to_string(&mut res)?;
/* values are separated by whitespace and are in a specific order */
if !res.is_empty() {
let mut vals = res.split_whitespace().skip(13);
ret.utime = err!(usize::from_str(none_err!(vals.next())));
ret.utime += err!(usize::from_str(none_err!(vals.next())));
ret.utime += err!(usize::from_str(none_err!(vals.next())));
ret.utime += err!(usize::from_str(none_err!(vals.next())));
}
Ok(ret)
}
Loading

0 comments on commit f759b54

Please sign in to comment.