use std::collections::{BTreeMap, VecDeque};
use super::Key;
use crate::sizing::{Capped, History, Measure, Ran};
pub const RECENT_RUNS: u64 = 20;
pub const SIZING_TIMES: u64 = 3;
pub const CLEAN_RUNS: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ending {
Finished,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Counted {
pub at: u64,
pub peak: u64,
pub cpu_peak: u64,
pub cap: u64,
pub capped: Capped,
pub succeeded: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hold {
pub at: u64,
pub clean: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Overrun {
pub at: u64,
pub peak: u64,
}
#[derive(Debug, Clone)]
pub struct JobRuns {
size: u64,
window: VecDeque<Counted>,
successes: u64,
sorted: Vec<u64>,
hold: Option<Hold>,
overruns: Vec<Overrun>,
}
impl JobRuns {
fn new(size: u64) -> JobRuns {
JobRuns {
size,
window: VecDeque::new(),
successes: 0,
sorted: Vec::new(),
hold: None,
overruns: Vec::new(),
}
}
pub fn window(&self) -> impl Iterator<Item = &Counted> {
self.window.iter()
}
pub fn runs(&self) -> u64 {
self.window.len() as u64
}
pub fn succeeded(&self) -> u64 {
self.successes
}
pub fn peaks(&self) -> &[u64] {
&self.sorted
}
pub fn max(&self) -> u64 {
self.sorted.last().copied().unwrap_or(0)
}
pub fn held(&self, now: u64, for_ms: Option<u64>) -> Option<Hold> {
self.hold.filter(|hold| {
hold.clean < CLEAN_RUNS && for_ms.is_none_or(|ms| now < hold.at.saturating_add(ms))
})
}
pub fn overruns(&self, now: u64, for_ms: u64) -> impl Iterator<Item = &Overrun> {
let start = self.start();
self.overruns
.iter()
.filter(move |overrun| overrun.at >= start && now < overrun.at.saturating_add(for_ms))
}
fn start(&self) -> u64 {
if self.successes >= self.size {
self.window.front().map_or(0, |run| run.at)
} else {
0
}
}
fn push(&mut self, run: Counted) {
let size = self.size;
self.successes += u64::from(run.succeeded);
self.window.push_back(run);
while self.successes > size {
if self.window.pop_front().is_some_and(|old| old.succeeded) {
self.successes -= 1;
}
}
while self.successes == size && self.window.front().is_some_and(|old| !old.succeeded) {
self.window.pop_front();
}
self.sorted = self.window.iter().map(|run| run.peak).collect();
self.sorted.sort_unstable();
let start = self.start();
self.overruns.retain(|overrun| overrun.at >= start);
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct InProfile {
pub key: Key,
pub profile: String,
}
#[derive(Debug, Clone)]
pub struct Recent {
size: u64,
jobs: BTreeMap<InProfile, JobRuns>,
profiles: BTreeMap<String, Vec<u64>>,
}
impl Recent {
pub fn new(size: u64) -> Recent {
Recent {
size: size.max(1),
jobs: BTreeMap::new(),
profiles: BTreeMap::new(),
}
}
pub fn read(&mut self, run: &Ran, profile: &str, ending: Ending, cap: u64, at: u64) {
let size = self.size;
if run.measure == Measure::WithActiveFile && !run.oom_killed {
return;
}
let job = self
.jobs
.entry(InProfile {
key: Key::of(run),
profile: profile.to_owned(),
})
.or_insert_with(|| JobRuns::new(size));
if run.measure == Measure::WithActiveFile {
job.hold = Some(Hold { at, clean: 0 });
return;
}
let capped = run.capped(cap);
let overran = run
.reserved
.is_some_and(|reserved| run.memory_peak > reserved);
if capped.holds() {
job.hold = Some(Hold { at, clean: 0 });
} else if ending == Ending::Finished
&& run.succeeded
&& let Some(hold) = &mut job.hold
{
hold.clean = if overran { 0 } else { hold.clean + 1 };
}
if overran {
job.overruns.push(Overrun {
at,
peak: run.memory_peak,
});
}
if ending == Ending::Finished || capped.met() {
job.push(Counted {
at,
peak: if capped.met() {
run.memory_peak.max(cap)
} else {
run.memory_peak
},
cpu_peak: run.cpu_peak,
cap,
capped,
succeeded: ending == Ending::Finished && run.succeeded,
});
let mut peaks: Vec<u64> = self
.jobs
.iter()
.filter(|(job, _)| job.profile == profile)
.flat_map(|(_, runs)| runs.sorted.iter().copied())
.collect();
peaks.sort_unstable();
self.profiles.insert(profile.to_owned(), peaks);
}
}
pub fn read_history(&mut self, history: &History, cap_of: impl Fn(&str) -> Option<u64>) {
for (run, ending) in rows(history) {
let cap = run
.memory_cap
.or_else(|| cap_of(&run.profile))
.unwrap_or(u64::MAX);
self.read(run, &run.profile, ending, cap, finished(run));
}
}
pub fn job(&self, key: &Key, profile: &str) -> Option<&JobRuns> {
self.jobs.get(&InProfile {
key: key.clone(),
profile: profile.to_owned(),
})
}
pub fn jobs_of<'recent>(
&'recent self,
profile: &'recent str,
) -> impl Iterator<Item = (&'recent Key, &'recent JobRuns)> {
self.jobs
.iter()
.filter(move |(job, _)| job.profile == profile)
.map(|(job, runs)| (&job.key, runs))
}
pub fn profile(&self, profile: &str) -> &[u64] {
self.profiles.get(profile).map_or(&[], Vec::as_slice)
}
}
pub fn finished(run: &Ran) -> u64 {
run.finished_at.unwrap_or(run.queued_at)
}
pub fn rows(history: &History) -> Vec<(&Ran, Ending)> {
let mut rows: Vec<(&Ran, Ending)> = history
.jobs
.iter()
.map(|run| (run, Ending::Finished))
.chain(history.cancelled.iter().map(|run| (run, Ending::Cancelled)))
.collect();
rows.sort_by_key(|(run, _)| finished(run));
rows
}