Skip to content

open-source CLI · load testing & performance investigation

Will your system
hold up?

Barrage load tests HTTP, databases, and Redis on the same clock so you can see what breaks, what's slow, and where the bottleneck actually is — not just that something got slow.

sh
$ go install github.com/codetesla51/barrage/cmd/barrage@latest
  • One static binary
  • Runs in your terminal
  • No cloud, no account
barrage run — simulated

See it run

Watch three layers
take fire on one clock

HTTP, DB, and Redis requests stream side by side. When a storage runner's P99 crosses its threshold in a bucket where HTTP stayed quiet, Barrage flags it ▲ db-only — a masked spike.

Simulation for illustration; the output above shows real runs.

barrage run -c config.yaml
$ barrage run -c config.yaml

barrage v0.3.5
duration 15s · bucket 1s · concurrency 10 · ramp 3s
rates    http 10/s · db 5/s · redis 20/s

RUNNER  REQUESTS  SUCCESS  RATE    MEAN     P50      P95      P99      MAX
http    135       100.0%   9.5/s   927µs    509µs    2.5ms    4.4ms    6.6ms
db      67        100.0%   4.5/s   12.9ms   5.5ms    69.0ms   136.2ms  136.2ms
redis   269       100.0%   17.9/s  797µs    396µs    2.3ms    3.5ms    10.6ms

correlated spikes
TIME      RUNNER  HTTP_P99  STORAGE_P99   NOTE
20:52:22  db      <100ms    136.2ms       db-only
DB P99 spiked past its threshold while HTTP stayed under budget — a storage bottleneck the API hasn't absorbed yet.
Barrage HTML report: latency timeline with HTTP, Postgres and Redis P99 plotted on one shared time axis
One timeline, three layers. From a real 3-minute run against Gin + Postgres + Redis.

The core idea

Most tools tell you it got slow.
Barrage helps you find out why.

A typical load tester reports one latency curve for your endpoint. When it degrades, you're left guessing: app? database? cache? Barrage drives all three layers in one process and records every layer's latencies into the same time buckets — so they can be compared directly.

Same bucket size, same unix timestamps, same run. That alignment is what makes everything else — correlation, verdicts, regression diffs — possible.

Diagnosis

Where does it break?

Every second of a run, Barrage checks each storage layer's P99 against its threshold and reads the result against what HTTP was doing at the same moment:

Correlated spike

The database spiked and HTTP crossed its budget in the same bucket. The latency jumped together — the bottleneck backs up into your users.

TIME      RUNNER  HTTP_P99  STORAGE_P99  VERDICT
20:52:31  db      142ms     890ms        DB → HTTP

Masked spike

The database spiked but HTTP stayed under budget. The bottleneck is real, just not visible to users yet. Most tools never show you this.

TIME      RUNNER  HTTP_P99  STORAGE_P99  NOTE
20:52:22  db      <100ms    136.2ms      db-only
  • Application slow? An endpoint that's slow while DB and Redis stay idle is an application problem — Barrage leaves those buckets unflagged.
  • Database slow? Weighted read/write query mixes against Postgres, MySQL, or SQLite expose missing indexes, pool limits, and bad plans.
  • Cache slow? Redis runs as a first-class runner, not an afterthought.

Scenario mode

Not just one endpoint, hit harder

Real users don't GET a single URL in a loop. Scenario mode models sequential journeys: each virtual user logs in, carries the token forward, browses, checks out — looping until the run ends.

per virtual user · loops until the run ends

config.scenario.yaml
scenarios:
  - name: login-flow
    weight: 1
    steps:
      - method: POST
        url: http://localhost:8080/api/login
        body: '{"user":"alice"}'
        extract:
          token: $.token
      - method: GET
        url: http://localhost:8080/api/me
        headers:
          Authorization: Bearer {{token}}
      - method: GET
        url: http://localhost:8080/api/checkout?token={{token}}
Traffic shaped by weights Each virtual user picks its scenario once at launch, weighted. 70% browsing / 30% checkout means exactly that — not "roughly".
State that belongs to each VU extract pulls $.token or $.user.id out of a response and stores it on that virtual user alone.
Carry values forward {{var}} resolves in later URLs, bodies, and headers. A variable that never resolved stays visible as {{var}} — misconfigurations can't hide.
Journeys land on the same clock Scenario buckets share timestamps with DB and Redis, so a slow checkout correlates against your database out of the box.

Realistic load

Load that looks like usage, not like a benchmark

Don't just hit your endpoint harder. Reproduce how the system is actually used:

Rates that ramp

Rates grow linearly from zero over a configurable window, instead of full force from the first request.

Mixes, not loops

One query is picked per request from a weighted list, so 80% reads / 20% writes means exactly that.

Read/write routing

Each DB query declares read or write; routing is authoritative, not guessed from SQL text.

Real parallelism

Requests go through worker pools bounded by concurrency. If the target can't keep up, throughput settles below target — on purpose.

Regression testing

A regression gate, not just a load test

Run a baseline, change code or infrastructure, run again — then diff the two JSON exports. A runner is flagged when its current P99 crosses the latency budget while its baseline was under it.

CI
$ barrage compare --baseline base.json --current new.json --fail-on 100ms

comparing base.json -> new.json (fail-on 100ms)
RUNNER  BASELINE_P99  CURRENT_P99  CHANGE  VERDICT
DB      80ms          100ms        +25%    ok
HTTP    30ms          70ms         +133%   REGRESSION
Redis   20ms          22ms         +10%    ok

error: regression detected against --fail-on budget
$ echo $?
1
Crossings only, not noise A runner is flagged when it crosses the budget from below. Already-slow runners aren't re-flagged every run.
Spikes are diffed too Every correlated spike is matched across the two runs and classified: new, fixed, worsened, or improved.
The exit code is the gate Any regression exits non-zero, so the pipeline fails before the regression ships:
.github/workflows/perf.yml
- name: perf regression gate
  run: |
    barrage run --no-report --json current.json
    barrage compare --baseline base.json --current current.json --fail-on 100ms

Web UI

Prefer a browser? Barrage ships one.

barrage web starts a local web UI on localhost:7676: build configs with forms instead of YAML, edit and preview config files, launch runs, and read the story-style report — all without leaving the browser.

localhost:7676
Barrage web UI running locally on localhost:7676: config builder with YAML preview
Build configs without YAML HTTP targets, weighted query mixes, Redis workloads, and multi-scenario journeys — all through plain forms with live YAML preview.
Launch runs, read stories Start a run from the browser and read the report as a narrative — spikes explained in plain language, not just plotted.
Served by the same binary No extra deployment, no daemon, no database of its own. One command turns the CLI into a local web app.

Scope

Focused on purpose

Barrage doesn't try to be everything. It does one job well:

Load test the system you own.

Find the bottleneck.

Understand the result.

narrow on purpose — that's the point

What it is

  • One Go binary, entirely CLI-driven
  • Postgres, MySQL, and SQLite built in — any database/sql driver can be linked
  • Spike correlation with bottleneck verdicts
  • Sequential user-journey scenarios
  • Self-contained HTML reports, JSON exports, CI gating

What it isn't

  • A browser or E2E testing framework
  • A WebSocket / streaming-traffic generator
  • A distributed multi-region load platform
  • An observability or APM replacement

Install

Start investigating

One command to install, one to run. The report lands next to you either way.

install
go install github.com/codetesla51/barrage/cmd/barrage@latest
requires Go 1.25+
first run
barrage run                # writes report.html
barrage run -o             # ...and opens it
barrage run --no-report --json results.json
barrage compare --baseline base.json \
point config.yaml at your targets — config guide