Skip to main content

rigtop_core/linux/
procstat.rs

1//! Thread counts from `/proc/<pid>/stat`.
2//!
3//! `sysinfo` only knows a process's threads when it refreshes every thread
4//! as well (`/proc/<pid>/task/*`), which costs several reads per thread on
5//! every sample: on a desktop with thousands of browser and Electron
6//! threads, that is most of the collection time. The count itself is one
7//! field of the process's own `stat` file.
8
9use std::fs;
10
11use super::SysRoots;
12
13/// Number of threads of `pid`, or `None` if it is gone or unreadable.
14pub fn thread_count(roots: &SysRoots, pid: u32) -> Option<u32> {
15    let text = fs::read_to_string(roots.proc.join(pid.to_string()).join("stat")).ok()?;
16    parse_thread_count(&text)
17}
18
19/// Field 20 (`num_threads`) of a `stat` line. The command name (field 2) is
20/// in parentheses and may itself contain spaces and parentheses, so fields
21/// are counted from the last `)`.
22pub(crate) fn parse_thread_count(stat: &str) -> Option<u32> {
23    let after_name = &stat[stat.rfind(')')? + 1..];
24    // Field 3 (state) is the first one after the name, so field 20 is the 18th.
25    after_name.split_whitespace().nth(17)?.parse().ok()
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use crate::linux::testutil::write;
32
33    #[test]
34    fn counts_threads_even_with_odd_command_names() {
35        let stat = "4242 (Web Content (x)) S 1 4242 4242 0 -1 4194560 100 0 0 0 \
36                    50 20 0 0 20 0 37 0 1234 567890 1000 18446744073709551615";
37        assert_eq!(parse_thread_count(stat), Some(37));
38        assert_eq!(parse_thread_count("1 (init) S 1"), None, "truncated");
39        assert_eq!(parse_thread_count("garbage"), None);
40    }
41
42    #[test]
43    fn reads_from_proc() {
44        let tmp = tempfile::tempdir().unwrap();
45        write(
46            tmp.path(),
47            "proc/7/stat",
48            "7 (rigtop) R 1 7 7 0 -1 0 0 0 0 0 0 0 0 0 20 0 3 0 1 1 1\n",
49        );
50        let roots = SysRoots {
51            sys: tmp.path().join("sys"),
52            proc: tmp.path().join("proc"),
53        };
54        assert_eq!(thread_count(&roots, 7), Some(3));
55        assert_eq!(thread_count(&roots, 8), None);
56    }
57}