Chapter 21Lesson 01~165 minutes

CLI Mode, Headless Execution, Result Files, and Reproducible Runs: Core Concepts and Mental Model

Chapter 20 made JMeter projects portable by externalizing configuration and resolving module paths deliberately. That still leaves a production question: exactly how was the plan executed? A GUI click does not preserve the full command, run directory, JMeter/Java versions, input hashes, result policy, report path, or shell exit status. Chapter 21 turns execution itself into a versionable DevOps contract so the same bounded plan can be run on a developer workstation, scheduler, or CI runner with auditable evidence.

CLI modeJTLjmeter.logHTML reportRun manifest

Learning objectives

  • Explain why GUI is primarily an authoring/debug surface and CLI is the load-execution surface.
  • Map JMX/properties/run parameters into one explicit jmeter -n process.
  • Distinguish JTL sample results, jmeter.log engine diagnostics, console summaries, HTML reports, and run manifests.
  • Separate engine/process success from sample correctness and post-run performance gates.
  • Inspect versions, hashes, paths, result policy, target state, and output directories before running.
  • Preserve enough metadata to reproduce the same run contract from a clean shell.

1. The practical problem: “I ran the same JMX” is not reproducibility

Two engineers can load the same JMX and still run different experiments: one uses GUI, one CLI; one has 20 threads from a property file, one silently defaults to 1; one overwrites yesterday's JTL; one enables verbose logs; one runs JMeter 5.6.3/Java 17 while another has a floating installation. If only the HTML dashboard survives, the original execution cannot be reconstructed.

Mandatory chapter boundary: only http://127.0.0.1:8021, synthetic requests, maximum 2 threads ×4 samples/thread, 20 ms pacing, no credentials, no plugins, no remote engines, no containers, no paid CI, and no public/production target. Never substitute a public/shared/production endpoint for this localhost fixture. Never increase load to compensate for missing execution evidence.

2. Mental model: explicit inputs → one engine process → raw artifacts → independent gate

Headless execution and evidence flow

CLI execution turns a JMeter plan into a reproducible process contract: explicit inputs enter one engine process, the process emits separate raw/log/report artifacts, and a post-run validator decides whether the observed workload is acceptable.

flowchart TD
I[Versioned JMX + property files + deterministic data] --> C[Explicit jmeter -n CLI command]
C --> J[JVM / JMeter engine]
J --> T[Bounded thread execution]
T --> S[Authorized local target]
J --> R[JTL sample results]
J --> L[jmeter.log engine diagnostics]
J --> O[Process exit status]
R --> H[Optional HTML dashboard]
R --> G[Post-run sample/SLO gate]
L --> G
O --> G
M[Run manifest: command + versions + hashes + paths] --> G
S --> E[Target event telemetry]
E --> G

The JMX/property/data files are immutable inputs. The launcher constructs one exact CLI command and starts one JMeter JVM/engine. The engine creates thread activity against the target. Raw sample outcomes go to JTL; engine/classpath/configuration messages go to jmeter.log; the shell receives a process exit status. The HTML dashboard is derived evidence, not the raw source. A post-run validator then checks sample count, failures, latency and artifact integrity. The run manifest binds command, versions, hashes and paths to those outputs, while target telemetry independently confirms what work reached the service.

3. The core CLI contract

Flag Meaning / state boundary
-n Run JMeter in CLI/non-GUI mode; changes engine/UI execution mode.
-t plan.jmx Selects the versioned test plan input.
-l results.jtl Creates the top-level sample-result listener/artifact for this run.
-j jmeter.log Places JMeter engine/runtime logs in a run-specific file.
-q local.properties Loads additional JMeter properties before test execution.
-Jname=value Defines/overrides one local JMeter property for the process.
-e Generate HTML dashboard after the load test.
-o report-dir Select dashboard output directory; it must be non-existent/empty.
-g results.jtl Report-only mode from an existing compatible CSV sample log.
-Lcategory=LEVEL Targeted logging override for diagnostics; use sparingly under load.

4. GUI versus CLI

Use GUI to build, inspect scope, run tiny validation/debug checks, and temporarily inspect a few samples. Current JMeter guidance explicitly says not to run load tests in GUI mode. GUI components/renderers consume generator resources and a GUI click is harder to automate faithfully.

Use CLI for meaningful load because it gives explicit files, command-line properties, predictable log/result destinations, shell exit handling and automation hooks.

5. JTL is raw sample evidence

The -l file records the top-level test results using save-service settings. Current CSV defaults can include timestamp, elapsed, label, response code/message, thread name, success/failure message, bytes sent/received and active-thread counts. CSV is much smaller than XML and cannot store response bodies, which makes it a safer default for repeated load samples.

JTL is not just “data for the dashboard.” Your post-run gate should read it directly before derived reports are accepted.

6. jmeter.log is engine evidence

JTL answers “what happened to samples?” jmeter.log answers “what happened inside JMeter?” It captures plan loading, component/plugin/classpath messages, script exceptions, remote/engine problems and other diagnostics that may not appear as ordinary target samples.

Always use -j to put it inside the run directory. Do not rely on whatever default jmeter.log happens to exist in the launch directory.

7. HTML report is derived evidence

-e -o generates the dashboard after a successful load execution path; -g results.jtl -o report can generate it later from an existing compatible CSV log. The dashboard is useful for analysis but should be reproducible from the retained raw JTL/properties.

The output directory must be new/empty. A safe launcher creates the parent run directory but deliberately does not pre-create the report directory.

8. Process exit is not the performance gate

An engine/process exit code answers whether the JMeter invocation itself completed according to its process-level rules. It does not replace sample assertions, error-rate checks, expected-row counts or latency thresholds. A plan can finish as a process while some HTTP samples are unsuccessful.

Therefore the launcher records two statuses: engine_exit_code and a separate post-run gate_exit_code. CI should fail if either is non-zero, while preserving which layer failed.

9. Run manifest binds inputs to outputs

A manifest should record at least:

  • run ID and creation time;
  • caller cwd and resolved project root;
  • exact command tokens;
  • JMX/property-file absolute paths and SHA-256 hashes;
  • JMeter and Java version output;
  • JTL/log/report paths;
  • engine exit and gate exit;
  • gate metrics/sample counts.

Do not put passwords/tokens into command manifests.

10. State checklist before execution

State Question
Generator JMeter/Java versions, CPU/heap/network/disk, cwd/project root, free output space?
Thread/arrival Resolved threads/loops/timers/duration and expected sample count?
Component scope Are assertions/timers/controllers attached to the intended samplers?
Variables/properties/data Which inputs are immutable files/properties versus thread-local values?
Protocol/session What connection/session/correlation state will each thread create?
Target Exact authorized host/port, health, baseline event/state count?
Artifacts Unique run dir, JTL format, jmeter.log, report, console capture, manifest?
Credential/trust Any secret in CLI/environment/property file/log? None in mandatory lab.
Validity Can generator sustain configured work and does achieved sample/target count match expectation?

11. Read-only preflight

PowerShell:

& "$env:JMETER_HOME\bin\jmeter.bat" -v
java -version
Get-FileHash .\plans\cli-local.jmx -Algorithm SHA256
Get-FileHash .\config\local.properties -Algorithm SHA256
Get-Content .\config\local.properties
Test-Path .\results\planned-run-id
curl.exe --fail --silent http://127.0.0.1:8021/health
curl.exe --fail --silent http://127.0.0.1:8021/stats

Nothing above generates load. It proves current tool/input/path/target state first.

12. Why the safe launcher does not use -f

Current JMeter exposes -f/--forceDeleteResultFile to delete an existing result file and report folder before starting. That is convenient for disposable automation but dangerous with a misresolved/user-controlled path because it intentionally destroys evidence.

This chapter instead creates a unique run directory and refuses if it already exists. Historical evidence is never deleted as part of “run again.”

13. DevOps connection

A performance test becomes automatable when the same explicit input/command/output contract works in an interactive shell and an unattended runner. The runner should not need GUI state or undocumented local files; it should only need the versioned project, pinned JMeter/Java runtime, authorized target and sufficient generator resources.

Knowledge check

Why are engine_exit_code and gate_exit_code separate?

Why keep raw JTL after generating HTML?

Why explicitly pass -j inside each run directory?

What is dangerous about -f?

What makes a CLI run reproducible?

Next lesson

Build the local launcher and result gate

Lesson 2 creates the fixture/JMX/property contract, runs raw CLI, adds HTML reporting, then wraps the command with unique directories, version/hash capture, JTL validation and a clean-shell rerun.

Official references and version notes

Version and compatibility note

Version-sensitive statements were rechecked against current Apache JMeter primary documentation on 2026-09-05. The course baseline remains Apache JMeter 5.6.3 with a Java 17 JDK; JMeter 5.6.3 requires Java 8+. JMeter's own manual says GUI mode is for building/debugging while CLI mode must be used for load testing. Current CLI flags include -n (CLI), -t (JMX), -l (JTL), -j (run log), -g <CSV> (report only), -e (report after test), and -o (report output). The report output folder must not exist or must be empty. -f force-deletes an existing result file/report folder before a test, so the safe launcher in this chapter intentionally does not use it; instead it refuses to reuse an existing run directory. CSV result files are smaller than XML and are the normal large-run choice. Current CSV defaults include timing/status/thread and byte fields such as timeStamp, elapsed, label, responseCode, success, bytes, sentBytes, grpThreads, and allThreads when their save-service fields are enabled (the current defaults required by the dashboard are documented as correct unless changed). Response data is not supported in CSV. JMeter process/engine completion is therefore treated separately from a post-run SLO/result gate: a launcher first records the engine exit code, then validates JTL sample counts/failures/latency. A zero engine exit is not used as evidence that every sample met an SLO.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.