Tutorial

Build a load test step by step — from a minimal scenario to a complete test with checks, thresholds, and structured output.

Minimal scenario

A rampa scenario is an async function decorated with @rampa.scenario. It receives a Worker and runs one iteration of your workload.

import asyncio
import rampa


@rampa.scenario(executor="constant-vus", vus=1, duration="5s")
async def default(worker: rampa.Worker) -> None:
    await asyncio.sleep(0.01)
$ rampa run load_test.py

HTTP requests

The worker provides an HTTP client that auto-emits timing metrics:

@rampa.scenario(executor="constant-vus", vus=5, duration="30s")
async def default(worker: rampa.Worker) -> None:
    resp = await worker.http.get("https://httpbin.org/get")

Every request emits http_reqs, http_req_duration, http_req_failed, data transfer counters, and per-phase timing metrics automatically.

Checks

Checks validate response properties. Each condition emits a pass/fail sample on the checks metric:

@rampa.scenario(executor="constant-vus", vus=5, duration="30s")
async def default(worker: rampa.Worker) -> None:
    resp = await worker.http.get("https://httpbin.org/get")
    worker.check(resp, {
        "status is 200": lambda r: r.status == 200,
        "body is JSON": lambda r: r.json() is not None,
    })

Thresholds

Thresholds define pass/fail criteria. A breach produces exit code 1:

config = rampa.Config(
    thresholds={
        "http_req_duration": ["p(95)<500", "avg<200"],
        "http_req_failed": ["rate<0.01"],
        "checks": ["rate>0.99"],
    },
)

Custom metrics

Emit your own counters, gauges, and trends:

@rampa.scenario(executor="constant-vus", vus=5, duration="30s")
async def default(worker: rampa.Worker) -> None:
    resp = await worker.http.get("https://api.example.com/items")
    items = resp.json()
    worker.gauge("items_returned", float(len(items)))
    worker.counter("api_calls")

Setup and teardown

Module-level setup() and teardown() functions run once:

async def setup():
    return {"token": "abc123"}


async def teardown():
    pass


@rampa.scenario(executor="constant-vus", vus=5, duration="30s")
async def default(worker: rampa.Worker) -> None:
    token = worker.setup_data["token"]
    await worker.http.get(
        "https://api.example.com/data",
        headers={"Authorization": f"Bearer {token}"},
    )

Multiple scenarios

A single script can define multiple scenarios:

@rampa.scenario(
    name="smoke",
    executor="constant-vus",
    vus=1,
    duration="10s",
)
async def smoke(worker: rampa.Worker) -> None:
    await worker.http.get("https://api.example.com/health")


@rampa.scenario(
    name="load",
    executor="ramping-vus",
    stages=[
        rampa.Stage(duration="30s", target=50),
        rampa.Stage(duration="1m", target=100),
        rampa.Stage(duration="30s", target=0),
    ],
)
async def load(worker: rampa.Worker) -> None:
    await worker.http.post(
        "https://api.example.com/data",
        json={"key": "value"},
    )

Run a specific scenario:

$ rampa run load_test.py --scenario smoke