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.
60 lines
3.0 KiB
JavaScript
Executable File
60 lines
3.0 KiB
JavaScript
Executable File
#!/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.');
|
|
}
|