use std::{mem, process::Command, str::FromStr};
use sysinfo::System;
use thiserror::Error;
use crate::{
file::{FileReader, text::TextReader},
sys::Pid,
};
#[derive(Debug, Error)]
pub enum MemError {
#[error("stat for `{0}` not found")]
InvalidStat(String),
#[error("could not find system memory information")]
MemInfo,
#[error("could not clear memory HWM")]
Clear,
#[error("an internal error occurred")]
Internal,
}
pub fn stat<T>(key: &str, pid: Option<Pid>) -> Result<T, MemError>
where
T: FromStr + Copy,
{
let path = match pid {
Some(pid) => format!("/proc/{pid}/status"),
None => String::from("/proc/self/status"),
};
let reader = TextReader::from_path(path).map_err(|_| MemError::Internal)?;
for line in reader {
if line.starts_with(key) {
let parsed = &line[key.len() + 1..line.len() - 2]
.trim()
.parse::<T>();
return match parsed {
Ok(value) => Ok(*value),
Err(_) => Err(MemError::InvalidStat(key.to_string())),
};
}
}
Err(MemError::InvalidStat(key.to_string()))
}
#[inline]
pub fn hwm(pid: Option<Pid>) -> Result<u64, MemError> {
stat::<u64>("VmHWM", pid).map(|value| value * 1024)
}
#[inline]
pub fn rss(pid: Option<Pid>) -> Result<u64, MemError> {
stat::<u64>("VmRSS", pid).map(|value| value * 1024)
}
#[inline]
#[must_use]
pub fn total() -> u64 {
let mut sys = System::new();
sys.refresh_memory();
sys.total_memory()
}
pub fn clear(pid: Option<Pid>) -> Result<(), MemError> {
let command = match pid {
Some(pid) => format!("echo 1 > /proc/{pid}/clear_refs"),
None => String::from("echo 1 > /proc/self/clear_refs"),
};
let status = Command::new("sh")
.arg("-c")
.arg(command)
.status()
.map_err(|_| MemError::Internal)?;
match status.success() {
true => Ok(()),
false => Err(MemError::Clear),
}
}
#[inline]
#[must_use]
pub fn size_of<T>(value: &T) -> usize {
mem::size_of_val(value)
}
#[inline]
#[must_use]
pub fn size_of_vec<T>(value: &Vec<T>) -> usize {
let container_size = size_of(value);
if value.is_empty() {
return container_size;
}
container_size + value.len() * size_of(&value[0])
}