rakyll/hey
HTTP load generator, ApacheBench (ab) replacement
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 4mo ago
- ✓27+ active contributors
- ✓Distributed ownership (top contributor 47% of recent commits)
Show 4 more →Show less
- ✓Apache-2.0 licensed
- ✓CI configured
- ⚠Slowing — last commit 4mo ago
- ⚠No test directory detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/rakyll/hey)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/rakyll/hey on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: rakyll/hey
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/rakyll/hey shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 4mo ago
- 27+ active contributors
- Distributed ownership (top contributor 47% of recent commits)
- Apache-2.0 licensed
- CI configured
- ⚠ Slowing — last commit 4mo ago
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live rakyll/hey
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/rakyll/hey.
What it runs against: a local clone of rakyll/hey — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in rakyll/hey | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 149 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of rakyll/hey. If you don't
# have one yet, run these first:
#
# git clone https://github.com/rakyll/hey.git
# cd hey
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of rakyll/hey and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "rakyll/hey(\\.git)?\\b" \\
&& ok "origin remote is rakyll/hey" \\
|| miss "origin remote is not rakyll/hey (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "hey.go" \\
&& ok "hey.go" \\
|| miss "missing critical file: hey.go"
test -f "requester/requester.go" \\
&& ok "requester/requester.go" \\
|| miss "missing critical file: requester/requester.go"
test -f "requester/report.go" \\
&& ok "requester/report.go" \\
|| miss "missing critical file: requester/report.go"
test -f "requester/print.go" \\
&& ok "requester/print.go" \\
|| miss "missing critical file: requester/print.go"
test -f "go.mod" \\
&& ok "go.mod" \\
|| miss "missing critical file: go.mod"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 149 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~119d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/rakyll/hey"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
hey is a lightweight HTTP load generator written in Go that replaces ApacheBench (ab). It sends concurrent requests to a web server and reports detailed performance metrics including latency percentiles, throughput, and response times. It supports HTTP/1.1, HTTP/2, custom headers, basic auth, proxies, and request rate limiting via the -q QPS flag. Single-binary monolith: hey.go is the CLI entry point with flag parsing and orchestration; requester/ package (requester.go) contains the core load generation logic using goroutines; requester/print.go and requester/report.go handle metrics aggregation and output formatting (text summary or CSV via -o flag); requester/now_*.go files handle platform-specific timing (Windows vs Unix).
👥Who it's for
DevOps engineers and performance testers who need quick, portable load testing without Apache utilities; developers benchmarking web services locally during development and CI/CD pipelines.
🌱Maturity & risk
Production-ready and actively maintained. The project is well-established (renamed from 'boom' to avoid conflicts), has CI/CD via GitHub Actions (.github/workflows/go.yml), includes unit tests (hey_test.go, requester_test.go), and pre-built binaries for Linux/macOS/Windows. However, recent commit activity is not visible in the provided data, so check the git log to confirm active maintenance.
Low risk for a mature tool. Dependency surface is minimal—only golang.org/x/net and golang.org/x/text (both canonical Go libraries). Single maintainer (rakyll) is a potential bus factor. No breaking changes apparent from the stable CLI interface. Main risk is outdated x/net version (v0.48.0)—verify it handles current TLS/HTTP2 specs.
Active areas of work
No recent activity visible in the provided file list snapshot. Check GitHub Actions workflow status (.github/workflows/go.yml) and recent commits to confirm if actively developed or in maintenance-only mode.
🚀Get running
Clone, build with Make, and test: git clone https://github.com/rakyll/hey.git && cd hey && make && ./hey https://example.com. Requires Go 1.24+ (from go.mod). Alternatively, go install github.com/rakyll/hey@latest installs directly to $GOPATH/bin.
Daily commands:
make builds the binary; ./hey -h shows all flags. Example: ./hey -n 1000 -c 100 -z 30s https://example.com runs 1000 requests with 100 concurrent workers over 30 seconds. Output defaults to summary text; use -o csv for CSV format.
🗺️Map of the codebase
hey.go— Main entry point and CLI argument parsing; defines the load testing workflow and orchestrates the requester packagerequester/requester.go— Core load testing engine handling concurrent HTTP requests, result aggregation, and latency trackingrequester/report.go— Statistics calculation and formatting; generates the final performance report printed to usersrequester/print.go— Output formatting for results; handles histogram, summary stats, and various report display modesgo.mod— Dependency management; declares golang.org/x/net as the only external dependency for HTTP/2 support
🧩Components & responsibilities
- hey.go (CLI) (flag package, fmt, io) — Parse command-line arguments, validate inputs, instantiate requester, display results to user
- Failure mode: Invalid arguments → panic; network failure → errors propagated from requester
- requester.go (Load Engine) (net/http, sync, time, goroutines) — Spawn worker goroutines, dispatch HTTP requests in concurrent pool, collect responses, measure latencies, aggregate errors
- Failure mode: Connection errors → recorded in error count; timeout → counted as failed request; goroutine panic → may crash test
- report.go (Statistics) (math, sort, time) — Calculate percentiles, min/max/mean latencies, histogram buckets, response code distribution
- Failure mode: Empty result set → division by zero if not guarded; outliers → can skew percentile calculation
- print.go (Output Formatting) (fmt, strings) — Format histogram, summary stats, and detailed results for console output
- Failure mode: Malformed data → garbled output; large histograms → terminal overflow
- now_windows.go / now_other.go (Platform Timing) (syscall, time, cgo (Windows)) — Provide nanosecond-precision time measurement for latency tracking on Windows and POSIX systems respectively
- Failure mode: Timer drift → inaccurate latency measurements; cgo failure on Windows → panic
🔀Data flow
User shell→hey.go— Command-line arguments (URL, -n, -c, -q, -H, etc.)hey.go→requester.go— Config struct or parameters: concurrency, request count, rate limit, headers, method, bodyrequester.go→HTTP endpoint— HTTP requests (GET/POST/PUT/DELETE) with optional headers and bodyHTTP endpoint→requester.go— HTTP responses (status code, headers, body); timing samplesrequester.go→report.go— Result aggregates: latencies, status codes, error counts, response bytesreport.go→print.go— Computed statistics: percentiles, histogram, throughput, error distributionprint.go→User stdout— Formatted text report: summary table, histogram, errors
🛠️How to make changes
Add a new command-line flag for load testing
- Define the flag variable at the top of hey.go using the flag package (
hey.go) - Add the flag with flag.StringVar(), flag.IntVar(), or flag.BoolVar() (
hey.go) - Pass the flag value to the requester when calling requester.Run() or related functions (
hey.go) - Update the requester package to accept and use the new parameter (
requester/requester.go)
Add a new output metric or report format
- Calculate or aggregate the metric in requester/report.go by modifying the Results struct or calculation methods (
requester/report.go) - Format the metric for display in the print functions (
requester/print.go) - Call the new print function from hey.go after the load test completes (
hey.go)
Modify request behavior or headers
- Add a new flag or config option to hey.go to capture user input (
hey.go) - Pass the option to the requester, likely via a config struct or parameter to NewRequester() (
requester/requester.go) - In the worker loop within requester.go, modify the http.Request or client configuration based on the new option before sending requests (
requester/requester.go)
🔧Why these technologies
- Go — Lightweight concurrency with goroutines enables efficient worker pools; fast compilation and single binary distribution; minimal runtime overhead for high-throughput load testing
- golang.org/x/net — Provides HTTP/2 support and advanced networking primitives required for modern web service benchmarking
- flag package — Simple, built-in CLI argument parsing; no external dependency; sufficient for straightforward command-line tool use case
- goroutines — Native concurrent execution model; allows 50+ concurrent workers with minimal memory overhead, enabling high-concurrency load testing
⚖️Trade-offs already made
-
Single external dependency (golang.org/x/net) vs full stdlib
- Why: HTTP/2 is essential for testing modern web services but not available in stdlib; minimal dependency surface reduces maintenance burden
- Consequence: Users must have golang.org/x/net installed; slightly larger binary, but standardized and actively maintained
-
Synchronous result collection vs streaming results during test
- Why: Simpler implementation; accurate aggregate statistics; easier to ensure all requests complete before reporting
- Consequence: Cannot display partial results until test completes; high-concurrency tests may require significant memory to buffer all responses
-
No custom connection pooling—rely on http.Client defaults
- Why: Reduces code complexity; http.Client already implements connection reuse and keepalive
- Consequence: Less fine-grained control over connection lifecycle; users cannot customize pool size directly
-
OS-specific timer implementations (now_windows.go vs now_other.go)
- Why: Windows and POSIX systems have different high-resolution timing APIs; ensures nanosecond precision on both
- Consequence: Build-time platform detection required; slightly larger compiled binary
🚫Non-goals (don't propose these)
- Does not provide interactive/real-time result streaming during load test execution
- Does not support authentication mechanisms (basic auth, OAuth, mutual TLS client certs)
- Does not offer distributed load generation across multiple machines
- Does not include GUI or web dashboard for result visualization
- Does not generate traces or detailed request/response payloads for debugging
⚠️Anti-patterns to avoid
- Unbounded result buffering (Medium) —
requester/requester.go: All response bodies and metadata are held in memory during test execution; for millions of requests, this can exhaust heap memory - No graceful shutdown on error —
hey.go: If a worker goroutine panics, the test may hang or produce incomplete results; panic recovery is not implemented
🪤Traps & gotchas
Timing precision: requester/now_other.go vs now_windows.go—Windows may have coarser timing granularity; -cpus flag respects GOMAXPROCS but doesn't validate. Duration semantics: -z (duration mode) ignores -n; if both set, duration takes precedence. Keep-alive impact: -disable-keepalive multiplies connection overhead, skewing results for short-lived requests. Rate limiting: -q is per-worker QPS, not global (total QPS = -q * -c). HTTP/2: requires -h2 flag; default is HTTP/1.1 only.
🏗️Architecture
💡Concepts to learn
- Goroutine-based worker pool — hey's concurrency model in requester.go uses N goroutines (via -c flag) to issue requests in parallel; understanding WaitGroup synchronization and channel-less coordination is key to grasping load distribution
- Percentile latency quantiles — report.go calculates p50, p90, p99 latencies by sorting response times; critical for understanding tail latency vs. average, which is essential for realistic performance testing
- Token bucket rate limiting — The -q (QPS) flag implements rate limiting per worker; understanding token bucket prevents overwhelming the target server and enables controlled, reproducible load curves
- HTTP/2 Server Push and multiplexing — hey's -h2 flag leverages golang.org/x/net HTTP/2 client for multiplexed streams; key difference from HTTP/1.1 is connection reuse semantics, affecting latency interpretation
- Keep-Alive and TCP connection pooling — The -disable-keepalive flag shows importance of connection reuse; toggling it dramatically affects throughput, revealing whether bottleneck is TLS handshakes or application logic
- Atomic operation and wall-clock time measurement — requester/now_other.go and now_windows.go abstract timing via runtime.nanotime(); accurate measurement requires understanding goroutine scheduling overhead and platform-specific timer precision
- CSV export and structured metrics — The -o csv flag enables integration with monitoring/graphing tools; understanding print.go's CSV generation shows how to adapt hey output for downstream analysis pipelines
🔗Related repos
apache/ab— The original ApacheBench tool that hey explicitly replaces; hey-specific advantage is HTTP/2 and easier binary distributiontsenart/vegeta— Alternative Go-based load testing tool with piping/composition focus; hey is simpler and single-shot whereas vegeta is more pipeline-orientedgrpc-ecosystem/ghz— gRPC-specific load generator; complementary to hey which targets HTTP; both written in Go for portabilitygolang/go— Upstream project; hey depends on Go stdlib and x/net; understanding Go's net/http internals helps optimize requester.gorakyll/statik— Companion tool by same maintainer (rakyll) for embedding static files; used in hey documentation/example setup workflows
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive requester_test.go test coverage for HTTP/2 and timeout scenarios
The repo supports HTTP2 endpoints (mentioned in README) but requester/requester_test.go likely lacks tests for HTTP/2 specific behavior, connection timeouts, and edge cases in the core request logic. This is critical since requester/requester.go is the heart of the load generator.
- [ ] Examine requester/requester_test.go to identify missing test cases for HTTP/2 requests
- [ ] Add test cases for connection timeouts, read timeouts, and context cancellation
- [ ] Add test cases for various HTTP response codes and malformed responses
- [ ] Test concurrent request handling with different concurrency levels to ensure thread safety
- [ ] Run 'go test -cover ./requester' to verify coverage improvement
Add platform-specific tests for requester/now_windows.go and requester/now_other.go
The codebase has platform-specific timing implementations (Windows vs Unix), but there are no corresponding test files (now_windows_test.go, now_other_test.go). These high-resolution timer abstractions are critical for accurate load testing results and need validation.
- [ ] Create requester/now_windows_test.go with tests for Windows high-performance counter timing
- [ ] Create requester/now_other_test.go with tests for Unix-based timing (using time.Now)
- [ ] Verify timer accuracy and monotonicity across multiple calls
- [ ] Test edge cases like system clock adjustments or timer wraparound
- [ ] Document expected behavior differences between Windows and Unix implementations
Add GitHub Actions workflow for cross-platform binary builds and releases
The repo has .travis.yml (legacy) and .github/workflows/go.yml exists but the README hardcodes binary download URLs from storage.googleapis.com. Add a modern GitHub Actions workflow (.github/workflows/build-release.yml) to auto-build and release binaries for Linux/macOS/Windows (amd64/arm64), eliminating manual release management and enabling consistent versioning.
- [ ] Review .github/workflows/go.yml to understand existing CI setup
- [ ] Create .github/workflows/build-release.yml with matrix strategy for [linux, darwin, windows] × [amd64, arm64]
- [ ] Use Go's cross-compilation to build binaries for each platform
- [ ] Integrate with GitHub Releases API to auto-publish binaries on git tags
- [ ] Update README.md to link to GitHub Releases instead of hardcoded storage.googleapis.com URLs
- [ ] Test workflow by creating a test release tag
🌿Good first issues
- Add support for custom TLS client certificates via new flags -cert and -key (similar to curl's --cert/--key); update requester.go to load certs into tls.Config and modify hey.go flag parsing. Affects ~50 lines.
- Implement -H flag deduplication and value validation; currently allows duplicate headers without warning. Add header parsing tests in hey_test.go and validation logic in hey.go's flag handling.
- Add JSON output mode (-o json) to complement existing text/csv formats; extend requester/print.go with a PrintJSON function that marshals the Stats struct. Requires ~30 lines and no external dependencies.
⭐Top contributors
Click to expand
Top contributors
- @rakyll — 47 commits
- @jronak — 9 commits
- [@Jaana Burcu Dogan](https://github.com/Jaana Burcu Dogan) — 9 commits
- @mdakin — 5 commits
- @erwinvaneyk — 3 commits
📝Recent commits
Click to expand
Recent commits
5626f79— Remove the reference to the old fork (rakyll)a3f5859— Add some examples to README (rakyll)e64ec7a— Add upload target to the Makefile and update the links (rakyll)6731f19— Remove vendor directory (rakyll)dc0126a— Use the standard library request cloner (rakyll)2a27143— Bump the go version (rakyll)0dce5de— Remove custom min function (rakyll)a730114— Fix the broken fmt.Fprintf calls (rakyll)1bdb499— Remove obsolete build status badge from Travis (rakyll)289937f— Fix go version format (rakyll)
🔒Security observations
The rakyll/hey codebase has moderate security concerns, primarily related to outdated build infrastructure and dependency management. The use of Go 1.15 in Docker is the most critical issue, as it is severely outdated and unsupported. Additionally, the go.mod specifies a non-existent Go version (1.24.0), indicating configuration errors. The application itself (a simple HTTP load generator) has a relatively small attack surface with no apparent injection vulnerabilities or hardcoded secrets. However, the build and dependency management practices need significant improvement to meet security standards. Address the Go version mismatches immediately and implement regular dependency scanning in the CI/CD pipeline.
- High · Outdated Go Version in Dockerfile —
Dockerfile, line 1. The Dockerfile uses golang:1.15 as the base image for the build stage, which was released in August 2020 and is no longer supported. This version contains multiple known security vulnerabilities and will not receive security patches. The go.mod file specifies go 1.24.0, creating a version mismatch. Fix: Update the base image to a recent and supported Go version (1.22 or later). Ensure consistency between the Dockerfile Go version and the go.mod version requirement. - High · Dependency Version Mismatch —
go.mod, line 4. The go.mod file specifies 'go 1.24.0', which does not exist as of the current date. This indicates either a typo or misconfiguration. Additionally, the golang:1.15 Docker image cannot compile against go 1.24.0 requirements, creating a build compatibility issue. Fix: Correct the Go version to a real, supported release (e.g., 1.22.0 or 1.23.0). Verify that the Dockerfile build stage uses a compatible Go version. - Medium · Vulnerable Dependency: golang.org/x/net —
go.mod, line 3. The pinned version of golang.org/x/net v0.48.0 may contain known vulnerabilities. This is a critical networking library used for HTTP operations. Regular security audits are needed. Fix: Run 'go list -json -m all | nancy' or 'go vulnerabilities ./...' to check for CVEs. Update to the latest patched version of golang.org/x/net and perform security testing. - Medium · Missing Security Updates in go.sum —
go.sum (missing from analysis). The go.sum file is not provided for review, making it impossible to verify the integrity of downloaded dependencies and detect supply chain attacks or tampered packages. Fix: Ensure go.sum is committed to version control and reviewed. Use 'go mod verify' to validate dependency integrity before deployment. - Low · Missing HEALTHCHECK in Dockerfile —
Dockerfile, final stage. The final Docker image does not include a HEALTHCHECK instruction, which could impede container orchestration health monitoring in production environments. Fix: Add a HEALTHCHECK instruction to verify the application is running correctly, e.g., HEALTHCHECK CMD hey http://localhost:8080 || exit 1 - Low · Unpinned Indirect Dependencies —
go.mod, line 5. The golang.org/x/text v0.33.0 is marked as an indirect dependency. While current versions appear reasonable, indirect dependencies should be monitored for security updates. Fix: Regularly run 'go get -u' and 'go mod tidy' to keep all dependencies current. Consider using automated dependency scanning tools in CI/CD.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.