Eventum Logo

Eventum

API Load Testing

Stress-test a REST API with diverse, realistic request payloads at maximum throughput using sample mode.

Build a generator that fires a burst of diverse API requests at a target endpoint as fast as possible. Use it to benchmark throughput, uncover race conditions, and validate rate limiters — all without writing test scripts.

What you'll build

The generator uses:

  • static input — produces a burst of N timestamps instantly.
  • Sample mode (--live-mode false) — releases all timestamps without waiting for the clock.
  • chance picking mode — weighted mix of request types (GET, POST, PUT, DELETE).
  • HTTP output — sends each event as a request to the target API.
  • Batch tuning — controls how many requests are in flight at once.

Prerequisites

  • Eventum installed
  • A target API endpoint to test (this tutorial uses a placeholder URL — replace it with your own)

Only load-test APIs you own or have explicit permission to test. Sending high-volume traffic to third-party services may violate their terms of service.

Project structure

generator.yml
get-users.jinja
create-user.jinja
update-user.jinja
delete-user.jinja

Build it

Create the project directory

mkdir -p load-test/templates
cd load-test

Write request templates

Each template produces a JSON request body. The HTTP output sends it as a POST to the target endpoint. The template includes the HTTP method and path so the API can route accordingly (adjust the format to match your API's expectations).

GET users — list endpoint, no body needed.

templates/get-users.jinja
{
  "method": "GET",
  "path": "/api/v1/users",
  "params": {
    "page": {{ module.rand.number.integer(1, 100) }},
    "limit": {{ module.rand.choice([10, 25, 50]) }}
  }
}

Create user — POST with a realistic payload.

templates/create-user.jinja
{
  "method": "POST",
  "path": "/api/v1/users",
  "body": {
    "name": "{{ module.faker.locale.en.name() }}",
    "email": "{{ module.faker.locale.en.email() }}",
    "role": "{{ module.rand.weighted_choice(["viewer", "editor", "admin"], [0.6, 0.3, 0.1]) }}",
    "department": "{{ module.rand.choice(["engineering", "marketing", "sales", "support", "hr"]) }}"
  }
}

Update user — PUT with partial changes.

templates/update-user.jinja
{
  "method": "PUT",
  "path": "/api/v1/users/{{ module.rand.number.integer(1, 10000) }}",
  "body": {
    "name": "{{ module.faker.locale.en.name() }}",
    "role": "{{ module.rand.choice(["viewer", "editor", "admin"]) }}"
  }
}

Delete user — DELETE by ID.

templates/delete-user.jinja
{
  "method": "DELETE",
  "path": "/api/v1/users/{{ module.rand.number.integer(1, 10000) }}"
}

Configure the generator

The static input generates 5,000 timestamps at once. The chance mode distributes requests across the four types with a realistic CRUD mix.

generator.yml
input:
  - static:
      count: 5000

event:
  template:
    mode: chance
    templates:
      - get-users:
          template: templates/get-users.jinja
          chance: 0.50
      - create-user:
          template: templates/create-user.jinja
          chance: 0.25
      - update-user:
          template: templates/update-user.jinja
          chance: 0.15
      - delete-user:
          template: templates/delete-user.jinja
          chance: 0.10

output:
  - http:
      url: "http://localhost:8080/api/v1/batch"
      method: POST
      success_code: 200
      headers:
        Content-Type: "application/json"
        Authorization: "Bearer test-token"
      formatter:
        format: json-batch

Key settings:

  • static count: 5000 — the total number of requests to generate.
  • chance distribution — 50% reads, 25% creates, 15% updates, 10% deletes — mimics a typical web application workload.
  • json-batch formatter — groups events into a JSON array per batch, reducing HTTP round-trips.

Run it

Use eventum generate in sample mode with tuned batch settings:

eventum generate \
  --path generator.yml \
  --id load-test \
  --live-mode false \
  --batch.size 100 \
  --max-concurrency 10

Flags explained:

FlagEffect
--live-mode falseReleases all 5,000 timestamps instantly — maximum throughput
--batch.size 100Groups 100 events per batch before sending
--max-concurrency 10Up to 10 HTTP requests in flight simultaneously

The generator fires 5,000 requests grouped into 50 batches of 100, with up to 10 batches in flight at once. Adjust --batch.size and --max-concurrency to find your API's breaking point.

Monitor results

While the test runs (or immediately after), check the metrics:

# If running with eventum run, query the REST API:
curl -s http://localhost:9474/api/v1/instances/load-test/metrics | python -m json.tool

Key metrics to watch:

MetricWhat it tells you
write_okSuccessful API requests
write_failedFailed requests (timeouts, 5xx errors)
format_failedEvents that failed JSON validation before sending

For eventum generate runs, check the summary printed at exit, or add a stdout output to see events in real time.

Going further

  • Ramp-up pattern — replace static with linspace over a 5-minute window in live mode to gradually increase load.
  • Error injection — add a template that sends intentionally malformed requests (missing required fields, invalid types) to test API error handling.
  • Multi-endpoint testing — run several generators in parallel via startup.yml, each targeting a different API service.
  • Throughput measurement — pipe stdout output through pv -l to count events per second in real time.

What's next

On this page