use anyhow::{Result, anyhow};
use serde::Serialize;
use std::fs::read_to_string;
use std::path::Path;
use crate::traits::ToJson;
use crate::utils::Size;
#[derive(Debug, Serialize, Default, Clone)]
pub struct RAM {
pub total: Size,
pub free: Size,
pub available: Size,
pub buffers: Size,
pub cached: Size,
pub swap_cached: Size,
pub active: Size,
pub inactive: Size,
pub active_anon: Size,
pub inactive_anon: Size,
pub active_file: Size,
pub inactive_file: Size,
pub unevictable: Size,
pub mlocked: Size,
pub swap_total: Size,
pub swap_free: Size,
pub zswap: Size,
pub zswapped: Size,
pub dirty: Size,
pub writeback: Size,
pub anon_pages: Size,
pub mapped: Size,
pub shmem: Size,
pub kreclaimable: Size,
pub slab: Size,
pub sreclaimable: Size,
pub sunreclaim: Size,
pub kernel_stack: Size,
pub page_tables: Size,
pub sec_page_tables: Size,
pub nfs_unstable: Size,
pub bounce: Size,
pub writeback_tmp: Size,
pub commit_limit: Size,
pub commited_as: Size,
pub vmalloc_total: Size,
pub vmalloc_used: Size,
pub vmalloc_chunk: Size,
pub percpu: Size,
pub hardware_corrupted: Size,
pub anon_huge_pages: Size,
pub shmem_huge_pages: Size,
pub shmem_pmd_mapped: Size,
pub cma_total: Option<Size>,
pub cma_free: Option<Size>,
pub file_huge_pages: Size,
pub file_pmd_mapped: Size,
pub unaccepted: Size,
pub huge_pages_total: u32,
pub huge_pages_free: u32,
pub huge_pages_rsvd: u32,
pub huge_pages_surp: u32,
pub huge_page_size: Size,
pub huge_tlb: Size,
pub direct_map_4k: Size,
pub direct_map_2m: Size,
pub direct_map_1g: Size,
}
impl RAM {
pub fn new() -> Result<Self> {
let chunks = read_to_string("/proc/meminfo")?;
let chunks = chunks
.lines()
.map(|item| {
let mut items = item.split(':').map(|item| item.trim());
(items.next(), items.next())
})
.collect::<Vec<_>>();
let mut ram = RAM::default();
for chunk in chunks {
match chunk {
(Some(key), Some(val)) => match key {
"MemTotal" => ram.total = Size::try_from(val)?,
"MemFree" => ram.free = Size::try_from(val)?,
"MemAvailable" => ram.available = Size::try_from(val)?,
"Buffers" => ram.buffers = Size::try_from(val)?,
"Cached" => ram.cached = Size::try_from(val)?,
"SwapCached" => ram.swap_cached = Size::try_from(val)?,
"Active" => ram.active = Size::try_from(val)?,
"Inactive" => ram.inactive = Size::try_from(val)?,
"Active(anon)" => ram.active_anon = Size::try_from(val)?,
"Inactive(anon)" => ram.inactive_anon = Size::try_from(val)?,
"Active(file)" => ram.active_file = Size::try_from(val)?,
"Inactive(file)" => ram.inactive_file = Size::try_from(val)?,
"Unevictable" => ram.unevictable = Size::try_from(val)?,
"Mlocked" => ram.mlocked = Size::try_from(val)?,
"SwapTotal" => ram.swap_total = Size::try_from(val)?,
"SwapFree" => ram.swap_free = Size::try_from(val)?,
"Zswap" => ram.zswap = Size::try_from(val)?,
"Zswapped" => ram.zswapped = Size::try_from(val)?,
"Dirty" => ram.dirty = Size::try_from(val)?,
"Writeback" => ram.writeback = Size::try_from(val)?,
"AnonPages" => ram.anon_pages = Size::try_from(val)?,
"Mapped" => ram.mapped = Size::try_from(val)?,
"Shmem" => ram.shmem = Size::try_from(val)?,
"KReclaimable" => ram.kreclaimable = Size::try_from(val)?,
"Slab" => ram.slab = Size::try_from(val)?,
"SReclaimable" => ram.sreclaimable = Size::try_from(val)?,
"SUnreclaim" => ram.sunreclaim = Size::try_from(val)?,
"KernelStack" => ram.kernel_stack = Size::try_from(val)?,
"PageTables" => ram.page_tables = Size::try_from(val)?,
"SecPageTables" => ram.sec_page_tables = Size::try_from(val)?,
"NFS_Unstable" => ram.nfs_unstable = Size::try_from(val)?,
"Bounce" => ram.bounce = Size::try_from(val)?,
"WritebackTmp" => ram.writeback_tmp = Size::try_from(val)?,
"CommitLimit" => ram.commit_limit = Size::try_from(val)?,
"Committed_AS" => ram.commited_as = Size::try_from(val)?,
"VmallocTotal" => ram.vmalloc_total = Size::try_from(val)?,
"VmallocUsed" => ram.vmalloc_used = Size::try_from(val)?,
"VmallocChunk" => ram.vmalloc_chunk = Size::try_from(val)?,
"Percpu" => ram.percpu = Size::try_from(val)?,
"HardwareCorrupted" => ram.hardware_corrupted = Size::try_from(val)?,
"AnonHugePages" => ram.anon_huge_pages = Size::try_from(val)?,
"ShmemHugePages" => ram.shmem_huge_pages = Size::try_from(val)?,
"ShmemPmdMapped" => ram.shmem_pmd_mapped = Size::try_from(val)?,
"CmaTotal" => ram.cma_total = Size::try_from(val).ok(),
"CmaFree" => ram.cma_free = Size::try_from(val).ok(),
"FileHugePages" => ram.file_huge_pages = Size::try_from(val)?,
"FilePmdMapped" => ram.file_pmd_mapped = Size::try_from(val)?,
"Unaccepted" => ram.unaccepted = Size::try_from(val)?,
"HugePages_Total" => ram.huge_pages_total = val.parse()?,
"HugePages_Free" => ram.huge_pages_free = val.parse()?,
"HugePages_Rsvd" => ram.huge_pages_rsvd = val.parse()?,
"HugePages_Surp" => ram.huge_pages_surp = val.parse()?,
"Hugepagesize" => ram.huge_page_size = Size::try_from(val)?,
"Hugetlb" => ram.huge_tlb = Size::try_from(val)?,
"DirectMap4k" => ram.direct_map_4k = Size::try_from(val)?,
"DirectMap2M" => ram.direct_map_2m = Size::try_from(val)?,
"DirectMap1G" => ram.direct_map_1g = Size::try_from(val)?,
_ => continue,
},
_ => {}
}
}
Ok(ram)
}
pub fn used_ram(&self, base: u8) -> Size {
if base != 2 && base != 10 {
panic!("Unknown base: {base} (supported values: 2 or 10)");
}
let total = if base == 2 {
self.total.get_bytes2().unwrap_or(0) as f32 / 1024. / 1024. / 1024.
} else
{
self.total.get_bytes10().unwrap_or(0) as f32 / 1000. / 1000. / 1000.
};
let avail = if base == 2 {
self.available.get_bytes2().unwrap_or(0) as f32 / 1024. / 1024. / 1024.
} else
{
self.available.get_bytes10().unwrap_or(0) as f32 / 1000. / 1000. / 1000.
};
let used = total - avail;
Size::GB(used)
}
pub fn usage_percentage(&self) -> Option<f64> {
let total_ram = self.total.get_bytes2()?;
let avail_ram = self.available.get_bytes2()?;
let used_ram = total_ram - avail_ram;
Some(used_ram as f64 / total_ram as f64 * 100.)
}
}
impl ToJson for RAM {}
#[derive(Debug, Serialize, Clone)]
pub struct Swaps {
pub swaps: Vec<Swap>,
}
impl Swaps {
pub fn new() -> Result<Self> {
let mut swaps = vec![];
let data = read_to_string("/proc/swaps")?;
let items = data.lines().skip(1);
for swap in items {
swaps.push(Swap::try_from(swap)?)
}
Ok(Self { swaps })
}
}
impl ToJson for Swaps {}
#[derive(Debug, Serialize, Clone)]
pub struct Swap {
pub filename: String,
pub swap_type: String,
pub size: Size,
pub used: Size,
pub priority: i8,
}
impl Swap {
pub fn usage_percentage(&self) -> Option<f64> {
let total_swap = self.size.get_bytes2()?;
let used_swap = self.used.get_bytes2()?;
Some(used_swap as f64 / total_swap as f64 * 100.)
}
}
impl TryFrom<&str> for Swap {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self> {
let data = value.split_whitespace().collect::<Vec<_>>();
if data.len() != 5 {
return Err(anyhow!("Format of the \"{value}\" string is incorrect!"));
}
Ok(Self {
filename: data[0].to_string(),
swap_type: data[1].to_string(),
size: Size::KB(data[2].parse()?),
used: Size::KB(data[3].parse()?),
priority: data[4].parse()?,
})
}
}
impl ToJson for Swap {}
#[derive(Debug, Clone, Serialize)]
pub struct Zswap {
pub accept_threshold_percent: Option<u8>,
pub compressor: Option<String>,
pub enabled: Option<bool>,
pub max_pool_percent: Option<u8>,
pub non_same_filled_pages_enabled: Option<bool>,
pub same_filled_pages_enabled: Option<bool>,
pub zpool: Option<String>,
pub pool_total_size: Option<Size>,
pub stored_pages_size: Option<Size>,
pub compression_ratio: Option<f32>,
}
fn get_page_size() -> u64 {
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 }
}
impl Zswap {
pub fn new() -> Self {
let rdbg = |f: &str| read_to_string(Path::new("/sys/kernel/debug/zswap/").join(f)).ok();
let fcnf =
|f: &str| read_to_string(Path::new("/sys/module/zswap/parameters/").join(f)).ok();
let get_bool = |content: &str| {
if content.trim() == "Y" || content != "0" {
true
} else {
false
}
};
let accept_threshold_percent =
fcnf("accept_threshold_percent").and_then(|atp| atp.trim().parse::<u8>().ok());
let compressor = fcnf("compressor");
let enabled = fcnf("enabled").and_then(|e| Some(get_bool(e.trim())));
let max_pool_percent =
fcnf("max_pool_percent").and_then(|mpp| mpp.trim().parse::<u8>().ok());
let non_same_filled_pages_enabled =
fcnf("non_same_filled_pages_enabled").and_then(|nsfpe| Some(get_bool(nsfpe.trim())));
let same_filled_pages_enabled =
fcnf("same_filled_pages_enabled").and_then(|sfpe| Some(get_bool(sfpe.trim())));
let zpool = fcnf("zpool");
let pool_total_size =
rdbg("pool_total_size").and_then(|pts| pts.trim().parse::<u64>().ok()); let stored_page_size =
rdbg("stored_page_size").and_then(|sps| sps.trim().parse::<u64>().ok()); let compression_ratio = match (pool_total_size, stored_page_size) {
(Some(pts), Some(sps)) => Some(sps as f32 / pts as f32),
_ => None,
};
Self {
accept_threshold_percent,
compressor,
enabled,
max_pool_percent,
non_same_filled_pages_enabled,
same_filled_pages_enabled,
zpool,
pool_total_size: pool_total_size.and_then(|pts| Some(Size::B(pts))),
stored_pages_size: stored_page_size
.and_then(|sps| Some(Size::B(sps * get_page_size()))),
compression_ratio,
}
}
}