Skip to content

Live JMeter properties

MaxoPerf can set JMeter properties on a test that is already running. Anything your plan reads through ${__P(key, default)} gets the new value on its next sample. You need no restart and no new run, and the properties feature needs no changes to your plan.

The virtual-user and throughput controls use the same mechanism. You can also use it directly for your own keys.

MaxoPerf starts JMeter with the BeanShell server listening on 127.0.0.1:9000 inside the runner container. MaxoPerf sends a live change to the runner over the control stream and applies it with setprop("key", "value"). The property functions read from that same call.

  1. Open the run, select the Live tab, and find the Properties editor.

  2. Add or edit keys, then commit. Each commit is one revision on the run’s timeline: who, when, and which keys changed.

  3. Read it in the plan with ${__P(feature.flag.checkout, v1)}. The next sample sees the new value.

From the API:

Terminal window
curl -X PUT "$MAXOPERF_API/v1/runs/$RUN_ID/live-controls/properties" \
-H "Authorization: Bearer $MAXOPERF_API_KEY" \
-H "X-Account-Id: $ACCOUNT_ID" \
-H 'Content-Type: application/json' \
-d '{"scope":{"type":"all"},"properties":{"feature.flag.checkout":"v2","think.time.ms":"250"}}'

Read properties with the built-in function. Always give a default, so a run started without the property still works:

<stringProp name="HTTPSampler.path">/checkout?variant=${__P(feature.flag.checkout,v1)}</stringProp>

For a number that JMeter must re-evaluate on each sample, use __P directly. Do not cache it in a User Defined Variable. JMeter evaluates UDVs once, at plan start, so they never see your change:

${__P(think.time.ms,500)}
RuleLimit
Key charactersa–z, 0–9, _, ., -, up to 64 characters
Keys per request100
Value size4 KiB
Reserved namespacemaxoperf_* is rejected with 422 PROPERTY_RESERVED

MaxoPerf reserves the maxoperf_ namespace because it drives the virtual-user and throughput controls through it. maxoperf_vus_target holds the thread count. maxoperf_throughput_rpm_total holds samples per minute, the unit of JMeter’s Constant Throughput Timer field. MaxoPerf converts it for you from the requests per second you type. (The run’s runtime file also states the per-second number, as maxoperf_throughput_rps_total, for scripts that are not JMeter timers.) If a plan could write those keys directly, one number would have two authorities.

MaxoPerf passes values to the engine as string literals. A value that contains quotes, semicolons or code is data, not code. Logs redact values longer than 256 characters.

Both controls work live on JMeter. Both have a precondition, because JMeter itself has one.

ControlWorks whenOtherwise
Virtual usersthe plan uses a Concurrency Thread Groupdisabled with jmeter.concurrency-thread-group; a PUT returns 422 CONTROL_PRECONDITION_UNMET
Throughputthe plan contains a Constant Throughput Timer (a Throughput Shaping Timer keeps its schedule in a table and cannot be steered)disabled with jmeter.throughput-timer
Pause / resumesame as virtual users: pause is the VU target moved to 0, and you can reverse itdisabled with jmeter.concurrency-thread-group

Classic, Stepping and Ultimate thread groups read their thread count only when the plan loads. A Concurrency Thread Group re-reads it all the time, so it is the one that can follow a live change. MaxoPerf detects which thread group your plan uses when you create the run. It disables the controls that cannot work, instead of accepting a change that would do nothing.

When MaxoPerf builds the bundle, it rewrites the thread group’s target and the throughput timer’s rate to read from its own properties. You do not have to edit your plan to use the sliders. For the step-by-step version (which elements to use, and the same recipe for k6, Locust and Gatling), see Make your test controllable mid-run.