gioser
⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76 crates/runtime/sandokan-lifecycle/src/quota.rs Rust · 124 siq'ikuna · 3.5 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(sandokan-lifecycle): A4 — primitivas de lifecycle agnósticas 545dd59c7 · sergio ·

1//! Cuotas de recursos + chequeo de breaches.
2
3use serde::{Deserialize, Serialize};
4
5/// Acción a tomar cuando una cuota se excede.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
7pub enum QuotaAction {
8    /// No hacer nada (accounting puro).
9    #[default]
10    None,
11    /// Sólo loggear el breach.
12    Log,
13    /// Terminar la entidad supervisada.
14    Kill,
15}
16
17/// Límites declarativos de recursos. `None` = sin límite para ese recurso.
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct ResourceQuota {
20    pub mem_bytes: Option<u64>,
21    pub nproc: Option<u32>,
22    /// Porcentaje de CPU (100.0 = 1 core saturado).
23    pub cpu_pct: Option<f64>,
24}
25
26/// Uso de recursos medido en un instante.
27#[derive(Debug, Clone, Default)]
28pub struct ResourceUsage {
29    pub mem_bytes: u64,
30    pub nproc: u32,
31    pub cpu_pct: f64,
32}
33
34/// Un recurso que excedió su límite.
35#[derive(Debug, Clone, PartialEq)]
36pub struct Breach {
37    pub resource: &'static str,
38    pub used: f64,
39    pub limit: f64,
40}
41
42/// Resultado de chequear `ResourceUsage` contra `ResourceQuota`.
43#[derive(Debug, Clone, Default)]
44pub struct QuotaReport {
45    pub breaches: Vec<Breach>,
46}
47
48impl QuotaReport {
49    /// `true` si no hay ningún breach.
50    pub fn ok(&self) -> bool {
51        self.breaches.is_empty()
52    }
53}
54
55/// Compara uso contra cuota y reporta cada recurso excedido.
56pub fn check_quota(usage: &ResourceUsage, quota: &ResourceQuota) -> QuotaReport {
57    let mut breaches = Vec::new();
58    if let Some(limit) = quota.mem_bytes {
59        if usage.mem_bytes > limit {
60            breaches.push(Breach {
61                resource: "mem_bytes",
62                used: usage.mem_bytes as f64,
63                limit: limit as f64,
64            });
65        }
66    }
67    if let Some(limit) = quota.nproc {
68        if usage.nproc > limit {
69            breaches.push(Breach {
70                resource: "nproc",
71                used: usage.nproc as f64,
72                limit: limit as f64,
73            });
74        }
75    }
76    if let Some(limit) = quota.cpu_pct {
77        if usage.cpu_pct > limit {
78            breaches.push(Breach {
79                resource: "cpu_pct",
80                used: usage.cpu_pct,
81                limit,
82            });
83        }
84    }
85    QuotaReport { breaches }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn within_limits_is_ok() {
94        let usage = ResourceUsage { mem_bytes: 100, nproc: 2, cpu_pct: 50.0 };
95        let quota = ResourceQuota {
96            mem_bytes: Some(200), nproc: Some(4), cpu_pct: Some(90.0),
97        };
98        assert!(check_quota(&usage, &quota).ok());
99    }
100
101    #[test]
102    fn detects_mem_breach() {
103        let usage = ResourceUsage { mem_bytes: 300, nproc: 1, cpu_pct: 0.0 };
104        let quota = ResourceQuota { mem_bytes: Some(200), ..Default::default() };
105        let report = check_quota(&usage, &quota);
106        assert!(!report.ok());
107        assert_eq!(report.breaches[0].resource, "mem_bytes");
108    }
109
110    #[test]
111    fn no_limit_means_no_breach() {
112        let usage = ResourceUsage { mem_bytes: u64::MAX, nproc: 9999, cpu_pct: 999.0 };
113        assert!(check_quota(&usage, &ResourceQuota::default()).ok());
114    }
115
116    #[test]
117    fn multiple_breaches_reported() {
118        let usage = ResourceUsage { mem_bytes: 300, nproc: 10, cpu_pct: 200.0 };
119        let quota = ResourceQuota {
120            mem_bytes: Some(100), nproc: Some(2), cpu_pct: Some(100.0),
121        };
122        assert_eq!(check_quota(&usage, &quota).breaches.len(), 3);
123    }
124}