use std::process::Command;
use std::time::{Duration, Instant};
const FAILED_UNITS_REFRESH: Duration = Duration::from_secs(30);
#[derive(Debug, Default)]
pub struct FailedUnitsReader {
last: Option<(Instant, Vec<String>)>,
}
impl FailedUnitsReader {
pub fn new() -> Self {
Self::default()
}
pub fn read(&mut self, now: Instant) -> Vec<String> {
if let Some((at, units)) = &self.last
&& now.saturating_duration_since(*at) < FAILED_UNITS_REFRESH
{
return units.clone();
}
let units = Command::new("systemctl")
.args([
"list-units",
"--state=failed",
"--plain",
"--no-legend",
"--no-pager",
])
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| parse(&String::from_utf8_lossy(&out.stdout)))
.unwrap_or_default();
self.last = Some((now, units.clone()));
units
}
}
pub(crate) fn parse(text: &str) -> Vec<String> {
text.lines()
.map(|line| line.trim_start().trim_start_matches('●'))
.filter_map(|line| line.split_whitespace().next())
.map(str::to_owned)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_unit_names() {
let text = "nginx.service loaded failed failed A high performance web server\n\
● backup.timer loaded failed failed Nightly backup\n\n";
assert_eq!(parse(text), vec!["nginx.service", "backup.timer"]);
assert!(parse("").is_empty());
}
}