Skip to main content

peek_proc_reader/
limits.rs

1// /proc/PID/limits — selected resource limits.
2//
3// For now we expose just the "Max open files" soft/hard limits, which are
4// enough to drive fd-usage warnings in higher-level crates.
5
6use crate::error::{io_to_error, Result};
7use std::path::PathBuf;
8
9/// Subset of `/proc/<pid>/limits` that we care about.
10#[derive(Debug, Clone, Default)]
11pub struct Limits {
12    /// Soft limit for "Max open files", if set and not "unlimited".
13    pub max_open_files_soft: Option<u64>,
14    /// Hard limit for "Max open files", if set and not "unlimited".
15    pub max_open_files_hard: Option<u64>,
16}
17
18/// Read `/proc/<pid>/limits` and extract the "Max open files" limits.
19#[cfg(target_os = "linux")]
20pub fn read_limits(pid: i32) -> Result<Limits> {
21    let path = PathBuf::from(format!("/proc/{}/limits", pid));
22    let raw = std::fs::read_to_string(&path).map_err(|e| io_to_error(path.clone(), e, pid))?;
23
24    let mut out = Limits::default();
25
26    for line in raw.lines() {
27        if !line.starts_with("Max open files") {
28            continue;
29        }
30
31        // Example line:
32        // Max open files            1024                 4096                 files
33        let mut parts = line.split_whitespace();
34
35        // "Max", "open", "files", <soft>, <hard>, <units>...
36        let _ = parts.next();
37        let _ = parts.next();
38        let _ = parts.next();
39
40        let soft = parts.next();
41        let hard = parts.next();
42
43        out.max_open_files_soft = parse_limit_field(soft);
44        out.max_open_files_hard = parse_limit_field(hard);
45
46        break;
47    }
48
49    Ok(out)
50}
51
52/// On non-Linux platforms we don't have /proc; return empty limits.
53#[cfg(not(target_os = "linux"))]
54pub fn read_limits(_pid: i32) -> Result<Limits> {
55    Ok(Limits::default())
56}
57
58fn parse_limit_field(field: Option<&str>) -> Option<u64> {
59    let v = field?;
60    if v == "unlimited" {
61        return None;
62    }
63    v.parse::<u64>().ok()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::parse_limit_field;
69    use proptest::prelude::*;
70
71    #[test]
72    fn parses_numeric_limits() {
73        assert_eq!(parse_limit_field(Some("1024")), Some(1024));
74        assert_eq!(parse_limit_field(Some("0")), Some(0));
75    }
76
77    #[test]
78    fn treats_unlimited_as_none() {
79        assert_eq!(parse_limit_field(Some("unlimited")), None);
80    }
81
82    #[test]
83    fn handles_missing_field() {
84        assert_eq!(parse_limit_field(None), None);
85    }
86
87    proptest! {
88        #[test]
89        fn parse_limit_field_never_panics_for_arbitrary_strings(s in ".*") {
90            // Should not panic for any arbitrary input.
91            let _ = parse_limit_field(Some(&s));
92        }
93    }
94}