build(test): benchmark safe concurrency ramps

req: test/004
This commit is contained in:
slhx agent
2026-05-11 10:31:19 +02:00
parent 7a2c5ecae0
commit 9ceea7de94
2 changed files with 88 additions and 18 deletions
+87 -17
View File
@@ -1,11 +1,13 @@
use std::env;
use std::path::Path;
use std::process::{Command, ExitCode};
use std::time::Instant;
fn main() -> ExitCode {
let mut args = env::args().skip(1);
match args.next().as_deref() {
Some("test") | None => run_test_plan(),
Some("bench") => run_bench_plan(),
Some("help") | Some("--help") | Some("-h") => {
print_help();
ExitCode::SUCCESS
@@ -20,24 +22,14 @@ fn main() -> ExitCode {
fn print_help() {
println!(
"slhx-ci — resource-aware project checks\n\n cargo run -p slhx-xtask -- test\n\nEnvironment overrides:\n SLHX_CI_JOBS=N compile jobs\n SLHX_CI_TEST_THREADS=N Rust test threads\n SLHX_CI_SKIP_BROWSER=1 skip browser E2E"
"slhx-ci — resource-aware project checks\n\n cargo run -p slhx-xtask -- test\n cargo run -p slhx-xtask -- bench\n\nEnvironment overrides:\n SLHX_CI_JOBS=N compile jobs, capped by detected resources\n SLHX_CI_TEST_THREADS=N Rust test threads, capped by detected resources\n SLHX_CI_SKIP_BROWSER=1 skip browser E2E"
);
}
fn run_test_plan() -> ExitCode {
// req: test/004
let budget = Budget::detect();
eprintln!(
"slhx-ci: cpus={} mem={}GiB jobs={} test_threads={} browser={}",
budget.cpus,
budget
.mem_gib
.map(|mem| mem.to_string())
.unwrap_or_else(|| "unknown".into()),
budget.jobs,
budget.test_threads,
if budget.skip_browser { "skip" } else { "run" }
);
budget.report();
let mut steps = vec![
Step::new(
@@ -71,6 +63,40 @@ fn run_test_plan() -> ExitCode {
ExitCode::SUCCESS
}
fn run_bench_plan() -> ExitCode {
// req: test/004
let budget = Budget::detect();
budget.report();
eprintln!(
"slhx-ci: benchmarking small safe test slices from 1 to {} job(s)",
budget.jobs
);
let steps = [
Step::new("bench-xtask", ["test", "-p", "slhx-xtask"]),
Step::new("bench-runtime", ["test", "-p", "slhx-js"]),
];
for jobs in bench_values(budget.jobs) {
let bench_budget = budget.with_jobs(jobs);
for step in &steps {
let started = Instant::now();
if let Err(code) = step.run(&bench_budget) {
return code;
}
println!(
"bench\t{}\tjobs={}\ttest_threads={}\telapsed_ms={}",
step.name,
bench_budget.jobs,
bench_budget.test_threads,
started.elapsed().as_millis()
);
}
}
ExitCode::SUCCESS
}
#[derive(Clone, Copy, Debug)]
struct Budget {
cpus: usize,
@@ -108,8 +134,11 @@ impl Budget {
let cpus = cpus.max(1);
let mem_jobs = mem_gib.map_or(cpus, |gib| (gib / 2).max(1));
let auto_jobs = cpus.min(mem_jobs).clamp(1, 6);
let jobs = jobs_override.unwrap_or(auto_jobs).max(1);
let test_threads = test_threads_override.unwrap_or(jobs.min(4)).max(1);
let jobs = jobs_override.unwrap_or(auto_jobs).clamp(1, auto_jobs);
let max_test_threads = jobs.min(4);
let test_threads = test_threads_override
.unwrap_or(max_test_threads)
.clamp(1, max_test_threads);
Self {
cpus,
mem_gib,
@@ -118,6 +147,41 @@ impl Budget {
skip_browser,
}
}
fn report(&self) {
eprintln!(
"slhx-ci: cpus={} mem={}GiB jobs={} test_threads={} browser={}",
self.cpus,
self.mem_gib
.map(|mem| mem.to_string())
.unwrap_or_else(|| "unknown".into()),
self.jobs,
self.test_threads,
if self.skip_browser { "skip" } else { "run" }
);
}
fn with_jobs(self, jobs: usize) -> Self {
let jobs = jobs.clamp(1, self.jobs);
Self {
jobs,
test_threads: self.test_threads.min(jobs.min(4)).max(1),
..self
}
}
}
fn bench_values(limit: usize) -> Vec<usize> {
let limit = limit.max(1);
let mut values = Vec::new();
let mut value = 1;
while value < limit {
values.push(value);
value *= 2;
}
values.push(limit);
values.dedup();
values
}
#[derive(Clone, Debug)]
@@ -269,10 +333,16 @@ mod tests {
}
#[test]
fn budget_allows_explicit_overrides() {
fn budget_clamps_explicit_overrides_to_resource_budget() {
let budget = Budget::from_resources(2, Some(2), Some(8), Some(7), true);
assert_eq!(budget.jobs, 8);
assert_eq!(budget.test_threads, 7);
assert_eq!(budget.jobs, 1);
assert_eq!(budget.test_threads, 1);
}
#[test]
fn bench_values_grow_gradually_and_include_limit() {
assert_eq!(super::bench_values(1), vec![1]);
assert_eq!(super::bench_values(6), vec![1, 2, 4, 6]);
}
}