hey is a tiny program that sends some load to a web application.
This is a Ruby port of rakyll/hey, translated
file by file from the Go original. It is built on
async-http: Go's goroutines and
channels become Async tasks and queues, and net/http becomes
Async::HTTP::Client.
hey was originally called boom and was influenced from Tarek Ziade's tool at tarekziade/boom. Using the same name was a mistake as it resulted in cases where binary name conflicts created confusion. To preserve the name for its original owner, we renamed this project to hey.
Installation
Requires Ruby 3.2 or later. The gem is named hey.rb, because hey was
already taken; the command it installs is hey.
gem install hey.rb
hey https://google.com
From a checkout:
git clone https://github.com/n-at-han-k/hey.rb
cd hey.rb
bundle install
./exe/hey https://google.com
With Nix, the included flake provides a dev shell with every dependency:
nix develop
./exe/hey https://google.com
Usage
hey runs provided number of requests in the provided concurrency level and prints stats.
It also supports HTTP2 endpoints.
Usage: hey [options...] <url>
Options:
-n Number of requests to run. Default is 200.
-c Number of workers to run concurrently. Total number of requests cannot
be smaller than the concurrency level. Default is 50.
-q Rate limit, in queries per second (QPS) per worker. Default is no rate limit.
-z Duration of application to send requests. When duration is reached,
application stops and exits. If duration is specified, n is ignored.
Examples: -z 10s -z 3m.
-o Output type. If none provided, a summary is printed.
"csv" is the only supported alternative. Dumps the response
metrics in comma-separated values format.
-m HTTP method, one of GET, POST, PUT, DELETE, HEAD, OPTIONS.
-H Custom HTTP header. You can specify as many as needed by repeating the flag.
For example, -H "Accept: text/html" -H "Content-Type: application/xml" .
-t Timeout for each request in seconds. Default is 20, use 0 for infinite.
-A HTTP Accept header.
-d HTTP request body.
-D HTTP request body from file. For example, /home/user/file.txt or ./file.txt.
-T Content-type, defaults to "text/html".
-U User-Agent, defaults to version "hey/0.0.1".
-a Basic authentication, username:password.
-x HTTP Proxy address as host:port.
-h2 Enable HTTP/2.
-host HTTP Host header.
-disable-compression Disable compression.
-disable-keepalive Disable keep-alive, prevents re-use of TCP
connections between different HTTP requests.
-disable-redirects Disable following of HTTP redirects
-cpus Number of used cpu cores.
(default for current machine is 4 cores)
Examples
Make requests with default settings:
hey https://google.com
Make 1000 requests with 100 concurrent workers:
hey -n 1000 -c 100 https://google.com
Run load test for 30 seconds:
hey -z 30s https://google.com
Make POST request with custom body:
hey \
-m POST \
-d "param1=value1¶m2=value2" \
https://google.com
Add custom headers:
hey \
-H "Accept: application/json" \
-H "Authorization: Bearer token" \
https://google.com
Test with HTTP/2:
hey -h2 https://google.com
Rate limit to 10 queries per second per worker:
hey -q 10 -c 5 -z 30s https://google.com
Dump per-request metrics as CSV:
hey -n 1000 -o csv https://google.com
Development
rspec
Notes on the port
The translation is file for file, and the summary and CSV output are byte-identical to the Go original's, including two quirks that were kept rather than fixed:
- The latency distribution prints
%%where a single%was intended, and a percentile that was not reached prints as a0%row. - In the "Details" block the columns labelled fastest and slowest are the other way round for every stage except the overall figures.
-a (basic authentication) also has no effect, because hey.go replaces the
request's header set after writing the Authorization header. Setting the
header explicitly with -H "Authorization: ..." works.
Three flags differ because the runtime does:
-
-cpusis accepted and ignored; async runs on a single thread per process. -
-xtunnels via HTTP CONNECT, so the DNS and connection timings reported for a proxied run are not meaningful. - Request-write and response-wait timings are derived from when async-http
finishes writing the body and returns response headers, rather than from
net/http's per-phase trace hooks. Bodyless requests therefore report a request-write time of zero.