oxihuman_core/
capacity_planner.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct CapacitySpec {
10 pub resource_name: String,
11 pub current_capacity: f64,
12 pub current_usage: f64,
13 pub growth_rate: f64,
15 pub headroom_factor: f64,
17}
18
19impl CapacitySpec {
20 pub fn new(
21 resource_name: &str,
22 current_capacity: f64,
23 current_usage: f64,
24 growth_rate: f64,
25 headroom_factor: f64,
26 ) -> Self {
27 Self {
28 resource_name: resource_name.to_string(),
29 current_capacity,
30 current_usage,
31 growth_rate,
32 headroom_factor,
33 }
34 }
35
36 pub fn utilization(&self) -> f64 {
37 if self.current_capacity <= 0.0 {
38 1.0
39 } else {
40 (self.current_usage / self.current_capacity).clamp(0.0, 1.0)
41 }
42 }
43
44 pub fn predicted_usage(&self, periods: u32) -> f64 {
46 self.current_usage * (1.0 + self.growth_rate).powi(periods as i32)
47 }
48
49 pub fn required_capacity(&self, periods: u32) -> f64 {
51 self.predicted_usage(periods) * self.headroom_factor
52 }
53
54 pub fn periods_until_full(&self) -> Option<u32> {
56 if self.growth_rate <= 0.0 || self.current_usage <= 0.0 {
57 return None;
58 }
59 let mut usage = self.current_usage;
60 for p in 0..10_000 {
61 if usage >= self.current_capacity {
62 return Some(p);
63 }
64 usage *= 1.0 + self.growth_rate;
65 }
66 None
67 }
68}
69
70#[derive(Debug, Default)]
72pub struct CapacityPlanner {
73 specs: Vec<CapacitySpec>,
74}
75
76impl CapacityPlanner {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn add(&mut self, spec: CapacitySpec) {
82 self.specs.push(spec);
83 }
84
85 pub fn spec_count(&self) -> usize {
86 self.specs.len()
87 }
88
89 pub fn most_utilized(&self) -> Option<&CapacitySpec> {
90 self.specs.iter().max_by(|a, b| {
91 a.utilization()
92 .partial_cmp(&b.utilization())
93 .unwrap_or(std::cmp::Ordering::Equal)
94 })
95 }
96
97 pub fn over_threshold(&self, threshold: f64) -> Vec<&CapacitySpec> {
98 self.specs
99 .iter()
100 .filter(|s| s.utilization() > threshold)
101 .collect()
102 }
103
104 pub fn specs(&self) -> &[CapacitySpec] {
105 &self.specs
106 }
107}
108
109pub fn new_capacity_planner() -> CapacityPlanner {
110 CapacityPlanner::new()
111}
112
113pub fn cp_add(planner: &mut CapacityPlanner, spec: CapacitySpec) {
114 planner.add(spec);
115}
116
117pub fn cp_spec_count(planner: &CapacityPlanner) -> usize {
118 planner.spec_count()
119}
120
121pub fn cp_most_utilized(planner: &CapacityPlanner) -> Option<&CapacitySpec> {
122 planner.most_utilized()
123}
124
125pub fn cp_over_threshold(planner: &CapacityPlanner, threshold: f64) -> Vec<&CapacitySpec> {
126 planner.over_threshold(threshold)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn make_spec(name: &str, cap: f64, usage: f64, growth: f64) -> CapacitySpec {
134 CapacitySpec::new(name, cap, usage, growth, 1.2)
135 }
136
137 #[test]
138 fn test_utilization() {
139 let spec = make_spec("cpu", 100.0, 80.0, 0.05);
140 assert!((spec.utilization() - 0.8).abs() < 1e-10);
141 }
142
143 #[test]
144 fn test_predicted_usage_grows() {
145 let spec = make_spec("mem", 1000.0, 500.0, 0.1);
146 let pred = spec.predicted_usage(1);
147 assert!(pred > 500.0);
148 }
149
150 #[test]
151 fn test_required_capacity_includes_headroom() {
152 let spec = CapacitySpec::new("disk", 100.0, 50.0, 0.0, 1.2);
153 let req = spec.required_capacity(0);
154 assert!((req - 60.0).abs() < 1e-5);
155 }
156
157 #[test]
158 fn test_periods_until_full() {
159 let spec = make_spec("net", 100.0, 50.0, 0.2);
160 let p = spec.periods_until_full();
161 assert!(p.is_some());
162 assert!(p.expect("should succeed") > 0);
163 }
164
165 #[test]
166 fn test_no_growth_no_full() {
167 let spec = make_spec("x", 100.0, 50.0, 0.0);
168 assert_eq!(spec.periods_until_full(), None);
169 }
170
171 #[test]
172 fn test_add_and_count() {
173 let mut planner = new_capacity_planner();
174 cp_add(&mut planner, make_spec("cpu", 100.0, 40.0, 0.05));
175 assert_eq!(cp_spec_count(&planner), 1);
176 }
177
178 #[test]
179 fn test_most_utilized() {
180 let mut planner = new_capacity_planner();
181 cp_add(&mut planner, make_spec("cpu", 100.0, 80.0, 0.05));
182 cp_add(&mut planner, make_spec("mem", 100.0, 30.0, 0.05));
183 let top = cp_most_utilized(&planner).expect("should succeed");
184 assert_eq!(top.resource_name, "cpu");
185 }
186
187 #[test]
188 fn test_over_threshold() {
189 let mut planner = new_capacity_planner();
190 cp_add(&mut planner, make_spec("a", 100.0, 90.0, 0.0));
191 cp_add(&mut planner, make_spec("b", 100.0, 50.0, 0.0));
192 let over = cp_over_threshold(&planner, 0.8);
193 assert_eq!(over.len(), 1);
194 }
195
196 #[test]
197 fn test_compound_growth_5_periods() {
198 let spec = make_spec("svc", 1000.0, 100.0, 0.1);
199 let pred5 = spec.predicted_usage(5);
200 assert!(pred5 > 160.0 && pred5 < 162.0);
202 }
203}