rigtop_core/linux/
systemd.rs1use std::process::Command;
7use std::time::{Duration, Instant};
8
9const FAILED_UNITS_REFRESH: Duration = Duration::from_secs(30);
11
12#[derive(Debug, Default)]
14pub struct FailedUnitsReader {
15 last: Option<(Instant, Vec<String>)>,
16}
17
18impl FailedUnitsReader {
19 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn read(&mut self, now: Instant) -> Vec<String> {
26 if let Some((at, units)) = &self.last
27 && now.saturating_duration_since(*at) < FAILED_UNITS_REFRESH
28 {
29 return units.clone();
30 }
31 let units = Command::new("systemctl")
32 .args([
33 "list-units",
34 "--state=failed",
35 "--plain",
36 "--no-legend",
37 "--no-pager",
38 ])
39 .output()
40 .ok()
41 .filter(|out| out.status.success())
42 .map(|out| parse(&String::from_utf8_lossy(&out.stdout)))
43 .unwrap_or_default();
44 self.last = Some((now, units.clone()));
45 units
46 }
47}
48
49pub(crate) fn parse(text: &str) -> Vec<String> {
52 text.lines()
53 .map(|line| line.trim_start().trim_start_matches('●'))
55 .filter_map(|line| line.split_whitespace().next())
56 .map(str::to_owned)
57 .collect()
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn parses_unit_names() {
66 let text = "nginx.service loaded failed failed A high performance web server\n\
67 ● backup.timer loaded failed failed Nightly backup\n\n";
68 assert_eq!(parse(text), vec!["nginx.service", "backup.timer"]);
69 assert!(parse("").is_empty());
70 }
71}