README
¶
gg — Gopher Glide
A lightweight, terminal-based HTTP load testing tool built in Go. gg reads your requests from a standard .http file, runs them through a multi-stage load plan, and delivers a live terminal dashboard of throughput, latency, and errors — no agents, no servers, no config sprawl.
Features
.httpfile support — define requests (with headers and bodies) using the familiar.http/ REST Client format; pointggat your existing file and go- Multi-stage load engine — define any number of stages; the engine linearly interpolates (LERP) RPS between stages automatically
- Ramp Up — smoothly increase load to a target RPS
- Sustain — hold a fixed RPS for a duration
- Spike — instant step jump (
duration: 0s) with no interpolation - Ramp Down — smoothly reduce load back to zero (cool-down)
- Named stages — optional
name:field used in the TUI timeline label
- RPS-based scheduler — drift-free ticker dispatches requests at the configured rate; never accumulates lag across second boundaries
- Concurrent worker pool — powered by
errgroup+ channels; worker count scales to peak RPS across all stages; minimal memory footprint - Jitter — configurable
±N%organic noise on the RPS ticker so load patterns look realistic rather than mechanical - Time scale —
time_scalecompresses or stretches the stage clock for fast local iteration (e.g.time_scale: 10runs a 10-minute plan in 60 seconds) - Director Mode — live RPS bias while a run is in progress:
↑/↓keys adjust the running RPS by ±5 in real-time- Bias is applied on top of the LERP'd stage target and shown in the TUI
- Live TUI dashboard — rendered with Bubble Tea & Lip Gloss:
- Status header — version, run state (Running / Stopped), uptime
- Three stat panels — Configuration, Throughput, Latency
- Stage timeline graph — visual representation of all stages with a live cursor showing current position and achieved RPS marker per block
- Scrollable call log — toggle between all calls and errors only with
f
- Stamped binaries — version, git commit, and build date embedded at compile time via
-ldflags - Cross-platform — pre-built binaries for Linux (amd64/arm64), macOS (amd64/arm64), and Windows (amd64)
Quick Start — pre-built binary
1. Download the latest release
Go to the Releases page and download the archive for your platform:
| Platform | Archive |
|---|---|
| macOS (Apple Silicon) | gg-<version>-darwin-arm64.tar.gz |
| Linux (x86-64) | gg-<version>-linux-amd64.tar.gz |
| Windows (x86-64) | gg-<version>-windows-amd64.zip |
2. Extract
# macOS / Linux
tar -xzf gg-<version>-darwin-arm64.tar.gz
cd gg-<version>-darwin-arm64
Each archive contains:
gg ← the binary
config.yaml ← configuration file
request.http ← sample HTTP request file
3. macOS — remove quarantine (first run only)
macOS Gatekeeper quarantines unsigned binaries downloaded from the internet:
xattr -dr com.apple.quarantine ./gg
4. Configure
Edit config.yaml:
config:
httpFile: "request.http" # .http file to load (same directory as config.yaml)
jitter: 0.1 # ±10% organic noise on the RPS ticker (0 = off)
time_scale: 1.0 # 1.0 = real-time; 2.0 = run 2× faster
stages:
- duration: 10s
target_rps: 50 # ramp 0 → 50 RPS over 10s
- duration: 30s
target_rps: 50 # sustain at 50 RPS for 30s
- duration: 10s
target_rps: 0 # ramp down to 0 (cool-down)
Edit request.http with your target endpoints (see .http file format below).
5. Run
./gg config.yaml
Build from source
Prerequisites
- Go 1.21+
makegit
Clone
git clone https://github.com/shyam-s00/gopher-glide.git
cd gopher-glide
Build for the current platform
make build
Produces a ./gg binary in the project root.
Run directly
make run
Builds and immediately runs with config.yaml.
Cross-compile all platforms
make build-all
Outputs binaries to dist/:
dist/gg-linux-amd64
dist/gg-darwin-arm64
dist/gg-windows-amd64.exe
Package release archives
make release
Produces versioned .tar.gz (Unix) and .zip (Windows) archives in dist/, each bundling the binary + config.yaml + request.http.
Other make targets
| Target | Description |
|---|---|
make build |
Compile for current OS/ARCH |
make build-all |
Cross-compile all platforms into dist/ |
make release |
build-all + package archives |
make run |
Build + run with config.yaml |
make clean |
Remove dist/ and local binary |
make version |
Print version, git commit, and build date |
.http file format
gg uses the standard .http / REST Client file format. Requests are separated by ###.
### Simple GET
GET https://httpbin.org/get
Accept: application/json
X-Request-ID: gg-001
### GET with query param
GET https://httpbin.org/get?userId=1
Accept: application/json
Cache-Control: no-cache
### POST with JSON body
POST https://httpbin.org/post
Content-Type: application/json
Accept: application/json
{
"title": "hello",
"userId": 1
}
- Supported methods:
GET,POST, and any valid HTTP verb - Headers are placed directly after the request line
- Body (for POST/PUT/PATCH) follows a blank line after the headers
###separates requests; the text after###is an optional label- All requests are dispatched in round-robin order across stages
Configuration reference
config:
httpFile: "request.http" # .http file to load (relative to config.yaml)
jitter: 0.1 # ±10% noise on the RPS ticker; 0 disables (default: 0)
time_scale: 1.0 # stage clock multiplier; 2.0 = run 2× faster (default: 1.0)
prometheus: false # (planned) expose /metrics endpoint
breaker_threshold_pct: 20.0 # (planned) circuit-breaker error-rate threshold %
stages:
- name: "Warm Up" # optional; inferred from shape if omitted
duration: 10s # how long this stage runs
target_rps: 50 # RPS target at the end of this stage
- duration: 30s
target_rps: 50 # same as previous → Sustain
- duration: 0s
target_rps: 200 # duration 0 → instant Spike (no interpolation)
- duration: 10s
target_rps: 200 # hold after spike → Sustain
- duration: 10s
target_rps: 0 # ramp down → cool-down
Stage inference rules
When name is omitted, gg infers the display label from the stage shape:
| Shape | Inferred label |
|---|---|
target_rps higher than previous |
Ramp Up |
target_rps same as previous |
Sustain |
target_rps lower than previous |
Ramp Down |
duration: 0s |
Spike |
target_rps: 0 |
Ramp Down |
jitter
Adds symmetric ±N% noise to the inter-request interval so traffic looks organic. For example jitter: 0.1 varies each interval by ±10%. The average rate is unchanged.
time_scale
Compresses the stage clock by a multiplier. Useful for local testing:
time_scale: 10 # a 10-minute plan finishes in 1 minute
TUI Dashboard
gg — Gopher Glide v0.1.0 ● RUNNING ⏱ 00:42
╭─ Configuration ──────╮ ╭─ Throughput ─────────╮ ╭─ Latency ────────────╮
│ Target RPS 50 │ │ RPS 48.2 │ │ Avg 142.3 ms │
│ Duration 70s │ │ Completed 1 446 │ │ Min 88.1 ms │
│ Uptime 00:42 │ │ Errors 12 │ │ Max 310.5 ms │
│ Active VPUs 18 │ │ Error Rate 0.8% │ │ P50 138.0 ms │
│ │ │ Jitter ±10% │ │ P95 278.4 ms │
│ │ │ │ │ P99 305.2 ms │
╰──────────────────────╯ ╰──────────────────────╯ ╰──────────────────────╯
╭─ Stage Timeline ──────────────────────────────────────────────────────────╮
│ 500 ┤ ▓▓▓▓▓▓▓▓▓ │
│ │ ░░░░░░░░▓▓▓▓▓▓▓▓▓░░░░░░ │
│ │ ░░░░░░░░░░░░░░░ ░░░░░░░░ │
│ 0 ┤░░░░░░░ ░░░░░░░░░ │
│ [1/5] Ramp Up • stage 0:10 / 0:10 • total 0:42 / 1:10 │
╰───────────────────────────────────────────────────────────────────────────╯
[↑] +5 rps [↓] -5 rps [f] logs (FAILURES ONLY) [q] quit BIAS +10 RPS
╭─ Call Log ────────────────────────────────────────────────────────────────╮
│ …scrollable call log… │
╰───────────────────────────────────────────────────────────────────────────╯
Keybindings
| Key | Action |
|---|---|
↑ |
Increase live RPS by +5 (Director Mode bias) |
↓ |
Decrease live RPS by -5 (Director Mode bias) |
f |
Toggle call log between all calls and errors only |
q / Ctrl+C |
Quit |
Multi-stage load profiles
gg uses implicit ramping — each stage defines a target_rps and duration. The engine automatically interpolates (LERP) from the previous stage's rate to the new target. You never specify the "ramp type" explicitly; it is inferred from the numbers.
stages:
# Ramp Up: 0 → 100 RPS over 30s
- duration: 30s
target_rps: 100
# Sustain: hold 100 RPS for 1 minute
- duration: 1m
target_rps: 100
# Spike: jump instantly to 500 RPS
- duration: 0s
target_rps: 500
# Sustain spike for 15s
- duration: 15s
target_rps: 500
# Ramp Down: 500 → 0 RPS over 30s
- duration: 30s
target_rps: 0
The TUI timeline visualises the entire plan before and during the run, with a live cursor showing current position and an RPS marker per block showing the actual throughput achieved.
Director Mode
While a run is in progress, use ↑ / ↓ to apply a live RPS bias on top of the configured stage target. The bias accumulates (e.g. three ↑ presses = +15 RPS) and is shown in the TUI and reflected in GetMetrics(). The bias is applied on top of the LERP'd value — the stage plan continues to run unaffected.
Project structure
.
├── cmd/gg/main.go entry point
├── config.yaml default configuration
├── request.http sample request file
├── internal/
│ ├── config/ YAML config parser + validation
│ ├── httpreader/ .http file parser
│ ├── engine/ load engine — LERP scheduler + worker pool + metrics
│ ├── tui/ Bubble Tea TUI — dashboard, timeline, log panel
│ └── version/ build-time version info
└── Makefile
Roadmap
- VPU mode — fixed virtual users instead of fixed RPS
- Circuit breaker — auto-stop on configurable error-rate threshold
- Prometheus
/metricsendpoint - HTML / JSON result export