Core
JMeter load testing
perfscale runs your existing JMeter test plans (.jmx) headless and
folds the results into the same live log stream and k6-compatible summary
format as every other engine. There is nothing to rewrite: keep the plan
you already have, point perfscale at it, and get live output plus a
machine-readable summary you can diff against k6, locust, and native runs.
perfscale run --jmeter plan.jmx
Prerequisites
A JMeter installation on the machine that runs the test — perfscale wraps it, it does not embed it:
jmeter --version # must be on PATH (requires a JRE)
If the binary is missing the run fails fast with
jmeter not found in PATH — install from https://jmeter.apache.org/download_jmeter.cgi (requires a JRE).
On PerfScale Cloud agents the dev docker image already ships a JRE with
JMeter 5.6.3.
How a run works
perfscale spawns the plan in non-GUI mode and streams everything live:
jmeter -n -t <plan.jmx>
- JMeter owns the load shape. Thread groups, ramp-up, timers, and
throughput all come from the plan. perfscale passes no
-Jproperties, and perfscale run config (vus,duration,stages:) does not apply — the plan is the config. - Live output. JMeter's own stdout/stderr stream through while the run is in progress, same as k6 or locust output.
- Exit code. JMeter exits non-zero on plan/startup errors; sample failures inside a plan do not change the exit code unless the plan itself gates on them. In CI the exit code is your plan-health gate.
The translated summary
After the process exits, perfscale captures JMeter's final console line
(summary = N in HH:MM:SS = R/s Avg: .. Min: .. Max: .. Err: .. (E%)) and
translates it into the k6-compatible summary block every engine reports in:
http_req_duration......: avg=4.00ms min=3.00ms max=72.00ms
http_req_failed........: 0.00%
http_reqs..............: 100 109.2/s
No percentiles. The console summary does not carry them, so
http_req_duration has avg/min/max only. If you need p95/p99, use
JMeter's own reporting (an HTML report or a Backend Listener) alongside the
run — see Limits.
Parameterizing plans
Since no -J properties are passed from outside, parameterize the plan
itself with JMeter's property functions and defaults:
<stringProp name="ThreadGroup.num_threads">${__P(vus,5)}</stringProp>
<stringProp name="LoopController.loops">${__P(loops,20)}</stringProp>
<stringProp name="HTTPSampler.domain">${__P(host,127.0.0.1)}</stringProp>
<stringProp name="HTTPSampler.port">${__P(port,8080)}</stringProp>
The defaults keep the plan runnable as-is; to vary the shape, wrap the
jmeter invocation in a script that passes -Jvus=50 -Jloops=100, or keep
several thin plan variants for different profiles.
Running on an agent (PerfScale Cloud)
The same runner serves the platform: a JMeter-type test stores the plan
XML, the control plane dispatches it to an agent, and the agent executes it
via POST /api/v1/run/jmeter — streaming the log live and landing the
translated summary in the same dashboard and metrics pipeline as k6 runs.
Limits
- No percentiles in the summary (avg/min/max only) — the JMeter console summary doesn't carry them.
- No
std/thresholds@v1gates over JMeter runs — the exit code is the CI gate. .jtlresult files are not parsed (throughput parsing of.jtlis future work).- No external parameterization — perfscale config and
-Jproperties are not applied; the plan owns the load shape.
Per-engine trade-offs at a glance live in the runners reference.