aboutsummaryrefslogtreecommitdiff
path: root/bench/websocket-server
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-10-18 21:59:47 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-10-18 22:16:51 -0700
commit9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293 (patch)
tree57b0e3ba3b6248067f93bcf6a4d25a961bb873e9 /bench/websocket-server
parentb0fe1679107541ef5691111f55a7b3db9a81457d (diff)
downloadbun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.tar.gz
bun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.tar.zst
bun-9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293.zip
websocker-server
Diffstat (limited to 'bench/websocket-server')
-rw-r--r--bench/websocket-server/.gitignore169
-rw-r--r--bench/websocket-server/README.md37
-rw-r--r--bench/websocket-server/chat-client.mjs182
-rw-r--r--bench/websocket-server/chat-server.bun.js56
-rw-r--r--bench/websocket-server/chat-server.deno.mjs48
-rw-r--r--bench/websocket-server/chat-server.node.mjs52
-rw-r--r--bench/websocket-server/package.json13
-rw-r--r--bench/websocket-server/tsconfig.json14
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"]
+ }
+}