feat(loadtest): load-test tooling + capacity model
boot-labs.js drives the real admin Prepare-Labs endpoint (reusing the existing session-helper.js from Phase 3A) and records boot time/failure rate; sample-resources.sh samples real docker stats for running Labs. capacity-model.js turns measured per-Lab CPU/memory into a recommended/ warning/hard-admission-control concurrency threshold - no concurrency number is hardcoded as a marketing figure, every input is explicitly placeholder data until measured against the real host. Plus deploy/rollback/smoke-test scripts used by the CI/CD and deployment runbooks. All syntax-checked; validate-compose.sh and capacity-model.js actually run against real inputs.
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
// Capacity planning worksheet (Phase 4 §25) — computes recommended-concurrent-Labs from real
|
||||
// inputs, not a guessed marketing number. Inputs come from infra/environments/production/capacity.yml
|
||||
// (edit that file with real measured values from infra/scripts/loadtest/, then re-run this).
|
||||
//
|
||||
// Usage: node capacity-model.js [path-to-capacity.yml]
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
// Reuses the Provisioner's existing js-yaml dependency (platform/provisioner/src/config.js
|
||||
// already parses YAML config this same way) rather than adding a new package.json just for
|
||||
// this one script.
|
||||
const yaml = require(path.join(__dirname, '../../platform/provisioner/node_modules/js-yaml'));
|
||||
|
||||
const configPath = process.argv[2] || path.join(__dirname, '../environments/production/capacity.yml');
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.error(`No capacity config at ${configPath} — copy capacity.yml.example first and fill in real measured values.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cfg = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
||||
|
||||
function computeFor(resource, hostTotal, platformReserved, proxmoxOverheadPct, perLab, safetyMarginPct) {
|
||||
const usable = hostTotal * (1 - proxmoxOverheadPct / 100) - platformReserved;
|
||||
const withMargin = usable * (1 - safetyMarginPct / 100);
|
||||
return Math.floor(withMargin / perLab);
|
||||
}
|
||||
|
||||
const cpuLimit = computeFor(
|
||||
'CPU', cfg.host_cpu_cores, cfg.platform_reserved_cpu_cores,
|
||||
cfg.proxmox_overhead_pct, cfg.measured_lab_cpu_cores, cfg.safety_margin_pct
|
||||
);
|
||||
const memLimit = computeFor(
|
||||
'Memory', cfg.host_mem_mb, cfg.platform_reserved_mem_mb,
|
||||
cfg.proxmox_overhead_pct, cfg.measured_lab_mem_mb, cfg.safety_margin_pct
|
||||
);
|
||||
|
||||
const recommended = Math.min(cpuLimit, memLimit);
|
||||
const warningThreshold = Math.floor(recommended * 0.8);
|
||||
const hardAdmissionControl = recommended; // never provision past the more conservative of the two limits
|
||||
|
||||
console.log('RootNode capacity model');
|
||||
console.log('========================');
|
||||
console.log(`Host: ${cfg.host_cpu_cores} cores, ${cfg.host_mem_mb} MB RAM`);
|
||||
console.log(`Platform-reserved: ${cfg.platform_reserved_cpu_cores} cores, ${cfg.platform_reserved_mem_mb} MB`);
|
||||
console.log(`Proxmox overhead: ${cfg.proxmox_overhead_pct}%`);
|
||||
console.log(`Measured per-Lab: ${cfg.measured_lab_cpu_cores} cores, ${cfg.measured_lab_mem_mb} MB`);
|
||||
console.log(`Safety margin: ${cfg.safety_margin_pct}%`);
|
||||
console.log('');
|
||||
console.log(`CPU-limited concurrent Labs: ${cpuLimit}`);
|
||||
console.log(`Memory-limited concurrent Labs: ${memLimit}`);
|
||||
console.log('');
|
||||
console.log(`RECOMMENDED concurrent Labs: ${recommended} (binding constraint: ${cpuLimit < memLimit ? 'CPU' : 'memory'})`);
|
||||
console.log(`WARNING threshold (80%): ${warningThreshold}`);
|
||||
console.log(`HARD admission-control threshold: ${hardAdmissionControl}`);
|
||||
console.log('');
|
||||
if (cfg.measured_lab_cpu_cores === undefined || cfg._placeholder) {
|
||||
console.log('WARNING: this config still has placeholder/example values — see infra/scripts/loadtest/ for how to measure real ones.');
|
||||
}
|
||||
Reference in New Issue
Block a user