liboj_cgroups/subsystem/
memory.rs

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    // TODO: Unimplemented method
29    // memory.numa_stat
30    // memory.oom_control
31    // memory.move_charge_at_immigrate
32    // memory.pressure_level
33}
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
145/// Per-memory cgroup local status
146pub struct Stat {
147    /// of bytes of page cache memory.
148    pub cache: usize,
149    /// of bytes of anonymous and swap cache memory (includes transparent hugepages).
150    pub rss: usize,
151    /// of bytes of anonymous transparent hugepages.
152    pub rss_huge: usize,
153    /// of bytes of mapped file (includes tmpfs/shmem)
154    pub mapped_file: usize,
155    /// of charging events to the memory cgroup. The charging event happens each time a page is accounted as either mapped anon page(RSS) or cache page(Page Cache) to the cgroup.
156    pub pgpgin: usize,
157    /// of uncharging events to the memory cgroup. The uncharging event happens each time a page is unaccounted from the cgroup.
158    pub pgpgout: usize,
159    /// of bytes of swap usage
160    pub swap: usize,
161    /// of bytes that are waiting to get written back to the disk.
162    pub dirty: usize,
163    /// of bytes of file/anon cache that are queued for syncing to disk.
164    pub writeback: usize,
165    /// of bytes of anonymous and swap cache memory on inactive LRU list.
166    pub inactive_anon: usize,
167    /// of bytes of anonymous and swap cache memory on active LRU list.
168    pub active_anon: usize,
169    /// of bytes of file-backed memory on inactive LRU list.
170    pub inactive_file: usize,
171    /// of bytes of file-backed memory on active LRU list.
172    pub active_file: usize,
173    /// of bytes of memory that cannot be reclaimed (mlocked etc).
174    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
244/// ```
245/// use liboj_cgroups::subsystem::memory::Bytes;
246/// const K: usize = 1024;
247/// const M: usize = 1048576;
248/// const G: usize = 1073741824;
249/// assert_eq!(Bytes::from_bytes(K), "1k".parse().unwrap());
250/// assert_eq!(Bytes::from_bytes(K), "1K".parse().unwrap());
251/// assert_eq!(Bytes::from_bytes(M), "1m".parse().unwrap());
252/// assert_eq!(Bytes::from_bytes(M), "1M".parse().unwrap());
253/// assert_eq!(Bytes::from_bytes(G), "1g".parse().unwrap());
254/// assert_eq!(Bytes::from_bytes(G), "1G".parse().unwrap());
255/// assert_eq!(Bytes::infinity(), "-1".parse().unwrap());
256/// ```
257impl 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}