CLI interface for benchmarking WebSocket servers
websocket-bench allows you to perform load testing on WebSocket servers of various types, including standard WebSocket servers, ActionCable, Phoenix, Pusher, and AnyCable.
This project is inspired by the websocket-shootout project.
- Go 1.16 or higher
git clone https://github.com/anycable/websocket-bench.git
cd websocket-bench
# Build the binary
go build -o websocket-benchwebsocket-bench provides three main benchmark commands:
Tests 1-to-1 performance by sending messages to the server and measuring how long it takes for the server to echo them back.
./websocket-bench echo [options] ws://server-urlCommon options:
-c, --concurrent: Number of concurrent echo requests (default: 50)-s, --sample-size: Number of echoes in a sample (default: 10000)--step-size: Number of clients to increase each step (default: 5000)--server-type: Server type to connect to (json, binary, actioncable, phoenix) (default: json)-f, --format: Output format (json or text)-n, --filename: Output filename
Tests 1-to-many performance by sending broadcast messages and measuring how long it takes for all clients to receive them.
./websocket-bench broadcast [options] ws://server-urlCommon options:
-c, --concurrent: Number of concurrent broadcast requests (default: 4)--connect-concurrent: Concurrent connection initialization requests (default: 100)-s, --sample-size: Number of broadcasts in a sample (default: 20)--initial-clients: Initial number of clients (default: 0)--step-size: Number of clients to increase each step (default: 5000)--server-type: Server type (json,binary,actioncable,phoenix,pusher,anycable-pusher) (default:json)-f, --format: Output format (jsonortext)-n, --filename: Output filename
Pusher‑compatible flags (for pusher and anycable-pusher):
--pusher-app-id: Pusher App ID (default:app-id)--pusher-app-key: Pusher App Key (default:app-key)--pusher-app-secret: Pusher App Secret (default:app-secret)--pusher-channel: Channel name (default:private-benchmark)--pusher-host: HTTP trigger host (default:127.0.0.1)--pusher-port: HTTP trigger port (default:6001)
Additional flags for anycable-pusher only:
--anycable-backend: Broadcast transport (httporredis) (default:http)--anycable-http-endpoint: AnyCable HTTP broadcast endpoint (default:http://localhost:8080/_broadcast)--anycable-redis-addr: Redis address for broadcasts (default:127.0.0.1:6379)
Tests connection initialization performance by measuring how long it takes to establish WebSocket connections.
./websocket-bench connect [options] ws://server-urlCommon options:
-c, --concurrent: Number of concurrent connection requests (default: 50)--step-size: Number of clients to connect at each step (default: 5000)--server-type: Server type to connect to (default: json)actioncable-connect: Test connection to ActionCable serverspusher-connect: Test connection to Pusher-compatible servers (with channel subscription)anycable-connect: Test connection to AnyCable (similar to pusher-connect, but optimized for AnyCable)
-f, --format: Output format (json or text)-n, --filename: Output filename--pusher-channel: Pusher channel name for connect tests (default Benchmark)
Worker mode allows you to distribute connections across multiple machines for scaling tests to a large number of connections.
./websocket-bench worker [options]Options:
-a, --address: Address to listen on (default: 0.0.0.0)-p, --port: Port to listen on (default: 3000)
When using worker mode, you can run the master process with the --worker-addr option to specify worker node addresses separated by commas.
[2025-06-24T21:00:17+09:00] clients: 500 95per-rtt: 25ms min-rtt: 1ms median-rtt: 12ms max-rtt: 25ms
[2025-06-24T21:00:19+09:00] clients: 1000 95per-rtt: 109ms min-rtt: 5ms median-rtt: 25ms max-rtt: 109ms
[2025-06-24T21:00:22+09:00] clients: 1500 95per-rtt: 63ms min-rtt: 3ms median-rtt: 44ms max-rtt: 63ms
[2025-06-24T21:00:25+09:00] clients: 2000 95per-rtt: 94ms min-rtt: 10ms median-rtt: 53ms max-rtt: 94ms
...
[2025-06-24T21:00:58+09:00] clients: 5000 95per-rtt: 474ms min-rtt: 32ms median-rtt: 149ms max-rtt: 474ms
You can generate JSON output files and visualize them using the included Ruby script:
# Run benchmark with JSON output
./websocket-bench echo -f json -n dist/echo_$(date +%s).json ws://server-url
# Generate HTML chart
ruby etc/chart.rb dist chart.htmlThis will generate an HTML file with charts showing the median, 95th percentile, and maximum RTT for each benchmark.
You can benchmark a Pusher Channels–compatible server soketi by running a local instance and pointing websocket‑bench at it
docker run \
--network host \
-e SOKETI_DEBUG=1 \
-p 6001:6001 \
-p 9601:9601 \
quay.io/soketi/soketi:1.4-16-debian- WebSocket API will be listening on 127.0.0.1:6001
- HTTP API (for triggers) will also be on http://127.0.0.1:6001
It creates a single app with next accesses:
app_id=app-id
key=app-key
secret=app-secret
See the official install guide
Pushed client url looks like that:
ws://ws-[cluster].pusher.com:[port]/app/[APP_KEY]?client=[library]&version=[lib_version]&protocol=[protocol_version]
- scheme: ws or wss
- cluster: your cluster name (for soketi, this is omitted; use host directly)
- port: soketi defaults to 6001
- key: your Pusher App Key
- client / version / protocol: library metadata
Soketi example:
ws://127.0.0.1:6001/app/app-key?client=js&version=7.0.3&protocol=7
More details here
./websocket-bench broadcast "ws://127.0.0.1:6001/app/app-key?client=js&version=7.0.3&protocol=7" \
--server-type pusher \
--concurrent 5 \
--step-size 500 \
--steps-delay 1 \
--total-steps 10
If your app-id, app-key, or app-secret differ from the defaults above, pass them via --pusher-* flags so that the HMAC signature is computed correctly for private/presence channels
websocket-bench supports multiple modes for working with AnyCable:
anycable-pusher: For testing broadcasting messages using the Pusher-compatible protocolanycable-connect: For testing connection performance to AnyCable servers
./websocket-bench broadcast \
"ws://localhost:8080/app/app-key?client=bench&version=1.0&protocol=7" \
--server-type anycable-pusher \
--pusher-app-id app-id \
--pusher-app-key app-key \
--pusher-app-secret app-secret \
--anycable-backend http \
--concurrent 5 --step-size 500 --steps-delay 1 --total-steps 10 ./websocket-bench broadcast \
"ws://localhost:8080/app/app-key?client=bench&version=1.0&protocol=7" \
--server-type anycable-pusher \
--pusher-app-id app-id \
--pusher-app-key app-key \
--pusher-app-secret app-secret \
--anycable-backend redis \
--anycable-redis-addr 127.0.0.1:6379 \
--concurrent 5 --step-size 500 --steps-delay 1 --total-steps 10 ./websocket-bench connect \
"ws://localhost:8080/app/app-key?client=bench&version=1.0&protocol=7" \
--server-type anycable-connect \
--pusher-app-id app-id \
--pusher-app-key app-key \
--pusher-app-secret app-secret \
--concurrent 50 --step-size 500 --steps-delay 1 --total-steps 10Note: The
anycable-connectserver type works similarly topusher-connectbut is corrected for AnyCable - it skips the channel unsubscribe phase during connection closing, which is not needed for AnyCable