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 render(cpu_percent: f64, format: &str) -> Vec<Value> {
let contents = format.replace("{0:.0f}%", &format!("{:.0}%", cpu_percent));
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);
}
}
}
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)| {
fmt.replace("{days}", &v.to_string())
.replace("{hours}", &v.to_string())
.replace("{minutes}", &v.to_string())
.replace("{seconds}", &v.to_string())
})
.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);
}
}
}