Skip to content

Change virtual users mid-run

You can change a test while it runs. On the run’s Live tab you move the virtual user target and the throughput cap while the load is in flight, and you pause and resume it. The run’s timeline records every change, with who made it and when.

Nothing restarts. The run keeps its id, its result series and its report.

  • The run must be running. A queued or finished run answers 409 RUN_NOT_RUNNING.
  • You need the controller role on the run (account members and test editors have it).
  • Your executor and your script must support the control. JMeter, k6, Locust and Gatling can move virtual users live. The other executors cannot, and the console says so. On every engine except Locust, the script also has to be built for it: a Concurrency Thread Group on JMeter, an externally-controlled scenario on k6, the helper opt-in on Gatling. Make your test controllable mid-run has the recipe per engine. What each executor supports at runtime has the full matrix.
  1. Open the run and select the Live tab.

  2. Set the Virtual users field to the new fleet-wide total and commit it.

    The number you type is always the whole fleet. Under the field, MaxoPerf shows how it will divide that number, for example 2 000 = 20 runners × 100. You always see what each runner is told.

  3. Watch the ack pill. It counts the runners that have confirmed the change: applied 4 987 / 5 000. It normally converges in well under a second.

  4. Check that the change reached the load. The run’s charts get a vertical marker at the moment the change applied, so a step in the VU or throughput series lines up with the change that caused it.

The flow is the same, with the Requests per second field. 0 means unlimited.

The Pause toggle stops the load without ending the run. Use it to fail something over, take a snapshot, or let a queue drain, then continue in the same run and the same charts. Resume restores the previous target.

By default a change applies to every live runner. The scope chips narrow it:

ScopeUse it for
Allthe whole fleet (default)
Locationsone cloud region, or one private datacenter
Runnersnamed individual runners

If a scope resolves to zero live runners, MaxoPerf rejects it with 422 SELECTOR_NO_MATCHING_RUNNERS. It never accepts the change and then quietly applies it to nothing.

Your plan’s maxLiveConcurrentVus and maxLiveThroughputRps cap increases. An increase past them returns 403 PLAN_LIMIT_EXCEEDED. Enterprise sets both to -1 (unlimited), so that plan never refuses a mid-run increase.

Decreases are never blocked, even on a run that started above the cap. If your test is overloading the system under test, you can always turn it down.

Terminal window
curl -X PUT "$MAXOPERF_API/v1/runs/$RUN_ID/live-controls/virtual-users" \
-H "Authorization: Bearer $MAXOPERF_API_KEY" \
-H "X-Account-Id: $ACCOUNT_ID" \
-H 'Content-Type: application/json' \
-d '{"scope":{"type":"all"},"total":2000}'
// 202 Accepted
{ "revision": 7, "perRunner": 100, "runnerCount": 20, "dispatchState": "dispatched", "delivered": true }

MaxoPerf rounds the per-runner share up and never below 1, so a small total across a wide fleet cannot be delivered exactly. When that happens, the response says so, and the timeline shows the total as approximate:

// 202 Accepted — 7 RPS across 20 runners is 1 each, so the fleet will really run 20
{ "revision": 8, "perRunner": 1, "runnerCount": 20, "clamped": true, "effectiveTotal": 20, … }

clamped is absent whenever the divide is exact. If a decrease comes back clamped, the fleet is too wide for the target. Release runners, or accept effectiveTotal as the floor.

To read the outcome back, use GET /v1/runs/:runId/live-controls for the desired state, capabilities and ack tallies. Use GET /v1/runs/:runId/live-controls/history for the full change log. Use GET /v1/runs/:runId/live-controls/revisions/:revision/acks?status=failed for the exact runners that have not applied a revision.