Checkpoint Lab — Distributed Testing, Remote Engines, Network Topology, and Synchronization
The checkpoint treats two engines as a small production-like generator fleet. You must prove the load equation and shard allocation before execution, run either the secured low-scale RMI topology or the faithful independent-worker simulation, verify 6+6=12 achieved samples, then intentionally point engine-b at the wrong shard and diagnose the failure without changing the target or hiding the original evidence.
Learning objectives
- Inventory controller/engine versions, ports, files, properties and trust boundary.
- Predict exact per-engine/aggregate load and data records.
- Execute a tiny remote RMI/SSL run or faithful independent-worker simulation.
- Verify aggregate/per-engine sample and target counts independently.
- Trigger one engine-b data-file mismatch and interpret JTL/log/target evidence.
- Repair only engine-b and prove a new clean 12-sample run.
1. Exact assumptions and hard ceilings
| Item | Checkpoint baseline |
|---|---|
| JMeter | Apache JMeter 5.6.3 on controller + both engines. |
| Java | Java 17 JDK on controller + both engines; no mixed Java baseline. |
| Plugins | No third-party plugins; plugin/user-JAR manifest expected empty. |
| Target | http://127.0.0.1:8025 only. |
| Engine A |
registry 1099, server engine 4000,
engine.id=engine-a, shard a001-a006.
|
| Engine B |
registry 1100, server engine 4100,
engine.id=engine-b, shard b001-b006.
|
| Controller callbacks |
client.rmi.localport=5000; up to 5000–5002.
|
| RMI security | SSL enabled; disposable shared lab keystore only. |
| Per-engine workload | 2 threads ×3 loops ×1 sampler = 6. |
| Aggregate workload | 2 engines ×6 = 12 target samples, peak 4 configured threads. |
| Pacing | 50 ms. |
| Result sender | StrippedBatch explicit/default. |
| Data policy | No recycle; stop thread at EOF; six unique rows/shard. |
| Evidence | aggregate JTL + ENGINE_ID/ACCOUNT_ID, controller/engine logs, target JSONL, versions/hashes/ports/resource state. |
| Fallback | two independent CLI workers + JTL merge; does not validate RMI. |
2. Record the topology before execution
Controller:
127.0.0.1
callbacks 5000-5002
JMX + remote-global.properties + aggregate JTL
Engine A:
registry 1099
server engine 4000
engine.id=engine-a
data.file=.../data/engine-a.csv
Engine B:
registry 1100
server engine 4100
engine.id=engine-b
data.file=.../data/engine-b.csv
Target:
127.0.0.1:8025
3. Engine/controller inventory and file manifest
For every node record:
jmeter -voutput = 5.6.3;java -version= approved Java 17 build;- JMX SHA-256;
- engine shard SHA-256;
-
third-party
lib/extplugin/JAR list (expected none); - RMI registry/server/callback ports and SSL keystore identity;
- UTC epoch/time and generator CPU/heap/network baseline.
4. Predictions before the valid run
P1 — workload: engine-a=6, engine-b=6, aggregate JTL=12, target events=12.
P2 — data: engine-a uses exactly a001-a006; engine-b uses exactly b001-b006; 12 unique accounts; no duplicates.
P3 — properties:
-Gthreads/-Gloops/-Grun.id/-Gtarget.* are identical on
both engines; engine.id/data.file stay distinct because
they were set at each server startup.
P4 — failure run: if engine-b is deliberately restarted with engine-a.csv, its six requests should fail 409 wrong_shard while RMI/controller and engine-a remain healthy.
5. Target authorization/preflight
curl.exe --fail --silent http://127.0.0.1:8025/health
curl.exe --fail --silent -X POST http://127.0.0.1:8025/reset
curl.exe --fail --silent http://127.0.0.1:8025/stats
State must show zero requests/errors before each run.
6. Execute the valid two-engine run
Start the two engines exactly as in Lesson 2, then run:
& "$env:JMETER_HOME\bin\jmeter.bat" `
-n `
-t .\plans\distributed-local.jmx `
-q .\config\remote-results.properties `
-R127.0.0.1:1099,127.0.0.1:1100 `
-G.\config\remote-global.properties `
-l .\results\p25-check-valid\aggregate.jtl `
-j .\results\p25-check-valid\controller-jmeter.log
If RMI is not feasible on the workstation, run the two independent
workers described in Lesson 2 with run ID
p25-check-valid, merge their JTLs, and clearly label
the evidence simulation—not RMI validation.
7. Verify valid run independently
python tools/verify_distributed.py results/p25-check-valid/aggregate.jtl results/server-events.jsonl p25-check-valid 6
Require PASS. Also inspect controller/engine logs for RMI/CSV/script errors and record generator/network headroom. A 12-row central file alone is insufficient if, for example, all 12 somehow came from one engine.
8. Deliberate configuration mismatch: wrong engine-b shard
- Preserve the entire valid run.
- Stop only engine-b.
-
Restart engine-b with
engine.id=engine-bbutdata.file=.../engine-a.csv. - Reset target state.
-
Use new run ID
p25-check-brokenand run the same 2×3 plan.
Expected:
- 12 offered samples still possible;
- engine-a six successes;
- engine-b six 409 wrong_shard failures;
-
aggregate JTL identifies
ENGINE_ID=engine-bwith a-prefixedACCOUNT_ID; - target JSONL reason=
wrong_shardfor engine-b; - controller/RMI logs need not show transport failure, proving this is an engine data-state error.
9. Diagnose before repairing
Preserve:
- broken aggregate JTL;
- controller log;
- engine-a/engine-b logs;
- engine-b startup command/property manifest;
- target event log/stats;
- file hashes.
The causal chain is: engine-b startup property → wrong local CSV → ACCOUNT_ID values → HTTP query → target shard validation → 409. Do not disable assertions or change the target.
10. Least-destructive repair
- Stop only engine-b.
- Restore
data.file=.../engine-b.csv. - Restart engine-b with same secured RMI ports/version.
- Reset target state.
- Run new ID
p25-check-repaired. - Require verifier PASS (6+6=12, 12 unique accounts, zero failures).
11. Aggregate/result evidence requirements
| Evidence | Required observation |
|---|---|
| Total-load calculation | 2 engines ×(2×3×1)=12; peak configured threads=4. |
| -R/-G command | exact engine endpoints and remote-global properties. |
| File/plugin manifest | JMX/shard hashes; identical core/plugin state; no third-party plugin. |
| Aggregate JTL | 12 rows, ENGINE_ID/ACCOUNT_ID columns, zero failures in valid/repaired. |
| Per-engine count | six engine-a + six engine-b in JTL and target events. |
| Controller log | remote initialization/completion/result-transfer state. |
| Engine logs | startup/RMI/CSV/script/test state for both engines. |
| Target JSONL | 12 unique accounts valid/repaired; broken run shows six wrong_shard. |
| Generator state | controller + both engines CPU/heap/network and sender mode. |
12. Configured-versus-achieved validity statement
13. Network/security verification
-
RMI SSL enabled; no
server.rmi.ssl.disable=true. - Only approved private/loopback registry/server/callback ports reachable.
- Controller callbacks 5000–5002 accounted for.
- No public port-forward/NAT exposure.
- Lab keystore treated as disposable and not reused as production PKI.
- Controller/engine clocks recorded/synchronized.
14. Cleanup / rollback
-
Stop both remote engines gracefully after evidence capture;
-Xis an optional CLI remote-exit mechanism when intentionally included in the run contract. - Stop the localhost fixture.
- Preserve valid/broken/repaired JTL/log/manifests/events until review.
- Delete the disposable RMI keystore only after no lab engine/controller needs it.
- Remove temporary CSV shards/simulation files after the retention window.
- No production/public target, real credential, remote public RMI service, recorder CA, managed cloud, paid CI, database/message system or OS/JVM global tuning was changed.
15. What Chapter 25 adds to the operating model
The production performance-testing operating model now has a distributed generator contract: controller/engine inventory, exact JMeter/Java/plugin versions, engine-local properties/files, total-load equation, secured/fixed RMI topology, remote-global property manifest, data-shard ownership/hashes, sender/result strategy, central/per-engine logs/results, clock synchronization, controller/engine resource evidence, missing-engine policy, target per-engine counts and cleanup are required before distributed capacity results are trusted.
Chapter 26 moves to Load Generator Sizing, JVM Tuning, OS Limits, and Network Capacity. It measures how many threads/sockets/bytes/requests each generator can sustain before the generator—not the SUT—becomes the limiting system.
Knowledge check
What are the two most important pre-run predictions in this checkpoint?
Total offered load (6+6=12) and exact data ownership (a001-a006 on engine-a, b001-b006 on engine-b).
Why does the broken engine-b run prove a data configuration failure rather than RMI failure?
Engine-b still executes and returns samples; its ENGINE_ID is correct, but ACCOUNT_ID comes from the wrong shard and the target explicitly rejects it.
If aggregate JTL has 12 rows but target shows 12 from engine-a and zero from engine-b, is the run valid?
No. Per-engine distribution is wrong; total count alone cannot prove the planned distributed workload.
What does the simulation fallback fail to validate?
RMI SSL, registry/server/callback ports, remote sample transport and controller bottleneck behavior.
What is Chapter 26's bridge?
Measure generator JVM/OS/network/socket capacity so the distributed fleet can be sized from evidence instead of adding engines blindly.
Official references and version notes
- JMeter User Manual — Remote (Distributed) Testing — full-plan replication, same-version guidance, data-file behavior, RMI SSL, remote ports, CLI remote execution, and sample sender modes.
-
JMeter Getting Started
—
-r,-R,-G,-X, CLI/server mode, property semantics. - JMeter Properties Reference — remote hosts, controller/server RMI ports, SSL keystore/truststore settings, client failure policy and result properties.
- Component Reference — CSV Data Set Config — distributed CSV file placement and relative/absolute path behavior.
- JMeter Listeners / Result files — CSV result fields, sample variables and host attribution.
- Apache JMeter downloads — current stable release and Java requirement.
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 remote mode sends the test
plan to every remote server, but each engine runs the entire test
plan; workload is not divided automatically. All controller/server
nodes should run exactly the same JMeter version, and JMeter
discourages mixing Java versions. External data files are not sent
by the controller and must exist on every server in the expected
path; plugins/user JARs likewise need an explicit identical
deployment. CLI -r starts servers listed in
remote_hosts; -Rhost1,host2 explicitly
selects/overrides the server list; -Gname=value or
-Gpropertyfile sends JMeter properties to remote
servers. Since JMeter 4.0, RMI uses SSL by default. JMeter ships
create-rmi-keystore, whose generated test certificate
is documented as valid for seven days and uses default alias
rmi/passphrase changeit; these defaults
are suitable only for a disposable private lab.
server.rmi.ssl.disable defaults to false and this
chapter never disables it. The remote-testing manual describes
dynamic server-engine ports conceptually, while the current
properties reference lists
server.rmi.localport default 4000; production/private
labs should set registry/server/callback ports explicitly instead
of relying on defaults. The controller reverse callback
client.rmi.localport defaults to 0 (random); when set
non-zero JMeter can use up to three consecutive ports. Current
default remote sample sender mode is StrippedBatch:
successful response bodies are stripped and results are batched.
Remote mode can consume more resources than equivalent independent
CLI workers, and the controller/client or its network can become
the bottleneck.
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.