diff options
author | 2022-10-18 21:59:47 -0700 | |
---|---|---|
committer | 2022-10-18 22:16:51 -0700 | |
commit | 9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293 (patch) | |
tree | 57b0e3ba3b6248067f93bcf6a4d25a961bb873e9 /bench/websocket-server | |
parent | b0fe1679107541ef5691111f55a7b3db9a81457d (diff) | |
download | bun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.tar.gz bun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.tar.zst bun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.zip |
websocker-server
Diffstat (limited to 'bench/websocket-server')
-rw-r--r-- | bench/websocket-server/.gitignore | 169 | ||||
-rw-r--r-- | bench/websocket-server/README.md | 37 | ||||
-rw-r--r-- | bench/websocket-server/chat-client.mjs | 182 | ||||
-rw-r--r-- | bench/websocket-server/chat-server.bun.js | 56 | ||||
-rw-r--r-- | bench/websocket-server/chat-server.deno.mjs | 48 | ||||
-rw-r--r-- | bench/websocket-server/chat-server.node.mjs | 52 | ||||
-rw-r--r-- | bench/websocket-server/package.json | 13 | ||||
-rw-r--r-- | bench/websocket-server/tsconfig.json | 14 |
8 files changed, 571 insertions, 0 deletions
diff --git a/bench/websocket-server/.gitignore b/bench/websocket-server/.gitignore new file mode 100644 index 000000000..f81d56eaa --- /dev/null +++ b/bench/websocket-server/.gitignore @@ -0,0 +1,169 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* diff --git a/bench/websocket-server/README.md b/bench/websocket-server/README.md new file mode 100644 index 000000000..2578c8d51 --- /dev/null +++ b/bench/websocket-server/README.md @@ -0,0 +1,37 @@ +# websocket-server + +This benchmarks a websocket server intended as a simple but very active chat room. + +First, start the server. By default, it will wait for 16 clients which the client script will handle. + +Run in Bun (`Bun.serve`): + +```bash +bun ./chat-server.bun.js +``` + +Run in Node (`"ws"` package): + +```bash +node ./chat-server.node.mjs +``` + +Run in Deno (`Deno.serve`): + +```bash +deno run -A --unstable ./chat-server.deno.mjs +``` + +Then, run the client script. By default, it will connect 16 clients. This client script can run in Bun, Node, or Deno + +```bash +node ./chat-client.mjs +``` + +The client script loops through a list of messages for each connected client and sends a message. + +For example, when the client sends `"foo"`, the server sends back `"John: foo"` so that all members of the chatroom receive the message. + +The client script waits until it receives all the messages for each client before sending the next batch of messages. + +This project was created using `bun init` in bun v0.2.1. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bench/websocket-server/chat-client.mjs b/bench/websocket-server/chat-client.mjs new file mode 100644 index 000000000..786ebde1c --- /dev/null +++ b/bench/websocket-server/chat-client.mjs @@ -0,0 +1,182 @@ +const env = + "process" in globalThis + ? process.env + : "Deno" in globalThis + ? Deno.env.toObject() + : {}; + +const SERVER = env.SERVER || "ws://0.0.0.0:4001"; +const WebSocket = globalThis.WebSocket || (await import("ws")).WebSocket; +const LOG_MESSAGES = env.LOG_MESSAGES === "1"; +const CLIENTS_TO_WAIT_FOR = parseInt(env.CLIENTS_COUNT || "", 10) || 16; +const DELAY = 64; +const MESSAGES_TO_SEND = Array.from({ length: 32 }, () => [ + "Hello World!", + "Hello World! 1", + "Hello World! 2", + "Hello World! 3", + "Hello World! 4", + "Hello World! 5", + "Hello World! 6", + "Hello World! 7", + "Hello World! 8", + "Hello World! 9", + "What is the meaning of life?", + "where is the bathroom?", + "zoo", + "kangaroo", + "erlang", + "elixir", + "bun", + "mochi", + "typescript", + "javascript", + "Hello World! 7", + "Hello World! 8", + "Hello World! 9", + "What is the meaning of life?", + "where is the bathroom?", + "zoo", + "kangaroo", + "erlang", + "elixir", + "bun", + "mochi", + "typescript", + "javascript", + "Hello World! 7", + "Hello World! 8", + "Hello World! 9", + "What is the meaning of life?", + "Hello World! 7", + "Hello World! 8", + "Hello World! 9", + "What is the meaning of life?", + "where is the bathroom?", + "zoo", + "kangaroo", + "erlang", + "elixir", + "bun", + "mochi", + "typescript", + "javascript", +]).flat(); + +const NAMES = Array.from({ length: 50 }, (a, i) => [ + "Alice" + i, + "Bob" + i, + "Charlie" + i, + "David" + i, + "Eve" + i, + "Frank" + i, + "Grace" + i, + "Heidi" + i, + "Ivan" + i, + "Judy" + i, + "Karl" + i, + "Linda" + i, + "Mike" + i, + "Nancy" + i, + "Oscar" + i, + "Peggy" + i, + "Quentin" + i, + "Ruth" + i, + "Steve" + i, + "Trudy" + i, + "Ursula" + i, + "Victor" + i, + "Wendy" + i, + "Xavier" + i, + "Yvonne" + i, + "Zach" + i, +]) + .flat() + .slice(0, CLIENTS_TO_WAIT_FOR); + +console.log(`Connecting ${CLIENTS_TO_WAIT_FOR} WebSocket clients...`); +console.time(`All ${CLIENTS_TO_WAIT_FOR} clients connected`); + +var remainingClients = CLIENTS_TO_WAIT_FOR; +var promises = []; + +const clients = new Array(CLIENTS_TO_WAIT_FOR); +for (let i = 0; i < CLIENTS_TO_WAIT_FOR; i++) { + clients[i] = new WebSocket(`${SERVER}?name=${NAMES[i]}`); + promises.push( + new Promise((resolve, reject) => { + clients[i].onmessage = (event) => { + resolve(); + }; + }) + ); +} + +await Promise.all(promises); +console.timeEnd(`All ${clients.length} clients connected`); + +var received = 0; +var total = 0; +var more = false; +var remaining; + +for (let i = 0; i < CLIENTS_TO_WAIT_FOR; i++) { + clients[i].onmessage = (event) => { + if (LOG_MESSAGES) console.log(event.data); + received++; + remaining--; + + if (remaining === 0) { + more = true; + remaining = total; + } + }; +} + +// each message is supposed to be received +// by each client +// so its an extra loop +for (let i = 0; i < CLIENTS_TO_WAIT_FOR; i++) { + for (let j = 0; j < MESSAGES_TO_SEND.length; j++) { + for (let k = 0; k < CLIENTS_TO_WAIT_FOR; k++) { + total++; + } + } +} +remaining = total; + +function restart() { + for (let i = 0; i < CLIENTS_TO_WAIT_FOR; i++) { + for (let j = 0; j < MESSAGES_TO_SEND.length; j++) { + clients[i].send(MESSAGES_TO_SEND[j]); + } + } +} + +var runs = []; +setInterval(() => { + const last = received; + runs.push(last); + received = 0; + console.log( + last, + `messages per second (${CLIENTS_TO_WAIT_FOR} clients x ${MESSAGES_TO_SEND.length} msg, min delay: ${DELAY}ms)` + ); + + if (runs.length >= 10) { + console.log("10 runs"); + console.log(JSON.stringify(runs, null, 2)); + if ("process" in globalThis) process.exit(0); + runs.length = 0; + } +}, 1000); +var isRestarting = false; +setInterval(() => { + if (more && !isRestarting) { + more = false; + isRestarting = true; + restart(); + isRestarting = false; + } +}, DELAY); +restart(); diff --git a/bench/websocket-server/chat-server.bun.js b/bench/websocket-server/chat-server.bun.js new file mode 100644 index 000000000..b015dec7e --- /dev/null +++ b/bench/websocket-server/chat-server.bun.js @@ -0,0 +1,56 @@ +// See ./README.md for instructions on how to run this benchmark. +const CLIENTS_TO_WAIT_FOR = parseInt(process.env.CLIENTS_COUNT || "", 10) || 16; +var remainingClients = CLIENTS_TO_WAIT_FOR; +const COMPRESS = process.env.COMPRESS === "1"; +const port = process.PORT || 4001; + +const server = Bun.serve({ + port: port, + websocket: { + open(ws) { + ws.subscribe("room"); + + remainingClients--; + console.log(`${ws.data.name} connected (${remainingClients} remain)`); + + if (remainingClients === 0) { + console.log("All clients connected"); + setTimeout(() => { + console.log('Starting benchmark by sending "ready" message'); + ws.publishText("room", `ready`); + }, 100); + } + }, + message(ws, msg) { + const out = `${ws.data.name}: ${msg}`; + if (ws.publishText("room", out) !== out.length) { + throw new Error("Failed to publish message"); + } + }, + close(ws) { + remainingClients++; + }, + + perMessageDeflate: false, + }, + + fetch(req, server) { + if ( + server.upgrade(req, { + data: { + name: + new URL(req.url).searchParams.get("name") || + "Client #" + (CLIENTS_TO_WAIT_FOR - remainingClients), + }, + }) + ) + return; + + return new Response("Error"); + }, +}); + +console.log( + `Waiting for ${remainingClients} clients to connect...\n`, + ` http://${server.hostname}:${port}/` +); diff --git a/bench/websocket-server/chat-server.deno.mjs b/bench/websocket-server/chat-server.deno.mjs new file mode 100644 index 000000000..3cea9cf2f --- /dev/null +++ b/bench/websocket-server/chat-server.deno.mjs @@ -0,0 +1,48 @@ +// See ./README.md for instructions on how to run this benchmark. +const port = Deno.env.get("PORT") || 4001; +const CLIENTS_TO_WAIT_FOR = + parseInt(Deno.env.get("CLIENTS_COUNT") || "", 10) || 16; + +var clients = []; +async function reqHandler(req) { + if (req.headers.get("upgrade") != "websocket") { + return new Response(null, { status: 501 }); + } + const { socket: client, response } = Deno.upgradeWebSocket(req); + + clients.push(client); + const name = new URL(req.url).searchParams.get("name"); + + console.log( + `${name} connected (${CLIENTS_TO_WAIT_FOR - clients.length} remain)` + ); + + client.onmessage = (event) => { + const msg = `${name}: ${event.data}`; + for (let client of clients) { + client.send(msg); + } + }; + client.onclose = () => { + clients.splice(clients.indexOf(client), 1); + }; + + if (clients.length === CLIENTS_TO_WAIT_FOR) { + sendReadyMessage(); + } + return response; +} + +function sendReadyMessage() { + console.log("All clients connected"); + setTimeout(() => { + console.log("Starting benchmark"); + for (let client of clients) { + client.send(`ready`); + } + }, 100); +} + +console.log(`Waiting for ${CLIENTS_TO_WAIT_FOR} clients to connect..`); + +Deno.serve(reqHandler, { port }); diff --git a/bench/websocket-server/chat-server.node.mjs b/bench/websocket-server/chat-server.node.mjs new file mode 100644 index 000000000..e57b9b982 --- /dev/null +++ b/bench/websocket-server/chat-server.node.mjs @@ -0,0 +1,52 @@ +// See ./README.md for instructions on how to run this benchmark. +const port = process.env.PORT || 4001; +const CLIENTS_TO_WAIT_FOR = parseInt(process.env.CLIENTS_COUNT || "", 10) || 16; + +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +var WebSocketServer = require("ws").Server, + config = { + host: "0.0.0.0", + port, + }, + wss = new WebSocketServer(config, function () { + console.log(`Waiting for ${CLIENTS_TO_WAIT_FOR} clients to connect..`); + }); + +var clients = []; + +wss.on("connection", function (ws, { url }) { + const name = new URL(new URL(url, "http://localhost:3000")).searchParams.get( + "name" + ); + console.log( + `${name} connected (${CLIENTS_TO_WAIT_FOR - clients.length} remain)` + ); + clients.push(ws); + + ws.on("message", function (message) { + const out = `${name}: ${message}`; + for (let client of clients) { + client.send(out); + } + }); + + // when a connection is closed + ws.on("close", function (ws) { + clients.splice(clients.indexOf(ws), 1); + }); + + if (clients.length === CLIENTS_TO_WAIT_FOR) { + sendReadyMessage(); + } +}); + +function sendReadyMessage() { + console.log("All clients connected"); + setTimeout(() => { + console.log("Starting benchmark"); + for (let client of clients) { + client.send(`ready`); + } + }, 100); +} diff --git a/bench/websocket-server/package.json b/bench/websocket-server/package.json new file mode 100644 index 000000000..4f1dfa334 --- /dev/null +++ b/bench/websocket-server/package.json @@ -0,0 +1,13 @@ +{ + "name": "websocket-server", + "module": "index.ts", + "type": "module", + "devDependencies": { + "bun-types": "^0.2.0" + }, + "dependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^5.0.10", + "ws": "^8.9.0" + } +} diff --git a/bench/websocket-server/tsconfig.json b/bench/websocket-server/tsconfig.json new file mode 100644 index 000000000..feee4b584 --- /dev/null +++ b/bench/websocket-server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", + + // so that if your project isn't using TypeScript, it still has autocomplete + "allowJs": true, + + // "bun-types" is the important part + "types": ["bun-types"] + } +} |