runnerd_core/api/
budget.rs1use super::{AdmissionPolicy, Budget, HostMemory, Status};
5use crate::config::ByteSize;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Reading {
11 pub figure: String,
13 pub text: String,
15}
16
17impl Reading {
18 pub fn line(&self) -> String {
20 format!("{} {}", self.figure, self.text)
21 }
22}
23
24impl Budget {
25 pub fn memory_reading(&self) -> Reading {
29 let of = self
30 .memory_max
31 .map_or_else(String::new, |max| format!("of {} ", ByteSize(max)));
32 let mut text = format!("{of}reserved in {}", self.slice);
33 if let Some(note) = self.admission_note() {
34 text = format!("{text} {note}");
35 }
36 if self.memory_max.is_none() {
37 text.push_str(", which has no MemoryMax");
38 }
39 Reading {
40 figure: ByteSize(self.memory_in_use).to_string(),
41 text,
42 }
43 }
44
45 pub fn clauses(&self) -> Vec<String> {
48 let mut clauses = vec![self.memory_reading().line()];
49 clauses.extend(self.cpu.map(|cpu| cpu.line()));
50 clauses.extend(self.host.as_ref().map(super::HostBudget::line));
51 clauses
52 }
53
54 pub fn line(&self) -> String {
56 self.clauses().join("; ")
57 }
58
59 pub fn remaining(&self) -> Option<u64> {
62 self.memory_max
63 .map(|max| max.saturating_sub(self.memory_in_use))
64 }
65}
66
67impl HostMemory {
68 pub fn available_line(&self) -> String {
71 format!(
72 "{} of {} available",
73 ByteSize(self.available),
74 ByteSize(self.total)
75 )
76 }
77}
78
79impl Status {
80 pub fn budget_parts(&self) -> BudgetParts {
84 let budget = &self.budget;
85 let slots: Vec<SlotPart> = self
86 .profiles
87 .iter()
88 .enumerate()
89 .flat_map(|(profile, status)| {
90 status.slots.iter().filter_map(move |slot| {
91 let memory = match budget.admission {
92 AdmissionPolicy::Caps => match &slot.runner {
93 Some(runner) if runner.reserved > 0 => runner.reserved,
94 _ => status.memory,
95 },
96 AdmissionPolicy::Measured => slot.runner.as_ref()?.reserved,
97 };
98 Some(SlotPart {
99 profile,
100 slot: slot.slot,
101 memory,
102 reserved: slot.runner.is_some(),
103 })
104 })
105 })
106 .collect();
107 let mirror = (budget.mirror_memory > 0).then_some(HeldPart {
108 kind: HeldKind::Mirror,
109 memory: budget.mirror_memory,
110 });
111 let seeds = budget.seed_memory.iter().map(|seed| HeldPart {
112 kind: HeldKind::Seed {
113 profile: seed.profile.clone(),
114 },
115 memory: seed.memory,
116 });
117 let shells = budget.shell_memory.iter().map(|shell| HeldPart {
118 kind: HeldKind::Shell { job: shell.job },
119 memory: shell.memory,
120 });
121 let held: Vec<HeldPart> = mirror.into_iter().chain(seeds).chain(shells).collect();
122 let drawn: u64 = slots
123 .iter()
124 .map(|part| part.memory)
125 .chain(held.iter().map(|part| part.memory))
126 .sum();
127 let whole = budget.memory_max.unwrap_or(drawn).max(drawn);
128 let unclaimed: u64 = slots
129 .iter()
130 .filter(|part| !part.reserved)
131 .map(|part| part.memory)
132 .sum();
133 let free = budget.memory_max.and_then(|max| {
134 let memory = max.saturating_sub(drawn);
135 let text = if unclaimed == 0 {
136 format!("of {} free", ByteSize(max))
137 } else {
138 let remaining = budget.remaining().unwrap_or(0);
139 format!(
140 "of {} free past every slot's cap; {} is free beside what runs",
141 ByteSize(max),
142 ByteSize(remaining)
143 )
144 };
145 (memory > 0).then(|| FreePart {
146 memory,
147 reading: Reading {
148 figure: ByteSize(memory).to_string(),
149 text,
150 },
151 })
152 });
153 BudgetParts {
154 slots,
155 held,
156 free,
157 whole,
158 }
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct BudgetParts {
165 pub slots: Vec<SlotPart>,
167 pub held: Vec<HeldPart>,
169 pub free: Option<FreePart>,
171 pub whole: u64,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct SlotPart {
178 pub profile: usize,
180 pub slot: usize,
182 pub memory: u64,
184 pub reserved: bool,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct HeldPart {
191 pub kind: HeldKind,
193 pub memory: u64,
195}
196
197impl HeldPart {
198 pub fn name(&self) -> String {
200 match &self.kind {
201 HeldKind::Mirror => "mirror".to_owned(),
202 HeldKind::Seed { profile } => format!("{profile} seed"),
203 HeldKind::Shell { job } => format!("job {job} shell"),
204 }
205 }
206
207 pub fn holder(&self) -> String {
210 match &self.kind {
211 HeldKind::Mirror => "held by the mirror".to_owned(),
212 HeldKind::Seed { profile } => format!("held by {profile}'s seed container"),
213 HeldKind::Shell { job } => format!("held by the shell in job {job}'s record"),
214 }
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220pub enum HeldKind {
221 Mirror,
223 Seed {
225 profile: String,
227 },
228 Shell {
230 job: u64,
232 },
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct FreePart {
238 pub memory: u64,
240 pub reading: Reading,
242}
243
244#[cfg(test)]
245mod tests;