1use std::{
2 fs::{self, File},
3 io::{self, Read},
4 mem::MaybeUninit,
5};
6
7const SMAPS_ROLLUP_PATH: &str = "/proc/self/smaps_rollup";
8const SMAPS_PATH: &str = "/proc/self/smaps";
9const STATM: &str = "/proc/self/statm";
10const RSS_LINE_PREFIX: &[u8] = b"Rss: ";
11
12enum StatSource {
13 SmapsRollup(Scanner<File>),
14 Smaps(Scanner<File>),
15 Statm(Option<usize>),
16}
17
18pub struct Querier {
20 source: StatSource,
21}
22
23impl Querier {
24 pub fn resident_set_size(&mut self) -> Option<usize> {
29 match &mut self.source {
30 StatSource::SmapsRollup(scanner) => {
31 scanner.reset_with_path(SMAPS_ROLLUP_PATH).ok()?;
34
35 while let Ok(Some(raw_rss_line)) = scanner.next_matching_line(RSS_LINE_PREFIX) {
36 let raw_rss_value = skip_to_line_value(raw_rss_line)?;
37 if let Some(rss_bytes) = parse_kb_value_as_bytes(raw_rss_value) {
38 return Some(rss_bytes);
39 }
40 }
41
42 None
43 }
44 StatSource::Smaps(scanner) => {
45 scanner.reset_with_path(SMAPS_PATH).ok()?;
49
50 let mut total_rss_bytes = 0;
51 while let Ok(Some(raw_rss_line)) = scanner.next_matching_line(RSS_LINE_PREFIX) {
52 let raw_rss_value = skip_to_line_value(raw_rss_line)?;
53 if let Some(rss_bytes) = parse_kb_value_as_bytes(raw_rss_value) {
54 total_rss_bytes += rss_bytes;
55 }
56 }
57
58 if total_rss_bytes > 0 {
59 Some(total_rss_bytes)
60 } else {
61 None
62 }
63 }
64 StatSource::Statm(maybe_page_size) => {
65 let page_size = maybe_page_size.as_ref().copied()?;
66
67 let mut buf = [0; 256];
74 let mut file = File::open(STATM).ok()?;
75 let n = file.read(&mut buf).ok()?;
76 if n == 0 || n == buf.len() {
77 return None;
79 }
80
81 let raw_rss_field = buf.split(|b| *b == b' ').nth(1)?;
83
84 let rss_pages = std::str::from_utf8(raw_rss_field).ok()?.parse::<usize>().ok()?;
86 Some(rss_pages * page_size)
87 }
88 }
89 }
90}
91
92impl Default for Querier {
93 fn default() -> Self {
94 Self {
95 source: determine_stat_source(),
96 }
97 }
98}
99
100fn determine_stat_source() -> StatSource {
101 if fs::metadata(SMAPS_ROLLUP_PATH).is_ok() {
102 StatSource::SmapsRollup(Scanner::new())
103 } else if fs::metadata(SMAPS_PATH).is_ok() {
104 StatSource::Smaps(Scanner::new())
105 } else {
106 StatSource::Statm(page_size())
107 }
108}
109
110fn page_size() -> Option<usize> {
111 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
112 if page_size <= 0 {
113 None
114 } else {
115 Some(page_size as usize)
116 }
117}
118
119fn parse_kb_value_as_bytes(raw_rss_value: &[u8]) -> Option<usize> {
120 match raw_rss_value.iter().position(|&b| b == b' ') {
123 Some(space_idx) => {
124 let raw_value = &raw_rss_value[..space_idx];
125 std::str::from_utf8(raw_value)
126 .ok()?
127 .parse::<usize>()
128 .ok()
129 .map(|value| value * 1024)
130 }
131 None => None,
132 }
133}
134
135struct Scanner<T> {
136 io: Option<T>,
137 eof: bool,
138 buf: Vec<u8>,
139 pending_consume: Option<usize>,
140}
141
142impl<T> Scanner<T>
143where
144 T: Read,
145{
146 fn new() -> Self {
147 Self {
148 io: None,
149 eof: false,
150 buf: Vec::with_capacity(8192),
151 pending_consume: None,
152 }
153 }
154
155 fn reset(&mut self, io: T) {
156 self.buf.clear();
157 self.eof = false;
158 self.pending_consume = None;
159 self.io = Some(io);
160 }
161
162 fn get_io_mut(&mut self) -> io::Result<&mut T> {
163 match self.io.as_mut() {
164 Some(io) => Ok(io),
165 None => Err(io::Error::other("no file set in scanner")),
166 }
167 }
168
169 fn fill_buf(&mut self) -> io::Result<()> {
170 if self.eof {
171 return Ok(());
172 }
173
174 if self.buf.len() < self.buf.capacity() {
176 let read_buf = unsafe { &mut *(self.buf.spare_capacity_mut() as *mut [MaybeUninit<u8>] as *mut [u8]) };
180 let n = self.get_io_mut()?.read(read_buf)?;
181 if n == 0 {
182 self.eof = true;
183 }
184
185 unsafe {
188 self.buf.set_len(self.buf.len() + n);
189 }
190 }
191
192 Ok(())
193 }
194
195 fn next_matching_line(&mut self, prefix: &[u8]) -> io::Result<Option<&[u8]>> {
196 loop {
197 if self.eof && self.buf.is_empty() {
199 return Ok(None);
200 }
201
202 if let Some(consume) = self.pending_consume {
205 self.buf.drain(..consume);
206 self.pending_consume = None;
207 }
208
209 self.fill_buf()?;
211
212 if self.buf.starts_with(prefix) {
216 let maybe_newline_idx = self.buf.iter().position(|&b| b == b'\n');
217 if let Some(newline_idx) = maybe_newline_idx {
218 self.pending_consume = Some(newline_idx + 1);
220
221 return Ok(Some(&self.buf[..newline_idx]));
222 }
223 } else {
224 let maybe_newline_idx = self.buf.iter().position(|&b| b == b'\n');
229 if let Some(newline_idx) = maybe_newline_idx {
230 self.pending_consume = Some(newline_idx + 1);
231 } else {
232 self.buf.clear();
234 }
235 }
236 }
237 }
238}
239
240impl Scanner<File> {
241 fn reset_with_path(&mut self, path: &str) -> io::Result<()> {
242 let file = File::open(path)?;
243 self.reset(file);
244
245 Ok(())
246 }
247}
248
249fn skip_to_line_value(raw_line: &[u8]) -> Option<&[u8]> {
250 raw_line
254 .iter()
255 .position(|b| b.is_ascii_digit())
256 .map(|idx| &raw_line[idx..])
257}
258
259#[cfg(test)]
260mod tests {
261 use super::Querier;
262
263 #[test]
264 fn basic() {
265 let mut querier = Querier::default();
266 assert!(querier.resident_set_size().is_some());
267 }
268
269 #[test]
270 fn skip_to_line_value() {
271 let passing_lines = [
272 "1234 kB".as_bytes(),
273 " 1234 kB".as_bytes(),
274 "\t1234 kB".as_bytes(),
275 "Rss:1234 kB".as_bytes(),
276 "Rss: 1234 kB".as_bytes(),
277 ];
278 for line in &passing_lines {
279 assert_eq!(super::skip_to_line_value(line), Some("1234 kB".as_bytes()));
280 }
281
282 let failing_lines = [
283 "Rss: ".as_bytes(),
284 "Rss: \n".as_bytes(),
285 "Rss: kB".as_bytes(),
286 "Rss: kB\n".as_bytes(),
287 ];
288 for line in &failing_lines {
289 assert_eq!(super::skip_to_line_value(line), None);
290 }
291 }
292
293 #[test]
294 fn scanner_basic() {
295 let prefix = "Rss: ".as_bytes();
296
297 let mut scanner = super::Scanner::new();
298 let mut buf = Vec::new();
299 buf.extend_from_slice(b"Rss: 1234 kB\nRss: 5678 kB\nRss: 91011 kB\n");
300 scanner.reset(buf.as_slice());
301
302 assert_eq!(
303 scanner.next_matching_line(prefix).unwrap(),
304 Some(b"Rss: 1234 kB".as_ref())
305 );
306 assert_eq!(
307 scanner.next_matching_line(prefix).unwrap(),
308 Some(b"Rss: 5678 kB".as_ref())
309 );
310 assert_eq!(
311 scanner.next_matching_line(prefix).unwrap(),
312 Some(b"Rss: 91011 kB".as_ref())
313 );
314 assert_eq!(scanner.next_matching_line(prefix).unwrap(), None);
315 }
316
317 #[test]
318 fn scanner_skip_non_matching_lines() {
319 let prefix = "Rss: ".as_bytes();
320
321 let mut scanner = super::Scanner::new();
322 let mut buf = Vec::new();
323 buf.extend_from_slice(b"Rss: 1234 kB\nPss: 5678 kB\nHugepages: 42069 kB\nRss: 91011 kB\n");
324 scanner.reset(buf.as_slice());
325
326 assert_eq!(
327 scanner.next_matching_line(prefix).unwrap(),
328 Some(b"Rss: 1234 kB".as_ref())
329 );
330 assert_eq!(
331 scanner.next_matching_line(prefix).unwrap(),
332 Some(b"Rss: 91011 kB".as_ref())
333 );
334 assert_eq!(scanner.next_matching_line(prefix).unwrap(), None);
335 }
336
337 #[test]
338 fn scanner_skips_lines_larger_than_buffer() {
339 let prefix = "Rss: ".as_bytes();
340
341 let mut scanner = super::Scanner::new();
342
343 let mut buf = Vec::new();
346 buf.resize(9000, b'@');
347 buf.extend_from_slice(b"\nRss: 1234 kB\n");
348 scanner.reset(buf.as_slice());
349
350 assert_eq!(
351 scanner.next_matching_line(prefix).unwrap(),
352 Some(b"Rss: 1234 kB".as_ref())
353 );
354 assert_eq!(scanner.next_matching_line(prefix).unwrap(), None);
355 }
356}