//! Systemd go-sbot process statistics retrieval functions and associated data types. use std::{process::Command, str}; use crate::StatsError; /// go-sbot process statistics. #[derive(Debug)] pub struct SbotStat { /// Current process state. state: String, /// Current process memory usage in bytes. memory: Option, /// Uptime for the process (if state is `active`). uptime: Option, /// Downtime for the process (if state is `inactive`). downtime: Option, } impl SbotStat { /// Default builder for `SbotStat`. fn default() -> Self { Self { state: String::new(), memory: None, uptime: None, downtime: None, } } } /// Retrieve statistics for the go-sbot systemd process by querying `systemctl`. pub fn sbot_stats() -> Result { let mut status = SbotStat::default(); let info_output = Command::new("/usr/bin/systemctl") .arg("--user") .arg("show") .arg("go-sbot.service") .arg("--no-page") .output() .map_err(StatsError::Systemctl)?; let service_info = std::str::from_utf8(&info_output.stdout).map_err(StatsError::Utf8String)?; for line in service_info.lines() { if line.starts_with("ActiveState=") { if let Some(state) = line.strip_prefix("ActiveState=") { status.state = state.to_string() } } else if line.starts_with("MemoryCurrent=") { if let Some(memory) = line.strip_prefix("MemoryCurrent=") { status.memory = memory.parse().ok() } } } let status_output = Command::new("/usr/bin/systemctl") .arg("--user") .arg("status") .arg("go-sbot.service") .output() .unwrap(); let service_status = str::from_utf8(&status_output.stdout).map_err(StatsError::Utf8String)?; // example of the output line we're looking for: // `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago` for line in service_status.lines() { if line.contains("Active:") { let before_time = line.find(';'); let after_time = line.find(" ago"); if let (Some(start), Some(end)) = (before_time, after_time) { // extract the uptime / downtime from the `Active: ...` line // using the index of ';' + 2 and the index of " ago" let time = Some(&line[start + 2..end]); // if service is active then the `time` reading is uptime if status.state == "active" { status.uptime = time.map(|t| t.to_string()) // if service is inactive then the `time` reading is downtime } else if status.state == "inactive" { status.downtime = time.map(|t| t.to_string()) } } } } Ok(status) }