Merge pull request 'Retrieve go-sbot systemd process statistics' (#76) from sbot_stats into main

Reviewed-on: #76
This commit is contained in:
glyph 2022-01-25 09:46:50 +00:00
commit 178af281ed
5 changed files with 111 additions and 5 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "peach-stats"
version = "0.2.0"
version = "0.3.0"
authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018"
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."

View File

@ -1,10 +1,10 @@
# peach-stats
![Generic badge](https://img.shields.io/badge/version-0.2.0-<COLOR>.svg)
![Generic badge](https://img.shields.io/badge/version-0.3.0-<COLOR>.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...

View File

@ -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)
}
}
}
}

View File

@ -43,6 +43,7 @@
//! ```
pub mod error;
pub mod sbot;
pub mod stats;
pub use crate::error::StatsError;

90
peach-stats/src/sbot.rs Normal file
View File

@ -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<u32>,
/// Uptime for the process (if state is `active`).
uptime: Option<String>,
/// Downtime for the process (if state is `inactive`).
downtime: Option<String>,
}
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<SbotStat, StatsError> {
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)
}