# Run an Arena load test

Run an `ArenaJob` of type `loadtest` to replay a project's scenarios at controlled concurrency against providers or agents, and fail the run when it breaks your SLO thresholds.

## Prerequisites

- Arena is an Enterprise feature: the app chart has `enterprise.enabled=true`, and any license you install covers load testing. See [Install with a License](/how-to/operations/install-license/).
- A `Ready` ArenaProject whose arena config holds the scenarios to replay.
- A `Provider`, or a deployed `AgentRuntime`, in the same namespace to load.

## Start a load test from the dashboard

1. Open the project and select **Run this project**.
2. In the dialog, select **Load test**. The dialog shows concurrency, virtual users, workers, duration, arrivals per second, ramp up and down, a budget and any number of thresholds.
3. Tick the providers to load. To load a deployed agent, tick it under **Deployed agents**; agents are off by default.
4. Select **Run**.

A field you leave blank is omitted from the job, so the defaults apply. **Arrivals / s** stays disabled until you enter a duration.

After a run finishes, select **Rerun profile** on its page to repeat the load profile, SLO gates, budget and session recording.

The remaining steps write the same job as YAML, which is the form CI uses.

## Create the job

1. Write the job. The values here are examples; replace the names and numbers with your own.

   ```yaml
   apiVersion: omnia.altairalabs.ai/v1alpha1
   kind: ArenaJob
   metadata:
     name: checkout-loadtest
     namespace: default
   spec:
     sourceRef:
       name: checkout-config
     type: loadtest
     trials: 500
     providers:
       default:
         - providerRef:
             name: gpt4-prod
     workers:
       replicas: 4
     loadTest:
       concurrency: 20
       vusPerWorker: 8
       thresholds:
         - metric: latency_p95
           operator: "<"
           value: "3s"
         - metric: error_rate
           operator: "<"
           value: "0.01"
   ```

   The job type defaults to `evaluation`, so set `type: loadtest`. The volume is `scenarios × providers × trials`, and `spec.trials` overrides any per-scenario trial count.

2. Choose what to load. Each `providers` entry is a `providerRef` or an `agentRef`:

   ```yaml
   providers:
     default:
       - agentRef:
           name: checkout-agent
   ```

   An `agentRef` entry drives the agent's facade over WebSocket and is the only target that measures time-to-first-token. A `providerRef` entry measures total duration only.

3. Size the load. `concurrency` caps the work items in flight across all workers, and the cap is divided among the worker pods. Set `workers.replicas × loadTest.vusPerWorker` to at least `concurrency`, or the virtual users become the limit. Both settings default to `1`, which runs sequentially.

4. To hold the load for a length of time instead of draining a fixed matrix, add a duration:

   ```yaml
   loadTest:
     concurrency: 20
     duration: "10m"
   ```

   With a `duration`, the workers generate requests from the scenario × provider pool until the clock runs out, and `trials` is ignored.

5. To offer a fixed arrival rate instead of holding a fixed concurrency, add `rate` to a job that has a `duration`:

   ```yaml
   loadTest:
     concurrency: 50
     duration: "10m"
     rate: 20
   ```

   `concurrency` becomes a safety ceiling. Arrivals the workers cannot keep up with are dropped and counted, never queued; gate on them with an `arrival_lag` threshold.

6. To ramp the load, add a `ramp` block with Go duration strings:

   ```yaml
   loadTest:
     ramp:
       up: "2m"
       down: "30s"
   ```

7. Gate on SLOs with `loadTest.thresholds`. Each rule has a `metric`, an `operator` (`<`, `>`, `<=`, `>=`) and a `value`. Latency and time-to-first-token values are durations (`"3s"`, `"500ms"`), rate metrics are ratios from 0 to 1 (`"0.01"`), and `total_cost` is a number (`"50.00"`). See [the ArenaJob CRD page](/reference/evaluation/arenajob/#loadtestthresholds) for every metric and what it needs.

8. Cap spend with `budgetLimit` at the top of `spec`, not under `loadTest`:

   ```yaml
   spec:
     budgetLimit: "25.00"
     budgetCurrency: "USD"
   ```

   The value is a bare decimal with no currency symbol. When the run's accumulated cost crosses the limit, the run stops, discards the queued work and fails. The cap is a ceiling, so requests already in flight finish.

   A `budgetLimit` written under `spec.loadTest` is pruned by the API server without an error, and the run has no cap.

9. To store the conversations as sessions, set `sessionRecording: true`. It is `false` by default, which keeps session-api out of the path of a high-volume run. A `datagen` run records its conversations regardless, because its sessions are its corpus. In a workspace with several service groups, name the one to record into with `spec.serviceGroup`.

10. Apply the job:

   ```bash
    kubectl apply -f checkout-loadtest.yaml
    ```

## Verify

The ArenaJob creates an ArenaRun for the execution. Its phase and results are on the run, not on the job.

```bash
kubectl get arenarun
kubectl get arenarun <run-name> -o yaml
```

Find `<run-name>` in the first command, or in the job's `status.lastRunRef`. In `status.result.summary`, each threshold appears as a `threshold:<metric>` entry and `thresholds_passed` counts them. The run reaches `Succeeded` when every threshold passes. An enumerated run (no `duration`) also fails when any item fails.

See [Monitor Arena jobs](/how-to/evaluation/monitor-arena-jobs/) for progress and results.

## Troubleshooting

### A threshold reports `unavailable` and the run passes

A metric that could not be measured passes. A latency percentile needs a latency distribution, which a run keeps when it has a `duration` or when `concurrency` or `vusPerWorker` is above `1`. A `ttft_*` metric also needs an `agentRef` entry, and `arrival_lag` needs `loadTest.rate`. Check that a rule you rely on in CI reports a number.

### A threshold reports `parse error` and the run fails

The `value` cannot be read for its metric, for example `"3 seconds"` or `"$25"`. Use a duration, a ratio from 0 to 1, or a plain number as listed in step 7.

### The run fails with `BudgetExceeded`

The run's cost crossed `budgetLimit`. `status.result.summary` holds `totalCost` and `drainedItems`, the number of queued items discarded. Raise the limit or reduce the load.

### The run fails with `ServiceGroupAmbiguous`

The workspace has several service groups and the job names none. Set `spec.serviceGroup`.

### The run stays `Pending` with `JobCreationFailed`

With `sessionRecording: true`, the named service group does not exist or declares no session-api, so the controller cannot create the worker Job and retries with backoff. Fix `spec.serviceGroup` or the workspace's service groups.

## Related pages

- [ArenaJob CRD](/reference/evaluation/arenajob/): every `spec.loadTest` field, metric and status key
- [Monitor Arena jobs](/how-to/evaluation/monitor-arena-jobs/): track progress and results
- [Set up Arena scheduled jobs](/how-to/evaluation/setup-arena-scheduled-jobs/): run recurring load tests
- [Troubleshoot Arena Fleet](/how-to/evaluation/troubleshoot-arena/): diagnose stuck or failing jobs