linux_stats/
lib.rs

1// Copyright 2016 Nathan Sizemore <nathanrsizemore@gmail.com>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4// If a copy of the MPL was not distributed with this file, You can obtain one at
5// http://mozilla.org/MPL/2.0/.
6
7//! Data structures representative of various [procfs][procfs-url] reports.
8//!
9//! [procfs-url]: https://github.com/torvalds/linux/blob/master/Documentation/filesystems/proc.txt
10
11#[macro_use]
12extern crate enum_primitive;
13extern crate hex;
14extern crate num;
15
16use hex::FromHex;
17use num::FromPrimitive;
18
19use std::convert::Infallible;
20use std::default::Default;
21use std::fs::File;
22use std::io;
23use std::io::Read;
24use std::net::Ipv4Addr;
25use std::str::FromStr;
26
27/// Represents the output of `cat /proc/stat`
28#[derive(Debug, PartialEq, Clone, Default)]
29pub struct Stat {
30    pub cpu: Vec<u64>,
31    pub cpus: Vec<Vec<u64>>,
32    pub intr: Vec<u64>,
33    pub ctxt: u64,
34    pub btime: u32,
35    pub processes: u32,
36    pub procs_running: u32,
37    pub procs_blocked: u32,
38    pub softirq: Vec<u64>,
39}
40
41impl FromStr for Stat {
42    type Err = Infallible;
43
44    fn from_str(s: &str) -> Result<Stat, Infallible> {
45        let mut stat: Stat = Default::default();
46        for (line_num, line) in s.lines().enumerate() {
47            if line_num == 0 {
48                stat.cpu = to_vecu64(line);
49            }
50
51            if line.starts_with("cpu") && line_num > 0 {
52                stat.cpus.push(to_vecu64(line));
53            }
54
55            if line.starts_with("intr") {
56                stat.intr = to_vecu64(line);
57            }
58
59            if line.starts_with("ctxt") {
60                let mut chunks = line.split_whitespace();
61                chunks.next();
62
63                stat.ctxt = chunks.next().unwrap().parse::<u64>().unwrap();
64            }
65
66            if line.starts_with("btime") {
67                let mut chunks = line.split_whitespace();
68                chunks.next();
69
70                stat.btime = chunks.next().unwrap().parse::<u32>().unwrap();
71            }
72
73            if line.starts_with("processes") {
74                let mut chunks = line.split_whitespace();
75                chunks.next();
76
77                stat.processes = chunks.next().unwrap().parse::<u32>().unwrap();
78            }
79
80            if line.starts_with("procs_running") {
81                let mut chunks = line.split_whitespace();
82                chunks.next();
83
84                stat.procs_running = chunks.next().unwrap().parse::<u32>().unwrap();
85            }
86
87            if line.starts_with("procs_blocked") {
88                let mut chunks = line.split_whitespace();
89                chunks.next();
90
91                stat.procs_blocked = chunks.next().unwrap().parse::<u32>().unwrap();
92            }
93
94            if line.starts_with("softirq") {
95                stat.softirq = to_vecu64(line);
96            }
97        }
98
99        Ok(stat)
100    }
101}
102
103/// Represents the output of `cat /proc/meminfo`
104#[derive(Debug, PartialEq, Clone, Default)]
105pub struct MemInfo {
106    pub mem_total: u64,
107    pub mem_free: u64,
108    pub mem_available: u64,
109    pub bufers: u64,
110    pub cached: u64,
111    pub swap_cached: u64,
112    pub active: u64,
113    pub inactive: u64,
114    pub active_anon: u64,
115    pub inactive_anon: u64,
116    pub active_file: u64,
117    pub inactive_file: u64,
118    pub unevictable: u64,
119    pub mlocked: u64,
120    pub swap_total: u64,
121    pub swap_free: u64,
122    pub dirty: u64,
123    pub writeback: u64,
124    pub anon_pages: u64,
125    pub mapped: u64,
126    pub shmem: u64,
127    pub slab: u64,
128    pub s_reclaimable: u64,
129    pub s_unreclaim: u64,
130    pub kernel_stack: u64,
131    pub page_tables: u64,
132    pub nfs_unstable: u64,
133    pub bounce: u64,
134    pub writeback_tmp: u64,
135    pub commit_limit: u64,
136    pub committed_as: u64,
137    pub vmalloc_total: u64,
138    pub vmalloc_used: u64,
139    pub vmalloc_chunk: u64,
140    pub hardware_corrupted: u64,
141    pub anon_huge_pages: u64,
142    pub cma_total: u64,
143    pub cma_free: u64,
144    pub huge_pages_total: u64,
145    pub huge_pages_free: u64,
146    pub huge_pages_rsvd: u64,
147    pub huge_pages_surp: u64,
148    pub hugepagesize: u64,
149    pub direct_map_4k: u64,
150    pub direct_map_2m: u64,
151}
152
153impl FromStr for MemInfo {
154    type Err = Infallible;
155
156    fn from_str(s: &str) -> Result<MemInfo, Infallible> {
157        let mut meminfo: MemInfo = Default::default();
158
159        for line in s.lines() {
160            if line.starts_with("MemTotal") {
161                meminfo.mem_total = to_u64(line);
162            }
163
164            if line.starts_with("MemFree") {
165                meminfo.mem_free = to_u64(line);
166            }
167
168            if line.starts_with("MemAvailable") {
169                meminfo.mem_available = to_u64(line);
170            }
171
172            if line.starts_with("Buffers") {
173                meminfo.bufers = to_u64(line);
174            }
175
176            if line.starts_with("Cached") {
177                meminfo.cached = to_u64(line);
178            }
179
180            if line.starts_with("SwapCached") {
181                meminfo.swap_cached = to_u64(line);
182            }
183
184            if line.starts_with("Active") {
185                meminfo.active = to_u64(line);
186            }
187
188            if line.starts_with("Inactive") {
189                meminfo.inactive = to_u64(line);
190            }
191
192            if line.starts_with("Active(anon)") {
193                meminfo.active_anon = to_u64(line);
194            }
195
196            if line.starts_with("Inactive(anon)") {
197                meminfo.inactive_anon = to_u64(line);
198            }
199
200            if line.starts_with("Active(file)") {
201                meminfo.active_file = to_u64(line);
202            }
203
204            if line.starts_with("Inactive(file)") {
205                meminfo.inactive_file = to_u64(line);
206            }
207
208            if line.starts_with("Unevictable") {
209                meminfo.unevictable = to_u64(line);
210            }
211
212            if line.starts_with("Mlocked") {
213                meminfo.mlocked = to_u64(line);
214            }
215
216            if line.starts_with("SwapTotal") {
217                meminfo.swap_total = to_u64(line);
218            }
219
220            if line.starts_with("SwapFree") {
221                meminfo.swap_free = to_u64(line);
222            }
223
224            if line.starts_with("Dirty") {
225                meminfo.dirty = to_u64(line);
226            }
227
228            if line.starts_with("Writeback") {
229                meminfo.writeback = to_u64(line);
230            }
231
232            if line.starts_with("AnonPages") {
233                meminfo.anon_pages = to_u64(line);
234            }
235
236            if line.starts_with("Mapped") {
237                meminfo.mapped = to_u64(line);
238            }
239
240            if line.starts_with("Shmem") {
241                meminfo.shmem = to_u64(line);
242            }
243
244            if line.starts_with("Slab") {
245                meminfo.slab = to_u64(line);
246            }
247
248            if line.starts_with("SReclaimable") {
249                meminfo.s_reclaimable = to_u64(line);
250            }
251
252            if line.starts_with("SUnreclaim") {
253                meminfo.s_unreclaim = to_u64(line);
254            }
255
256            if line.starts_with("KernelStack") {
257                meminfo.kernel_stack = to_u64(line);
258            }
259
260            if line.starts_with("PageTables") {
261                meminfo.page_tables = to_u64(line);
262            }
263
264            if line.starts_with("NFS_Unstable") {
265                meminfo.nfs_unstable = to_u64(line);
266            }
267
268            if line.starts_with("Bounce") {
269                meminfo.bounce = to_u64(line);
270            }
271
272            if line.starts_with("WritebackTmp") {
273                meminfo.writeback_tmp = to_u64(line);
274            }
275
276            if line.starts_with("CommitLimit") {
277                meminfo.commit_limit = to_u64(line);
278            }
279
280            if line.starts_with("Committed_AS") {
281                meminfo.committed_as = to_u64(line);
282            }
283
284            if line.starts_with("VmallocTotal") {
285                meminfo.vmalloc_total = to_u64(line);
286            }
287
288            if line.starts_with("VmallocUsed") {
289                meminfo.vmalloc_used = to_u64(line);
290            }
291
292            if line.starts_with("VmallocChunk") {
293                meminfo.vmalloc_chunk = to_u64(line);
294            }
295
296            if line.starts_with("HardwareCorrupted") {
297                meminfo.hardware_corrupted = to_u64(line);
298            }
299
300            if line.starts_with("AnonHugePages") {
301                meminfo.anon_huge_pages = to_u64(line);
302            }
303
304            if line.starts_with("CmaTotal") {
305                meminfo.cma_total = to_u64(line);
306            }
307
308            if line.starts_with("CmaFree") {
309                meminfo.cma_free = to_u64(line);
310            }
311
312            if line.starts_with("HugePages_Total") {
313                meminfo.huge_pages_total = to_u64(line);
314            }
315
316            if line.starts_with("HugePages_Free") {
317                meminfo.huge_pages_free = to_u64(line);
318            }
319
320            if line.starts_with("HugePages_Rsvd") {
321                meminfo.huge_pages_rsvd = to_u64(line);
322            }
323
324            if line.starts_with("HugePages_Surp") {
325                meminfo.huge_pages_surp = to_u64(line);
326            }
327
328            if line.starts_with("Hugepagesize") {
329                meminfo.hugepagesize = to_u64(line);
330            }
331
332            if line.starts_with("DirectMap4k") {
333                meminfo.direct_map_4k = to_u64(line);
334            }
335
336            if line.starts_with("DirectMap2M") {
337                meminfo.direct_map_2m = to_u64(line);
338            }
339        }
340
341        Ok(meminfo)
342    }
343}
344
345enum_from_primitive! {
346    /// Represents TCP socket's state.
347    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
348    pub enum SocketState {
349        Established = 1,
350        SynSent = 2,
351        SynRecv = 3,
352        FinWait1 = 4,
353        FinWait2 = 5,
354        TimeWait = 6,
355        Close = 7,
356        CloseWait = 8,
357        LastAck = 9,
358        Listen = 10,
359        Closing = 11
360    }
361}
362
363/// Represents TCP socket's timer status.
364#[derive(Clone, Debug, PartialEq)]
365pub enum SocketTimerState {
366    // TODO: other timer states, timeout
367    Inactive,
368    Active(u64),
369}
370
371/// Represents a line (socket) in output of `cat /proc/net/{tcp,udp}`
372#[derive(Clone)]
373pub struct Socket {
374    pub sl: u64,
375    pub local_address: Ipv4Addr,
376    pub local_port: u16,
377    pub remote_address: Ipv4Addr,
378    pub remote_port: u16,
379    pub state: SocketState,
380    pub tx_queue: u64,
381    pub rx_queue: u64,
382    pub timer: SocketTimerState,
383    pub uid: u32,
384    pub inode: u64,
385}
386
387pub fn stat() -> io::Result<Stat> {
388    read_file("/proc/stat")?
389        .parse()
390        .map_err(|_| panic!("Infallible result occured"))
391}
392
393pub fn meminfo() -> io::Result<MemInfo> {
394    read_file("/proc/meminfo")?
395        .parse()
396        .map_err(|_| panic!("Infallible result occured"))
397}
398
399pub fn tcp() -> io::Result<Vec<Socket>> {
400    net("/proc/net/tcp")
401}
402
403pub fn udp() -> io::Result<Vec<Socket>> {
404    net("/proc/net/udp")
405}
406
407fn read_file(path: &str) -> io::Result<String> {
408    let file = File::open(path);
409    let mut content = String::new();
410
411    file.map(|mut f| f.read_to_string(&mut content))
412        .and(Ok(content))
413}
414
415fn net(file: &str) -> io::Result<Vec<Socket>> {
416    let content = read_file(file);
417    match content {
418        Ok(c) => Ok(c.lines().skip(1).map(to_net_socket).collect()),
419        Err(e) => Err(e),
420    }
421}
422
423fn to_vecu64(line: &str) -> Vec<u64> {
424    let mut chunks = line.split_whitespace();
425    let mut buf = Vec::<u64>::new();
426
427    // First chunk is always a non-number, descriptive text.
428    chunks.next();
429
430    for chunk in chunks {
431        buf.push(chunk.parse::<u64>().unwrap());
432    }
433
434    buf
435}
436
437fn to_u64(line: &str) -> u64 {
438    let mut chunks = line.split_whitespace();
439    chunks.next();
440
441    chunks.next().unwrap().parse::<u64>().unwrap()
442}
443
444fn to_net_socket(line: &str) -> Socket {
445    let mut chunks = line.split_whitespace();
446    let sl = chunks
447        .next()
448        .unwrap()
449        .split(':')
450        .next()
451        .unwrap()
452        .parse::<u64>()
453        .unwrap();
454
455    // Both local and remote addresses are formatted as <host>:<port> pair, so
456    // split them further.
457    let local: Vec<&str> = chunks.next().unwrap().split(':').collect();
458    let remote: Vec<&str> = chunks.next().unwrap().split(':').collect();
459    let state = Vec::<u8>::from_hex(chunks.next().unwrap()).unwrap()[0];
460    let queues: Vec<&str> = chunks.next().unwrap().split(':').collect();
461    let timer: Vec<&str> = chunks.next().unwrap().split(':').collect();
462    // retrnsmt - unused
463    chunks.next().unwrap();
464    let uid = chunks.next().unwrap().parse::<u32>().unwrap();
465    // timeout - unused
466    chunks.next().unwrap();
467    let inode = chunks.next().unwrap().parse::<u64>().unwrap();
468
469    Socket {
470        sl,
471        local_address: to_ipaddr(local[0]),
472        local_port: u16::from_str_radix(local[1], 16).unwrap(),
473        remote_address: to_ipaddr(remote[0]),
474        remote_port: u16::from_str_radix(remote[1], 16).unwrap(),
475        state: SocketState::from_u8(state).unwrap(),
476        tx_queue: u64::from_str_radix(queues[0], 16).unwrap(),
477        rx_queue: u64::from_str_radix(queues[1], 16).unwrap(),
478        timer: match timer[0].parse::<u8>().unwrap() {
479            0 => SocketTimerState::Inactive,
480            _ => SocketTimerState::Active(u64::from_str_radix(timer[1], 16).unwrap()),
481        },
482        uid,
483        inode,
484    }
485}
486
487fn to_ipaddr(hex: &str) -> Ipv4Addr {
488    let bytes = Vec::<u8>::from_hex(hex).unwrap();
489    Ipv4Addr::from([bytes[3], bytes[2], bytes[1], bytes[0]])
490}
491
492#[test]
493fn test_to_ipaddr() {
494    let addr = to_ipaddr("0100007F");
495    assert_eq!(addr.octets(), [127, 0, 0, 1]);
496}
497
498#[test]
499fn test_to_net_socket() {
500    let sock = to_net_socket("  49: 0100007F:1132 5B41EE2E:0050 0A 0000000A:00000002 01:0000000B 00000000  1001        0 2796814 1 ffff938ed0741080 20 4 29 10 -1");
501    assert_eq!(sock.local_address.octets(), [127, 0, 0, 1]);
502    assert_eq!(sock.local_port, 4402);
503    assert_eq!(sock.remote_address.octets(), [46, 238, 65, 91]);
504    assert_eq!(sock.remote_port, 80);
505    assert_eq!(sock.state, SocketState::Listen);
506    assert_eq!(sock.tx_queue, 0xA);
507    assert_eq!(sock.rx_queue, 2);
508    assert_eq!(sock.timer, SocketTimerState::Active(0xB));
509    assert_eq!(sock.uid, 1001);
510    assert_eq!(sock.inode, 2796814);
511}