surgesurge

Feature map

What surge does, and where to read more. Written to be checked against whatever you use today.

Writing a test

surge
Scenario in codeGo — s.User(...), u.Task(...), compiled, any Go library available
Scenario without codeYAML — flow, extract, check, feeders, templating
Generate from a browser recordingsurge import har session.har — with automatic correlation
Generate from an API descriptionsurge import openapi spec.yaml
User populations with weightss.User("buyer", 3)
Exactly N of a populationu.FixedCount(2)
Weighted tasksu.Task("browse", 4, fn)
Sequential journeysu.Sequential()
Task tags, include/excludeu.TaggedTask(...), -tags smoke -exclude-tags slow
Per-user setup / cleanupu.OnStart() / u.OnStop()
Global setup / teardowns.Setup() / s.Teardown()
Groups (nested, timed)c.Group("checkout", fn)
Think time.Wait(min,max) · Constant · Pacing · Throughput · Poisson
Parallel requestsc.Batch(surge.Req(...), ...)
Per-user cookie jars.CookieJar()
Per-request timeoutsurge.Timeout(d)
Response validationsurge.Validate(fn) — a 200 that is wrong is a failure
Name from the responsesurge.NameFrom(fn)
Structured logging, cappedc.Log(), c.Infof(), s.Logging(surge.LogDebug)

Load models

surge
Fixed virtual users-vus N · s.Closed(n, dur)
Arrival rate (open model)-rate R · s.Open(r, dur)
Ramping stagesstages: · s.ClosedStages(...) / s.OpenStages(...)
Fixed iteration count-iterations N · s.Iterations(n, vus)
Automatic capacity searchsurge discover — climbs, bisects, verifies
Warmup excluded from stats-warmup 30s
Graceful stopgrace_stop, default 5s
Arrivals that could not be appliedcounted as dropped, never hidden

Correlation and data

surge
Extract from JSONextract: { token: { json: data.token } }
Extract from a header{ header: X-Request-Id }
Extract with a regex{ regex: 'name="csrf" value="([^"]+)"' }
CSV feederscircular · random · once
Templating${var} ${env.X} ${__VU} ${uuid()} ${randint(a,b)} ${randstr(n)} ${now()} ${now_iso()}
Per-VU statec.Set() / c.Str() / c.Value()

Checks and gating

surge
Status / body / JSON / latency checkscheck: block, c.Check(), surge.Expect(), surge.MaxDuration()
Thresholdsp50/p90/p95/p99/avg/min/max, error_rate, check_rate, rps, count, scoped per request
Exit code0 pass · 1 threshold failed · 2 setup error
Regression gate vs baselinesurge compare — median ±MAD, refuses to gate on too few runs
Trend across releasessurge trend — the slope no single comparison can see
Auto-stop-abort-on "error_rate > 50% for 15s" — stop a test that has already failed
Custom gatings.OnTestStop(func(*Summary))

Protocols

surge
HTTP/1.1, HTTP/2native, keep-alive, redirects, proxy, TLS options
WebSocketin YAML: ws:/as: to dial, on:+send/receive for messages, repeat: for a stream, checks and extracts on the message. In Go: c.Dial(), Send, Receive, Request, Listen
gRPCnative — grpcx.Dial, grpcx.Call, grpcx.Stream, status-code classification
Databasesdbx — any database/sql driver, errors grouped by cause, pool queueing reported
SAP OData / Fiorisapx — CSRF handshake, expiry retry, $batch with changesets
Mainframe TN3270tn3270 — telnet negotiation, EBCDIC, screen buffer, transaction timing
Anything elsec.Time(name, fn) — queue, TCP, an SDK, your own client

Metrics and reports

surge
Latency histogramlog-linear, ~3% error, fixed memory, mergeable across nodes
Percentilesp50/p90/p95/p99 by default, -percentiles 99.9,99.99 for more
Full distributionthe ladder to p99.9 and max, as columns on the request row — one table, shown on request
Per-second percentilesp50 and p95 on the timeline, not just the mean — blank for a merged run, because percentiles cannot be averaged
Custom metricsCounter · Gauge · Trend · Rate
Error classificationtimeout · refused · reset · dns · HTTP n
Why it passed or faileda verdict that names the breached threshold and the actual number — console, report, summary.json, webhook
Reasons to distrust the resulta run cut short, arrivals never applied, a saturated generator, a feeder too small, nothing asserted at all — stated, not implied
Failure evidenceone real example per distinct failure: role, URL, status, failed checks, and what the server replied
Bounded by designfailures grouped by signature, bodies capped at 512 bytes, so a million failures cost what fifty do
Private by requestoutput.no_failure_bodies keeps the replies out
Checks by namejson status = confirmed — 113 of 799 failed, not 97.1%
Exceptionsa task that raised, with its message and stack, grouped
Timing breakdown-timings — blocked, DNS, connect, TLS, send, TTFB, receive, with the dominant phase explained
Server-side correlation-monitor — scrape the target's own metrics and rank what moved with the latency
Consolelive line + final table
JSON summaryfull, machine-readable
Per-second CSVsec,reqs,errs,avg_ms,p50_ms,p95_ms,max_ms,vus
Per-request CSV-log-requests — for outliers and forensics
Failures CSV-failures — one row per distinct failure, with its example
HTML reportone self-contained file, light and dark with a toggle, no CDN
Report chartsthroughput with failures · p50/p95/max · virtual users
Report tablessortable, filterable, paged — a run that failed ten thousand ways stays readable
Test kind`type: smoke\load\stress\spike\soak\capacity` — inferred from the plan when absent, and the report says when the thresholds do not suit the kind
Setup / teardownsetup: and teardown: run around the load, in the scenario's directory, once on the coordinator. Teardown runs on a failed run, a breached threshold and a Ctrl-C
Preflightsurge preflight — DNS, TCP, TLS expiry, redirects, clock skew, feeder rows, monitor endpoints, every agent's health and build, this machine's fd limit. One request each, no load, GO or NO-GO with an exit code
User actionsgroup: on consecutive steps — one action timed end to end, think time excluded, in its own table and never added to the totals
Who did the workthe mix that ran against the mix that was asked for — a weight decides how many users pick a role, not how fast they go round
Run logtimed events you can line up against the charts: warnings, generator saturation, abort rules firing, with the node in a fleet
Report tabspinned to the top: overview · over time · actions · requests · failures · checks · exceptions · agents · log · server · run · data; a failing run opens on the failures, a passing one on the overview
Users and workersin the header, measured not planned: the peak virtual users reached and the machines that produced them
Per-node breakdowneach generator's users, share, iterations, its own latency and whether it was healthy while producing it
The plan, recordedtarget, ramp, warmup, shard, populations, feeders, thresholds — what the number was measured under
Control UIsurge web — pick a scenario, override users/duration/target, Start, watch, Stop, open the report. Binds loopback; anywhere else needs -token; scenarios that run shell commands need -allow-exec
Live web dashboard-web :8088, read-only, attached to a running test
Prometheus-prometheus text exposition → Pushgateway → Grafana
InfluxDB-influx line protocol
Webhook-webhook URL posts the summary
Automatic analysis-analyze — knee detection, bottleneck, error meaning, trustworthiness
Raw, mergeable form-snapshot — histogram buckets, combined by surge merge

Running it

surge
One binary, no runtimestatic, cross-compiled; 35MB container
Headless / CIthe default; the console is the extra
Distributed, freesurge agent + -agents a,b,c, raw histograms merged
Fails fast on a bad fleetunreachable, busy or mismatched agent stops the run before traffic
Ctrl-C reaches the agentsyes
Dockerdeploy/docker — image plus compose with replica scaling
KubernetesDeployment + headless Service + Job (deploy/kubernetes)
Config filesurge.conf, searched upward then in $HOME
EnvironmentSURGE_* for every flag
Precedenceconfig file → environment → flags
Generator-bottleneck warning85% CPU, or goroutine scheduling delay above a tenth of the measured p95 — a throttled container reads low on CPU precisely because the kernel is holding it back
Watched on every workernot just the coordinator; a fleet is made of small containers, which is where it happens
Validate without trafficsurge validate
Rehearse against the real systemsurge verify, or surge run -verify — one pass of every role, every extract and check reported, before any load
Split with no coordinator-shard 2/5, feeder rows divided too, then surge merge
A run that measured nothingcannot pass; says NO MEASUREMENTS
A panicking virtual userrecorded as an exception with its stack; the run continues

Driving it from Claude Code

surge
In this repository.claude/skills and .claude/agents — open it in Claude Code and they are there, nothing installed
Skillsperf-plan · perf-setup · perf-run · perf-gate · perf-capacity · scenario-reference
Agentsperf-author drafts a scenario from a codebase · perf-analyst diagnoses a finished run
In another projectthe same files as a plugin: claude plugin marketplace add <repo> then claude plugin install surge@surge
Kept honestscripts/sync-plugin.sh copies one onto the other; a test fails the build if they drift

Deliberately not here