Lemmings — a session-aware load simulator that walks your site like people do
A Go binary that simulates real visitors with cookies, think time and waiting rooms before you launch.
What it is
Lemmings simulates real-world NPC traffic during high-load events — a load test that models people rather than requests. It is written in Go, ships as a single self-contained binary (the dashboard and report templates are embedded), and is licensed under Apache 2.0. The current release tag is v0.1.0.
It is deliberately not a request blaster. Tools like wrk, vegeta and k6 fire requests at a rate; lemmings simulates visitors. Each lemming is a virtual browser session with its own cookie jar, connection pool, user agent (desktop or mobile) and language, kept for its whole life. It lands on your site, reads the page, follows one of the links it found there — sending the previous page as Referer — pauses like a person reading, checks every page for the failures a human would notice, and keeps going until its time runs out.
The point is to reveal what actually happens when real people use an application under load: not just how fast it answers, but how many visitors got through their whole visit without anything going wrong. The README frames the problem plainly: your company spends $50,000 on a TV commercial, traffic spikes the moment it airs — does the app survive? Most teams find out at 8PM in front of customers.
Try it
The repo ships Neon Arcade, a small local demo site with healthy pages, a slow leaderboard, a flaky page, an error-with-200 page, a blank page, a broken link and a waiting room. Two terminals:
git clone https://github.com/andreimerlescu/lemmings && cd lemmings
make demo # terminal 1: Neon Arcade on 127.0.0.1:8080
make run # terminal 2: 72 lemmings for 45 seconds
make run prints a dashboard: link with the auth token in its fragment, so there is nothing to paste.
The geographic model
Concurrency is organized as three tiers that map onto how engineers think about real traffic events:
- Terrain — the top-level group (
-terrain), like a region or state. - Pack — lemmings per terrain (
-pack), population density. - Limit — the ceiling on lemmings alive at once (
-limit). Lemmings waiting for a slot cost nothing;sema, from the author's semaphore package, enforces it.
A launch plan translates directly:
lemmings -hit https://myapp.com -terrain 50 -pack 100 -until 30s -ramp 5m
That is 5,000 lemmings, each reading pages for 30 seconds. The -ramp flag brings terrain groups online linearly over the ramp duration rather than spawning everything at once — a 5-minute ramp with 50 terrains means one new group every 6 seconds.
What a lemming does
Each lemming is born with a name (Neon Rider, Chrome Moth…), browser identity and language. During its life it lands on -hit, reads the page, picks a link to follow — or any indexed page with -navigation random — records status, redirects, time-to-first-byte, latency and bytes, checks that the status is 2xx, an HTML page isn't blank, and any -journey step passes. It compares the body's SHA-512 against what was indexed at boot (informational by default; -strict-checksum fails visits on change). If it meets a waiting room it queues, reporting its position until admitted or time runs out. It pauses for random think time between -think-min and -think-max, longer if the server sent Retry-After. It repeats until -until expires, -max-pages is reached, or its journey completes — then walks home and reports its whole life.
Session cookies never leak between lemmings; each pays for its own TLS handshake. Lemmings never follow log-out, delete or download links, or anything off your origin, so a test can't sign sessions out or mutate data by wandering.
The dashboard
While running, a live dashboard is served at http://localhost:4000 (port via -dashboard-port). A one-time token rides in the URL fragment; only its SHA-512 hash is kept in memory. Lemmings drop from an entrance hatch onto a synthwave grid and walk it — a green sparkle for a passing 2xx, a cyan bounce for a redirect, a yellow question mark and stumble for a 4xx or failed check, a red burst for a 5xx, a glitch for no response. Up to 160 lemmings are on the terrain at once; the rest live in the surrounding numbers.
Clicking a lemming (or pressing f) opens an inspector following its life: name, browser, language, every page read with status and latency, queue time, redirects, why pages failed, and — when it dies — its epitaph. Around the terrain are traffic and latency charts, response codes, a terrain map that lights up as terrains come online, busiest paths, and a live feed. The theme follows your system's light/dark preference; t switches theme, x toggles motion effects.
Journeys
A journey is an ordered list of pages with checks for the paths that matter most — checkout, signup, purchase:
{
"name": "buy-tokens",
"steps": [
{ "name": "arrive", "path": "/", "expect": { "status": [200], "title_contains": "Neon Arcade" } },
{ "name": "checkout", "path": "/checkout", "expect": { "contains": ["Tokens are on the way"] } }
]
}
lemmings -hit http://127.0.0.1:8080/ -journey examples/journeys/buy-tokens.json
Checks cover status, content_type, contains / not_contains, title_contains and CSS selectors. Journeys are GET-only, every path must stay on the origin, unknown fields are rejected so a typo can't silently disable a check, and cookies set along the way persist for the lemming's life.
URL discovery
Before any lemming moves, lemmings indexes your application's URL surface with a three-strategy waterfall: sitemap.xml first (including nested sitemap index files), then a Sitemap: directive in robots.txt on the same origin, then — only if -crawl is set — a depth-bounded crawl capped at 5 minutes. If nothing yields results it falls back to the origin URL alone and still runs. Every indexed URL gets a SHA-512 checksum computed at boot.
Waiting rooms
Lemmings has first-class support for the author's room package, detected automatically with no configuration: it reads queue position, polls on room's 3-second interval, and reports every change. The report tells you how many visits were held, the deepest position, mean/p95/longest wait, and how many lemmings died in line — with queue time kept out of latency percentiles.
Reports, gates, delivery
When the swarm completes or on Ctrl-C, three reports are written: a self-contained HTML report (light/dark Synthwave '84, no scripts), Markdown for PRs and email, and JSON for tooling. It leads with how many lemmings you rescued — the share whose whole life had no failed page — then traffic/latency over time, response codes, per-path p50/p95/p99, waiting-room data, and a few notable lemmings: The Explorer (most pages), Patient Zero (first failure), The Unlucky One (slowest page), The Patient One (longest in the queue). Reports never contain credentials, addresses or query strings.
CI gates map to exit codes:
| Exit | Meaning | |---|---| | 0 | run finished, every gate passed | | 1 | configuration, indexing or delivery failed | | 2 | -max-failure-rate or -p95-budget exceeded | | 130 | interrupted; partial report still delivered |
lemmings -hit https://staging.myapp.com -terrain 5 -pack 10 -until 60s \
-tty=false -max-failure-rate 0.01 -p95-budget 800ms
Delivery targets are parsed by URI prefix and run concurrently — local path, s3://bucket/prefix (standard AWS credential chain), or mailto: via SMTP env vars with STARTTLS. The LEMMINGS_SAVE_TO environment variable adds destinations additively. With -observe, a Prometheus metrics endpoint exposes thirteen lemmings_* metrics, including a visit-duration histogram whose URL label granularity (full, path, none) is capped at 999 distinct values to protect default Prometheus cardinality.
Trust and testing
The README's claim about accuracy is backed by the suite: TESTS.md documents 438 tests — unit, integration, end-to-end, fuzz and benchmark — passing under the race detector on Linux, macOS and Windows. Visit counts are atomic counters updated at completion; lifecycle events (born/died/failed) are emitted exclusively by Terrain so alive and completed stay exact; percentiles are nearest-rank while samples fit in memory. Parsers for externally-sourced bytes — sitemap XML, HTML anchors, waiting-room detection, SHA-512 hashing, dashboard tokens — have fuzz targets proving they never panic.
When to use it
The README is explicit about scope: lemmings reads what your server sends and does not run JavaScript or judge how a page looks. It runs from one machine, so it doesn't model network distance; each lemming waits for a page before choosing the next, so a slow server receives fewer requests — as real people would behave. Within that scope, every number is exact or states its method.
Suggested uses: before a major traffic event with parameters matching your expected shape, waiting-room capacity planning, infrastructure sizing (raise -terrain and -pack until the first 5xx appears), daily CI health checks against staging, and post-incident reconstruction. The README's closing line sums up its audience: engineers who find out their application crashes the hard way — "and for the ones who use lemmings so they never have to."
andreimerlescu/lemmings: 1 MB in 58 files, indexed Nov 9, 2026 after 0.00 hours of indexing. License: Apache-2.0. Source: https://github.com/andreimerlescu/lemmings.
Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).
Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf