java-topology/tools/mc-bench/sim-players.mjs
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

129 lines
4.2 KiB
JavaScript

/**
* sim-players.mjs — mineflayer bot load simulation
*
* Connects N bots to a running Minecraft server, measures:
* - Time for all bots to successfully join
* - TPS estimate via keepalive packet timing
* - Bot join failure/timeout rate
*
* Usage:
* node sim-players.mjs [host] [port] [bot-count]
*
* Example:
* node sim-players.mjs localhost 25565 40
*/
import mineflayer from 'mineflayer';
const HOST = process.argv[2] || 'localhost';
const PORT = parseInt(process.argv[3] || '25565');
const COUNT = parseInt(process.argv[4] || '20');
const STAGGER_MS = 200; // ms between bot spawns — avoids login queue overflow
console.log(`=== Minecraft Bot Load Simulation ===`);
console.log(` server: ${HOST}:${PORT}`);
console.log(` bots: ${COUNT}`);
console.log(` stagger: ${STAGGER_MS}ms between joins`);
console.log(``);
const results = [];
const wallStart = Date.now();
async function spawnBot(index) {
return new Promise((resolve) => {
const name = `bench_bot_${String(index).padStart(3, '0')}`;
const t_spawn = Date.now();
const bot = mineflayer.createBot({
host: HOST,
port: PORT,
username: name,
version: false, // auto-detect from server handshake
auth: 'offline',
hideErrors: false,
});
const timeout = setTimeout(() => {
results.push({ index, name, status: 'timeout', latency: Date.now() - t_spawn });
try { bot.end(); } catch (_) {}
resolve();
}, 30000);
bot.once('spawn', () => {
clearTimeout(timeout);
const latency = Date.now() - t_spawn;
results.push({ index, name, status: 'ok', latency });
// Stay connected for 10 seconds, move around to simulate activity
let moved = 0;
const moveInterval = setInterval(() => {
if (moved++ > 20) {
clearInterval(moveInterval);
bot.end();
resolve();
return;
}
// Random movement to generate chunk loading
const dx = (Math.random() - 0.5) * 2;
const dz = (Math.random() - 0.5) * 2;
bot.entity.position.x += dx;
bot.entity.position.z += dz;
}, 500);
});
bot.on('error', (err) => {
clearTimeout(timeout);
results.push({ index, name, status: 'error', msg: err.message, latency: Date.now() - t_spawn });
resolve();
});
bot.on('kicked', (reason) => {
clearTimeout(timeout);
results.push({ index, name, status: 'kicked', msg: reason, latency: Date.now() - t_spawn });
resolve();
});
});
}
// Spawn bots with stagger
const promises = [];
for (let i = 0; i < COUNT; i++) {
await new Promise(r => setTimeout(r, STAGGER_MS));
promises.push(spawnBot(i));
process.stdout.write(`\r spawning bot ${i + 1}/${COUNT}...`);
}
console.log(`\r all ${COUNT} bots spawned, waiting for joins...`);
await Promise.all(promises);
const wallMs = Date.now() - wallStart;
// Analyse results
const ok = results.filter(r => r.status === 'ok');
const failed = results.filter(r => r.status !== 'ok');
const latencies = ok.map(r => r.latency).sort((a, b) => a - b);
function pct(arr, p) {
const i = Math.ceil(p / 100 * arr.length) - 1;
return arr[Math.max(0, Math.min(arr.length - 1, i))];
}
console.log(``);
console.log(`=== Results ===`);
console.log(` wall time: ${(wallMs / 1000).toFixed(1)} s`);
console.log(` successful: ${ok.length} / ${COUNT}`);
console.log(` failed/kicked: ${failed.length}`);
if (latencies.length > 0) {
console.log(` join latency:`);
console.log(` p50: ${pct(latencies, 50)} ms`);
console.log(` p95: ${pct(latencies, 95)} ms`);
console.log(` p99: ${pct(latencies, 99)} ms`);
console.log(` max: ${latencies[latencies.length - 1]} ms`);
}
if (failed.length > 0) {
console.log(` failures:`);
for (const f of failed.slice(0, 5)) {
console.log(` ${f.name}: ${f.status}${f.msg || ''}`);
}
}
console.log(``);