use trusty_common::memory_pressure::MemoryPressure;
use super::config::BuildLeaseConfig;
pub const MIN_SLOTS: u32 = 1;
use crate::core::build_probe::BuildGroup;
use crate::core::builders::BuildersConfig;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Readings {
pub pressure: Result<MemoryPressure, String>,
pub load_avg_1min: Result<f64, String>,
pub logical_cores: usize,
pub foreign: Result<Vec<BuildGroup>, String>,
pub host: String,
}
impl Readings {
#[must_use]
pub fn new(
pressure: Result<MemoryPressure, String>,
load_avg_1min: Result<f64, String>,
logical_cores: usize,
foreign: Result<Vec<BuildGroup>, String>,
host: impl Into<String>,
) -> Self {
Self {
pressure,
load_avg_1min,
logical_cores,
foreign,
host: host.into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Decision {
pub admit: bool,
pub n_effective: u32,
pub ceiling: u32,
pub held: u32,
pub withheld: Vec<String>,
pub degraded: Vec<String>,
pub readings: String,
}
#[must_use]
pub fn decide(
builders: &BuildersConfig,
lease: &BuildLeaseConfig,
ceiling: u32,
held: u32,
readings: &Readings,
) -> Decision {
let mut withheld = Vec::new();
let mut degraded = Vec::new();
let (builders, lease) = valid_or_default(builders, lease, &mut degraded);
let foreign_count = match &readings.foreign {
Ok(groups) if lease.effective_count_foreign_builds() => {
u32::try_from(groups.len()).unwrap_or(u32::MAX)
}
Ok(_) => 0,
Err(err) => {
degraded.push(format!(
"process census unreadable ({err}) — counting leases only"
));
0
}
};
let n_effective = ceiling.saturating_sub(foreign_count);
pressure_gate(&lease, &readings.pressure, &mut withheld, &mut degraded);
let cores = readings.logical_cores.max(1);
#[allow(clippy::cast_precision_loss)]
let threshold = cores as f64 * builders.effective_load_factor();
match &readings.load_avg_1min {
Ok(load) if *load > threshold && held >= MIN_SLOTS => withheld.push(format!(
"1-minute load {load:.2} is above {threshold:.2} (logical cores x \
builders.load_factor) and {held} build(s) already hold a lease"
)),
Ok(_) => {}
Err(err) => degraded.push(format!(
"load average unreadable ({err}) — load gate skipped"
)),
}
if held >= n_effective {
withheld.push(format!(
"{held} of {n_effective} slot(s) held (ceiling {ceiling} \
builders.max_concurrent, minus {foreign_count} foreign build(s))"
));
}
Decision {
admit: withheld.is_empty(),
n_effective,
ceiling,
held,
withheld,
degraded,
readings: render_readings(readings, threshold, held, ceiling),
}
}
#[must_use]
pub fn decide_unleased(lease: &BuildLeaseConfig, ceiling: u32, readings: &Readings) -> Decision {
let mut withheld = Vec::new();
let mut degraded = Vec::new();
let default_lease = BuildLeaseConfig::default();
let lease = if lease.validate().is_ok() {
lease
} else {
&default_lease
};
pressure_gate(lease, &readings.pressure, &mut withheld, &mut degraded);
let foreign = match &readings.foreign {
Ok(groups) => {
let n = u32::try_from(groups.len()).unwrap_or(u32::MAX);
if n >= ceiling {
withheld.push(format!(
"no lease could be taken, and {n} build(s) holding no lease already \
fill the ceiling {ceiling} (builders.max_concurrent)"
));
}
n
}
Err(err) => {
withheld.push(format!(
"no lease could be taken AND the process census is unreadable ({err}), so \
nothing bounds this build; it does not start until one of them recovers — \
`tm doctor`'s builder_cap row names the fault"
));
0
}
};
#[allow(clippy::cast_precision_loss)]
let threshold =
readings.logical_cores.max(1) as f64 * crate::core::builders::DEFAULT_LOAD_FACTOR;
Decision {
admit: withheld.is_empty(),
n_effective: ceiling.saturating_sub(foreign),
ceiling,
held: 0,
withheld,
degraded,
readings: render_readings(readings, threshold, 0, ceiling),
}
}
fn valid_or_default(
builders: &BuildersConfig,
lease: &BuildLeaseConfig,
degraded: &mut Vec<String>,
) -> (BuildersConfig, BuildLeaseConfig) {
let builders = match builders.validate() {
Ok(()) => builders.clone(),
Err(err) => {
degraded.push(format!("{err} — using that section's defaults"));
BuildersConfig::default()
}
};
let lease = match lease.validate() {
Ok(()) => lease.clone(),
Err(err) => {
degraded.push(format!("{err} — using the build-lease defaults"));
BuildLeaseConfig::default()
}
};
(builders, lease)
}
fn pressure_gate(
lease: &BuildLeaseConfig,
pressure: &Result<MemoryPressure, String>,
withheld: &mut Vec<String>,
degraded: &mut Vec<String>,
) {
let reading = match pressure {
Ok(reading) => reading,
Err(err) => {
degraded.push(format!(
"memory pressure unreadable ({err}) — pressure gate skipped, ceiling and load \
still apply"
));
return;
}
};
let max = lease.effective_memory_pressure_max();
if reading.level > max {
withheld.push(format!(
"memory pressure is {} ({}), above builders.memory_pressure_max = {max}; no new \
build starts until it falls, running builds continue",
reading.level,
reading.render_signals()
));
}
let min_pct = lease.effective_min_available_pct();
if let Some(pct) = reading.available_pct
&& pct < min_pct
{
withheld.push(format!(
"available memory {pct:.0}% is below builders.min_available_pct = {min_pct:.0}%"
));
}
}
fn render_readings(readings: &Readings, threshold: f64, held: u32, ceiling: u32) -> String {
let pressure = match &readings.pressure {
Ok(p) => format!("memory pressure {} ({})", p.level, p.render_signals()),
Err(err) => format!("memory pressure UNREADABLE ({err})"),
};
let load = match &readings.load_avg_1min {
Ok(l) => format!(
"1-min load {l:.2} (threshold {threshold:.2}, {} cores)",
readings.logical_cores
),
Err(err) => format!("load UNREADABLE ({err})"),
};
let foreign = match &readings.foreign {
Ok(groups) if groups.is_empty() => "foreign builds 0".to_string(),
Ok(groups) => format!(
"foreign builds {}: {}",
groups.len(),
groups
.iter()
.map(BuildGroup::render)
.collect::<Vec<_>>()
.join("; ")
),
Err(err) => format!("census UNREADABLE ({err})"),
};
format!(
"host {}, {pressure}, {load}, lease holders {held}, {foreign}, ceiling {ceiling}",
readings.host
)
}
#[cfg(test)]
#[path = "admission_tests.rs"]
mod tests;