build(test): benchmark safe concurrency ramps
req: test/004
This commit is contained in:
+87
-17
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user