B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
129 lines
4.2 KiB
JavaScript
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(``);
|