codetether_agent/telemetry/memory.rs
1//! Process memory telemetry.
2//!
3//! Reads `/proc/self/status` on Linux to surface VmRSS / VmPeak / Threads.
4//! All fields are best-effort; a missing or unreadable field is reported as
5//! `None` rather than failing the caller.
6
7use serde::{Deserialize, Serialize};
8
9/// Snapshot of a process's memory usage, in kilobytes.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct MemorySnapshot {
12 /// Resident set size, in KiB.
13 pub rss_kb: Option<u64>,
14 /// Peak resident set size, in KiB.
15 pub peak_rss_kb: Option<u64>,
16 /// Virtual memory size, in KiB.
17 pub vsize_kb: Option<u64>,
18 /// Peak virtual memory size, in KiB.
19 pub peak_vsize_kb: Option<u64>,
20 /// Current thread count for the process.
21 pub threads: Option<u64>,
22}
23
24impl MemorySnapshot {
25 /// Capture a snapshot of the current process's memory usage.
26 ///
27 /// Returns a default-filled snapshot (all `None`) on non-Linux targets
28 /// or when `/proc/self/status` is unreadable.
29 pub fn capture() -> Self {
30 #[cfg(target_os = "linux")]
31 {
32 match std::fs::read_to_string("/proc/self/status") {
33 Ok(text) => Self::parse(&text),
34 Err(_) => Self::default(),
35 }
36 }
37 #[cfg(not(target_os = "linux"))]
38 {
39 Self::default()
40 }
41 }
42
43 /// Parse `/proc/self/status`-style content.
44 ///
45 /// Only `VmRSS`, `VmHWM`, `VmSize`, `VmPeak`, and `Threads` are extracted.
46 ///
47 /// # Examples
48 ///
49 /// ```rust
50 /// use codetether_agent::telemetry::memory::MemorySnapshot;
51 ///
52 /// let text = "VmRSS:\t 1234 kB\nVmHWM:\t 2345 kB\nThreads:\t 7\n";
53 /// let snap = MemorySnapshot::parse(text);
54 /// assert_eq!(snap.rss_kb, Some(1234));
55 /// assert_eq!(snap.peak_rss_kb, Some(2345));
56 /// assert_eq!(snap.threads, Some(7));
57 /// ```
58 pub fn parse(text: &str) -> Self {
59 let mut s = Self::default();
60 for line in text.lines() {
61 let (key, rest) = match line.split_once(':') {
62 Some((k, v)) => (k.trim(), v.trim()),
63 None => continue,
64 };
65 // Values like "1234 kB" or bare "7".
66 let num = rest
67 .split_whitespace()
68 .next()
69 .and_then(|n| n.parse::<u64>().ok());
70 match key {
71 "VmRSS" => s.rss_kb = num,
72 "VmHWM" => s.peak_rss_kb = num,
73 "VmSize" => s.vsize_kb = num,
74 "VmPeak" => s.peak_vsize_kb = num,
75 "Threads" => s.threads = num,
76 _ => {}
77 }
78 }
79 s
80 }
81
82 /// RSS in MiB, rounded down. Returns `None` if RSS is unavailable.
83 pub fn rss_mib(&self) -> Option<u64> {
84 self.rss_kb.map(|kb| kb / 1024)
85 }
86}