Compare commits

...

7 Commits

4 changed files with 129 additions and 161 deletions

View File

@ -2,8 +2,6 @@ use chrono::NaiveTime;
use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection}; use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection};
use num_cpus;
use structopt::StructOpt; use structopt::StructOpt;
use i3xs::widgets::cpu::CpuWidget; use i3xs::widgets::cpu::CpuWidget;
@ -22,10 +20,7 @@ fn main() {
let opts = Opt::from_args(); let opts = Opt::from_args();
//Display all the cpu usage for each core bar.push(CpuWidget::new());
for i in 0..num_cpus::get() as u32 {
bar.push(CpuWidget::new(i));
}
// Realtime upload/download rate for a interface // Realtime upload/download rate for a interface
bar.push(NetworkSpeedWidget::new(&opts.nic, 6)); bar.push(NetworkSpeedWidget::new(&opts.nic, 6));

View File

@ -1,12 +1,10 @@
use chrono::NaiveTime; use chrono::NaiveTime;
use i3monkit::widgets::CpuWidget;
use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection}; use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection};
use num_cpus;
use structopt::StructOpt; use structopt::StructOpt;
use i3xs::widgets::cpu::CpuWidget;
use i3xs::widgets::datetime::{DateTimeWidget, TimeColor}; use i3xs::widgets::datetime::{DateTimeWidget, TimeColor};
use i3xs::widgets::network::NetworkSpeedWidget; use i3xs::widgets::network::NetworkSpeedWidget;
@ -22,26 +20,48 @@ fn main() {
let opts = Opt::from_args(); let opts = Opt::from_args();
//Display all the cpu usage for each core bar.push(CpuWidget::new());
for i in 0..num_cpus::get() as u32 {
bar.push(CpuWidget::new(i));
}
// Realtime upload/download rate for a interface // Realtime upload/download rate for a interface
bar.push(NetworkSpeedWidget::new(&opts.nic, 6)); bar.push(NetworkSpeedWidget::new(&opts.nic, 6));
bar.push(DateTimeWidget::tz( let mut dt = DateTimeWidget::tz("%H:%M %Z".to_string(), "Europe/London".to_string());
"%H:%M %Z".to_string(),
"Europe/London".to_string(),
));
let mut dt = DateTimeWidget::new("%m/%d %H:%M".to_string());
dt.set_colors(&vec![ dt.set_colors(&vec![
TimeColor { TimeColor {
start: NaiveTime::from_hms(13, 0, 0), start: NaiveTime::from_hms(0, 0, 0),
color: ColorRGB::red(),
},
TimeColor {
start: NaiveTime::from_hms(8, 0, 0),
color: ColorRGB(255, 255, 255),
},
TimeColor {
start: NaiveTime::from_hms(17, 0, 0),
color: ColorRGB::yellow(), color: ColorRGB::yellow(),
}, },
TimeColor { TimeColor {
start: NaiveTime::from_hms(14, 0, 0), start: NaiveTime::from_hms(18, 0, 0),
color: ColorRGB::red(),
},
]);
bar.push(dt);
let mut dt = DateTimeWidget::new("%m/%d %H:%M".to_string());
dt.set_colors(&vec![
TimeColor {
start: NaiveTime::from_hms(0, 0, 0),
color: ColorRGB::red(),
},
TimeColor {
start: NaiveTime::from_hms(7, 0, 0),
color: ColorRGB(255, 255, 255),
},
TimeColor {
start: NaiveTime::from_hms(15, 0, 0),
color: ColorRGB::yellow(),
},
TimeColor {
start: NaiveTime::from_hms(16, 0, 0),
color: ColorRGB::red(), color: ColorRGB::red(),
}, },
]); ]);

View File

@ -6,24 +6,21 @@ use i3monkit::{Block, ColorRGB, Widget, WidgetUpdate};
use crate::spark; use crate::spark;
#[derive(Debug)]
struct Stat {
usage: u64,
total: u64,
}
/// The CPU usage widget /// The CPU usage widget
/// ///
/// This widget draws a CPU usage pertentage bar on your i3 status bar. /// This widget draws a CPU usage pertentage bar on your i3 status bar.
pub struct CpuWidget { pub struct CpuWidget {
id: u32, last_stats: Vec<Stat>,
user: u64, history: Vec<VecDeque<f32>>,
nice: u64,
system: u64,
idel: u64,
width: u8,
user_color: String,
nice_color: String,
system_color: String,
history: VecDeque<f32>,
} }
fn color_lerp(c1: ColorRGB, c2: ColorRGB, alpha: f32) -> ColorRGB { fn color_lerp(c1: ColorRGB, c2: ColorRGB, alpha: f32) -> ColorRGB {
// TODO(wathiede): actual lerp.
ColorRGB( ColorRGB(
(c1.0 as f32 * (1. - alpha) + c2.0 as f32 * alpha) as u8, (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, (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 { impl CpuWidget {
fn read_status(id: u32) -> Result<(u64, u64, u64, u64)> { fn read_status() -> Result<Vec<Stat>> {
let name = format!("cpu{}", id);
let file = File::open("/proc/stat")?; let file = File::open("/proc/stat")?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
let mut stats: Vec<_> = Vec::new();
for line in reader.lines() { for line in reader.lines() {
let line = line?; let line = line?;
let tokens: Vec<_> = line.trim().split(|c| c == ' ' || c == '\t').collect(); 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] let parsed = tokens[1..5]
.iter() .iter()
.map(|x| u64::from_str_radix(x, 10).unwrap()) .map(|x| u64::from_str_radix(x, 10).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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 })
} }
} }
Ok(stats)
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"No such CPU core",
))
} }
/// Create a new CPU usage monitor widget for specified core pub fn new() -> Self {
/// let last_stats = Self::read_status().unwrap();
/// **id** The core id
pub fn new(id: u32) -> Self {
let (user, nice, system, idel) = Self::read_status(id).unwrap();
let num_samples = 6; let num_samples = 6;
let history = (0..last_stats.len())
.map(|_| (0..num_samples).map(|_| 0.).collect::<VecDeque<f32>>())
.collect::<Vec<VecDeque<f32>>>();
let ret = Self { let ret = Self {
id, last_stats,
user, history,
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(),
}; };
return ret; return ret;
} }
fn draw_history(&mut self) -> Option<String> { fn draw_history(&mut self) -> Option<String> {
let (user, nice, system, idel) = Self::read_status(self.id).ok()?; let stats = Self::read_status().ok()?;
let used_diff = (user + nice + system) - (self.user + self.nice + self.system) + 1; let percentages: Vec<_> = stats
let total_diff = .iter()
(user + nice + system + idel) - (self.user + self.nice + self.system + self.idel) + 1; .zip(self.last_stats.iter())
.map(|(cur, old)| {
self.nice = nice; if cur.total == old.total {
self.user = user; return 0.;
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::<Vec<f32>>());
let colored_sg = self
.history
.iter()
.zip(sg.chars())
.map(|(v, g)| {
let c = color_lerp(ColorRGB::green(), ColorRGB::red(), *v);
format!(
r##"<span foreground="{}">{}</span>"##,
color_to_string(c),
g
)
})
.collect();
Some(colored_sg)
} else {
Some("N/A".to_string())
}
}
fn draw_bar(&mut self) -> Option<String> {
let mut ret = Vec::new();
for _ in 0..self.width {
ret.push("<span foreground=\"grey\">|</span>".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!("<span foreground=\"{}\">|</span>", c);
idx += 1;
} }
} (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();
} }
self.last_stats = stats;
let mut result = String::new(); let g = spark::Graph {
for s in ret { min: Some(0.),
result.push_str(&s); max: Some(1.),
} };
let graphs: Vec<String> = self
self.nice = nice; .history
self.user = user; .iter()
self.idel = idel; .map(|history| {
self.system = system; let sg = g.render(&history.iter().map(|v| *v).collect::<Vec<f32>>());
history
return Some(result); .iter()
.zip(sg.chars())
.map(|(v, g)| {
let c = color_lerp(ColorRGB::green(), ColorRGB::red(), *v);
format!(
r##"<span foreground="{}">{}</span>"##,
color_to_string(c),
g
)
})
.collect()
})
.collect();
Some(format!("CPU|{}", graphs.join("|")))
} }
} }
impl Widget for CpuWidget { impl Widget for CpuWidget {
fn update(&mut self) -> Option<WidgetUpdate> { fn update(&mut self) -> Option<WidgetUpdate> {
if self.history.len() > 0 { if let Some(history) = self.draw_history() {
if let Some(history) = self.draw_history() { let mut data = Block::new();
let mut data = Block::new();
data.use_pango(); data.use_pango();
data.append_full_text(&history); data.append_full_text(&history);
return Some(WidgetUpdate { return Some(WidgetUpdate {
refresh_interval: std::time::Duration::new(1, 0), refresh_interval: std::time::Duration::new(1, 0),
data: Some(data), 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),
});
}
} }
None None

View File

@ -47,28 +47,36 @@ impl DateTimeWidget {
impl Widget for DateTimeWidget { impl Widget for DateTimeWidget {
fn update(&mut self) -> Option<WidgetUpdate> { fn update(&mut self) -> Option<WidgetUpdate> {
let now = chrono::Local::now(); let (time, time_string) = match &self.tz {
Some(tz) => {
let tz: Tz = tz.parse().unwrap();
let now = chrono::Local::now();
let now = now.with_timezone(&tz);
(now.time(), now.format(&self.fmt).to_string())
}
None => {
let now = chrono::Local::now();
(now.time(), now.format(&self.fmt).to_string())
}
};
let color = if let Some(colors) = &self.colors { let color = if let Some(colors) = &self.colors {
colors colors
.iter() .iter()
.filter(|tc| tc.start < now.time()) .filter(|tc| tc.start < time)
.last() .last()
.map(|tc| tc.color.clone()) .map(|tc| tc.color.clone())
} else { } else {
None None
}; };
let time_string = match &self.tz { let time_string = format!(
Some(tz) => { r#"<span size="x-large" weight="heavy">{}</span>"#,
let tz: Tz = tz.parse().unwrap(); time_string
let now = now.with_timezone(&tz); );
//TODO(wathiede): convert to timezone. let mut data = Block::new()
now.format(&self.fmt).to_string() .append_full_text(&time_string)
} .use_pango()
None => now.format(&self.fmt).to_string(), .clone();
};
let mut data = Block::new().append_full_text(&time_string).clone();
if let Some(color) = color { if let Some(color) = color {
data.color(color); data.color(color);
} }