Spaces:
Running
Running
| /** | |
| * Monitors docker container CPU/memory during a stress test. | |
| * | |
| * Usage (run in parallel with run-stress-test.js): | |
| * node stress/monitor-docker.js --containers pdf-test --interval 1000 --duration 300000 --out stress/results/monitor.json | |
| */ | |
| const { spawnSync } = require('child_process'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| function parseArgs(argv) { | |
| const args = { containers: 'pdf-test,pdf-prod', interval: 1000, duration: 300000, out: '' }; | |
| for (let i = 0; i < argv.length; i++) { | |
| const m = argv[i].match(/^--([^=]+)(?:=(.*))?$/); | |
| if (!m) continue; | |
| if (m[2] !== undefined) { | |
| args[m[1]] = m[2]; | |
| } else if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) { | |
| args[m[1]] = argv[++i]; | |
| } else { | |
| args[m[1]] = true; | |
| } | |
| } | |
| return args; | |
| } | |
| function sample(containers) { | |
| try { | |
| // Use spawnSync with an args array (no shell) to avoid Windows quoting bugs. | |
| const res = spawnSync('docker', ['stats', '--no-stream', '--format', '{{json .}}', '--', ...containers], { encoding: 'utf8', timeout: 10000 }); | |
| if (res.status !== 0 || !res.stdout) return {}; | |
| const rows = {}; | |
| for (const line of res.stdout.split('\n').filter(Boolean)) { | |
| let obj; | |
| try { obj = JSON.parse(line); } catch (e) { continue; } | |
| const name = obj.Name; | |
| if (!name) continue; | |
| rows[name] = { | |
| cpuPerc: parseFloat(obj.CPUPerc) || 0, | |
| memUsedGiB: parseGiB(obj.MemUsage), | |
| memTotalGiB: 0, | |
| memPerc: parseFloat(obj.MemPerc) || 0, | |
| }; | |
| } | |
| return rows; | |
| } catch (e) { | |
| return {}; | |
| } | |
| } | |
| function parseGiB(memUsageStr) { | |
| if (!memUsageStr) return 0; | |
| const m = memUsageStr.match(/^([\d.]+)\s*(\w+)\s*\/\s*([\d.]+)\s*(\w+)$/); | |
| if (!m) return 0; | |
| const used = parseFloat(m[1]); | |
| const unit = m[2]; | |
| if (unit === 'GiB') return used; | |
| if (unit === 'MiB') return used / 1024; | |
| if (unit === 'KiB') return used / (1024 * 1024); | |
| return used; | |
| } | |
| async function main() { | |
| const args = parseArgs(process.argv.slice(2)); | |
| const containers = args.containers.split(','); | |
| const interval = parseInt(args.interval, 10); | |
| const duration = parseInt(args.duration, 10); | |
| const outFile = args.out || path.join(__dirname, 'results', `monitor-${Date.now()}.json`); | |
| const samples = []; | |
| const started = Date.now(); | |
| console.log(`Monitoring ${containers.join(',')} for ${duration}ms...`); | |
| while (Date.now() - started < duration) { | |
| const rows = sample(containers); | |
| samples.push({ t: Date.now() - started, rows }); | |
| process.stdout.write(`\rt=${Date.now() - started}ms ` + containers.map((c) => { | |
| const r = rows[c]; | |
| return r ? `${c}: cpu=${r.cpuPerc.toFixed(1)}% mem=${r.memUsedGiB.toFixed(2)}GiB` : `${c}: n/a`; | |
| }).join(' ')); | |
| await new Promise((r) => setTimeout(r, interval)); | |
| } | |
| process.stdout.write('\n'); | |
| const summary = {}; | |
| for (const c of containers) { | |
| const vals = samples.map((s) => s.rows[c]).filter(Boolean); | |
| if (!vals.length) { summary[c] = null; continue; } | |
| const cpu = vals.map((v) => v.cpuPerc).sort((a, b) => a - b); | |
| const mem = vals.map((v) => v.memUsedGiB).sort((a, b) => a - b); | |
| summary[c] = { | |
| cpuPerc: { avg: +(cpu.reduce((a, b) => a + b, 0) / cpu.length).toFixed(1), peak: Math.round(cpu[cpu.length - 1]) }, | |
| memUsedGiB: { avg: +(mem.reduce((a, b) => a + b, 0) / mem.length).toFixed(2), peak: +mem[mem.length - 1].toFixed(2) }, | |
| }; | |
| } | |
| const report = { started, duration, interval, containers, summary, samples }; | |
| fs.writeFileSync(outFile, JSON.stringify(report, null, 2)); | |
| console.log(`\nMonitor report saved: ${outFile}`); | |
| console.log(JSON.stringify(summary, null, 2)); | |
| } | |
| main().catch((e) => { | |
| console.error(e); | |
| process.exit(1); | |
| }); | |