crawlkit_engine/
resource_monitor.rs1use std::sync::Arc;
2use std::time::{Duration, Instant};
3
4use parking_lot::RwLock;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ResourceLimits {
14 pub max_memory_bytes: Option<u64>,
16 pub max_cpu_seconds: Option<u64>,
18 pub max_disk_bytes: Option<u64>,
20 pub max_open_files: Option<u32>,
22 pub max_duration: Option<Duration>,
24 pub max_pages: Option<usize>,
26}
27
28impl Default for ResourceLimits {
29 fn default() -> Self {
30 Self {
31 max_memory_bytes: Some(512 * 1024 * 1024), max_cpu_seconds: Some(3600), max_disk_bytes: Some(1024 * 1024 * 1024), max_open_files: Some(1024),
35 max_duration: Some(Duration::from_secs(3600)),
36 max_pages: Some(10000),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Default)]
46pub struct ResourceUsage {
47 pub memory_bytes: u64,
49 pub cpu_seconds: u64,
51 pub disk_bytes: u64,
53 pub open_files: u32,
55 pub elapsed: Duration,
57 pub pages_processed: usize,
59}
60
61pub struct ResourceMonitor {
78 limits: ResourceLimits,
79 usage: Arc<RwLock<ResourceUsage>>,
80 start_time: Instant,
81}
82
83impl ResourceMonitor {
84 #[must_use]
86 pub fn new(limits: ResourceLimits) -> Self {
87 Self {
88 limits,
89 usage: Arc::new(RwLock::new(ResourceUsage::default())),
90 start_time: Instant::now(),
91 }
92 }
93
94 #[must_use]
96 pub fn with_default_limits() -> Self {
97 Self::new(ResourceLimits::default())
98 }
99
100 pub fn update(&self, usage: ResourceUsage) {
102 *self.usage.write() = usage;
103 }
104
105 pub fn record_page(&self) {
107 self.usage.write().pages_processed += 1;
108 }
109
110 #[must_use]
112 pub fn current_usage(&self) -> ResourceUsage {
113 let mut usage = self.usage.read().clone();
114 usage.elapsed = self.start_time.elapsed();
115 usage
116 }
117
118 #[must_use]
120 pub fn is_over_limit(&self) -> bool {
121 let usage = self.current_usage();
122
123 if let Some(max_mem) = self.limits.max_memory_bytes {
124 if usage.memory_bytes > max_mem {
125 return true;
126 }
127 }
128
129 if let Some(max_cpu) = self.limits.max_cpu_seconds {
130 if usage.cpu_seconds > max_cpu {
131 return true;
132 }
133 }
134
135 if let Some(max_disk) = self.limits.max_disk_bytes {
136 if usage.disk_bytes > max_disk {
137 return true;
138 }
139 }
140
141 if let Some(max_files) = self.limits.max_open_files {
142 if usage.open_files > max_files {
143 return true;
144 }
145 }
146
147 if let Some(max_duration) = self.limits.max_duration {
148 if usage.elapsed > max_duration {
149 return true;
150 }
151 }
152
153 if let Some(max_pages) = self.limits.max_pages {
154 if usage.pages_processed > max_pages {
155 return true;
156 }
157 }
158
159 false
160 }
161
162 #[must_use]
164 pub fn exceeded_limits(&self) -> Vec<String> {
165 let usage = self.current_usage();
166 let mut exceeded = Vec::new();
167
168 if let Some(max_mem) = self.limits.max_memory_bytes {
169 if usage.memory_bytes > max_mem {
170 exceeded.push(format!(
171 "Memory: {} / {} bytes",
172 usage.memory_bytes, max_mem
173 ));
174 }
175 }
176
177 if let Some(max_cpu) = self.limits.max_cpu_seconds {
178 if usage.cpu_seconds > max_cpu {
179 exceeded.push(format!("CPU: {} / {} seconds", usage.cpu_seconds, max_cpu));
180 }
181 }
182
183 if let Some(max_pages) = self.limits.max_pages {
184 if usage.pages_processed > max_pages {
185 exceeded.push(format!("Pages: {} / {}", usage.pages_processed, max_pages));
186 }
187 }
188
189 if let Some(max_duration) = self.limits.max_duration {
190 if usage.elapsed > max_duration {
191 exceeded.push(format!(
192 "Duration: {:?} / {:?}",
193 usage.elapsed, max_duration
194 ));
195 }
196 }
197
198 exceeded
199 }
200}
201
202impl Default for ResourceMonitor {
203 fn default() -> Self {
204 Self::with_default_limits()
205 }
206}
207
208#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_resource_monitor_default_limits() {
218 let monitor = ResourceMonitor::with_default_limits();
219 assert!(!monitor.is_over_limit());
220 }
221
222 #[test]
223 fn test_resource_monitor_page_limit() {
224 let limits = ResourceLimits {
225 max_pages: Some(2),
226 ..Default::default()
227 };
228 let monitor = ResourceMonitor::new(limits);
229
230 monitor.record_page();
231 assert!(!monitor.is_over_limit());
232
233 monitor.record_page();
234 assert!(!monitor.is_over_limit());
235
236 monitor.record_page();
237 assert!(monitor.is_over_limit());
238 }
239
240 #[test]
241 fn test_resource_monitor_exceeded_limits() {
242 let limits = ResourceLimits {
243 max_pages: Some(1),
244 ..Default::default()
245 };
246 let monitor = ResourceMonitor::new(limits);
247
248 monitor.record_page();
249 monitor.record_page();
250
251 let exceeded = monitor.exceeded_limits();
252 assert!(!exceeded.is_empty());
253 assert!(exceeded[0].contains("Pages"));
254 }
255}