diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-08-31 22:46:02 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-08-31 22:46:02 +0300 |
commit | a635232820dc4544686010b2cc1a49a36a4f664b (patch) | |
tree | 5fb448eee498ed1f28529e1c5554d1c160776611 /src/ui/components/processes.rs | |
parent | 31bf144ecdfa3c1da9e38e1f6338329aab996680 (diff) |
add username, etc
Diffstat (limited to 'src/ui/components/processes.rs')
-rw-r--r-- | src/ui/components/processes.rs | 419 |
1 files changed, 328 insertions, 91 deletions
diff --git a/src/ui/components/processes.rs b/src/ui/components/processes.rs index 88d1fa3..dba2236 100644 --- a/src/ui/components/processes.rs +++ b/src/ui/components/processes.rs @@ -4,13 +4,19 @@ use std::io::prelude::*; use std::path::PathBuf; use std::str::FromStr; +type Pid = usize; + /* process list components */ #[derive(Debug)] pub struct ProcessList { page_movement: Option<PageMovement>, + cpu_stat: Stat, pid_max: usize, cursor: usize, dirty: bool, + processes: HashMap<Pid, usize>, + /* refresh process list every 4 cycles */ + cycle: u8, } enum State { @@ -86,10 +92,11 @@ impl std::fmt::Display for State { struct Process { pid: usize, ppid: usize, - vm_size: usize, + vm_rss: usize, state: State, - uid: usize, + uid: u32, cmd_line: String, + utime: usize, } impl fmt::Display for ProcessList { @@ -106,62 +113,49 @@ impl ProcessList { ProcessList { cursor: 0, page_movement: None, + cpu_stat: get_stat(&mut 0).remove(0), pid_max: usize::from_str(pid_max.trim()).unwrap(), + processes: Default::default(), + cycle: 0, dirty: true, } } } +/* Prints the process list */ impl Component for ProcessList { fn draw( &mut self, grid: &mut CellBuffer, area: Area, dirty_areas: &mut VecDeque<Area>, - tick: bool, + mut tick: bool, ) { if !is_valid_area!(area) { return; } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); + + if !self.dirty && !tick { + return; + } + + let upper_left = pos_inc(upper_left!(area), (1, 0)); + let bottom_right = pos_dec(bottom_right!(area), (1, 1)); /* Reserve first row for column headers */ let height = height!(area) - 1; + let old_pages = self.cursor / height; let width = width!(area); - clear_area(grid, area); - dirty_areas.push_back(area); - - let mut processes: Vec<(String, String, String, String, String)> = Vec::with_capacity(1024); - let mut pid = 0; - let mut maxima = ("PID".len(), "PPID".len(), "VM_SIZE".len(), "STATE".len()); - 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 = get_pid_info(dir.path()); - let strings = ( - process.pid.to_string(), - process.ppid.to_string(), - Bytes(process.vm_size * 1024).as_convenient_string(), - process.state.to_string(), - process.cmd_line, - ); - maxima.0 = std::cmp::max(maxima.0, strings.0.len()); - maxima.1 = std::cmp::max(maxima.1, strings.1.len()); - maxima.2 = std::cmp::max(maxima.2, strings.2.len()); - maxima.3 = std::cmp::max(maxima.3, strings.3.len()); - processes.push(strings); - } - - processes.sort_unstable_by(|a, b| a.4.cmp(&b.4)); + let old_cursor = self.cursor; if let Some(mvm) = self.page_movement.take() { match mvm { + PageMovement::Up => { + self.cursor = self.cursor.saturating_sub(1); + } + PageMovement::Down => { + self.cursor = + std::cmp::min(self.processes.len().saturating_sub(1), self.cursor + 1); + } PageMovement::Home => { self.cursor = 0; } @@ -170,27 +164,106 @@ impl Component for ProcessList { } PageMovement::PageDown => { self.cursor = - std::cmp::min(processes.len().saturating_sub(1), self.cursor + height); + std::cmp::min(self.processes.len().saturating_sub(1), self.cursor + height); } PageMovement::End => { - self.cursor = processes.len().saturating_sub(1); + self.cursor = self.processes.len().saturating_sub(1); } } } - if self.dirty { + let pages = self.cursor / height; + if pages != old_pages { + tick = true; + } + + if tick { + clear_area(grid, area); + dirty_areas.push_back(area); + + let mut processes: Vec<(String, String, String, usize, String, String, String)> = + Vec::with_capacity(1024); + + let cpu_stat = get_stat(&mut 0).remove(0); + + /* Keep tabs on biggest element in each column */ + let mut maxima = ( + "PID".len(), + "PPID".len(), + "VM_RSS".len(), + "CPU%".len(), + " ".len(), + "USER".len(), + ); + + 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 strings = ( + process.pid.to_string(), + process.ppid.to_string(), + Bytes(process.vm_rss * 1024).as_convenient_string(), + (100.0 + * ((process.utime + - self + .processes + .get(&process.pid) + .map(|v| *v) + .unwrap_or(process.utime)) as f64 + / ((cpu_stat.total_time() - self.cpu_stat.total_time()) as f64))) + as usize, + process.state.to_string(), + process.cmd_line, + crate::ui::username(process.uid), + ); + + self.processes.insert(process.pid, process.utime); + + maxima.0 = std::cmp::max(maxima.0, strings.0.len()); + maxima.1 = std::cmp::max(maxima.1, strings.1.len()); + maxima.2 = std::cmp::max(maxima.2, strings.2.len()); + maxima.4 = std::cmp::max(maxima.4, strings.4.len()); + maxima.5 = std::cmp::max(maxima.5, strings.6.len()); + processes.push(strings); + } + self.cpu_stat = cpu_stat; + + processes.sort_unstable_by(|a, b| b.3.cmp(&a.3)); + /* Write column headers */ let (x, y) = write_string_to_grid( &format!( - "{:>maxima0$} {:>maxima1$} {:>maxima2$} {:>maxima3$} {}", + "{:>maxima0$} {:>maxima1$} {:>maxima5$} {:>maxima2$} {:>maxima3$} {:>maxima4$} {}", "PID", "PPID", - "VM_SIZE", - "STATE", + "USER", + "VM_RSS", + "CPU%", + " ", "CMD_LINE", maxima0 = maxima.0, maxima1 = maxima.1, maxima2 = maxima.2, maxima3 = maxima.3, + maxima4 = maxima.4, + maxima5 = maxima.5, ), grid, Color::Black, @@ -202,69 +275,185 @@ impl Component for ProcessList { change_colors( grid, ((x, y), set_y(bottom_right, y)), - Color::Black, - Color::White, + Some(Color::Black), + Some(Color::White), ); - } - let mut y_offset = 0; - let pages = self.cursor / height; - let p_len = processes.len(); - for (pid, ppid, vm_size, state, cmd_line) in processes.into_iter().skip(pages * height) { - let (x, y) = write_string_to_grid( - &format!( - "{:>maxima0$} {:>maxima1$} {:>maxima2$} {:>maxima3$} {}", - pid, - ppid, - vm_size, - state, - cmd_line, - maxima0 = maxima.0, - maxima1 = maxima.1, - maxima2 = maxima.2, - maxima3 = maxima.3, - ), - grid, - if pages * height + y_offset == self.cursor { - Color::Black + let mut y_offset = 0; + let p_len = processes.len(); + for (pid, ppid, vm_rss, cpu, state, cmd_line, username) in + processes.into_iter().skip(pages * height) + { + let fg_color = Color::Default; + let bg_color = if pages * height + y_offset == self.cursor { + Color::Byte(235) } else { Color::Default - }, - if pages * height + y_offset == self.cursor { - Color::Byte(243) - } else { - Color::Default - }, - Attr::Default, - (pos_inc(upper_left, (0, y_offset + 3)), bottom_right), - false, - ); - y_offset += 1; - if y_offset >= height { - break; + }; + match executable_path_color(&cmd_line) { + Ok((path, bin, rest)) => { + let (x, y) = write_string_to_grid( + &format!( + "{:>maxima0$} {:>maxima1$} {:>maxima5$} {:>maxima2$} {:>maxima3$}% {:>maxima4$}", + pid, + ppid, + username, + vm_rss, + cpu, + state, + maxima0 = maxima.0, + maxima1 = maxima.1, + maxima2 = maxima.2, + maxima3 = maxima.3, + maxima4 = maxima.4, + maxima5 = maxima.5, + ), + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (0, y_offset + 3)), bottom_right), + false, + ); + let (x, _) = write_string_to_grid( + path, + grid, + Color::Byte(243), + bg_color, + Attr::Default, + (pos_inc(upper_left, (x, y_offset + 3)), bottom_right), + false, + ); + let (x, y) = write_string_to_grid( + bin, + grid, + Color::Byte(34), + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + false, + ); + let (x, y) = write_string_to_grid( + rest, + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + false, + ); + change_colors( + grid, + ((x, y), set_y(bottom_right, y)), + Some(fg_color), + Some(bg_color), + ); + } + Err((bin, rest)) => { + let(x,y)=write_string_to_grid( + &format!( + "{:>maxima0$} {:>maxima1$} {:>maxima5$} {:>maxima2$} {:>maxima3$}% {:>maxima4$} ", + pid, + ppid, + username, + vm_rss, + cpu, + state, + maxima0 = maxima.0, + maxima1 = maxima.1, + maxima2 = maxima.2, + maxima3 = maxima.3, + maxima4 = maxima.4, + maxima5 = maxima.5, + ), + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (0, y_offset + 3)), bottom_right), + false, + ); + let (x, y) = write_string_to_grid( + bin, + grid, + Color::Byte(34), + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + false, + ); + let (x, y) = write_string_to_grid( + rest, + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + false, + ); + change_colors( + grid, + ((x, y), set_y(bottom_right, y)), + Some(fg_color), + Some(bg_color), + ); + } + } + y_offset += 1; + if y_offset >= height { + break; + } } + self.cycle += 1; + self.cycle %= 4; + } else if old_cursor != self.cursor { + let new_area = ( + pos_inc(upper_left, (0, self.cursor - pages * height + 3)), + set_y( + bottom_right, + get_y(upper_left) + self.cursor - pages * height + 3, + ), + ); + let old_area = ( + pos_inc(upper_left, (0, old_cursor - old_pages * height + 3)), + set_y( + bottom_right, + get_y(upper_left) + old_cursor - old_pages * height + 3, + ), + ); + change_colors(grid, new_area, None, Some(Color::Byte(235))); + change_colors(grid, old_area, None, Some(Color::Default)); + dirty_areas.push_back(old_area); + dirty_areas.push_back(new_area); } + self.dirty = false; } fn process_event(&mut self, event: &mut UIEvent) { match event { UIEvent::Input(Key::Up) => { - self.cursor = self.cursor.saturating_sub(1); + self.page_movement = Some(PageMovement::Up); + self.dirty = true; } UIEvent::Input(Key::Down) => { - self.cursor += 1; + self.page_movement = Some(PageMovement::Down); + self.dirty = true; } UIEvent::Input(Key::Home) => { self.page_movement = Some(PageMovement::Home); + self.dirty = true; } UIEvent::Input(Key::PageUp) => { self.page_movement = Some(PageMovement::PageUp); + self.dirty = true; } UIEvent::Input(Key::PageDown) => { self.page_movement = Some(PageMovement::PageDown); + self.dirty = true; } UIEvent::Input(Key::End) => { self.page_movement = Some(PageMovement::End); + self.dirty = true; } _ => {} } @@ -277,17 +466,19 @@ impl Component for ProcessList { fn set_dirty(&mut self) {} } -fn get_pid_info(mut path: PathBuf) -> Process { +/* proc file structure can be found in man 5 proc */ +fn get_pid_info(mut path: PathBuf) -> Result<Process, std::io::Error> { path.push("status"); - let mut file: File = File::open(&path).unwrap(); + let mut file: File = File::open(&path)?; let mut res = String::with_capacity(2048); - file.read_to_string(&mut res).unwrap(); + file.read_to_string(&mut res)?; let mut lines_iter = res.lines(); let mut ret = Process { pid: 0, ppid: 0, - vm_size: 0, + vm_rss: 0, uid: 0, + utime: 0, state: State::Waiting, cmd_line: String::new(), }; @@ -301,11 +492,8 @@ fn get_pid_info(mut path: PathBuf) -> Process { line = line_opt.unwrap(); let mut mut_value_iter = line.split_whitespace(); match mut_value_iter.next().unwrap() { - "Name:" => { - ret.cmd_line = mut_value_iter.next().unwrap().to_string(); - } - "VmSize:" => { - ret.vm_size = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); + "VmRSS:" => { + ret.vm_rss = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); } "State:" => { ret.state = State::from(mut_value_iter.next().unwrap().chars().next().unwrap()); @@ -317,7 +505,7 @@ fn get_pid_info(mut path: PathBuf) -> Process { ret.ppid = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); } "Uid:" => { - ret.uid = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); + ret.uid = u32::from_str(mut_value_iter.next().unwrap()).unwrap(); } _ => {} } @@ -328,7 +516,56 @@ fn get_pid_info(mut path: PathBuf) -> Process { res.clear(); file.read_to_string(&mut res).unwrap(); if !res.is_empty() { + /* values are separated by null bytes */ ret.cmd_line = format!("{}", res.split('\0').collect::<Vec<&str>>().join(" ")); } - ret + path.pop(); + path.push("stat"); + let mut file: File = File::open(&path).unwrap(); + res.clear(); + file.read_to_string(&mut res).unwrap(); + /* 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 = usize::from_str(vals.next().unwrap()).unwrap(); + ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); + ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); + ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); + } + Ok(ret) +} + +fn executable_path_color(p: &str) -> Result<(&str, &str, &str), (&str, &str)> { + if !p.starts_with("/") { + return if let Some(first_whitespace) = p.as_bytes().iter().position(|c| *c == b' ') { + Err(p.split_at(first_whitespace)) + } else { + Err((p, "")) + }; + } + + if let Some(first_whitespace) = p.as_bytes().iter().position(|c| *c == b' ') { + if let Some(path_end) = p[0..first_whitespace] + .as_bytes() + .iter() + .rposition(|c| *c == b'/') + { + let (path, rest) = p.split_at(path_end + 1); + if let Some(first_whitespace) = rest.as_bytes().iter().position(|c| *c == b' ') { + let (bin, rest) = rest.split_at(first_whitespace); + Ok((path, bin, rest)) + } else { + Ok((path, rest, "")) + } + } else { + return Err(p.split_at(first_whitespace)); + } + } else { + if let Some(path_end) = p.as_bytes().iter().rposition(|c| *c == b'/') { + let (path, bin) = p.split_at(path_end + 1); + Ok((path, bin, "")) + } else { + return Err((p, "")); + } + } } |