1use crate::error::{EdgeError, Result};
7use parking_lot::RwLock;
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ResourceConstraints {
15 pub max_memory_bytes: usize,
17 pub max_cpu_percent: f64,
19 pub max_storage_bytes: usize,
21 pub max_concurrent_ops: usize,
23 pub operation_timeout_secs: u64,
25}
26
27impl Default for ResourceConstraints {
28 fn default() -> Self {
29 Self {
30 max_memory_bytes: crate::MAX_MEMORY_USAGE,
31 max_cpu_percent: 80.0,
32 max_storage_bytes: 100 * 1024 * 1024, max_concurrent_ops: 10,
34 operation_timeout_secs: 30,
35 }
36 }
37}
38
39impl ResourceConstraints {
40 pub fn minimal() -> Self {
42 Self {
43 max_memory_bytes: 10 * 1024 * 1024, max_cpu_percent: 50.0,
45 max_storage_bytes: 10 * 1024 * 1024, max_concurrent_ops: 3,
47 operation_timeout_secs: 10,
48 }
49 }
50
51 pub fn moderate() -> Self {
53 Self {
54 max_memory_bytes: 50 * 1024 * 1024, max_cpu_percent: 70.0,
56 max_storage_bytes: 100 * 1024 * 1024, max_concurrent_ops: 10,
58 operation_timeout_secs: 30,
59 }
60 }
61
62 pub fn powerful() -> Self {
64 Self {
65 max_memory_bytes: 200 * 1024 * 1024, max_cpu_percent: 90.0,
67 max_storage_bytes: 500 * 1024 * 1024, max_concurrent_ops: 50,
69 operation_timeout_secs: 60,
70 }
71 }
72
73 pub fn validate(&self) -> Result<()> {
75 if self.max_memory_bytes == 0 {
76 return Err(EdgeError::invalid_config("max_memory_bytes must be > 0"));
77 }
78 if self.max_cpu_percent <= 0.0 || self.max_cpu_percent > 100.0 {
79 return Err(EdgeError::invalid_config(
80 "max_cpu_percent must be between 0 and 100",
81 ));
82 }
83 if self.max_storage_bytes == 0 {
84 return Err(EdgeError::invalid_config("max_storage_bytes must be > 0"));
85 }
86 if self.max_concurrent_ops == 0 {
87 return Err(EdgeError::invalid_config("max_concurrent_ops must be > 0"));
88 }
89 Ok(())
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ResourceMetrics {
96 pub memory_bytes: usize,
98 pub cpu_percent: f64,
100 pub storage_bytes: usize,
102 pub active_operations: usize,
104 pub peak_memory_bytes: usize,
106 pub peak_cpu_percent: f64,
108 pub total_operations: u64,
110 pub failed_operations: u64,
112}
113
114impl Default for ResourceMetrics {
115 fn default() -> Self {
116 Self {
117 memory_bytes: 0,
118 cpu_percent: 0.0,
119 storage_bytes: 0,
120 active_operations: 0,
121 peak_memory_bytes: 0,
122 peak_cpu_percent: 0.0,
123 total_operations: 0,
124 failed_operations: 0,
125 }
126 }
127}
128
129impl ResourceMetrics {
130 pub fn memory_percent(&self, max_memory: usize) -> f64 {
132 if max_memory == 0 {
133 return 0.0;
134 }
135 (self.memory_bytes as f64 / max_memory as f64) * 100.0
136 }
137
138 pub fn storage_percent(&self, max_storage: usize) -> f64 {
140 if max_storage == 0 {
141 return 0.0;
142 }
143 (self.storage_bytes as f64 / max_storage as f64) * 100.0
144 }
145
146 pub fn success_rate(&self) -> f64 {
148 if self.total_operations == 0 {
149 return 100.0;
150 }
151 let successful = self.total_operations - self.failed_operations;
152 (successful as f64 / self.total_operations as f64) * 100.0
153 }
154}
155
156pub struct ResourceManager {
158 constraints: ResourceConstraints,
159 memory_used: Arc<AtomicUsize>,
160 storage_used: Arc<AtomicUsize>,
161 active_ops: Arc<AtomicUsize>,
162 total_ops: Arc<AtomicU64>,
163 failed_ops: Arc<AtomicU64>,
164 peak_memory: Arc<AtomicUsize>,
165 cpu_samples: Arc<RwLock<Vec<f64>>>,
166}
167
168impl ResourceManager {
169 pub fn new(constraints: ResourceConstraints) -> Result<Self> {
171 constraints.validate()?;
172
173 Ok(Self {
174 constraints,
175 memory_used: Arc::new(AtomicUsize::new(0)),
176 storage_used: Arc::new(AtomicUsize::new(0)),
177 active_ops: Arc::new(AtomicUsize::new(0)),
178 total_ops: Arc::new(AtomicU64::new(0)),
179 failed_ops: Arc::new(AtomicU64::new(0)),
180 peak_memory: Arc::new(AtomicUsize::new(0)),
181 cpu_samples: Arc::new(RwLock::new(Vec::new())),
182 })
183 }
184
185 pub fn can_start_operation(&self) -> Result<()> {
187 let active = self.active_ops.load(Ordering::Relaxed);
189 if active >= self.constraints.max_concurrent_ops {
190 return Err(EdgeError::resource_constraint(format!(
191 "Maximum concurrent operations ({}) reached",
192 self.constraints.max_concurrent_ops
193 )));
194 }
195
196 let memory = self.memory_used.load(Ordering::Relaxed);
198 if memory >= self.constraints.max_memory_bytes {
199 return Err(EdgeError::resource_constraint(format!(
200 "Memory limit ({} bytes) exceeded",
201 self.constraints.max_memory_bytes
202 )));
203 }
204
205 Ok(())
206 }
207
208 pub fn start_operation(&self) -> Result<OperationGuard> {
210 self.can_start_operation()?;
211
212 self.active_ops.fetch_add(1, Ordering::Relaxed);
213 self.total_ops.fetch_add(1, Ordering::Relaxed);
214
215 Ok(OperationGuard {
216 active_ops: Arc::clone(&self.active_ops),
217 })
218 }
219
220 pub fn record_failure(&self) {
222 self.failed_ops.fetch_add(1, Ordering::Relaxed);
223 }
224
225 pub fn allocate_memory(&self, bytes: usize) -> Result<MemoryGuard> {
227 let current = self.memory_used.load(Ordering::Relaxed);
228 let new_total = current.saturating_add(bytes);
229
230 if new_total > self.constraints.max_memory_bytes {
231 return Err(EdgeError::resource_constraint(format!(
232 "Memory allocation of {} bytes would exceed limit of {} bytes",
233 bytes, self.constraints.max_memory_bytes
234 )));
235 }
236
237 self.memory_used.fetch_add(bytes, Ordering::Relaxed);
238
239 let mut peak = self.peak_memory.load(Ordering::Relaxed);
241 while new_total > peak {
242 match self.peak_memory.compare_exchange_weak(
243 peak,
244 new_total,
245 Ordering::Relaxed,
246 Ordering::Relaxed,
247 ) {
248 Ok(_) => break,
249 Err(current) => peak = current,
250 }
251 }
252
253 Ok(MemoryGuard {
254 bytes,
255 memory_used: Arc::clone(&self.memory_used),
256 })
257 }
258
259 pub fn allocate_storage(&self, bytes: usize) -> Result<StorageGuard> {
261 let current = self.storage_used.load(Ordering::Relaxed);
262 let new_total = current.saturating_add(bytes);
263
264 if new_total > self.constraints.max_storage_bytes {
265 return Err(EdgeError::resource_constraint(format!(
266 "Storage allocation of {} bytes would exceed limit of {} bytes",
267 bytes, self.constraints.max_storage_bytes
268 )));
269 }
270
271 self.storage_used.fetch_add(bytes, Ordering::Relaxed);
272
273 Ok(StorageGuard {
274 bytes,
275 storage_used: Arc::clone(&self.storage_used),
276 })
277 }
278
279 pub fn record_cpu_sample(&self, cpu_percent: f64) {
281 let mut samples = self.cpu_samples.write();
282 samples.push(cpu_percent);
283
284 if samples.len() > 100 {
286 samples.remove(0);
287 }
288 }
289
290 pub fn current_cpu(&self) -> f64 {
292 let samples = self.cpu_samples.read();
293 if samples.is_empty() {
294 return 0.0;
295 }
296
297 let sum: f64 = samples.iter().sum();
298 sum / samples.len() as f64
299 }
300
301 pub fn is_cpu_overloaded(&self) -> bool {
303 self.current_cpu() > self.constraints.max_cpu_percent
304 }
305
306 pub fn metrics(&self) -> ResourceMetrics {
308 let memory = self.memory_used.load(Ordering::Relaxed);
309 let storage = self.storage_used.load(Ordering::Relaxed);
310 let active_ops = self.active_ops.load(Ordering::Relaxed);
311 let total_ops = self.total_ops.load(Ordering::Relaxed);
312 let failed_ops = self.failed_ops.load(Ordering::Relaxed);
313 let peak_memory = self.peak_memory.load(Ordering::Relaxed);
314
315 let cpu_samples = self.cpu_samples.read();
316 let (cpu_current, cpu_peak) = if cpu_samples.is_empty() {
317 (0.0, 0.0)
318 } else {
319 let sum: f64 = cpu_samples.iter().sum();
320 let avg = sum / cpu_samples.len() as f64;
321 let peak = cpu_samples.iter().copied().fold(0.0, f64::max);
322 (avg, peak)
323 };
324
325 ResourceMetrics {
326 memory_bytes: memory,
327 cpu_percent: cpu_current,
328 storage_bytes: storage,
329 active_operations: active_ops,
330 peak_memory_bytes: peak_memory,
331 peak_cpu_percent: cpu_peak,
332 total_operations: total_ops,
333 failed_operations: failed_ops,
334 }
335 }
336
337 pub fn constraints(&self) -> &ResourceConstraints {
339 &self.constraints
340 }
341
342 pub fn reset_metrics(&self) {
344 self.total_ops.store(0, Ordering::Relaxed);
345 self.failed_ops.store(0, Ordering::Relaxed);
346 self.peak_memory.store(0, Ordering::Relaxed);
347 self.cpu_samples.write().clear();
348 }
349
350 pub fn health_check(&self) -> HealthStatus {
352 let metrics = self.metrics();
353
354 let memory_ok = metrics.memory_percent(self.constraints.max_memory_bytes) < 90.0;
355 let cpu_ok = metrics.cpu_percent < self.constraints.max_cpu_percent * 0.9;
356 let storage_ok = metrics.storage_percent(self.constraints.max_storage_bytes) < 90.0;
357 let success_ok = metrics.success_rate() > 95.0;
358
359 if memory_ok && cpu_ok && storage_ok && success_ok {
360 HealthStatus::Healthy
361 } else if !memory_ok || !cpu_ok || !storage_ok {
362 HealthStatus::Critical
363 } else {
364 HealthStatus::Degraded
365 }
366 }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
371pub enum HealthStatus {
372 Healthy,
374 Degraded,
376 Critical,
378}
379
380pub struct OperationGuard {
382 active_ops: Arc<AtomicUsize>,
383}
384
385impl Drop for OperationGuard {
386 fn drop(&mut self) {
387 self.active_ops.fetch_sub(1, Ordering::Relaxed);
388 }
389}
390
391pub struct MemoryGuard {
393 bytes: usize,
394 memory_used: Arc<AtomicUsize>,
395}
396
397impl Drop for MemoryGuard {
398 fn drop(&mut self) {
399 self.memory_used.fetch_sub(self.bytes, Ordering::Relaxed);
400 }
401}
402
403pub struct StorageGuard {
405 bytes: usize,
406 storage_used: Arc<AtomicUsize>,
407}
408
409impl Drop for StorageGuard {
410 fn drop(&mut self) {
411 self.storage_used.fetch_sub(self.bytes, Ordering::Relaxed);
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_constraints_creation() {
421 let constraints = ResourceConstraints::default();
422 assert!(constraints.validate().is_ok());
423
424 let minimal = ResourceConstraints::minimal();
425 assert!(minimal.validate().is_ok());
426 assert!(minimal.max_memory_bytes < constraints.max_memory_bytes);
427 }
428
429 #[test]
430 fn test_constraints_validation() {
431 let mut invalid = ResourceConstraints {
432 max_memory_bytes: 0,
433 ..Default::default()
434 };
435 assert!(invalid.validate().is_err());
436
437 invalid.max_memory_bytes = 1024;
438 invalid.max_cpu_percent = 150.0;
439 assert!(invalid.validate().is_err());
440 }
441
442 #[test]
443 fn test_resource_manager_creation() {
444 let constraints = ResourceConstraints::minimal();
445 let manager = ResourceManager::new(constraints);
446 assert!(manager.is_ok());
447 }
448
449 #[test]
450 fn test_operation_guard() -> Result<()> {
451 let constraints = ResourceConstraints::minimal();
452 let manager = ResourceManager::new(constraints)?;
453
454 let metrics_before = manager.metrics();
455 assert_eq!(metrics_before.active_operations, 0);
456
457 {
458 let _guard = manager.start_operation()?;
459 let metrics_during = manager.metrics();
460 assert_eq!(metrics_during.active_operations, 1);
461 }
462
463 let metrics_after = manager.metrics();
464 assert_eq!(metrics_after.active_operations, 0);
465
466 Ok(())
467 }
468
469 #[test]
470 fn test_memory_allocation() -> Result<()> {
471 let constraints = ResourceConstraints::minimal();
472 let manager = ResourceManager::new(constraints)?;
473
474 let _guard = manager.allocate_memory(1024)?;
475 let metrics = manager.metrics();
476 assert_eq!(metrics.memory_bytes, 1024);
477
478 Ok(())
479 }
480
481 #[test]
482 fn test_memory_limit() -> Result<()> {
483 let mut constraints = ResourceConstraints::minimal();
484 constraints.max_memory_bytes = 2048;
485 let manager = ResourceManager::new(constraints)?;
486
487 let _guard1 = manager.allocate_memory(1024)?;
488 let _guard2 = manager.allocate_memory(512)?;
489
490 let result = manager.allocate_memory(1024);
492 assert!(result.is_err());
493
494 Ok(())
495 }
496
497 #[test]
498 fn test_concurrent_operations_limit() -> Result<()> {
499 let mut constraints = ResourceConstraints::minimal();
500 constraints.max_concurrent_ops = 2;
501 let manager = ResourceManager::new(constraints)?;
502
503 let _guard1 = manager.start_operation()?;
504 let _guard2 = manager.start_operation()?;
505
506 let result = manager.start_operation();
508 assert!(result.is_err());
509
510 Ok(())
511 }
512
513 #[test]
514 fn test_cpu_tracking() -> Result<()> {
515 let constraints = ResourceConstraints::minimal();
516 let manager = ResourceManager::new(constraints)?;
517
518 manager.record_cpu_sample(10.0);
519 manager.record_cpu_sample(20.0);
520 manager.record_cpu_sample(30.0);
521
522 let cpu = manager.current_cpu();
523 assert!((cpu - 20.0).abs() < 1.0);
524
525 Ok(())
526 }
527
528 #[test]
529 fn test_metrics() -> Result<()> {
530 let constraints = ResourceConstraints::minimal();
531 let manager = ResourceManager::new(constraints)?;
532
533 let _guard = manager.start_operation()?;
534 manager.record_failure();
535
536 let metrics = manager.metrics();
537 assert_eq!(metrics.active_operations, 1);
538 assert_eq!(metrics.total_operations, 1);
539 assert_eq!(metrics.failed_operations, 1);
540
541 Ok(())
542 }
543
544 #[test]
545 fn test_health_check() -> Result<()> {
546 let constraints = ResourceConstraints::minimal();
547 let max_memory = constraints.max_memory_bytes;
548 let manager = ResourceManager::new(constraints)?;
549
550 assert_eq!(manager.health_check(), HealthStatus::Healthy);
551
552 let _guard = manager.allocate_memory(max_memory - 100)?;
554 assert_eq!(manager.health_check(), HealthStatus::Critical);
555
556 Ok(())
557 }
558
559 #[test]
560 fn test_peak_memory() -> Result<()> {
561 let constraints = ResourceConstraints::minimal();
562 let manager = ResourceManager::new(constraints)?;
563
564 {
565 let _guard = manager.allocate_memory(1000)?;
566 let metrics = manager.metrics();
567 assert_eq!(metrics.peak_memory_bytes, 1000);
568 }
569
570 let metrics = manager.metrics();
571 assert_eq!(metrics.memory_bytes, 0);
572 assert_eq!(metrics.peak_memory_bytes, 1000);
573
574 Ok(())
575 }
576}