use serde::{Deserialize, Serialize};
mod admission;
mod control;
mod events;
mod insights;
mod jobs;
mod records;
mod sizing;
pub use admission::{Admission, AdmissionPolicy, Basis, CpuAdmission, JobPeak, cores, cpus};
pub use control::{
Busy, Check, CheckStatus, ConfigCheck, ConfigFile, Doctor, PauseQuery, Paused, ReplaceQuery,
RunningJob,
};
pub use events::{Event, EventEnvelope, EventQuery, RunnerFailure};
pub use insights::{
AutoNote, Delivery, Digest, INSIGHT_DAYS, INSIGHT_DAYS_MAX, Insight, InsightKind, Insights,
InsightsQuery, LongWait, Notification, NotifyEvent, NotifyTest, NotifyTestQuery, Priority,
RepositoryWaits, RunLink, Severity, WaitSpread, WeekWaits,
};
pub use jobs::{
Activity, ActivityQuery, Bucket, BuildCacheCounts, BuildCacheStats, CacheHistory,
CacheHistoryQuery, CacheKind, CacheSize, CacheVolume, Caches, HostSample, HostSampleQuery,
HostSamples, Job, JobMetrics, JobPage, JobQuery, JobRunner, JobState, JobSummary, LogQuery,
LogSummary, Sample, SampleSeries, Started, Stats, StatsQuery, StatsRow, Step, Timeline,
};
pub use records::{
Record, RecordLimits, RecordNote, RecordRemoval, Records, RemoveRecordsQuery, RemovedRecords,
SHELL_PROTOCOL, ShellQuery,
};
pub use sizing::{
Advice, AutoPlan, JobSizing, Peaks, ProfileSizing, Resize, Sizing, SizingChange, SizingHold,
SizingQuery, Spread, Starts, period,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "kind")]
pub enum Principal {
LocalOwner {
uid: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Status {
pub version: String,
pub shutting_down: bool,
pub budget: Budget,
pub profiles: Vec<ProfileStatus>,
pub repositories: Vec<RepositoryStatus>,
#[serde(default)]
pub queued: Vec<QueuedJob>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Budget {
pub slice: String,
pub memory_max: Option<u64>,
pub memory_in_use: u64,
#[serde(default)]
pub admission: AdmissionPolicy,
#[serde(default)]
pub held: bool,
#[serde(default)]
pub mirror_memory: u64,
#[serde(default)]
pub seed_memory: Vec<SeedMemory>,
#[serde(default)]
pub shell_memory: Vec<ShellMemory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cpu: Option<CpuBudget>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub slice_cpus: Option<u64>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CpuBudget {
pub cpus: u64,
pub budget: u64,
pub in_use: u64,
pub held: bool,
}
impl CpuBudget {
pub fn line(&self) -> String {
let mut line = format!(
"CPU {} of {} CPUs reserved",
cores(self.in_use),
cores(self.budget)
);
if self.held {
line.push_str("; a job waits for CPU");
}
line
}
}
impl Budget {
pub fn admission_note(&self) -> Option<&'static str> {
match (self.admission, self.held) {
(AdmissionPolicy::Caps, _) => None,
(AdmissionPolicy::Measured, false) => Some("by predicted peaks"),
(AdmissionPolicy::Measured, true) => Some(
"by predicted peaks; the slice is past 90% of its MemoryMax, so no runner starts",
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ShellMemory {
pub job: u64,
pub memory: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SeedMemory {
pub profile: String,
pub memory: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileStatus {
pub name: String,
pub image: String,
pub cpus: u32,
pub memory: u64,
pub reseeding: bool,
pub slots: Vec<SlotStatus>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SlotStatus {
pub slot: usize,
pub runner: Option<RunnerStatus>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunnerStatus {
pub repository: String,
pub name: String,
pub runner_id: u64,
pub container: String,
pub busy: bool,
pub started_at: u64,
#[serde(default)]
pub reserved: u64,
#[serde(default)]
pub warm: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryStatus {
pub name: String,
pub profile: String,
pub labels: Vec<String>,
pub paused: bool,
pub queued_jobs: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QueuedJob {
pub id: u64,
pub repository: String,
pub run_id: u64,
pub run_attempt: u32,
pub workflow: Option<String>,
pub name: String,
pub branch: Option<String>,
pub url: Option<String>,
pub queued_at: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub waiting: Option<Waiting>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "kind")]
pub enum Waiting {
Paused,
Backoff {
until: u64,
},
RunnerStarting,
Reseeding {
profile: String,
},
PullingImage {
profile: String,
},
NoSlot {
profile: String,
},
Memory {
needs: u64,
free: u64,
},
Cpu {
needs: u64,
free: u64,
},
BehindCpu {
job: u64,
},
BehindStart {
job: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WaitKind {
Act,
Soon,
Full,
}
impl Waiting {
pub fn kind(&self) -> WaitKind {
match self {
Waiting::Paused | Waiting::Backoff { .. } => WaitKind::Act,
Waiting::RunnerStarting
| Waiting::Reseeding { .. }
| Waiting::PullingImage { .. }
| Waiting::BehindStart { .. } => WaitKind::Soon,
Waiting::NoSlot { .. }
| Waiting::Memory { .. }
| Waiting::Cpu { .. }
| Waiting::BehindCpu { .. } => WaitKind::Full,
}
}
pub fn reason(&self, job: &QueuedJob, status: &Status, now: u64) -> String {
let size = |bytes: u64| crate::config::ByteSize(bytes).to_string();
let older_name = |id: u64| {
status
.queued
.iter()
.find(|candidate| candidate.id == id)
.map_or_else(|| "an older job".to_owned(), |older| older.name.clone())
};
match self {
Waiting::Paused => format!("{} is paused", job.repository),
Waiting::Backoff { until } => format!(
"Runners failed to take a job; retrying in {}",
admission::wait(until.saturating_sub(now))
),
Waiting::RunnerStarting => {
let slot = status.profiles.iter().find_map(|profile| {
profile
.slots
.iter()
.find(|slot| {
slot.runner.as_ref().is_some_and(|runner| {
runner.repository == job.repository && !runner.busy
})
})
.map(|slot| format!("{}\u{a0}{}", profile.name, slot.slot))
});
match slot {
Some(slot) => format!("A runner is starting for it in {slot}"),
None => "A runner is starting for it".to_owned(),
}
}
Waiting::Reseeding { profile } => format!("{profile} is rebuilding its snapshot"),
Waiting::PullingImage { profile } => format!("Pulling {profile}'s image"),
Waiting::NoSlot { profile } => {
let slots = status
.profiles
.iter()
.find(|candidate| &candidate.name == profile)
.map_or(0, |profile| profile.slots.len());
if slots == 1 {
format!("The {profile} slot is busy")
} else {
format!("All {slots} {profile} slots are busy")
}
}
Waiting::Memory { .. } if status.budget.held => {
"The slice is past 90% of its MemoryMax, so nothing starts".to_owned()
}
Waiting::Memory { needs, free } => format!(
"Needs {} of memory; {} of the budget is free",
size(*needs),
size(*free)
),
Waiting::Cpu { needs, free } => {
format!("Needs {}; {} are free", cpus(*needs), cores(*free))
}
Waiting::BehindCpu { job: older } => {
format!("Behind {}, which waits for CPU", older_name(*older))
}
Waiting::BehindStart { job: older } => {
format!(
"Next after {}, whose runner starts first",
older_name(*older)
)
}
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Dashboard {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub open: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PairingQuery {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pairings {
pub links: Vec<Pairing>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pairing {
pub origin: String,
pub url: String,
pub lifetime: u64,
}
impl std::fmt::Debug for Pairing {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Pairing")
.field("origin", &self.origin)
.field("lifetime", &self.lifetime)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ShutdownRequest {
#[serde(default)]
pub force: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ErrorResponse {
pub message: String,
}