/*
* 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 .
*/
use super::*;
use std::fs::File;
use std::io::prelude::*;
use std::str::FromStr;
/* if cpu_no > MAX_CPU_ROWS, the cpu bars wrap in columns */
static MAX_CPU_ROWS: usize = 5;
/* Kernel metrics components */
#[derive(Debug)]
pub struct KernelMetrics {
hostname: String,
kernel: String,
os_type: String,
uptime: String,
cpu_stat: Vec,
boot_time: usize,
dirty: bool,
}
impl fmt::Display for KernelMetrics {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "kernel")
}
}
impl KernelMetrics {
pub fn new() -> Self {
let mut file = File::open("/proc/sys/kernel/hostname").expect(crate::PROC_FS_ERROR_STR);
let mut hostname = String::new();
file.read_to_string(&mut hostname).unwrap();
let mut kernel = String::new();
file = File::open("/proc/sys/kernel/version").expect(crate::PROC_FS_ERROR_STR);
file.read_to_string(&mut kernel).unwrap();
let mut os_type = String::new();
file = File::open("/proc/sys/kernel/ostype").expect(crate::PROC_FS_ERROR_STR);
file.read_to_string(&mut os_type).unwrap();
let mut boot_time = 0;
let cpu_stat = get_stat(&mut boot_time);
KernelMetrics {
hostname,
kernel,
os_type,
uptime: String::with_capacity(60),
cpu_stat,
boot_time,
dirty: true,
}
}
/* Returns width of entire widget */
fn draw_cpu_bars(&mut self, grid: &mut CellBuffer, area: Area) -> usize {
let upper_left = upper_left!(area);
let total_cols = width!(area);
/* no of bars is no of CPUs along with the total CPU usage */
let cpu_no = self.cpu_stat.len();
/* Calculate how much horizontal space the labels (ie CPU0, CPU1, CPU2) take in order to
* distribute the remainder for each column, specifically the bars */
let mut cpu_label_space = 0;
let mut cpu_bar_columns = 0;
let mut i = 0;
while i < cpu_no {
/* Reserve space for CPU labels */
if i < 10 {
/* label will be " CPU0 " */
cpu_label_space += 7;
} else if i < 100 {
/* label will be " CPU32 " */
cpu_label_space += 8;
} else {
/* label will be " CPU128 " */
cpu_label_space += 9;
}
/* each column holds MAX_CPU_ROWS */
i += MAX_CPU_ROWS;
cpu_bar_columns += 1;
}
/* max width of each cpu bar */
let bar_width = if cpu_no < MAX_CPU_ROWS {
total_cols
} else {
(total_cols - cpu_label_space) / (cpu_bar_columns)
};
let mut boot_time: usize = 0;
let mut x_offset = 0;
for (i, cpu_stat) in get_stat(&mut boot_time).into_iter().enumerate() {
let label_len = if i < 10 {
8
} else if i < 100 {
9
} else {
10
};
let bottom_right = pos_inc(
upper_left,
(x_offset + bar_width + label_len, i % MAX_CPU_ROWS + 2),
);
let (mut x, y) = if i > 0 {
write_string_to_grid(
&format!("CPU{}", i),
grid,
Color::Default,
Color::Default,
Attr::Bold,
(
pos_inc(upper_left, (x_offset, 2 + (i % MAX_CPU_ROWS))),
bottom_right,
),
None,
)
} else {
/* add padding */
let (x, y) = write_string_to_grid(
"Σ",
grid,
Color::Default,
Color::Default,
Attr::Default,
(
pos_inc(upper_left, (x_offset, 2 + i % MAX_CPU_ROWS)),
bottom_right,
),
None,
);
write_string_to_grid(
"CPU",
grid,
Color::Default,
Color::Default,
Attr::Bold,
((x, y), bottom_right),
None,
)
};
x += 2;
/* Calculate percentages for the cpu usage bar */
let bar_length: usize = ((cpu_stat
.busy_time()
.saturating_sub(self.cpu_stat[i].busy_time())
as f64
/ (cpu_stat
.total_time()
.saturating_sub(self.cpu_stat[i].total_time())) as f64)
* bar_width as f64) as usize;
if bar_length >= width!(area) {
return x_offset;
}
/* Sometimes you draw the bar */
let mut _x_offset = 0;
while _x_offset < bar_length {
write_string_to_grid(
"▁",
grid,
Color::Byte(235),
Color::Byte(240),
Attr::Default,
((x + _x_offset, y), bottom_right),
None,
);
_x_offset += 1;
}
/* and sometimes the bar draws you */
while _x_offset <= bar_width {
write_string_to_grid(
"▁",
grid,
Color::Byte(236),
Color::Byte(235),
Attr::Default,
((x + _x_offset, y), bottom_right),
None,
);
_x_offset += 1;
}
self.cpu_stat[i] = cpu_stat;
if (i + 1) % MAX_CPU_ROWS == 0 {
x_offset += bar_width + label_len;
}
}
self.boot_time = boot_time;
if (self.cpu_stat.len()) % MAX_CPU_ROWS == 0 {
x_offset
} else {
x_offset
+ bar_width
+ if self.cpu_stat.len() < 10 {
8
} else if self.cpu_stat.len() < 100 {
9
} else {
10
}
}
}
fn draw_ram_bar(&mut self, grid: &mut CellBuffer, area: Area, bars_max: usize) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if bars_max == 0 {
/* In first draw, we have no cpu data since there is no previous measurement to use
* in the calculation so bars_max will be 0 */
return;
}
let (available, total) = get_mem_info();
/* available_length == the length the spaces takes up in this case:
* |******** | 50%
*/
let available_length = ((available as f64 / total as f64) * (bars_max * 8) as f64) as usize;
/* mem_bar length == the length the asterisks takes up in this case:
* |******** | 50%
*/
let mem_bar_length = bars_max * 8 - available_length;
let mem_display = format!(
"RAM {}/{}",
Bytes((total - available) * 1024).as_convenient_string(),
Bytes(total * 1024).as_convenient_string()
);
/* Put the "RAM XGB/YGB" in the middle of the RAM bar */
let mem_display_padding = bars_max.saturating_sub(mem_display.len()) / 2;
let y_offset = 2 + MAX_CPU_ROWS;
for x in 2..=(mem_bar_length / 8 + 2) {
write_string_to_grid(
"█",
grid,
Color::Byte(240),
Color::Default,
Attr::Default,
(pos_inc(upper_left, (x, y_offset)), bottom_right),
None,
);
}
let x = mem_bar_length / 8 + 3;
write_string_to_grid(
if mem_bar_length % 8 == 7 {
"▉"
} else if mem_bar_length % 8 == 6 {
"▊"
} else if mem_bar_length % 8 == 5 {
"▋"
} else if mem_bar_length % 8 == 4 {
"▌"
} else if mem_bar_length % 8 == 3 {
"▍"
} else if mem_bar_length % 8 == 2 {
"▎"
} else if mem_bar_length % 8 == 1 {
"▏"
} else {
" "
},
grid,
Color::Byte(240),
Color::Default,
Attr::Default,
(pos_inc(upper_left, (x, y_offset)), bottom_right),
None,
);
for x in x..(bars_max + 2) {
write_string_to_grid(
" ",
grid,
Color::Byte(240),
Color::Default,
Attr::Default,
(pos_inc(upper_left, (x, y_offset)), bottom_right),
None,
);
}
if available_length > 0 {
write_string_to_grid(
"▕",
grid,
Color::Byte(240),
Color::Default,
Attr::Default,
(pos_inc(upper_left, (bars_max + 2, y_offset)), bottom_right),
None,
);
}
write_string_to_grid(
&mem_display,
grid,
Color::White,
Color::Byte(235),
Attr::Default,
(
pos_inc(upper_left, (mem_display_padding + 2, y_offset)),
bottom_right,
),
None,
);
}
}
impl Component for KernelMetrics {
fn draw(
&mut self,
grid: &mut CellBuffer,
area: Area,
dirty_areas: &mut VecDeque,
tick: bool,
) {
if !is_valid_area!(area) {
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_cols = width!(area);
dirty_areas.push_back(area);
if self.dirty {
clear_area(grid, area);
let (x, y) = write_string_to_grid(
&self.hostname,
grid,
Color::Default,
Color::Default,
Attr::Bold,
area,
None,
);
let (x, y) = write_string_to_grid(
&self.os_type,
grid,
Color::Default,
Color::Default,
Attr::Default,
((x + 2, y), bottom_right),
None,
);
write_string_to_grid(
&self.kernel,
grid,
Color::Default,
Color::Default,
Attr::Default,
((x + 2, y), bottom_right),
None,
);
self.dirty = false;
}
/* Draw uptime */
let mut file = File::open("/proc/uptime").expect(crate::PROC_FS_ERROR_STR);
self.uptime.clear();
file.read_to_string(&mut self.uptime).unwrap();
let seconds: usize =
f64::from_str(self.uptime.split(" ").next().unwrap()).unwrap() as usize;
let days = seconds / (60 * 60 * 24);
let hours = seconds / 3600 - days * 24;
let mins = seconds / 60 - hours * 60 - days * 24 * 60;
let seconds = seconds % 60;
let uptime = if days > 0 {
format!(
"uptime: {} days, {:02}:{:02}:{:02}",
days, hours, mins, seconds
)
} else {
format!("uptime: {:02}:{:02}:{:02}", hours, mins, seconds)
};
write_string_to_grid(
&uptime,
grid,
Color::Default,
Color::Default,
Attr::Default,
(
(get_x(bottom_right) - uptime.len(), get_y(upper_left)),
bottom_right,
),
None,
);
if !tick {
return;
}
let old_cpu_stat = self.cpu_stat[0];
/* Draw CPU usage bars */
/* max width of cpu bar area */
let bars_max = (0.6 * total_cols as f32) as usize;
let cpu_widget_width = self.draw_cpu_bars(
grid,
(
pos_inc(upper_left, (2, 0)),
pos_inc(upper_left, (bars_max + 1, MAX_CPU_ROWS + 1)),
),
);
/* Draw RAM usage bar */
self.draw_ram_bar(grid, area, cpu_widget_width.saturating_sub(2));
/* Various values table */
/* max width of cpu bar area */
let bars_max = (0.6 * total_cols as f32) as usize;
/* CPU Times */
let mut cpu_column_width = "CPU".len();
let upper_left = pos_inc(upper_left, (bars_max + 5, 2));
clear_area(grid, (upper_left, bottom_right));
if get_x(upper_left) >= get_x(bottom_right) {
return;
}
write_string_to_grid(
"CPU%",
grid,
Color::Default,
Color::Default,
Attr::Bold,
(upper_left, bottom_right),
None,
);
for (i, (tag, s, fg_color, bg_color, attr)) in
get_cpu_times(&old_cpu_stat, &self.cpu_stat[0])
.into_iter()
.enumerate()
{
let (x, y) = write_string_to_grid(
tag,
grid,
Color::Default,
Color::Default,
Attr::Default,
(pos_inc(upper_left, (0, i + 1)), bottom_right),
None,
);
let padding = 6 - s.len();
clear_area(grid, ((x, y), (x + padding + 1, y)));
write_string_to_grid(
&s,
grid,
fg_color,
bg_color,
attr,
((x + 2 + padding, y), bottom_right),
None,
);
cpu_column_width = std::cmp::max(tag.len() + s.len() + 4, cpu_column_width);
}
/* Load average */
let upper_left = pos_inc(upper_left, (cpu_column_width + 3, 0));
if get_x(upper_left) >= get_x(bottom_right) {
return;
}
write_string_to_grid(
"LOAD_AVG",
grid,
Color::Default,
Color::Default,
Attr::Bold,
(upper_left, bottom_right),
None,
);
let loadavgs = get_loadavg();
for (i, avg) in loadavgs.iter().enumerate() {
write_string_to_grid(
&format!(
"{} {}",
match i {
0 => " 1",
1 => " 5",
_ => "15",
},
avg
),
grid,
Color::Default,
Color::Default,
Attr::Default,
(pos_inc(upper_left, (0, i + 1)), bottom_right),
None,
);
grid[pos_inc(upper_left, (0, i + 1))].set_attrs(Attr::Bold);
grid[pos_inc(upper_left, (1, i + 1))].set_attrs(Attr::Bold);
grid[pos_inc(upper_left, (0, i + 1))].set_fg(Color::Byte(8));
grid[pos_inc(upper_left, (1, i + 1))].set_fg(Color::Byte(8));
}
}
fn process_event(&mut self, event: &mut UIEvent, _ui_mode: &mut UIMode) {
match event {
UIEvent::Resize => {
self.dirty = true;
}
_ => {}
}
}
fn is_dirty(&self) -> bool {
true
}
fn set_dirty(&mut self) {
self.dirty = true;
}
}
fn get_mem_info() -> (usize, usize) {
let mut file = File::open("/proc/meminfo").expect(crate::PROC_FS_ERROR_STR);
let mut res = String::with_capacity(2048);
file.read_to_string(&mut res).unwrap();
let mut mem_total = 0;
let mut mem_available = 0;
let mut counter = 0;
for line in res.lines() {
if line.starts_with("MemTotal") {
mem_total = usize::from_str(line.split_whitespace().skip(1).next().unwrap()).unwrap();
counter += 1;
} else if line.starts_with("MemAvailable") {
mem_available =
usize::from_str(line.split_whitespace().skip(1).next().unwrap()).unwrap();
counter += 1;
}
if counter == 2 {
break;
}
}
(mem_available, mem_total)
}
fn get_loadavg() -> [String; 3] {
let mut file = File::open("/proc/loadavg").expect(crate::PROC_FS_ERROR_STR);
let mut res = String::with_capacity(2048);
file.read_to_string(&mut res).unwrap();
let mut mut_value_iter = res.split_whitespace();
let avg_1 = mut_value_iter.next().unwrap().to_string();
let avg_5 = mut_value_iter.next().unwrap().to_string();
let avg_15 = mut_value_iter.next().unwrap().to_string();
[avg_1, avg_5, avg_15]
}
fn get_cpu_times(
old_cpu_stat: &Stat,
cpu_stat: &Stat,
) -> Vec<(&'static str, String, Color, Color, Attr)> {
let mut ret = Vec::new();
macro_rules! val {
($tag:literal, $field:tt) => {
let percent = (cpu_stat.$field.saturating_sub(old_cpu_stat.$field)) as f64
/ (cpu_stat
.total_time()
.saturating_sub(old_cpu_stat.total_time())) as f64;
let s = format!("{:.1}%", percent * 100.0);
ret.push((
$tag,
s,
if percent < 0.15 {
Color::Default
} else {
Color::White
},
if percent < 0.15 {
Color::Default
} else if percent < 0.50 {
Color::Byte(70)
} else if $tag != "idle " {
Color::Red
} else {
Color::Default
},
if percent < 0.15 {
Attr::Default
} else if percent < 0.50 {
Attr::Bold
} else if $tag != "idle " {
Attr::Bold
} else {
Attr::Default
},
));
};
};
/* user % */
val!("user ", user_time);
/* system % */
val!("system ", system_time);
/* nice % */
val!("nice ", nice_time);
/* idle % */
val!("idle ", idle_time);
/* iowait % */
val!("iowait ", iowait_time);
ret
}