vtcode_core/memory/
mod.rs1use std::collections::VecDeque;
10use std::sync::{Arc, Mutex};
11use vtcode_commons::utils::current_timestamp;
12
13pub use self::pressure::MemoryPressure;
14
15mod pressure;
16
17#[derive(Clone)]
19pub struct MemoryMonitor {
20 state: Arc<Mutex<MemoryMonitorState>>,
21}
22
23struct MemoryMonitorState {
24 checkpoints: VecDeque<MemoryCheckpoint>,
26 last_rss_bytes: usize,
28 last_check_timestamp: u64,
30}
31
32#[derive(Debug, Clone)]
34pub struct MemoryCheckpoint {
35 pub timestamp: u64,
37 pub rss_bytes: usize,
39 pub label: String,
41}
42
43#[derive(Debug, Clone)]
45pub struct MemoryReport {
46 pub current_rss_mb: f64,
48 pub soft_limit_mb: f64,
50 pub hard_limit_mb: f64,
52 pub pressure: MemoryPressure,
54 pub usage_percent: f64,
56 pub recent_checkpoints: Vec<MemoryCheckpoint>,
58}
59
60impl MemoryMonitor {
61 pub fn new() -> Self {
63 Self {
64 state: Arc::new(Mutex::new(MemoryMonitorState {
65 checkpoints: VecDeque::with_capacity(
66 vtcode_config::constants::memory::MAX_CHECKPOINT_HISTORY,
67 ),
68 last_rss_bytes: 0,
69 last_check_timestamp: 0,
70 })),
71 }
72 }
73
74 #[cfg(target_os = "linux")]
76 pub fn get_rss_bytes() -> Result<usize, String> {
77 use std::fs;
78 use std::io::Read;
79
80 let mut status = String::new();
81 fs::File::open("/proc/self/status")
82 .and_then(|mut f| f.read_to_string(&mut status))
83 .map_err(|e| format!("Failed to read /proc/self/status: {}", e))?;
84
85 for line in status.lines() {
86 if line.starts_with("VmRSS:") {
87 let parts: Vec<&str> = line.split_whitespace().collect();
88 if parts.len() >= 2 {
89 let kb: usize = parts[1]
90 .parse()
91 .map_err(|_| "Failed to parse VmRSS value".to_string())?;
92 return Ok(kb * 1024); }
94 }
95 }
96
97 Err("VmRSS not found in /proc/self/status".to_string())
98 }
99
100 #[cfg(target_os = "macos")]
102 pub fn get_rss_bytes() -> Result<usize, String> {
103 use std::process::Command;
104
105 let output = Command::new("ps")
107 .args(["-o", "rss=", "-p"])
108 .arg(std::process::id().to_string())
109 .output()
110 .map_err(|e| format!("Failed to run ps command: {}", e))?;
111
112 let rss_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
113
114 let kb: usize = rss_str
115 .parse()
116 .map_err(|_| format!("Failed to parse ps output: {}", rss_str))?;
117
118 Ok(kb * 1024) }
120
121 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
123 pub fn get_rss_bytes() -> Result<usize, String> {
124 Err("Memory monitoring not supported on this platform".to_string())
125 }
126
127 pub fn check_pressure(&self) -> Result<MemoryPressure, String> {
129 let rss = Self::get_rss_bytes()?;
130 let pressure = MemoryPressure::from_rss(rss);
131
132 if let Ok(mut state) = self.state.lock() {
134 state.last_rss_bytes = rss;
135 state.last_check_timestamp = current_timestamp();
136 }
137
138 Ok(pressure)
139 }
140
141 pub fn record_checkpoint(&self, label: String) -> Result<(), String> {
143 let rss = Self::get_rss_bytes()?;
144
145 let min_threshold = vtcode_config::constants::memory::MIN_RSS_CHECKPOINT_BYTES;
147 if let Ok(state) = self.state.lock() {
148 let diff = (rss as i64 - state.last_rss_bytes as i64).unsigned_abs() as usize;
149 if diff < min_threshold {
150 return Ok(());
151 }
152 }
153
154 let checkpoint = MemoryCheckpoint {
155 timestamp: current_timestamp(),
156 rss_bytes: rss,
157 label,
158 };
159
160 if let Ok(mut state) = self.state.lock() {
161 state.checkpoints.push_back(checkpoint);
162
163 let max_history = vtcode_config::constants::memory::MAX_CHECKPOINT_HISTORY;
165 while state.checkpoints.len() > max_history {
166 state.checkpoints.pop_front();
167 }
168 }
169
170 Ok(())
171 }
172
173 pub fn get_report(&self) -> Result<MemoryReport, String> {
175 let rss_bytes = Self::get_rss_bytes()?;
176 let pressure = MemoryPressure::from_rss(rss_bytes);
177
178 let soft_limit =
179 vtcode_config::constants::memory::SOFT_LIMIT_BYTES as f64 / (1024.0 * 1024.0);
180 let hard_limit =
181 vtcode_config::constants::memory::HARD_LIMIT_BYTES as f64 / (1024.0 * 1024.0);
182 let current_rss_mb = rss_bytes as f64 / (1024.0 * 1024.0);
183
184 let usage_percent =
186 (rss_bytes as f64 / vtcode_config::constants::memory::HARD_LIMIT_BYTES as f64) * 100.0;
187
188 let recent_checkpoints = if let Ok(state) = self.state.lock() {
189 state.checkpoints.iter().cloned().collect()
190 } else {
191 Vec::new()
192 };
193
194 Ok(MemoryReport {
195 current_rss_mb,
196 soft_limit_mb: soft_limit,
197 hard_limit_mb: hard_limit,
198 pressure,
199 usage_percent,
200 recent_checkpoints,
201 })
202 }
203
204 pub fn adaptive_ttl_factor(&self) -> f64 {
206 match self.check_pressure() {
207 Ok(MemoryPressure::Normal) => 1.0,
208 Ok(MemoryPressure::Warning) => {
209 vtcode_config::constants::memory::WARNING_TTL_REDUCTION_FACTOR
210 }
211 Ok(MemoryPressure::Critical) => {
212 vtcode_config::constants::memory::CRITICAL_TTL_REDUCTION_FACTOR
213 }
214 Err(_) => 1.0, }
216 }
217
218 pub fn clear_checkpoints(&self) {
220 if let Ok(mut state) = self.state.lock() {
221 state.checkpoints.clear();
222 }
223 }
224
225 pub fn checkpoint_count(&self) -> usize {
227 self.state
228 .lock()
229 .map(|state| state.checkpoints.len())
230 .unwrap_or(0)
231 }
232}
233
234impl Default for MemoryMonitor {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_memory_monitor_creation() {
246 let monitor = MemoryMonitor::new();
247 assert_eq!(monitor.checkpoint_count(), 0);
248 }
249
250 #[test]
251 fn test_get_rss_bytes() {
252 match MemoryMonitor::get_rss_bytes() {
253 Ok(rss) => {
254 assert!(rss > 1024 * 1024, "RSS should be > 1MB");
256 assert!(rss < 10 * 1024 * 1024 * 1024, "RSS should be < 10GB");
257 }
258 Err(e) => {
259 println!("Warning: Could not get RSS: {}", e);
260 }
262 }
263 }
264
265 #[test]
266 fn test_check_pressure() {
267 let monitor = MemoryMonitor::new();
268 match monitor.check_pressure() {
269 Ok(pressure) => {
270 let _ = format!("{:?}", pressure);
272 }
273 Err(e) => {
274 println!("Warning: Could not check pressure: {}", e);
275 }
277 }
278 }
279
280 #[test]
281 fn test_record_checkpoint() {
282 let monitor = MemoryMonitor::new();
283 let _result = monitor.record_checkpoint("test_checkpoint".to_string());
285 }
286
287 #[test]
288 fn test_clear_checkpoints() {
289 let monitor = MemoryMonitor::new();
290 monitor.clear_checkpoints();
291 assert_eq!(monitor.checkpoint_count(), 0);
292 }
293
294 #[test]
295 fn test_adaptive_ttl_factor() {
296 let monitor = MemoryMonitor::new();
297 let factor = monitor.adaptive_ttl_factor();
298
299 assert!(factor > 0.0);
301 assert!(factor <= 1.0);
302 }
303
304 #[test]
305 fn test_memory_report() {
306 let monitor = MemoryMonitor::new();
307 match monitor.get_report() {
308 Ok(report) => {
309 assert!(report.usage_percent >= 0.0);
311 assert!(report.soft_limit_mb > 0.0);
312 assert!(report.hard_limit_mb > report.soft_limit_mb);
313 }
314 Err(e) => {
315 println!("Warning: Could not generate report: {}", e);
316 }
318 }
319 }
320}