swaybar_info/src/proc.rs
Stephen Seo 7bba60e323 Impl 0.1.5 of swaybar_info
Implemented `--netdev_width=<width>` which sets the minimum width of the netdev
byte/KiB/MiB text displays.

Implemented `--netgraph_max_bytes=<bytes>` which displays a graph in text using
Unicode "Block Elements" symbols. The `<bytes>` argument determines the maximum
amount of bytes that will determine which block-character is printed on the
interval. The graph is always 10 characters wide, and the right side is the
most-recent side. Note that this always checks against the maximum of either
download or upload rates. For example, if `<bytes>` is set to 1024, and 128
bytes were downloaded and 512 bytes were uploaded in an interval, the "Lower
Half Block" Unicode symbol will be emitted (exactly half).

SwaybarObject was changed to use an `Option<String>` instead of an `Option<u16>`
for `min_width`.
2022-10-18 21:04:28 +09:00

257 lines
7.2 KiB
Rust

use std::fmt::Write;
use std::fs::File;
use std::io::prelude::*;
#[derive(Debug)]
pub enum Error {
IO(std::io::Error),
ParseInt(std::num::ParseIntError),
Format(std::fmt::Error),
Generic(String),
}
impl From<std::io::Error> for Error {
fn from(io_error: std::io::Error) -> Self {
Self::IO(io_error)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(parse_error: std::num::ParseIntError) -> Self {
Self::ParseInt(parse_error)
}
}
impl From<std::fmt::Error> for Error {
fn from(fmt_error: std::fmt::Error) -> Self {
Self::Format(fmt_error)
}
}
impl From<String> for Error {
fn from(string: String) -> Self {
Self::Generic(string)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::IO(e) => e.fmt(f),
Error::ParseInt(e) => e.fmt(f),
Error::Format(e) => e.fmt(f),
Error::Generic(s) => f.write_str(s),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::IO(e) => e.source(),
Error::ParseInt(e) => e.source(),
Error::Format(e) => e.source(),
Error::Generic(_) => None,
}
}
}
pub struct NetInfo {
dev_name: String,
graph: String,
down: u64,
prev_down: u64,
up: u64,
prev_up: u64,
}
impl NetInfo {
pub fn new(dev_name: String) -> Self {
Self {
dev_name,
graph: String::from(" "),
down: 0,
prev_down: 0,
up: 0,
prev_up: 0,
}
}
pub fn update(&mut self) -> Result<(), Error> {
let mut netdev_string = String::new();
{
let mut netdev_file: File = File::open("/proc/net/dev")?;
netdev_file.read_to_string(&mut netdev_string)?;
}
let mut dev_line: Option<&str> = None;
for line in netdev_string.lines().map(|line| line.trim()) {
if line.starts_with(&self.dev_name) {
dev_line = Some(line);
break;
}
}
if let Some(line) = dev_line {
let entries: Vec<&str> = line.split_whitespace().collect();
if entries.len() < 10 {
return Err(format!("NetInfo::update: Failed to parse /proc/net/dev, \"{}\" device line is too short", self.dev_name).into());
}
self.down = entries[1].parse()?;
self.up = entries[9].parse()?;
} else {
return Err(format!(
"NetInfo::update: Failed to parse /proc/net/dev, can't find net device \"{}\"",
self.dev_name
)
.into());
}
Ok(())
}
pub fn get_netstring(&mut self, graph_max: Option<f64>) -> Result<(String, String), Error> {
let down_diff: f64 = (self.down - self.prev_down) as f64;
self.prev_down = self.down;
let up_diff: f64 = (self.up - self.prev_up) as f64;
self.prev_up = self.up;
let mut output = String::new();
if down_diff > 1024.0 * 1024.0 {
write!(&mut output, "{:.2} MiB ", down_diff / (1024.0 * 1024.0))?;
} else if down_diff > 1024.0 {
write!(&mut output, "{:.2} KiB ", down_diff / 1024.0)?;
} else {
write!(&mut output, "{:.0} B ", down_diff)?;
}
if up_diff > 1024.0 * 1024.0 {
write!(&mut output, "{:.2} MiB", up_diff / (1024.0 * 1024.0))?;
} else if up_diff > 1024.0 {
write!(&mut output, "{:.2} KiB", up_diff / 1024.0)?;
} else {
write!(&mut output, "{:.0} B", up_diff)?;
}
if let Some(graph_max) = graph_max {
let diff_max = if down_diff > up_diff {
down_diff
} else {
up_diff
};
let graph_value: u8 = if diff_max > graph_max {
8
} else {
(diff_max / graph_max * 8.0f64) as u8
};
let mut first = true;
let mut new_graph_string = String::with_capacity(10);
for current_char in self.graph.chars() {
if first {
first = false;
continue;
}
new_graph_string.push(current_char);
}
match graph_value {
0 => new_graph_string.push(' '),
1 => new_graph_string.push('▁'),
2 => new_graph_string.push('▂'),
3 => new_graph_string.push('▃'),
4 => new_graph_string.push('▄'),
5 => new_graph_string.push('▅'),
6 => new_graph_string.push('▆'),
7 => new_graph_string.push('▇'),
_ => new_graph_string.push('█'),
}
self.graph = new_graph_string;
}
Ok((output, self.graph.clone()))
}
}
pub fn get_meminfo() -> Result<String, Error> {
let mut meminfo_string = String::new();
{
let mut meminfo: File = File::open("/proc/meminfo")?;
meminfo.read_to_string(&mut meminfo_string)?;
}
let mut is_total_mega = false;
let mut total: u32 = 0;
let mut available: u32 = 0;
for line in meminfo_string.lines() {
if line.starts_with("MemTotal:") {
let line_parts = line
.split_whitespace()
.map(|s| s.to_owned())
.collect::<Vec<String>>();
total = line_parts[1].parse()?;
} else if line.starts_with("MemAvailable:") {
let line_parts = line
.split_whitespace()
.map(|s| s.to_owned())
.collect::<Vec<String>>();
available = line_parts[1].parse()?;
}
}
let mut used: f64 = (total - available) as f64;
let mut is_used_mega = false;
if total == 0 {
Ok("0".into())
} else {
let mut total = total as f64;
if total > 1024.0 {
total /= 1024.0;
is_total_mega = true;
}
if used > 1024.0 {
used /= 1024.0;
is_used_mega = true;
}
let mut output: String;
if is_used_mega {
output = format!("{:.2} ", used);
output.push_str("MiB / ");
} else {
output = format!("{:.0} ", used);
output.push_str("KiB / ");
}
if is_total_mega {
write!(&mut output, "{:.2} ", total)?;
output.push_str("MiB");
} else {
write!(&mut output, "{:.0} ", total)?;
output.push_str("KiB");
}
Ok(output)
}
}
pub fn get_loadavg() -> Result<String, Error> {
let mut loadavg_string = String::new();
{
let mut loadavg_file: File = File::open("/proc/loadavg")?;
loadavg_file.read_to_string(&mut loadavg_string)?;
}
let loadavg_parts: Vec<&str> = loadavg_string.split_whitespace().collect();
if loadavg_parts.len() < 3 {
return Err("loadavg: failed to parse".to_owned().into());
}
Ok(format!(
"{} {} {}",
loadavg_parts[0], loadavg_parts[1], loadavg_parts[2]
))
}