From 816d6c8a7386b67bcef1076575fd60b58ec6392c Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 25 Jan 2022 11:36:24 +0200 Subject: [PATCH 1/2] add sbot process stats function, struct and error variants --- peach-stats/src/error.rs | 12 +++++- peach-stats/src/lib.rs | 1 + peach-stats/src/sbot.rs | 90 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 peach-stats/src/sbot.rs diff --git a/peach-stats/src/error.rs b/peach-stats/src/error.rs index d782e35..e75a5e2 100644 --- a/peach-stats/src/error.rs +++ b/peach-stats/src/error.rs @@ -1,7 +1,7 @@ //! Custom error type for `peach-stats`. use probes::ProbeError; -use std::{error, fmt, io::Error as IoError}; +use std::{error, fmt, io::Error as IoError, str::Utf8Error}; /// Custom error type encapsulating all possible errors when retrieving system /// statistics. @@ -17,6 +17,10 @@ pub enum StatsError { MemStat(ProbeError), /// Failed to retrieve system uptime. Uptime(IoError), + /// Systemctl command returned an error. + Systemctl(IoError), + /// Failed to interpret sequence of `u8` as a string. + Utf8String(Utf8Error), } impl error::Error for StatsError {} @@ -39,6 +43,12 @@ impl fmt::Display for StatsError { StatsError::Uptime(ref source) => { write!(f, "Failed to retrieve system uptime: {}", source) } + StatsError::Systemctl(ref source) => { + write!(f, "Systemctl command returned an error: {}", source) + } + StatsError::Utf8String(ref source) => { + write!(f, "Failed to convert stdout to string: {}", source) + } } } } diff --git a/peach-stats/src/lib.rs b/peach-stats/src/lib.rs index cae7d50..7a0e0b0 100644 --- a/peach-stats/src/lib.rs +++ b/peach-stats/src/lib.rs @@ -43,6 +43,7 @@ //! ``` pub mod error; +pub mod sbot; pub mod stats; pub use crate::error::StatsError; diff --git a/peach-stats/src/sbot.rs b/peach-stats/src/sbot.rs new file mode 100644 index 0000000..b67a417 --- /dev/null +++ b/peach-stats/src/sbot.rs @@ -0,0 +1,90 @@ +//! 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) +} From e1aa7b1bb675d52d507c9f427005e7d1d9566ac9 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 25 Jan 2022 11:39:28 +0200 Subject: [PATCH 2/2] add sbot docs and bump version --- peach-stats/Cargo.toml | 2 +- peach-stats/README.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/peach-stats/Cargo.toml b/peach-stats/Cargo.toml index 488d637..a715323 100644 --- a/peach-stats/Cargo.toml +++ b/peach-stats/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "peach-stats" -version = "0.2.0" +version = "0.3.0" authors = ["Andrew Reid "] edition = "2018" description = "Query system statistics. Provides a wrapper around the probes and systemstat crates." diff --git a/peach-stats/README.md b/peach-stats/README.md index 1245532..9ea56ce 100644 --- a/peach-stats/README.md +++ b/peach-stats/README.md @@ -1,10 +1,10 @@ # peach-stats -![Generic badge](https://img.shields.io/badge/version-0.2.0-.svg) +![Generic badge](https://img.shields.io/badge/version-0.3.0-.svg) System statistics library for PeachCloud. Provides a wrapper around the [probes](https://crates.io/crates/probes) and [systemstat](https://crates.io/crates/systemstat) crates. -Currently offers the following statistics and associated data structures: +Currently offers the following system statistics and associated data structures: - CPU: `user`, `system`, `nice`, `idle` (as values or percentages) - Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`, @@ -13,10 +13,14 @@ Currently offers the following statistics and associated data structures: - Memory: `total`, `free`, `used` - Uptime: `seconds` +As well as the following go-sbot process statistics: + + - Sbot: `state`, `memory`, `uptime`, `downtime` + ## Example Usage ```rust -use peach_stats::{stats, StatsError}; +use peach_stats::{sbot, stats, StatsError}; fn main() -> Result<(), StatsError> { let cpu = stats::cpu_stats()?; @@ -25,6 +29,7 @@ fn main() -> Result<(), StatsError> { let load = stats::load_average()?; let mem = stats::mem_stats()?; let uptime = stats::uptime()?; + let sbot_process = sbot::sbot_stats()?; // do things with the retrieved values...