Skip to content
LiteTraffic

how it works

A small controller around stock k6.

LiteTraffic is a Python CLI. There is no server, database or background daemon: each run seeds its own state, drives journeys with a pinned k6, reads back what changed, and writes the evidence to disk.

seed · drive

interactive preview · examples/checkout

Seed a world, then drive it.

A scenario names its users, the state they start from, the pace they arrive at, and what must hold afterwards. LiteTraffic sets up that state, runs the journeys on a seeded schedule with stock k6, and reads back the result.


run · checkoutseed 42
manifest.jsontrimmed
{
  "name": "checkout",
  "actors": [{"class": "buyer", "count": 8}],
  "fixtures": {"recipe": "owned-shop"},
  "schedule": {
    "unit": "journeys_per_second",
    "phases": [
      {"name": "warmup",   "seconds": 1, "rate": 1},
      {"name": "measure",  "seconds": 5, "rate": 2},
      {"name": "recovery", "seconds": 1, "rate": 1}
    ]
  },
  "assertions": ["one_effect_per_payment",
                 "accepted_orders_persist",
                 "order_totals_match"],
  "budgets": {"max_seconds": 20, "max_requests": 40}
}
  1. Seeding, done
  2. Traffic, done
  3. Observing, done
  4. Verdict, done

run log

  1. preparingFixture owned-shop created for this run
  2. runningk6 v2.2.0 drove 12/12 journeys, 36 requests
  3. observingObserver owned-order-ledger read the final state
  4. finalizing3 of 3 assertions hold. report.html written
verdictPass

This replays the documented first run of examples/checkout against its demo server, with the timing shortened. A real run writes every step to .litetraffic/runs/<run_id>/.

A scenario is a directory you review

my-scenario/
├── manifest.json   # declared shape, schedule, assertions, budgets
└── journeys.js     # k6 script that performs the journeys and emits evidence

The manifest names the actors, the starting state (fixtures), the schedule, the assertions that must receive evidence for a pass, an optional final observation, and the budgets the run must fit. Unknown fields are rejected. The journey script is ordinary k6 JavaScript, and it is trusted code: review it as you would any test.

litetraffic inspect validates the manifest, checks the script and its relative imports stay inside the directory, resolves the schedule for a seed and checks the worst case against the budgets. It sends nothing to your app. The fastest start is to copy the bundled example closest to your case, or to generate a tenant-isolation scenario with litetraffic init.

One run, step by step

  1. Load. The manifest is parsed, the bundle digest is computed over the manifest, the script and every file it imports, and the budgets are checked against the worst case.
  2. Preflight. k6 must be exactly v2.2.0 and the target a credential-free HTTP(S) URL. An E2B sandbox ID and port can be resolved to a URL.
  3. Fixture. Optionally, a run-owned record is created over HTTP, setup commands run, or a pool hands each journey its own item.
  4. Engine. k6 runs a temporary copy of the scenario in its own process group, with a deadline derived from max_seconds.
  5. Evidence. LT_EVENT lines are parsed and checked against the run ID; metrics are aggregated (p50, p95, max, error rate, iterations).
  6. Observation. Optionally, a final read-only GET is compared by JSON Pointer, polling until it converges when the app settles asynchronously.
  7. Cleanup. Owned fixtures are deleted and teardown commands run, even after a failure. A failed cleanup is never hidden: it makes the verdict error.
  8. Verdict. Assertions, completeness, lifecycle and budgets combine into pass, fail, inconclusive or error, and a standalone report.html is written.

Seeded schedules

Traffic is measured in journeys per second. Explicit phases admit ceil(seconds × rate) journeys each. Profiles compile into phases from the seed, so the same seed always yields the same schedule:

ProfileShape
spikySteady baseline with short spikes, one per equal window
random_burstsMostly quiet (the rate may be 0) with seeded bursts
sustained_burstLinear ramp, plateau, return to baseline

The script receives the resolved phases as LT_SCHEDULE_JSON and turns them into a k6 ramping-arrival-rate executor. The seed is recorded with the run, so a rerun with the same seed sends the same pressure.

Evidence through logs

Scripts report each assertion on stdout, so no k6 extension is needed. Every journey writes one line per declared assertion, keyed by the journey that produced it:

LT_EVENT {"schema_version": 1, "type": "assertion", "run_id": "<run_id>",
          "assertion": "one_effect_per_payment", "passed": false,
          "logical_key": "<run_id>-traffic-0", "expected": 1, "actual": 2}

An assertion passes only when every sample passes, every sample has a key, and the number of distinct keys equals the planned journeys. One journey reporting twice cannot stand in for a journey that reported nothing. The optional expected, actual and detail fields never change the verdict; they make a failure readable.

Where it runs

Against a local app, a CI or preview URL, or an existing E2B sandbox. Your app must already be running. URL targets must not carry credentials, link-local and cloud-metadata addresses are refused before a request is sent, and k6 runs with --max-redirects 0. Point writes only at data you own: a local app, an isolated preview, or run-owned fixtures.

Design choices

  • Stock k6, pinned. No custom engine build; exact version pinning keeps metrics and behavior comparable.
  • Evidence through logs. Assertions travel on stdout, so any k6 script can report them.
  • Verdict separate from lifecycle. A run can finish and fail, or time out and still record a definite failure.
  • Local artifacts. Everything a verdict depends on is written to disk for review and comparison.

Read the full architecture and scenario reference.