prvng_platform/crates/buildkit-launcher/src/sizing.rs

92 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use anyhow::Result;
use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct RunnerSize {
pub cpu: u32,
pub memory_gb: u32,
pub disk_gb: u32,
pub time_budget_min: u32,
}
#[derive(Deserialize, Default)]
struct BuildSpec {
cpu: Option<u32>,
memory_gb: Option<u32>,
disk_gb: Option<u32>,
time_budget_min: Option<u32>,
language: Option<String>,
}
impl RunnerSize {
fn language_default(language: &str) -> Self {
match language {
"rust" => Self { cpu: 4, memory_gb: 8, disk_gb: 50, time_budget_min: 60 },
"go" => Self { cpu: 2, memory_gb: 4, disk_gb: 30, time_budget_min: 30 },
"java" | "kotlin" | "scala" => Self { cpu: 4, memory_gb: 8, disk_gb: 40, time_budget_min: 45 },
_ => Self { cpu: 2, memory_gb: 4, disk_gb: 30, time_budget_min: 30 },
}
}
pub fn from_p95(cpu_p95: f64, memory_mb_p95: f64) -> Self {
let cpu = ((cpu_p95 * 1.2).ceil() as u32).max(2);
let memory_gb = (((memory_mb_p95 * 1.2) / 1024.0).ceil() as u32).max(4);
Self { cpu, memory_gb, disk_gb: 30, time_budget_min: 60 }
}
}
fn parse_build_spec(context_path: &Path) -> Option<BuildSpec> {
let spec_path = context_path.join(".build-spec.ncl");
if !spec_path.exists() {
return None;
}
// Extract JSON from nickel export — buildkit-launcher shells out to nickel
let output = std::process::Command::new("nickel")
.args(["export", "--format", "json"])
.arg(&spec_path)
.output()
.ok()?;
if !output.status.success() {
return None;
}
serde_json::from_slice(&output.stdout).ok()
}
pub fn resolve(
context_path: &Path,
p95_cpu: Option<f64>,
p95_mem_mb: Option<f64>,
language_hint: Option<&str>,
) -> Result<RunnerSize> {
let spec = parse_build_spec(context_path).unwrap_or_default();
// Tier 1: explicit declaration in .build-spec.ncl
if spec.cpu.is_some() || spec.memory_gb.is_some() {
let base = language_hint
.or(spec.language.as_deref())
.map(RunnerSize::language_default)
.unwrap_or_else(|| RunnerSize::language_default("default"));
return Ok(RunnerSize {
cpu: spec.cpu.unwrap_or(base.cpu),
memory_gb: spec.memory_gb.unwrap_or(base.memory_gb),
disk_gb: spec.disk_gb.unwrap_or(base.disk_gb),
time_budget_min: spec.time_budget_min.unwrap_or(base.time_budget_min),
});
}
// Tier 2: P95 historical × 1.2
if let (Some(cpu_p95), Some(mem_p95)) = (p95_cpu, p95_mem_mb) {
let mut size = RunnerSize::from_p95(cpu_p95, mem_p95);
if let Some(budget) = spec.time_budget_min {
size.time_budget_min = budget;
}
return Ok(size);
}
// Tier 3: language defaults
let lang = language_hint
.or(spec.language.as_deref())
.unwrap_or("default");
Ok(RunnerSize::language_default(lang))
}