//! Systemd go-sbot process statistics retrieval functions and associated data types. use std::{process::Command, str}; #[cfg(feature = "miniserde_support")] use miniserde::{Deserialize, Serialize}; #[cfg(feature = "serde_support")] use serde::{Deserialize, Serialize}; use crate::StatsError; /// go-sbot process statistics. #[derive(Debug)] #[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub struct SbotStat { /// Current process state. pub state: Option, /// Current process boot state. pub boot_state: Option, /// Current process memory usage in bytes. pub memory: Option, /// Uptime for the process (if state is `active`). pub uptime: Option, /// Downtime for the process (if state is `inactive`). pub downtime: Option, } impl SbotStat { /// Default builder for `SbotStat`. fn default() -> Self { Self { state: None, boot_state: None, 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("sudo") .arg("systemctl") .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 = Some(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("sudo") .arg("systemctl") .arg("status") .arg("go-sbot.service") .output() .map_err(StatsError::Systemctl)?; let service_status = str::from_utf8(&status_output.stdout).map_err(StatsError::Utf8String)?; for line in service_status.lines() { // example of the output line we're looking for: // `Loaded: loaded (/home/glyph/.config/systemd/user/go-sbot.service; enabled; vendor // preset: enabled)` if line.contains("Loaded:") { let before_boot_state = line.find(';'); let after_boot_state = line.rfind(';'); if let (Some(start), Some(end)) = (before_boot_state, after_boot_state) { // extract the enabled / disabled from the `Loaded: ...` line // using the index of the first ';' + 2 and the last ';' status.boot_state = Some(line[start + 2..end].to_string()); } // example of the output line we're looking for here: // `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago` } else 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 == Some("active".to_string()) { status.uptime = time.map(|t| t.to_string()) // if service is inactive then the `time` reading is downtime } else if status.state == Some("inactive".to_string()) { status.downtime = time.map(|t| t.to_string()) } } } } Ok(status) }