⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76
crates/runtime/sandokan-lifecycle/src/quota.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(sandokan-lifecycle): A4 — primitivas de lifecycle agnósticas
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, "a).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, "a);
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, "a).breaches.len(), 3);
123 }
124}