use std::fs;
use std::io;
use std::path::Path;
use std::str::FromStr;
use lazy_static::lazy_static;
use regex::{Regex, RegexSet};
use super::Controller;
use crate::{
attr_file::{AttrFile, ReadAttr, ResetAttr, StatMap, WriteAttr},
hierarchy::HierarchyNode,
};
pub trait MemoryController: Controller {
fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>>;
fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
fn soft_limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>>;
fn use_hierarchy(&self) -> Box<dyn '_ + AttrFile<bool>>;
fn force_empty(&self) -> Box<dyn '_ + ResetAttr>;
fn swappiness(&self) -> Box<dyn '_ + AttrFile<u8>>;
fn swap_memory_controller(&self) -> &dyn SwapMemoryController;
}
pub trait SwapMemoryController {
fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>>;
fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
}
impl MemoryController for HierarchyNode {
fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
let file = self.as_path().join("memory.usage_in_bytes");
Box::new(BytesFile(file))
}
fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
let file = self.as_path().join("memory.limit_in_bytes");
Box::new(BytesFile(file))
}
fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>> {
let file = self.as_path().join("memory.failcnt");
Box::new(FailCntFile(file))
}
fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
let file = self.as_path().join("memory.max_usage_in_bytes");
Box::new(BytesFile(file))
}
fn soft_limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
let file = self.as_path().join("memory.soft_limit_in_bytes");
Box::new(BytesFile(file))
}
fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>> {
let file = self.as_path().join("memory.stat");
Box::new(StatFile(file))
}
fn use_hierarchy(&self) -> Box<dyn '_ + AttrFile<bool>> {
let file = self.as_path().join("memory.use_hierarchy");
Box::new(UseHierarchyFile(file))
}
fn force_empty(&self) -> Box<dyn '_ + ResetAttr> {
let file = self.as_path().join("memory.force_empty");
Box::new(ForceEmptyFile(file))
}
fn swappiness(&self) -> Box<dyn '_ + AttrFile<u8>> {
let file = self.as_path().join("memory.swappiness");
Box::new(SwappinessFile(file))
}
fn swap_memory_controller(&self) -> &dyn SwapMemoryController {
self
}
}
impl SwapMemoryController for HierarchyNode {
fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
let file = self.as_path().join("memory.memsw.usage_in_bytes");
Box::new(BytesFile(file))
}
fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
let file = self.as_path().join("memory.memsw.limit_in_bytes");
Box::new(BytesFile(file))
}
fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>> {
let file = self.as_path().join("memory.memsw.failcnt");
Box::new(FailCntFile(file))
}
fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
let file = self.as_path().join("memory.memsw.max_usage_in_bytes");
Box::new(BytesFile(file))
}
}
struct StatFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ReadAttr<Stat> for StatFile<P> {
fn read(&self) -> io::Result<Stat> {
let s = fs::read_to_string(&self.0)?;
let stat_map = StatMap::from(s.as_str());
let res = stat_attr!(
stat_map,
Stat,
[
cache,
rss,
rss_huge,
mapped_file,
pgpgin,
pgpgout,
swap,
dirty,
writeback,
inactive_anon,
active_anon,
inactive_file,
active_file,
unevictable
]
);
Ok(res)
}
}
pub struct Stat {
pub cache: usize,
pub rss: usize,
pub rss_huge: usize,
pub mapped_file: usize,
pub pgpgin: usize,
pub pgpgout: usize,
pub swap: usize,
pub dirty: usize,
pub writeback: usize,
pub inactive_anon: usize,
pub active_anon: usize,
pub inactive_file: usize,
pub active_file: usize,
pub unevictable: usize,
}
struct BytesFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ReadAttr<Bytes> for BytesFile<P> {
fn read(&self) -> io::Result<Bytes> {
let s = fs::read_to_string(&self.0)?;
match s.trim().parse() {
Ok(bytes) => Ok(Bytes::from_bytes(bytes)),
Err(_) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to parse bytes from {}", self.0.as_ref().display()),
)),
}
}
}
impl<P: AsRef<Path>> WriteAttr<Bytes> for BytesFile<P> {
fn write(&self, bytes: &Bytes) -> io::Result<()> {
match bytes.as_bytes() {
Some(bytes) => fs::write(&self.0, bytes.to_string()),
None => fs::write(&self.0, b"-1"),
}
}
}
struct FailCntFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ReadAttr<usize> for FailCntFile<P> {
fn read(&self) -> io::Result<usize> {
let file = self.0.as_ref();
let s = fs::read_to_string(&file)?;
match s.trim().parse() {
Ok(cnt) => Ok(cnt),
Err(_) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to parse failed count from {}", file.display()),
)),
}
}
}
impl<P: AsRef<Path>> ResetAttr for FailCntFile<P> {
fn reset(&self) -> io::Result<()> {
fs::write(&self.0, b"0")
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct Bytes(Option<usize>);
impl Bytes {
const fn new(bytes: Option<usize>) -> Bytes {
Bytes(bytes)
}
pub const fn infinity() -> Bytes {
Bytes::new(None)
}
pub const fn from_bytes(b: usize) -> Bytes {
Bytes::new(Some(b))
}
pub fn as_bytes(&self) -> Option<usize> {
self.0
}
}
impl FromStr for Bytes {
type Err = io::Error;
#[allow(clippy::trivial_regex)]
fn from_str(s: &str) -> io::Result<Bytes> {
lazy_static! {
static ref BYTES: Regex = Regex::new(r"^(?P<bytes>\d+)[kKmMgG]?$").unwrap();
static ref BYTES_SET: RegexSet = RegexSet::new(&[
r"^(\d+)$",
r"^(\d+)[kK]$",
r"^(\d+)[mM]$",
r"^(\d+)[gG]$",
r"^-1$",
])
.unwrap();
}
match BYTES_SET.matches(s).iter().next() {
Some(i @ 0..=3) => {
let bytes: usize = BYTES.captures(s).unwrap()["bytes"].parse().unwrap();
Ok(Bytes::from_bytes(bytes * 1024usize.pow(i as u32)))
}
Some(4) => Ok(Bytes::infinity()),
Some(_) => unreachable!("regex set out of range"),
None => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to parse bytes \"{}\"", s),
)),
}
}
}
struct UseHierarchyFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ReadAttr<bool> for UseHierarchyFile<P> {
fn read(&self) -> io::Result<bool> {
let file = self.0.as_ref();
let s = fs::read_to_string(&file)?;
match s.trim() {
"0" => Ok(false),
"1" => Ok(true),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to parse bool in {}", file.display()),
)),
}
}
}
impl<P: AsRef<Path>> WriteAttr<bool> for UseHierarchyFile<P> {
fn write(&self, b: &bool) -> io::Result<()> {
fs::write(&self.0, if *b { b"1" } else { b"0" })
}
}
struct ForceEmptyFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ResetAttr for ForceEmptyFile<P> {
fn reset(&self) -> io::Result<()> {
fs::write(&self.0, b"0")
}
}
struct SwappinessFile<P: AsRef<Path>>(P);
impl<P: AsRef<Path>> ReadAttr<u8> for SwappinessFile<P> {
fn read(&self) -> io::Result<u8> {
let file = self.0.as_ref();
let s = fs::read_to_string(&file)?;
match s.trim().parse() {
Ok(u @ 0..=100) => Ok(u),
Ok(_) => panic!("value of swappiness out of range"),
Err(_) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to parse swappiness in {}", file.display()),
)),
}
}
}
impl<P: AsRef<Path>> WriteAttr<u8> for SwappinessFile<P> {
fn write(&self, u: &u8) -> io::Result<()> {
match *u {
u @ 0..=100 => fs::write(&self.0, u.to_string()),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"value of swappiness out of range",
)),
}
}
}