1use std::collections::{BTreeMap, VecDeque};
8
9use super::Key;
10use crate::sizing::{Capped, History, Measure, Ran};
11
12pub const RECENT_RUNS: u64 = 20;
15
16pub const SIZING_TIMES: u64 = 3;
19
20pub const CLEAN_RUNS: u32 = 3;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Ending {
26 Finished,
28 Cancelled,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Counted {
35 pub at: u64,
37 pub peak: u64,
39 pub cpu_peak: u64,
41 pub cap: u64,
43 pub capped: Capped,
45 pub succeeded: bool,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct Hold {
53 pub at: u64,
55 pub clean: u32,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct Overrun {
62 pub at: u64,
64 pub peak: u64,
66}
67
68#[derive(Debug, Clone)]
70pub struct JobRuns {
71 size: u64,
73 window: VecDeque<Counted>,
74 successes: u64,
75 sorted: Vec<u64>,
76 hold: Option<Hold>,
77 overruns: Vec<Overrun>,
78}
79
80impl JobRuns {
81 fn new(size: u64) -> JobRuns {
82 JobRuns {
83 size,
84 window: VecDeque::new(),
85 successes: 0,
86 sorted: Vec::new(),
87 hold: None,
88 overruns: Vec::new(),
89 }
90 }
91
92 pub fn window(&self) -> impl Iterator<Item = &Counted> {
94 self.window.iter()
95 }
96
97 pub fn runs(&self) -> u64 {
99 self.window.len() as u64
100 }
101
102 pub fn succeeded(&self) -> u64 {
104 self.successes
105 }
106
107 pub fn peaks(&self) -> &[u64] {
109 &self.sorted
110 }
111
112 pub fn max(&self) -> u64 {
114 self.sorted.last().copied().unwrap_or(0)
115 }
116
117 pub fn held(&self, now: u64, for_ms: Option<u64>) -> Option<Hold> {
120 self.hold.filter(|hold| {
121 hold.clean < CLEAN_RUNS && for_ms.is_none_or(|ms| now < hold.at.saturating_add(ms))
122 })
123 }
124
125 pub fn overruns(&self, now: u64, for_ms: u64) -> impl Iterator<Item = &Overrun> {
128 let start = self.start();
129 self.overruns
130 .iter()
131 .filter(move |overrun| overrun.at >= start && now < overrun.at.saturating_add(for_ms))
132 }
133
134 fn start(&self) -> u64 {
136 if self.successes >= self.size {
137 self.window.front().map_or(0, |run| run.at)
138 } else {
139 0
140 }
141 }
142
143 fn push(&mut self, run: Counted) {
144 let size = self.size;
145 self.successes += u64::from(run.succeeded);
146 self.window.push_back(run);
147 while self.successes > size {
148 if self.window.pop_front().is_some_and(|old| old.succeeded) {
149 self.successes -= 1;
150 }
151 }
152 while self.successes == size && self.window.front().is_some_and(|old| !old.succeeded) {
154 self.window.pop_front();
155 }
156 self.sorted = self.window.iter().map(|run| run.peak).collect();
157 self.sorted.sort_unstable();
158 let start = self.start();
159 self.overruns.retain(|overrun| overrun.at >= start);
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
166pub struct InProfile {
167 pub key: Key,
169 pub profile: String,
171}
172
173#[derive(Debug, Clone)]
175pub struct Recent {
176 size: u64,
177 jobs: BTreeMap<InProfile, JobRuns>,
178 profiles: BTreeMap<String, Vec<u64>>,
180}
181
182impl Recent {
183 pub fn new(size: u64) -> Recent {
185 Recent {
186 size: size.max(1),
187 jobs: BTreeMap::new(),
188 profiles: BTreeMap::new(),
189 }
190 }
191
192 pub fn read(&mut self, run: &Ran, profile: &str, ending: Ending, cap: u64, at: u64) {
199 let size = self.size;
200 if run.measure == Measure::WithActiveFile && !run.oom_killed {
202 return;
203 }
204 let job = self
205 .jobs
206 .entry(InProfile {
207 key: Key::of(run),
208 profile: profile.to_owned(),
209 })
210 .or_insert_with(|| JobRuns::new(size));
211 if run.measure == Measure::WithActiveFile {
212 job.hold = Some(Hold { at, clean: 0 });
213 return;
214 }
215 let capped = run.capped(cap);
216 let overran = run
217 .reserved
218 .is_some_and(|reserved| run.memory_peak > reserved);
219 if capped.holds() {
220 job.hold = Some(Hold { at, clean: 0 });
221 } else if ending == Ending::Finished
222 && run.succeeded
223 && let Some(hold) = &mut job.hold
224 {
225 hold.clean = if overran { 0 } else { hold.clean + 1 };
226 }
227 if overran {
228 job.overruns.push(Overrun {
229 at,
230 peak: run.memory_peak,
231 });
232 }
233 if ending == Ending::Finished || capped.met() {
234 job.push(Counted {
235 at,
236 peak: if capped.met() {
237 run.memory_peak.max(cap)
238 } else {
239 run.memory_peak
240 },
241 cpu_peak: run.cpu_peak,
242 cap,
243 capped,
244 succeeded: ending == Ending::Finished && run.succeeded,
245 });
246 let mut peaks: Vec<u64> = self
247 .jobs
248 .iter()
249 .filter(|(job, _)| job.profile == profile)
250 .flat_map(|(_, runs)| runs.sorted.iter().copied())
251 .collect();
252 peaks.sort_unstable();
253 self.profiles.insert(profile.to_owned(), peaks);
254 }
255 }
256
257 pub fn read_history(&mut self, history: &History, cap_of: impl Fn(&str) -> Option<u64>) {
260 for (run, ending) in rows(history) {
261 let cap = run
262 .memory_cap
263 .or_else(|| cap_of(&run.profile))
264 .unwrap_or(u64::MAX);
265 self.read(run, &run.profile, ending, cap, finished(run));
266 }
267 }
268
269 pub fn job(&self, key: &Key, profile: &str) -> Option<&JobRuns> {
271 self.jobs.get(&InProfile {
272 key: key.clone(),
273 profile: profile.to_owned(),
274 })
275 }
276
277 pub fn jobs_of<'recent>(
279 &'recent self,
280 profile: &'recent str,
281 ) -> impl Iterator<Item = (&'recent Key, &'recent JobRuns)> {
282 self.jobs
283 .iter()
284 .filter(move |(job, _)| job.profile == profile)
285 .map(|(job, runs)| (&job.key, runs))
286 }
287
288 pub fn profile(&self, profile: &str) -> &[u64] {
290 self.profiles.get(profile).map_or(&[], Vec::as_slice)
291 }
292}
293
294pub fn finished(run: &Ran) -> u64 {
296 run.finished_at.unwrap_or(run.queued_at)
297}
298
299pub fn rows(history: &History) -> Vec<(&Ran, Ending)> {
302 let mut rows: Vec<(&Ran, Ending)> = history
303 .jobs
304 .iter()
305 .map(|run| (run, Ending::Finished))
306 .chain(history.cancelled.iter().map(|run| (run, Ending::Cancelled)))
307 .collect();
308 rows.sort_by_key(|(run, _)| finished(run));
309 rows
310}