Checkpoint Lab — JMeter Architecture, Java Setup, Installation, and First Test Plan
Prove that a JMeter project can be reconstructed from explicit inputs rather than workstation memory. Build the project from an empty directory, verify the official tool artifact, create one tiny local plan, execute it from a clean shell, deliberately break the tool path, diagnose and restore it, and retain the evidence.
Learning objectives
- Record exact Java/JMeter versions, installation path, release integrity result, and project layout.
- Create and save a tiny loopback-only JMX with a known sample-count ceiling.
- Execute the saved plan from a clean shell in CLI mode.
- Predict and verify target/result/generator changes.
- Introduce and diagnose one reversible JMeter path mismatch without modifying system-wide configuration.
- Produce a reviewable evidence packet and bridge to Chapter 03 tree/scope semantics.
1. Checkpoint scenario and hard safety boundary
You are onboarding a new performance-test workstation. Starting from
an empty jmeter-ch02-checkpoint directory, you must
prove which Java and JMeter binaries will run, verify the Apache
binary ZIP, create a minimal plan against the loopback fixture,
execute it in CLI mode, and preserve enough evidence for another
engineer to review the run.
127.0.0.1:8000. Abort if the host changes, the fixture
is not healthy, unexpected errors appear, or the workstation becomes
unstable. Do not increase the workload.
2. Create the project layout
jmeter-ch02-checkpoint/
├── fixtures/
│ └── fixture_server.py
├── plans/
│ └── first-plan.jmx
├── evidence/
│ ├── toolchain.txt
│ ├── checksum.txt
│ └── run-command.txt
└── results/
Create the directories only. Keep installation files outside the project so source and toolchain remain distinguishable.
3. Record toolchain identity
Capture path plus version before doing anything else.
Windows PowerShell:
New-Item -ItemType Directory -Force evidence, fixtures, plans, results | Out-Null
"=== Java commands ===" | Set-Content evidence\toolchain.txt
Get-Command java -All | Format-List * | Out-String |
Add-Content evidence\toolchain.txt
java -version 2>&1 | Out-String |
Add-Content evidence\toolchain.txt
"=== JMeter command ===" | Add-Content evidence\toolchain.txt
Get-Command jmeter.bat -All | Format-List * | Out-String |
Add-Content evidence\toolchain.txt
jmeter.bat -v 2>&1 | Out-String |
Add-Content evidence\toolchain.txt
Linux/macOS:
mkdir -p evidence fixtures plans results
{
echo "=== Java ==="
command -v java
java -version
echo "=== JMeter ==="
command -v jmeter
jmeter -v
} > evidence/toolchain.txt 2>&1
4. Verify the pinned JMeter archive
The checkpoint baseline is the official
apache-jmeter-5.6.3.zip. If it is already present from
Lesson 2, do not redownload merely for ritual; verify the exact
archive you plan to use. If it is absent, download it from the
current official mirror page and the checksum from the Apache
distribution service.
Expected SHA-512:
387fadca903ee0aa30e3f2115fdfedb3898b102e6b9fe7cc3942703094bd2e65b235df2b0c6d0d3248e74c9a7950a36e42625fd74425368342c12e40b0163076
Save the verification result into
evidence/checksum.txt.
PowerShell:
$Expected = "387fadca903ee0aa30e3f2115fdfedb3898b102e6b9fe7cc3942703094bd2e65b235df2b0c6d0d3248e74c9a7950a36e42625fd74425368342c12e40b0163076"
$Actual = (Get-FileHash .\apache-jmeter-5.6.3.zip -Algorithm SHA512).Hash.ToLowerInvariant()
"expected=$Expected" | Set-Content evidence\checksum.txt
"actual=$Actual" | Add-Content evidence\checksum.txt
if ($Actual -ne $Expected) { throw "Checksum mismatch" }
"status=OK" | Add-Content evidence\checksum.txt
Linux/macOS:
EXPECTED="387fadca903ee0aa30e3f2115fdfedb3898b102e6b9fe7cc3942703094bd2e65b235df2b0c6d0d3248e74c9a7950a36e42625fd74425368342c12e40b0163076"
ACTUAL="$(shasum -a 512 apache-jmeter-5.6.3.zip | awk '{print $1}')"
printf 'expected=%s\nactual=%s\n' "$EXPECTED" "$ACTUAL" > evidence/checksum.txt
test "$EXPECTED" = "$ACTUAL"
printf 'status=OK\n' >> evidence/checksum.txt
5. Start and preflight the disposable target
Save the same Chapter 02 fixture as
fixtures/fixture_server.py:
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse, parse_qs
import json
import threading
import time
HOST = "127.0.0.1"
PORT = 8000
lock = threading.Lock()
total = 0
active = 0
class Handler(BaseHTTPRequestHandler):
def _json(self, status, payload):
body = json.dumps(payload).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
global total, active
parsed = urlparse(self.path)
if parsed.path == "/health":
self._json(200, {"status": "ok"})
return
if parsed.path == "/stats":
with lock:
snapshot = {"active": active, "total": total}
self._json(200, snapshot)
return
if parsed.path != "/work":
self._json(404, {"error": "not_found"})
return
values = parse_qs(parsed.query)
try:
delay_ms = int(values.get("delay_ms", ["30"])[0])
except ValueError:
delay_ms = 30
delay_ms = min(max(delay_ms, 0), 250)
with lock:
total += 1
active += 1
request_number = total
active_now = active
try:
time.sleep(delay_ms / 1000.0)
self._json(
200,
{
"status": "ok",
"request_number": request_number,
"active_at_start": active_now,
"delay_ms": delay_ms,
},
)
finally:
with lock:
active -= 1
def log_message(self, format, *args):
return
if __name__ == "__main__":
print(f"fixture=http://{HOST}:{PORT}")
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
Start it in a separate terminal and verify:
python fixtures/fixture_server.py
# In another terminal:
curl --fail --silent http://127.0.0.1:8000/health
curl --fail --silent http://127.0.0.1:8000/stats
Before the run, total should be 0 from a fresh fixture.
6. Create and save the first plan
Use the GUI to construct the plan exactly as Lesson 2 describes,
then save plans/first-plan.jmx. If you need a text
comparison target, its essential saved structure is:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan"
testname="Chapter 02 First Plan" enabled="true">
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables"
elementType="Arguments"
guiclass="ArgumentsPanel"
testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup"
testname="One User — Three Iterations" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller"
elementType="LoopController"
guiclass="LoopControlPanel"
testclass="LoopController"
testname="Loop Controller">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">3</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui"
testclass="HTTPSamplerProxy"
testname="GET /work" enabled="true">
<elementProp name="HTTPsampler.Arguments"
elementType="Arguments"
guiclass="HTTPArgumentsPanel"
testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">127.0.0.1</stringProp>
<stringProp name="HTTPSampler.port">8000</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.path">/work?delay_ms=30</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.connect_timeout">1000</stringProp>
<stringProp name="HTTPSampler.response_timeout">2000</stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
Do not perform the checkpoint evidence run in the GUI. If you used a GUI debug run while authoring, restart the fixture so its counter returns to a known baseline.
7. Predict changes before execution
- Thread/result prediction: one thread executes three loops, so the JTL should contain three sampler results.
-
Target prediction: a fresh fixture should move
from
total=0tototal=3. -
Artifact prediction: the run directory should
gain
results.jtl,jmeter.log, and a report withindex.html. - Generator prediction: Java/JMeter will consume some CPU/memory/disk briefly, but this tiny run should not create sustained pressure.
Write these predictions into the runbook or evidence notes before executing.
8. Execute from a clean shell with explicit paths
Open a fresh terminal, set only the intended lab tool path, and verify versions again. Then execute:
Windows PowerShell:
New-Item -ItemType Directory -Force results\run-001 | Out-Null
$Command = @'
jmeter.bat -n -t plans\first-plan.jmx -l results\run-001\results.jtl -j results\run-001\jmeter.log -e -o results\run-001\report
'@
$Command | Set-Content evidence\run-command.txt
jmeter.bat -n `
-t plans\first-plan.jmx `
-l results\run-001\results.jtl `
-j results\run-001\jmeter.log `
-e -o results\run-001\report
Linux/macOS:
mkdir -p results/run-001
printf '%s
' 'jmeter -n -t plans/first-plan.jmx -l results/run-001/results.jtl -j results/run-001/jmeter.log -e -o results/run-001/report' > evidence/run-command.txt
jmeter -n -t plans/first-plan.jmx -l results/run-001/results.jtl -j results/run-001/jmeter.log -e -o results/run-001/report
9. Verify independent observations
- JTL exists and has three sample rows after its CSV header.
-
jmeter.logexists and contains no unexpected startup/runtime errors. results/run-001/report/index.htmlexists.-
Fixture
/statsreports total 3 from the fresh process. - Host resource monitoring shows no unsafe generator pressure.
- The run command, JMX, Java/JMeter versions, and checksum result are all preserved.
Do not infer production capacity from these observations. The checkpoint proves the toolchain and evidence path, not a capacity boundary.
10. Introduce one reversible path mismatch
Now create a controlled failure in the shell only. Do not rename/delete the real installation.
PowerShell:
$GoodJMeterHome = $env:JMETER_HOME
$env:JMETER_HOME = Join-Path $PWD "missing-jmeter-home"
"broken_jmeter_home=$env:JMETER_HOME" |
Set-Content evidence\broken-path.txt
try {
& "$env:JMETER_HOME\bin\jmeter.bat" -v
}
catch {
$_ | Out-String | Add-Content evidence\broken-path.txt
}
finally {
$env:JMETER_HOME = $GoodJMeterHome
}
& "$env:JMETER_HOME\bin\jmeter.bat" -v
Linux/macOS:
GOOD_JMETER_HOME="$JMETER_HOME"
export JMETER_HOME="$PWD/missing-jmeter-home"
printf 'broken_jmeter_home=%s
' "$JMETER_HOME" > evidence/broken-path.txt
"$JMETER_HOME/bin/jmeter" -v >> evidence/broken-path.txt 2>&1 || true
export JMETER_HOME="$GOOD_JMETER_HOME"
"$JMETER_HOME/bin/jmeter" -v
Diagnosis: the failure occurs before a JMeter engine exists because the referenced launcher path does not exist. JTL/SUT analysis is therefore irrelevant. The least destructive repair is restoring the correct installation path and re-verifying the tool identity.
11. Restore and rerun without overwriting evidence
Use a new run-002 directory after restoring the correct
path. Restart the fixture first so its total counter returns to 0.
Repeat the same CLI command with only output paths changed to
run-002. Verify three samples and fixture total 3
again.
The first successful run, broken-path evidence, and restored run together demonstrate reproducibility and diagnosis more clearly than a single green screenshot.
12. Required evidence packet
| Evidence | Required content | Review question |
|---|---|---|
evidence/toolchain.txt |
Java/JMeter paths and versions | Did the intended binaries actually run? |
evidence/checksum.txt |
Expected/actual SHA-512 and OK result | Was the pinned archive verified? |
| Installation path note | Exact JMETER_HOME used |
Can another runner reconstruct the tool path? |
plans/first-plan.jmx |
Saved JMeter tree | What exact workload/target was configured? |
evidence/run-command.txt |
Exact CLI invocation | Can execution be repeated without GUI memory? |
results/run-001/results.jtl |
Raw sample data | Were three samples recorded and successful? |
results/run-001/jmeter.log |
Engine diagnostics | Did JMeter report runtime problems? |
| Dashboard report | Derived presentation | Can a reviewer inspect summarized samples without losing raw data? |
evidence/broken-path.txt |
Controlled failure evidence | Was the failure correctly localized to path/tool identity? |
| Restored run | New run-002 artifacts | Did the smallest correction restore the original behavior? |
13. Cleanup and rollback
- Stop the loopback fixture with Ctrl+C.
- Restore any shell-local environment variables changed for the lab.
- Keep evidence and run artifacts until review is complete.
- Do not remove the verified JMeter installation if it will be used for subsequent chapters.
- No production targets, credentials, plugins, recorder certificates, databases, remote engines, containers, or system-wide OS/JVM settings were modified.
14. What Chapter 02 adds to the operating model
Chapter 01 gave you an experiment contract. Chapter 02 adds toolchain provenance: a specific Java runtime, a verified JMeter release, an explicit installation path, version-controlled JMX, a reproducible CLI command, and distinct raw/diagnostic/report artifacts. This makes a performance run transferable beyond one GUI session or workstation.
Chapter 03 now goes inside the JMX tree and explains scope, execution order, and component semantics so that a reproducible plan is also a correctly structured plan.
Knowledge check
Why is the path-mismatch exercise diagnosed before looking at JTL?
Because the launcher path fails before the JMeter engine can start, so there may be no sample execution to diagnose.
What two independent observations verify the expected sample count?
The JTL should contain three samples and the fresh loopback fixture should report total=3.
Why is the SHA-512 record part of performance-test evidence rather than merely installation trivia?
It establishes which binary artifact entered the toolchain, reducing ambiguity and supply-chain drift in later comparisons.
Why must the restored run use run-002 instead of overwriting run-001?
Preserving both runs keeps provenance and allows independent comparison; overwriting destroys first-run evidence.
What does this checkpoint prove—and what does it not prove?
It proves a reproducible local JMeter toolchain and tiny execution/evidence path under the stated versions. It does not prove production capacity, distributed scaling, SLO compliance, or root cause in another environment.
Official references and version notes
- Apache JMeter downloads — production release, binary/source archives, SHA-512, PGP signatures, and release Java requirement.
- Getting Started — installation layout, launchers, GUI/CLI boundary, CLI flags, logging, property overrides, and directory-path guidance.
- Best Practices — CLI execution and listener/resource guidance.
-
Properties Reference
—
user.properties,system.properties, and property layering. -
Generating Dashboard Report
—
-e -oand post-run report generation. - Apache JMeter development repository — current development-line runtime requirement; do not confuse it with the 5.6.3 release requirement.
Version-sensitive statements were rechecked against current Apache
JMeter primary sources on 2026-09-04. The current production
release is Apache JMeter 5.6.3, whose download
page states Java 8+; the 5.6.x change notes
recommend Java 17 or later. The current development
repository/next major line requires Java 17, so those development
requirements are not retroactively applied to the 5.6.3 release.
Mandatory labs use Java 17, the official 5.6.3 binary archive, no
third-party plugins, the loopback target
127.0.0.1:8000, and CLI mode for the actual load run.
The published SHA-512 for apache-jmeter-5.6.3.zip is
387fadca903ee0aa30e3f2115fdfedb3898b102e6b9fe7cc3942703094bd2e65b235df2b0c6d0d3248e74c9a7950a36e42625fd74425368342c12e40b0163076.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.