UNPKG

@colyseus/loadtest

Version:

Utility tool for load testing Colyseus.

8 lines (7 loc) 27.8 kB
{ "version": 3, "sources": ["../src/index.ts"], "sourcesContent": ["import fs from \"fs\";\nimport path from \"path\";\nimport util from \"util\";\nimport blessed from \"blessed\";\nimport WebSocket from \"ws\";\nimport timer from \"timers/promises\"\nimport minimist from \"minimist\";\n\nimport { Client, Room } from \"colyseus.js\";\n\nexport type RequestJoinOperations = {\n requestNumber?: number,\n};\n\nexport type Options = {\n endpoint: string,\n roomName: string,\n roomId: string,\n numClients: number,\n delay: number,\n logLevel: string,\n reestablishAllDelay: number,\n retryFailed: number,\n output: string,\n requestJoinOptions?: RequestJoinOperations,\n clientId: number,\n};\n\nexport type MainCallback = (options: Options) => Promise<void>;\n\nexport function cli(main: MainCallback) {\n const logWriter = {\n handle: null as fs.WriteStream,\n isClosing: false as boolean,\n create(filepath: string) {\n if (fs.existsSync(filepath)) {\n const moveTo = `${path.basename(filepath)}.bkp`;\n console.log(`Moving previous \"${path.basename(filepath)}\" file to \"${moveTo}\"`);\n fs.renameSync(filepath, path.resolve(path.dirname(filepath), moveTo));\n }\n this.handle = fs.createWriteStream(filepath);\n },\n\n write(contents: any, close?: boolean) {\n if (!this.handle || this.isClosing) { return; }\n if (close) { this.isClosing = true; }\n\n return new Promise<void>((resolve, reject) => {\n const now = new Date();\n this.handle.write(`[${now.toLocaleString()}] ${contents}\\n`, (err) => {\n if (err) { return reject(err); }\n if (this.isClosing) { this.handle.close(); }\n resolve();\n });\n\n })\n }\n };\n\n const argv = minimist(process.argv.slice(2));\n\n // const packageJson = import(\"../package.json\");\n const packageJson = { name: \"@colyseus/loadtest\", version: \"0.15\" };\n\n function displayHelpAndExit() {\n console.log(`${packageJson.name} v${packageJson.version}\n\nOptions:\n --endpoint: WebSocket endpoint for all connections (default: ws://localhost:2567)\n --room: room handler name (you can also use --roomId instead to join by id)\n --roomId: room id (specify instead of --room)\n [--numClients]: number of connections to open (default is 1)\n [--delay]: delay to start each connection (in milliseconds)\n [--project]: specify a tsconfig.json file path\n [--reestablishAllDelay]: delay for closing and re-establishing all connections (in milliseconds)\n [--retryFailed]: delay to retry failed connections (in milliseconds)\n [--output]: specify an output file for output logs\n\nExample:\n colyseus-loadtest example/bot.ts --endpoint ws://localhost:2567 --room state_handler`);\n process.exit();\n\n }\n\n if (argv.help) { displayHelpAndExit(); }\n\n const options: Options = {\n endpoint: argv.endpoint || `ws://localhost:2567`,\n roomName: argv.room,\n roomId: argv.roomId,\n numClients: argv.numClients || 1,\n delay: argv.delay || 0,\n logLevel: argv.logLevel?.toLowerCase() || \"all\", // TODO: not being used atm\n reestablishAllDelay: argv.reestablishAllDelay || 0,\n retryFailed: argv.retryFailed || 0,\n output: argv.output && path.resolve(argv.output),\n clientId: 0,\n }\n\n if (!main) {\n console.error(\"\u274C You must specify an entrypoint function.\");\n console.error(\"\");\n displayHelpAndExit();\n }\n\n const connections: Room[] = [];\n\n if (!options.roomName && !options.roomId) {\n console.error(\"\u274C You need to specify a room with either one of the '--room' or '--roomId' options.\");\n console.error(\"\");\n displayHelpAndExit();\n }\n\n if (options.output) {\n logWriter.create(options.output);\n logWriter.write(`@colyseus/loadtest\\n${Object.keys(options)\n .filter(key => options[key])\n .map((key) => `${key}: ${options[key]}`).join('\\n')}`)\n }\n\n const screen = blessed.screen({ smartCSR: true });\n\n const headerBox = blessed.box({\n label: ` \u2694 ${packageJson.name} ${packageJson.version} \u2694 `,\n top: 0,\n left: 0,\n width: \"70%\",\n height: 'shrink',\n children: [\n blessed.text({ top: 1, left: 1, tags: true, content: `{yellow-fg}endpoint:{/yellow-fg} ${options.endpoint}` }),\n blessed.text({ top: 2, left: 1, tags: true, content: `{yellow-fg}room:{/yellow-fg} ${options.roomName ?? options.roomId}` }),\n blessed.text({ top: 3, left: 1, tags: true, content: `{yellow-fg}serialization method:{/yellow-fg} ...` }),\n blessed.text({ top: 4, left: 1, tags: true, content: `{yellow-fg}time elapsed:{/yellow-fg} ...` }),\n ],\n border: { type: 'line' },\n style: {\n label: { fg: 'cyan' },\n border: { fg: 'green' }\n }\n });\n\n const currentStats = {\n connected: 0,\n disconnected: 0,\n failed: 0,\n };\n\n const totalStats = {\n connected: 0,\n disconnected: 0,\n failed: 0,\n errors: 0,\n };\n\n const successfulConnectionBox = blessed.text({ top: 2, left: 1, tags: true, content: `{yellow-fg}connected:{/yellow-fg} ${currentStats.connected}` });\n const disconnectedClientsBox = blessed.text({ top: 3, left: 1, tags: true, content: `{yellow-fg}disconnected:{/yellow-fg} ${currentStats.disconnected}` });\n const failedConnectionBox = blessed.text({ top: 4, left: 1, tags: true, content: `{yellow-fg}failed:{/yellow-fg} ${currentStats.failed}` });\n\n const clientsBox = blessed.box({\n label: ' clients ',\n left: \"70%\",\n width: \"30%\",\n height: 'shrink',\n children: [\n blessed.text({ top: 1, left: 1, tags: true, content: `{yellow-fg}numClients:{/yellow-fg} ${options.numClients}` }),\n successfulConnectionBox,\n disconnectedClientsBox,\n failedConnectionBox\n ],\n border: { type: 'line' },\n tags: true,\n style: {\n label: { fg: 'cyan' },\n border: { fg: 'green' },\n }\n })\n\n const processingBox = blessed.box({\n label: ' processing ',\n top: 7,\n left: \"70%\",\n width: \"30%\",\n height: 'shrink',\n border: { type: 'line' },\n children: [\n blessed.text({ top: 1, left: 1, tags: true, content: `{yellow-fg}memory:{/yellow-fg} ...` }),\n blessed.text({ top: 2, left: 1, tags: true, content: `{yellow-fg}cpu:{/yellow-fg} ...` }),\n // blessed.text({ top: 1, left: 1, content: `memory: ${process.memoryUsage().heapUsed} / ${process.memoryUsage().heapTotal}` })\n ],\n tags: true,\n style: {\n label: { fg: 'cyan' },\n border: { fg: 'green' },\n }\n });\n\n const networkingBox = blessed.box({\n label: ' networking ',\n top: 12,\n left: \"70%\",\n width: \"30%\",\n border: { type: 'line' },\n children: [\n blessed.text({ top: 1, left: 1, tags: true, content: `{yellow-fg}bytes received:{/yellow-fg} ...` }),\n blessed.text({ top: 2, left: 1, tags: true, content: `{yellow-fg}bytes sent:{/yellow-fg} ...` }),\n // blessed.text({ top: 1, left: 1, content: `memory: ${process.memoryUsage().heapUsed} / ${process.memoryUsage().heapTotal}` })\n ],\n tags: true,\n style: {\n label: { fg: 'cyan' },\n border: { fg: 'green' },\n }\n });\n\n const logBox = blessed.box({\n label: ' logs ',\n top: 7,\n width: \"70%\",\n padding: 1,\n border: { type: 'line' },\n tags: true,\n style: {\n label: { fg: 'cyan' },\n border: { fg: 'green' },\n },\n // scroll\n scrollable: true,\n input: true,\n alwaysScroll: true,\n scrollbar: {\n style: {\n bg: \"green\"\n },\n track: {\n bg: \"gray\"\n }\n },\n keys: true,\n vi: true,\n mouse: true\n });\n\n screen.key(['escape', 'q', 'C-c'], (ch, key) => beforeExit(\"SIGINT\")); // Quit on Escape, q, or Control-C.\n screen.title = \"@colyseus/loadtest\";\n screen.append(headerBox);\n screen.append(clientsBox);\n screen.append(logBox);\n screen.append(processingBox);\n screen.append(networkingBox);\n screen.render();\n\n const debug = console.debug;\n const log = console.log;\n const warn = console.warn;\n const info = console.info;\n const error = console.error;\n\n console.debug = function(...args) {\n logBox.content = `{grey-fg}${args.map(arg => util.inspect(arg)).join(\" \")}{/grey-fg}\\n${logBox.content}`;\n screen.render();\n };\n console.log = function(...args) {\n logBox.content = args.map(arg => util.inspect(arg)).join(\" \") + \"\\n\" + logBox.content;\n screen.render();\n };\n console.warn = function(...args) {\n logBox.content = `{yellow-fg}${args.map(arg => util.inspect(arg)).join(\" \")}{/yellow-fg}\\n${logBox.content}`;\n screen.render();\n };\n console.info = function(...args) {\n logBox.content = `{blue-fg}${args.map(arg => util.inspect(arg)).join(\" \")}{/blue-fg}\\n${logBox.content}`;\n screen.render();\n };\n console.error = function(...args) {\n totalStats.errors++;\n logBox.content = `{red-fg}${args.map(arg => util.inspect(arg)).join(\" \")}{/red-fg}\\n${logBox.content}`;\n screen.render();\n };\n\n process.on(\"uncaughtException\", (e) => {\n console.error(e);\n });\n\n let isExiting = false;\n async function beforeExit(signal: NodeJS.Signals, closeCode: number = 0) {\n log(\"Writing log file...\");\n\n if (isExiting) {\n return;\n\n } else {\n isExiting = true;\n }\n\n const hasError = (closeCode > 0);\n\n await logWriter.write(`Finished. Summary:\n Successful connections: ${totalStats.connected}\n Failed connections: ${totalStats.failed}\n Total errors: ${totalStats.errors}\n Logs:\n ${logBox.content}`, true /* closing */)\n\n process.exit(hasError ? 1 : 0);\n }\n\n // trap process signals\n process.once('exit', (code) => beforeExit(\"SIGINT\", code));\n ['SIGINT', 'SIGTERM', 'SIGUSR2'].forEach((signal) =>\n process.once(signal as NodeJS.Signals, (signal) => beforeExit(signal)));\n\n function formatBytes (bytes) {\n if (bytes < 1024) {\n return `${bytes} b`;\n\n } else if (bytes < Math.pow(1024, 2)) {\n return `${(bytes / 1024).toFixed(2)} kb`;\n\n } else if (bytes < Math.pow(1024, 4)) {\n return `${(bytes / 1024 / 1024).toFixed(2)} MB`;\n }\n }\n\n function elapsedTime(inputSeconds) {\n const days = Math.floor(inputSeconds / (60 * 60 * 24));\n const hours = Math.floor((inputSeconds % (60 * 60 * 24)) / (60 * 60));\n const minutes = Math.floor(((inputSeconds % (60 * 60 * 24)) % (60 * 60)) / 60);\n const seconds = Math.floor(((inputSeconds % (60 * 60 * 24)) % (60 * 60)) % 60);\n\n let ddhhmmss = '';\n\n if (days > 0) { ddhhmmss += days + ' day '; }\n if (hours > 0) { ddhhmmss += hours + ' hour '; }\n if (minutes > 0) { ddhhmmss += minutes + ' minutes '; }\n if (seconds > 0) { ddhhmmss += seconds + ' seconds '; }\n\n return ddhhmmss || \"...\";\n }\n\n /**\n * Update memory / cpu usage\n */\n const loadTestStartTime = Date.now();\n let startTime = process.hrtime()\n let startUsage = process.cpuUsage()\n let bytesReceived: number = 0;\n let bytesSent: number = 0;\n setInterval(() => {\n /**\n * Program elapsed time\n */\n const elapsedTimeText = (headerBox.children[3] as blessed.Widgets.TextElement);\n elapsedTimeText.content = `{yellow-fg}time elapsed:{/yellow-fg} ${elapsedTime(Math.round((Date.now() - loadTestStartTime) / 1000))}`;\n\n /**\n * Memory / CPU Usage\n */\n const memoryText = (processingBox.children[0] as blessed.Widgets.TextElement);\n memoryText.content = `{yellow-fg}memory:{/yellow-fg} ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`;\n\n var elapTime = process.hrtime(startTime)\n var elapUsage = process.cpuUsage(startUsage)\n\n var elapTimeMS = elapTime[0] * 1000 + elapTime[1] / 1000000;\n var elapUserMS = elapUsage.user / 1000;\n var elapSystMS = elapUsage.system / 1000;\n var cpuPercent = (100 * (elapUserMS + elapSystMS) / elapTimeMS).toFixed(1);\n\n const cpuText = (processingBox.children[1] as blessed.Widgets.TextElement);\n cpuText.content = `{yellow-fg}cpu:{/yellow-fg} ${cpuPercent}%`;\n\n screen.render();\n\n startTime = process.hrtime()\n startUsage = process.cpuUsage()\n\n /**\n * Networking\n */\n const bytesReceivedBox = (networkingBox.children[0] as blessed.Widgets.TextElement);\n bytesReceivedBox.content = `{yellow-fg}bytes received:{/yellow-fg} ${formatBytes(bytesReceived)}`\n\n const bytesSentBox = (networkingBox.children[1] as blessed.Widgets.TextElement);\n bytesSentBox.content = `{yellow-fg}bytes sent:{/yellow-fg} ${formatBytes(bytesSent)}`\n }, 1000);\n\n function handleError (message) {\n if (message) {\n console.error(message);\n logWriter.write(message);\n }\n\n currentStats.failed++;\n totalStats.failed++;\n\n failedConnectionBox.content = `{red-fg}failed:{/red-fg} ${currentStats.failed}`;\n screen.render();\n }\n\n async function connect(main: MainCallback, i: number) {\n try {\n await main({ ...options, clientId: i });\n } catch (e) {\n handleError(e);\n }\n }\n\n async function connectAll(main: MainCallback) {\n for (let i = 0; i < options.numClients; i++) {\n await connect(main, i);\n\n if (options.delay > 0) {\n await timer.setTimeout(options.delay);\n }\n }\n }\n\n async function reestablishAll(scripting: any) {\n // drop all connections, wait for acknowledgement\n connections.map((connection) => connection.connection.close());\n\n // clear array\n connections.splice(0, connections.length);\n connections.length = 0;\n\n // connect again\n await connectAll(scripting);\n }\n\n const handleClientJoin = function(room: Room) {\n // display serialization method in the UI\n const serializerIdText = (headerBox.children[2] as blessed.Widgets.TextElement);\n serializerIdText.content = `{yellow-fg}serialization method:{/yellow-fg} ${room.serializerId}`;\n\n const ws: WebSocket = (room.connection.transport as any).ws;\n ws.addEventListener('message', (event) => {\n bytesReceived += new Uint8Array(event.data).length;\n });\n\n // overwrite original send function to trap sent bytes.\n const _send = ws.send;\n ws.send = function (data: ArrayBuffer) {\n if (ws.readyState == 1) {\n bytesSent += data.byteLength;\n }\n _send.call(ws, data);\n }\n\n currentStats.connected++;\n totalStats.connected++;\n successfulConnectionBox.content = `{yellow-fg}connected:{/yellow-fg} ${currentStats.connected}`;\n screen.render();\n\n // update stats on leave\n room.onLeave(() => {\n currentStats.disconnected++;\n totalStats.disconnected++;\n disconnectedClientsBox.content = `{yellow-fg}disconnected:{/yellow-fg} ${currentStats.disconnected}`;\n screen.render();\n });\n\n connections.push(room);\n }\n\n const _originalJoinOrCreate = Client.prototype.joinOrCreate;\n Client.prototype.joinOrCreate = async function(this: Client) {\n const room = await _originalJoinOrCreate.apply(this, arguments);\n handleClientJoin(room);\n return room;\n }\n\n const _originalCreate = Client.prototype.create;\n Client.prototype.create = async function(this: Client) {\n const room = await _originalCreate.apply(this, arguments);\n handleClientJoin(room);\n return room;\n }\n\n const _originalJoin = Client.prototype.join;\n Client.prototype.join = async function(this: Client) {\n const room = await _originalJoin.apply(this, arguments);\n handleClientJoin(room);\n return room;\n }\n\n const _originalJoinById = Client.prototype.joinById;\n Client.prototype.joinById = async function(this: Client) {\n const room = await _originalJoinById.apply(this, arguments);\n handleClientJoin(room);\n return room;\n }\n\n try {\n (async () => {\n await connectAll(main);\n\n if (options.reestablishAllDelay > 0) {\n while (true) {\n // wait for delay\n await timer.setTimeout(options.reestablishAllDelay);\n\n await reestablishAll(main);\n }\n }\n })();\n\n } catch (e) {\n error(e.stack);\n }\n}\n"], "mappings": "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", "names": ["signal", "main"] }