From 10c7dcdeab7bbf2016afdb8e6b5538a9cda801f1 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Thu, 26 Sep 2019 21:57:42 -0700 Subject: [PATCH] Read all CPUs in one widget. --- src/bin/home.rs | 7 +- src/bin/work.rs | 7 +- src/widgets/cpu.rs | 201 ++++++++++++++++----------------------------- 3 files changed, 75 insertions(+), 140 deletions(-) diff --git a/src/bin/home.rs b/src/bin/home.rs index 8e421d0..6a5b29a 100644 --- a/src/bin/home.rs +++ b/src/bin/home.rs @@ -2,8 +2,6 @@ use chrono::NaiveTime; use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection}; -use num_cpus; - use structopt::StructOpt; use i3xs::widgets::cpu::CpuWidget; @@ -22,10 +20,7 @@ fn main() { let opts = Opt::from_args(); - // Display all the cpu usage for each core - for i in 0..num_cpus::get() as u32 { - bar.push(CpuWidget::new(i)); - } + bar.push(CpuWidget::new()); // Realtime upload/download rate for a interface bar.push(NetworkSpeedWidget::new(&opts.nic, 6)); diff --git a/src/bin/work.rs b/src/bin/work.rs index 482712a..184f772 100644 --- a/src/bin/work.rs +++ b/src/bin/work.rs @@ -2,8 +2,6 @@ use chrono::NaiveTime; use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection}; -use num_cpus; - use structopt::StructOpt; use i3xs::widgets::cpu::CpuWidget; @@ -22,10 +20,7 @@ fn main() { let opts = Opt::from_args(); - // Display all the cpu usage for each core - for i in 0..num_cpus::get() as u32 { - bar.push(CpuWidget::new(i)); - } + bar.push(CpuWidget::new()); // Realtime upload/download rate for a interface bar.push(NetworkSpeedWidget::new(&opts.nic, 6)); diff --git a/src/widgets/cpu.rs b/src/widgets/cpu.rs index 04a97a4..11b766a 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -6,24 +6,21 @@ use i3monkit::{Block, ColorRGB, Widget, WidgetUpdate}; use crate::spark; +#[derive(Debug)] +struct Stat { + usage: u64, + total: u64, +} + /// The CPU usage widget /// /// This widget draws a CPU usage pertentage bar on your i3 status bar. pub struct CpuWidget { - id: u32, - user: u64, - nice: u64, - system: u64, - idel: u64, - width: u8, - user_color: String, - nice_color: String, - system_color: String, - history: VecDeque, + last_stats: Vec, + history: Vec>, } fn color_lerp(c1: ColorRGB, c2: ColorRGB, alpha: f32) -> ColorRGB { - // TODO(wathiede): actual lerp. ColorRGB( (c1.0 as f32 * (1. - alpha) + c2.0 as f32 * alpha) as u8, (c1.1 as f32 * (1. - alpha) + c2.1 as f32 * alpha) as u8, @@ -36,153 +33,101 @@ fn color_to_string(c: ColorRGB) -> String { } impl CpuWidget { - fn read_status(id: u32) -> Result<(u64, u64, u64, u64)> { - let name = format!("cpu{}", id); + fn read_status() -> Result> { let file = File::open("/proc/stat")?; let reader = BufReader::new(file); + let mut stats: Vec<_> = Vec::new(); for line in reader.lines() { let line = line?; let tokens: Vec<_> = line.trim().split(|c| c == ' ' || c == '\t').collect(); - if tokens[0] == name { + // The sum total CPU entry has a space instead of a number, so we skip the line when + // there's an extra empty column. + if tokens[1] == "" { + continue; + } + if tokens[0].starts_with("cpu") { let parsed = tokens[1..5] .iter() .map(|x| u64::from_str_radix(x, 10).unwrap()) .collect::>(); - return Ok((parsed[0], parsed[1], parsed[2], parsed[3])); + let usage = parsed[0] + parsed[1] + parsed[2]; + let total = usage + parsed[3]; + stats.push(Stat { usage, total }) } } - - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "No such CPU core", - )) + Ok(stats) } - /// Create a new CPU usage monitor widget for specified core - /// - /// **id** The core id - pub fn new(id: u32) -> Self { - let (user, nice, system, idel) = Self::read_status(id).unwrap(); + pub fn new() -> Self { + let last_stats = Self::read_status().unwrap(); let num_samples = 6; + let history = (0..last_stats.len()) + .map(|_| (0..num_samples).map(|_| 0.).collect::>()) + .collect::>>(); let ret = Self { - id, - user, - nice, - system, - idel, - width: 20, - user_color: "#00ff00".to_string(), - nice_color: "#0000ff".to_string(), - system_color: "#ff0000".to_string(), - history: (0..num_samples).map(|_| 0.).collect(), + last_stats, + history, }; return ret; } fn draw_history(&mut self) -> Option { - let (user, nice, system, idel) = Self::read_status(self.id).ok()?; - let used_diff = (user + nice + system) - (self.user + self.nice + self.system) + 1; - let total_diff = - (user + nice + system + idel) - (self.user + self.nice + self.system + self.idel) + 1; - - self.nice = nice; - self.user = user; - self.idel = idel; - self.system = system; - - let percent = used_diff as f32 / total_diff as f32; - self.history.push_back(percent); - self.history.pop_front(); - if self.history.len() > 0 { - let g = spark::Graph { - min: Some(0.), - max: Some(1.), - }; - let sg = g.render(&self.history.iter().cloned().collect::>()); - let colored_sg = self - .history - .iter() - .zip(sg.chars()) - .map(|(v, g)| { - let c = color_lerp(ColorRGB::green(), ColorRGB::red(), *v); - format!( - r##"{}"##, - color_to_string(c), - g - ) - }) - .collect(); - Some(colored_sg) - } else { - Some("N/A".to_string()) - } - } - - fn draw_bar(&mut self) -> Option { - let mut ret = Vec::new(); - for _ in 0..self.width { - ret.push("|".to_string()); - } - - let (user, nice, system, idel) = Self::read_status(self.id).ok()?; - - let total_diff = - (user + nice + system + idel) - (self.user + self.nice + self.system + self.idel); - - if total_diff > 0 { - let diffs = [system - self.system, nice - self.nice, user - self.user]; - let color = [&self.system_color, &self.nice_color, &self.user_color]; - - let mut idx = 0; - for (d, c) in diffs.iter().zip(color.iter()) { - for _ in 0..(d * (self.width as u64) / total_diff) { - ret[idx] = format!("|", c); - idx += 1; + let stats = Self::read_status().ok()?; + let percentages: Vec<_> = stats + .iter() + .zip(self.last_stats.iter()) + .map(|(cur, old)| { + if cur.total == old.total { + return 0.; } - } + (cur.usage - old.usage) as f32 / (cur.total - old.total) as f32 + }) + .collect(); + for (i, p) in percentages.iter().enumerate() { + self.history[i].push_back(*p); + self.history[i].pop_front(); } - - let mut result = String::new(); - for s in ret { - result.push_str(&s); - } - - self.nice = nice; - self.user = user; - self.idel = idel; - self.system = system; - - return Some(result); + self.last_stats = stats; + let g = spark::Graph { + min: Some(0.), + max: Some(1.), + }; + let graphs: Vec = self + .history + .iter() + .map(|history| { + let sg = g.render(&history.iter().map(|v| *v).collect::>()); + history + .iter() + .zip(sg.chars()) + .map(|(v, g)| { + let c = color_lerp(ColorRGB::green(), ColorRGB::red(), *v); + format!( + r##"{}"##, + color_to_string(c), + g + ) + }) + .collect() + }) + .collect(); + Some(format!("CPU|{}", graphs.join("|"))) } } impl Widget for CpuWidget { fn update(&mut self) -> Option { - if self.history.len() > 0 { - if let Some(history) = self.draw_history() { - let mut data = Block::new(); + if let Some(history) = self.draw_history() { + let mut data = Block::new(); - data.use_pango(); - data.append_full_text(&history); + data.use_pango(); + data.append_full_text(&history); - return Some(WidgetUpdate { - refresh_interval: std::time::Duration::new(1, 0), - data: Some(data), - }); - } - } else { - if let Some(bar) = self.draw_bar() { - let mut data = Block::new(); - - data.use_pango(); - data.append_full_text(&format!("{}[{}]", self.id + 1, bar)); - - return Some(WidgetUpdate { - refresh_interval: std::time::Duration::new(1, 0), - data: Some(data), - }); - } + return Some(WidgetUpdate { + refresh_interval: std::time::Duration::new(1, 0), + data: Some(data), + }); } None