use std::collections::BTreeMap;
use std::time::{Duration, SystemTime};
use tracing::warn;
use super::Engine;
use crate::docker::DockerApi;
use crate::github::GithubApi;
use crate::scheduler::matches;
use crate::scheduler::warm::{self, Attempt, Learned, Runs};
use crate::time;
const REREAD: Duration = Duration::from_secs(300);
#[derive(Debug, Default)]
pub(super) struct Warm {
pub(super) learned: Option<(Learned, SystemTime)>,
runs: Runs,
}
impl<G: GithubApi + 'static, D: DockerApi + 'static> Engine<G, D> {
pub(super) async fn refresh_warm(&mut self) {
if self
.warm
.learned
.as_ref()
.is_some_and(|(_, read_at)| read_at.elapsed().is_ok_and(|age| age < REREAD))
{
return;
}
let now = time::now_ms();
match self.store.warm(now.saturating_sub(warm::LOOKBACK)).await {
Ok(seen) => {
let mut learned = Learned::new(&seen, now);
if let Some((older, _)) = &self.warm.learned {
learned.absorb(older);
}
self.warm.learned = Some((learned, SystemTime::now()));
}
Err(error) => warn!("cannot read the job history for warm runners: {error}"),
}
self.warm.runs.expire(now);
}
pub(super) fn note_job(
&mut self,
repository: &str,
attempt: Attempt,
job: u64,
workflow: Option<&str>,
at: u64,
) {
if let Some((learned, _)) = &mut self.warm.learned {
learned.note(repository, at);
}
self.warm.runs.note(repository, attempt, job, workflow, at);
}
pub(super) fn latest_attempt(&self, run_id: u64) -> Attempt {
self.warm.runs.latest(run_id)
}
pub(super) fn place_job(
&mut self,
repository: &str,
attempt: Attempt,
job: u64,
workflow: Option<&str>,
) {
self.warm
.runs
.place(repository, attempt, job, workflow, time::now_ms());
}
pub(super) fn note_workflow(&mut self, run_id: u64, workflow: Option<&str>) {
if let Some(workflow) = workflow {
self.warm.runs.name(run_id, workflow);
}
}
pub(super) fn note_queued(&mut self) {
let mut seen = Vec::new();
for repository in &self.config.repositories {
let Some(state) = self.repositories.get(&repository.name) else {
continue;
};
seen.extend(
state
.queued
.iter()
.filter(|job| matches(job, &repository.labels))
.map(|job| (repository.name.clone(), job.clone())),
);
}
let now = time::now_ms();
for (repository, job) in seen {
self.note_job(
&repository,
Attempt {
run_id: job.run_id,
run_attempt: job.run_attempt,
},
job.id,
job.workflow.as_deref(),
now,
);
}
}
pub(super) fn warm_targets(&self) -> BTreeMap<String, usize> {
let Some((learned, _)) = &self.warm.learned else {
return BTreeMap::new();
};
let now = time::now_ms();
self.config
.repositories
.iter()
.map(|repository| {
let wanted = warm::target(
repository.warm,
learned.active(&repository.name, now),
self.warm.runs.remaining(&repository.name, learned, now),
);
(repository.name.clone(), wanted)
})
.collect()
}
}