1use std::fs;
2use std::io;
3use std::path::Path;
4use std::str::FromStr;
5
6use lazy_static::lazy_static;
7use regex::{Regex, RegexSet};
8
9use super::Controller;
10use crate::{
11 attr_file::{AttrFile, ReadAttr, ResetAttr, StatMap, WriteAttr},
12 hierarchy::HierarchyNode,
13};
14
15pub trait MemoryController: Controller {
16 fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
17 fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
18 fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>>;
19 fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
20 fn soft_limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
21 fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>>;
22 fn use_hierarchy(&self) -> Box<dyn '_ + AttrFile<bool>>;
23 fn force_empty(&self) -> Box<dyn '_ + ResetAttr>;
24 fn swappiness(&self) -> Box<dyn '_ + AttrFile<u8>>;
25
26 fn swap_memory_controller(&self) -> &dyn SwapMemoryController;
27
28 }
34
35pub trait SwapMemoryController {
36 fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
37 fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>>;
38 fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>>;
39 fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>>;
40}
41
42impl MemoryController for HierarchyNode {
43 fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
44 let file = self.as_path().join("memory.usage_in_bytes");
45 Box::new(BytesFile(file))
46 }
47
48 fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
49 let file = self.as_path().join("memory.limit_in_bytes");
50 Box::new(BytesFile(file))
51 }
52
53 fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>> {
54 let file = self.as_path().join("memory.failcnt");
55 Box::new(FailCntFile(file))
56 }
57
58 fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
59 let file = self.as_path().join("memory.max_usage_in_bytes");
60 Box::new(BytesFile(file))
61 }
62
63 fn soft_limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
64 let file = self.as_path().join("memory.soft_limit_in_bytes");
65 Box::new(BytesFile(file))
66 }
67
68 fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>> {
69 let file = self.as_path().join("memory.stat");
70 Box::new(StatFile(file))
71 }
72
73 fn use_hierarchy(&self) -> Box<dyn '_ + AttrFile<bool>> {
74 let file = self.as_path().join("memory.use_hierarchy");
75 Box::new(UseHierarchyFile(file))
76 }
77
78 fn force_empty(&self) -> Box<dyn '_ + ResetAttr> {
79 let file = self.as_path().join("memory.force_empty");
80 Box::new(ForceEmptyFile(file))
81 }
82
83 fn swappiness(&self) -> Box<dyn '_ + AttrFile<u8>> {
84 let file = self.as_path().join("memory.swappiness");
85 Box::new(SwappinessFile(file))
86 }
87
88 fn swap_memory_controller(&self) -> &dyn SwapMemoryController {
89 self
90 }
91}
92
93impl SwapMemoryController for HierarchyNode {
94 fn usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
95 let file = self.as_path().join("memory.memsw.usage_in_bytes");
96 Box::new(BytesFile(file))
97 }
98
99 fn limit_in_bytes(&self) -> Box<dyn '_ + AttrFile<Bytes>> {
100 let file = self.as_path().join("memory.memsw.limit_in_bytes");
101 Box::new(BytesFile(file))
102 }
103
104 fn failcnt(&self) -> Box<dyn '_ + ReadAttr<usize>> {
105 let file = self.as_path().join("memory.memsw.failcnt");
106 Box::new(FailCntFile(file))
107 }
108
109 fn max_usage_in_bytes(&self) -> Box<dyn '_ + ReadAttr<Bytes>> {
110 let file = self.as_path().join("memory.memsw.max_usage_in_bytes");
111 Box::new(BytesFile(file))
112 }
113}
114
115struct StatFile<P: AsRef<Path>>(P);
116
117impl<P: AsRef<Path>> ReadAttr<Stat> for StatFile<P> {
118 fn read(&self) -> io::Result<Stat> {
119 let s = fs::read_to_string(&self.0)?;
120 let stat_map = StatMap::from(s.as_str());
121 let res = stat_attr!(
122 stat_map,
123 Stat,
124 [
125 cache,
126 rss,
127 rss_huge,
128 mapped_file,
129 pgpgin,
130 pgpgout,
131 swap,
132 dirty,
133 writeback,
134 inactive_anon,
135 active_anon,
136 inactive_file,
137 active_file,
138 unevictable
139 ]
140 );
141 Ok(res)
142 }
143}
144
145pub struct Stat {
147 pub cache: usize,
149 pub rss: usize,
151 pub rss_huge: usize,
153 pub mapped_file: usize,
155 pub pgpgin: usize,
157 pub pgpgout: usize,
159 pub swap: usize,
161 pub dirty: usize,
163 pub writeback: usize,
165 pub inactive_anon: usize,
167 pub active_anon: usize,
169 pub inactive_file: usize,
171 pub active_file: usize,
173 pub unevictable: usize,
175}
176
177struct BytesFile<P: AsRef<Path>>(P);
178
179impl<P: AsRef<Path>> ReadAttr<Bytes> for BytesFile<P> {
180 fn read(&self) -> io::Result<Bytes> {
181 let s = fs::read_to_string(&self.0)?;
182 match s.trim().parse() {
183 Ok(bytes) => Ok(Bytes::from_bytes(bytes)),
184 Err(_) => Err(io::Error::new(
185 io::ErrorKind::InvalidData,
186 format!("failed to parse bytes from {}", self.0.as_ref().display()),
187 )),
188 }
189 }
190}
191
192impl<P: AsRef<Path>> WriteAttr<Bytes> for BytesFile<P> {
193 fn write(&self, bytes: &Bytes) -> io::Result<()> {
194 match bytes.as_bytes() {
195 Some(bytes) => fs::write(&self.0, bytes.to_string()),
196 None => fs::write(&self.0, b"-1"),
197 }
198 }
199}
200
201struct FailCntFile<P: AsRef<Path>>(P);
202
203impl<P: AsRef<Path>> ReadAttr<usize> for FailCntFile<P> {
204 fn read(&self) -> io::Result<usize> {
205 let file = self.0.as_ref();
206 let s = fs::read_to_string(&file)?;
207 match s.trim().parse() {
208 Ok(cnt) => Ok(cnt),
209 Err(_) => Err(io::Error::new(
210 io::ErrorKind::InvalidData,
211 format!("failed to parse failed count from {}", file.display()),
212 )),
213 }
214 }
215}
216
217impl<P: AsRef<Path>> ResetAttr for FailCntFile<P> {
218 fn reset(&self) -> io::Result<()> {
219 fs::write(&self.0, b"0")
220 }
221}
222
223#[derive(Clone, Copy, Eq, PartialEq, Debug)]
224pub struct Bytes(Option<usize>);
225
226impl Bytes {
227 const fn new(bytes: Option<usize>) -> Bytes {
228 Bytes(bytes)
229 }
230
231 pub const fn infinity() -> Bytes {
232 Bytes::new(None)
233 }
234
235 pub const fn from_bytes(b: usize) -> Bytes {
236 Bytes::new(Some(b))
237 }
238
239 pub fn as_bytes(&self) -> Option<usize> {
240 self.0
241 }
242}
243
244impl FromStr for Bytes {
258 type Err = io::Error;
259
260 #[allow(clippy::trivial_regex)]
261 fn from_str(s: &str) -> io::Result<Bytes> {
262 lazy_static! {
263 static ref BYTES: Regex = Regex::new(r"^(?P<bytes>\d+)[kKmMgG]?$").unwrap();
264 static ref BYTES_SET: RegexSet = RegexSet::new(&[
265 r"^(\d+)$",
266 r"^(\d+)[kK]$",
267 r"^(\d+)[mM]$",
268 r"^(\d+)[gG]$",
269 r"^-1$",
270 ])
271 .unwrap();
272 }
273 match BYTES_SET.matches(s).iter().next() {
274 Some(i @ 0..=3) => {
275 let bytes: usize = BYTES.captures(s).unwrap()["bytes"].parse().unwrap();
276 Ok(Bytes::from_bytes(bytes * 1024usize.pow(i as u32)))
277 }
278 Some(4) => Ok(Bytes::infinity()),
279 Some(_) => unreachable!("regex set out of range"),
280 None => Err(io::Error::new(
281 io::ErrorKind::InvalidData,
282 format!("failed to parse bytes \"{}\"", s),
283 )),
284 }
285 }
286}
287
288struct UseHierarchyFile<P: AsRef<Path>>(P);
289
290impl<P: AsRef<Path>> ReadAttr<bool> for UseHierarchyFile<P> {
291 fn read(&self) -> io::Result<bool> {
292 let file = self.0.as_ref();
293 let s = fs::read_to_string(&file)?;
294 match s.trim() {
295 "0" => Ok(false),
296 "1" => Ok(true),
297 _ => Err(io::Error::new(
298 io::ErrorKind::InvalidData,
299 format!("failed to parse bool in {}", file.display()),
300 )),
301 }
302 }
303}
304
305impl<P: AsRef<Path>> WriteAttr<bool> for UseHierarchyFile<P> {
306 fn write(&self, b: &bool) -> io::Result<()> {
307 fs::write(&self.0, if *b { b"1" } else { b"0" })
308 }
309}
310
311struct ForceEmptyFile<P: AsRef<Path>>(P);
312
313impl<P: AsRef<Path>> ResetAttr for ForceEmptyFile<P> {
314 fn reset(&self) -> io::Result<()> {
315 fs::write(&self.0, b"0")
316 }
317}
318
319struct SwappinessFile<P: AsRef<Path>>(P);
320
321impl<P: AsRef<Path>> ReadAttr<u8> for SwappinessFile<P> {
322 fn read(&self) -> io::Result<u8> {
323 let file = self.0.as_ref();
324 let s = fs::read_to_string(&file)?;
325 match s.trim().parse() {
326 Ok(u @ 0..=100) => Ok(u),
327 Ok(_) => panic!("value of swappiness out of range"),
328 Err(_) => Err(io::Error::new(
329 io::ErrorKind::InvalidData,
330 format!("failed to parse swappiness in {}", file.display()),
331 )),
332 }
333 }
334}
335
336impl<P: AsRef<Path>> WriteAttr<u8> for SwappinessFile<P> {
337 fn write(&self, u: &u8) -> io::Result<()> {
338 match *u {
339 u @ 0..=100 => fs::write(&self.0, u.to_string()),
340 _ => Err(io::Error::new(
341 io::ErrorKind::InvalidData,
342 "value of swappiness out of range",
343 )),
344 }
345 }
346}