use serde_json::{json, Value};
use std::sync::atomic::{AtomicUsize, Ordering};
#[allow(non_upper_case_globals)]
pub static cpu_count: AtomicUsize = AtomicUsize::new(0);
fn _cpu_count() -> Option<usize> {
std::thread::available_parallelism().ok().map(|n| n.get())
}
fn _getloadavg() -> Option<[f64; 3]> {
#[cfg(unix)]
{
let mut buf = [0.0_f64; 3];
let n = unsafe { libc::getloadavg(buf.as_mut_ptr(), 3) };
if n == 3 {
Some(buf)
} else {
None
}
}
#[cfg(not(unix))]
{
None
}
}
pub fn system_load(
_pl: &(),
format: &str,
threshold_good: f64,
threshold_bad: f64,
track_cpu_count: bool,
short: bool,
) -> Option<Vec<Value>> {
let mut cpu_num = cpu_count.load(Ordering::Relaxed);
if cpu_num == 0 || track_cpu_count {
cpu_num = _cpu_count()?;
cpu_count.store(cpu_num, Ordering::Relaxed);
}
let cpu_num = cpu_num as f64;
let mut ret: Vec<Value> = Vec::new();
let loads = _getloadavg()?;
for avg in loads {
let normalized = avg / cpu_num;
let gradient_level = if normalized < threshold_good {
0.0
} else if normalized < threshold_bad {
(normalized - threshold_good) * 100.0 / (threshold_bad - threshold_good)
} else {
100.0
};
let contents = render_load_format(format, avg);
ret.push(json!({
"contents": contents,
"highlight_groups": ["system_load_gradient", "system_load"],
"divider_highlight_group": "background:divider",
"gradient_level": gradient_level,
}));
if short {
return Some(ret);
}
}
for r in ret.iter_mut().take(2) {
if let Some(v) = r
.get_mut("contents")
.and_then(|v| v.as_str().map(String::from))
{
r.as_object_mut()
.unwrap()
.insert("contents".to_string(), Value::String(format!("{} ", v)));
}
}
Some(ret)
}
pub fn update(measured_cpu_percent: f64) -> f64 {
measured_cpu_percent
}
pub fn run<M, S>(mut measure: M, is_shutdown: S)
where
M: FnMut() -> Result<f64, String>,
S: Fn() -> bool,
{
while !is_shutdown() {
let _ = measure();
}
}
pub fn startup() {
}
pub fn start() {
}
pub fn shutdown() {
}
pub fn render(cpu_percent: f64, format: &str) -> Vec<Value> {
let mut contents = String::new();
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
if c == '{' && chars.peek() == Some(&'0') {
chars.next(); let mut width: Option<usize> = None;
let mut prec: Option<usize> = None;
let mut is_float = false;
if chars.peek() == Some(&':') {
chars.next();
let mut width_buf = String::new();
while let Some(&p) = chars.peek() {
if p.is_ascii_digit() {
width_buf.push(p);
chars.next();
} else {
break;
}
}
if !width_buf.is_empty() {
width = width_buf.parse().ok();
}
if chars.peek() == Some(&'.') {
chars.next();
let mut prec_buf = String::new();
while let Some(&p) = chars.peek() {
if p.is_ascii_digit() {
prec_buf.push(p);
chars.next();
} else {
break;
}
}
prec = prec_buf.parse().ok();
}
if chars.peek() == Some(&'f') {
chars.next();
is_float = true;
}
}
if chars.peek() == Some(&'}') {
chars.next();
}
let rendered = match (width, prec, is_float) {
(Some(w), Some(p), true) => format!("{:>1$.2$}", cpu_percent, w, p),
(None, Some(p), true) => format!("{:.1$}", cpu_percent, p),
(Some(w), None, _) => format!("{:>1$}", cpu_percent, w),
(None, None, false) => format!("{}", cpu_percent),
(None, None, true) => format!("{}", cpu_percent),
(Some(w), Some(p), false) => format!("{:>1$.2$}", cpu_percent, w, p),
_ => format!("{}", cpu_percent),
};
contents.push_str(&rendered);
} else {
contents.push(c);
}
}
vec![json!({
"contents": contents,
"gradient_level": cpu_percent,
"highlight_groups": ["cpu_load_percent_gradient", "cpu_load_percent"],
})]
}
pub fn _get_uptime() -> Option<u64> {
if let Ok(content) = std::fs::read_to_string("/proc/uptime") {
if let Some(first) = content.split_whitespace().next() {
if let Ok(uptime) = first.parse::<f64>() {
return Some(uptime as u64);
}
}
}
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
if let Ok(name) = std::ffi::CString::new("kern.boottime") {
let mut tv: [libc::time_t; 2] = [0, 0];
let mut size = std::mem::size_of::<[libc::time_t; 2]>();
let rc = unsafe {
libc::sysctlbyname(
name.as_ptr(),
tv.as_mut_ptr() as *mut libc::c_void,
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc == 0 && tv[0] > 0 {
let now = unsafe { libc::time(std::ptr::null_mut()) };
if now > tv[0] {
return Some((now - tv[0]) as u64);
}
}
}
}
None
}
pub fn uptime(
_pl: &(),
days_format: &str,
hours_format: &str,
minutes_format: &str,
seconds_format: &str,
shorten_len: usize,
) -> Option<String> {
let total_seconds = _get_uptime()?;
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
let hours = minutes / 60;
let minutes = minutes % 60;
let days = hours / 24;
let hours = hours % 24;
let parts: Vec<(u64, &str)> = vec![
(days, days_format),
(hours, hours_format),
(minutes, minutes_format),
(seconds, seconds_format),
];
let first_non_zero = parts.iter().position(|(v, _)| *v != 0)?;
let end = (first_non_zero + shorten_len).min(parts.len());
let formatted: String = parts[first_non_zero..end]
.iter()
.map(|(v, fmt)| {
let mut out = String::with_capacity(fmt.len());
let mut chars = fmt.chars().peekable();
while let Some(c) = chars.next() {
if c != '{' {
out.push(c);
continue;
}
let mut name = String::new();
while let Some(&p) = chars.peek() {
if p == '}' || p == ':' {
break;
}
name.push(p);
chars.next();
}
let mut spec = String::new();
if chars.peek() == Some(&':') {
chars.next();
while let Some(&p) = chars.peek() {
if p == '}' {
break;
}
spec.push(p);
chars.next();
}
}
if chars.peek() == Some(&'}') {
chars.next();
}
let render = if spec == "d" || spec.is_empty() {
v.to_string()
} else if let Some(zero_pad) =
spec.strip_prefix('0').and_then(|s| s.strip_suffix('d'))
{
let width: usize = zero_pad.parse().unwrap_or(0);
format!("{:0width$}", v, width = width)
} else {
v.to_string()
};
let _ = name;
out.push_str(&render);
}
out
})
.collect();
Some(formatted.trim().to_string())
}
fn render_load_format(format: &str, avg: f64) -> String {
if let Some(rest) = format.strip_prefix("{avg:.") {
if let Some(idx) = rest.find("f}") {
if let Ok(n) = rest[..idx].parse::<usize>() {
return format!("{:.*}", n, avg);
}
}
}
format.replace("{avg}", &format!("{}", avg))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_load_format_one_decimal() {
assert_eq!(render_load_format("{avg:.1f}", 1.234), "1.2");
}
#[test]
fn render_load_format_two_decimal() {
assert_eq!(render_load_format("{avg:.2f}", 1.2345), "1.23");
}
#[test]
fn render_load_format_zero_decimal() {
assert_eq!(render_load_format("{avg:.0f}", 3.7), "4");
}
#[test]
fn render_load_format_plain_avg() {
assert!(render_load_format("{avg}", 1.5).starts_with("1.5"));
}
#[test]
fn cpu_count_helper_returns_at_least_one() {
let n = _cpu_count().unwrap();
assert!(n >= 1);
}
#[cfg(unix)]
#[test]
fn getloadavg_returns_three_values_on_unix() {
let loads = _getloadavg().unwrap();
for v in loads {
assert!(v >= 0.0);
}
}
#[test]
fn system_load_short_returns_one_entry() {
if let Some(result) = system_load(&(), "{avg:.1f}", 1.0, 2.0, false, true) {
assert_eq!(result.len(), 1);
}
}
#[test]
fn system_load_full_returns_three_entries_or_none() {
if let Some(result) = system_load(&(), "{avg:.1f}", 1.0, 2.0, false, false) {
assert_eq!(result.len(), 3);
assert!(result[0]["contents"].as_str().unwrap().ends_with(' '));
assert!(result[1]["contents"].as_str().unwrap().ends_with(' '));
}
}
#[test]
fn system_load_gradient_level_thresholds() {
if let Some(result) = system_load(&(), "{avg:.1f}", -1.0, 0.0, false, true) {
assert_eq!(result[0]["gradient_level"], 100.0);
}
if let Some(result) = system_load(&(), "{avg:.1f}", 1e9, 2e9, false, true) {
assert_eq!(result[0]["gradient_level"], 0.0);
}
}
#[test]
fn update_returns_supplied_cpu_percent() {
assert_eq!(update(42.5), 42.5);
assert_eq!(update(0.0), 0.0);
assert_eq!(update(100.0), 100.0);
}
#[test]
fn run_terminates_when_shutdown_returns_true() {
let count = std::sync::Arc::new(std::sync::Mutex::new(0u32));
let count_c = count.clone();
let shutdown_after_n = std::sync::Arc::new(std::sync::Mutex::new(3u32));
let shutdown_clone = shutdown_after_n.clone();
run(
move || {
*count_c.lock().unwrap() += 1;
Ok(50.0)
},
move || {
let mut n = shutdown_clone.lock().unwrap();
if *n == 0 {
true
} else {
*n -= 1;
false
}
},
);
assert_eq!(*count.lock().unwrap(), 3);
}
#[test]
fn startup_start_shutdown_are_documented_no_ops() {
startup();
start();
shutdown();
}
}