Checkpoint Lab — Performance Testing Foundations: Load, Stress, Spike, Soak, and Capacity
Complete the chapter with one evidence-driven performance experiment against a disposable local service. The goal is not a large load number; it is a defensible chain from question and authorization to observed samples, generator/target state, and an explicit validity limit.
Learning objectives
- Create a concrete performance-test charter with hypotheses, acceptance criteria, ceilings, and stop conditions.
- Predict workload, target, result, and generator changes before execution.
- Execute one deliberately small loopback baseline with JMeter 5.6.3 in CLI mode.
- Verify sample count, failures, elapsed-time percentiles, approximate throughput, fixture counters, and generator health.
- Preserve an evidence packet that supports independent review.
- Write a validity note that states both what the run demonstrates and what it cannot demonstrate.
1. Scenario: a local synthetic work endpoint
Use the same fixture_server.py from Lesson 2. Start
from a fresh fixture process so its request counter begins at zero.
The checkpoint plan requests /work?delay_ms=60 with two
threads and ten loops per thread, for a maximum of 20 samples. This
is deliberately tiny: the learning objective is experimental
discipline, not stress.
2. Exact assumptions
| Item | Checkpoint baseline |
|---|---|
| JMeter | Apache JMeter 5.6.3. |
| Java | Java 17 local lab runtime; JMeter 5.6.3 itself supports Java 8+. |
| Plugins | None. |
| Target | 127.0.0.1:8000 only. |
| Fixture |
Python standard-library
ThreadingHTTPServer from Lesson 2.
|
| Configured work | 2 threads, 2-second ramp, 10 loops each, one sampler = at most 20 samples. |
| Request | GET /work?delay_ms=60. |
| Timeouts | 1 s connect, 2 s response. |
| Evidence |
JMX, CLI command, JTL, jmeter.log, fixture
/stats, Java/JMeter versions, generator
resource snapshot, validity note.
|
3. Write the charter before execution
| Charter field | Checkpoint statement |
|---|---|
| Business question | Can this disposable fixture complete a tiny concurrent baseline correctly while producing reproducible client/server evidence? |
| Hypothesis | With two threads and 60 ms synthetic service delay, 20 samples should complete with zero failed samples; elapsed time should cluster near or above the injected delay, with ordinary local scheduling variation. |
| Acceptance | 20 recorded samples, zero failed samples, fixture total = 20 from a fresh process, no unsafe injector pressure. |
| Stop conditions | Target not loopback; unexpected errors; fixture health fails; workstation becomes unstable; any unplanned workload change. |
| Non-goal | Do not estimate production capacity, production SLO compliance, or a maximum sustainable request rate. |
4. Predict state changes before the run
- Workload state: two JMeter threads will each execute ten iterations, so 20 sampler attempts are expected.
-
Target state: the fresh fixture's
totalcounter should move from 0 to 20 if all requests reach it. - Result state: the JTL should contain 20 sample rows; each successful row should report an elapsed time that includes the synthetic delay and client/server overhead.
- Generator state: Java/JMeter will briefly consume CPU, memory, sockets, and disk for results; this tiny run should not create material sustained pressure.
These predictions make the run falsifiable. If any one is wrong, investigate rather than rewriting the story after the fact.
5. Preflight and authorization checklist
-
Fixture process prints
fixture=http://127.0.0.1:8000. /healthreturns{"status":"ok"}.-
/statsreportstotal: 0from the fresh fixture. -
jmeter -vandjava -versionoutputs are captured. -
JMX contains
127.0.0.1, port 8000, two threads, ten loops, and the bounded timeouts. -
A unique empty result directory such as
results/checkpoint-001exists. - You know how to stop JMeter/fixture and will abort if the target or resource state differs from the charter.
6. Checkpoint JMX
Save this as chapter01-checkpoint.jmx. It is
intentionally simple and plugin-free.
<?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 01 Checkpoint Baseline" 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="Checkpoint Baseline — 2 users × 10 loops" 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">10</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">2</stringProp>
<stringProp name="ThreadGroup.ramp_time">2</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=60</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>
7. Execute once in CLI mode
mkdir -p results/checkpoint-001
jmeter -n -t chapter01-checkpoint.jmx -l results/checkpoint-001/results.jtl -j results/checkpoint-001/jmeter.log
PowerShell:
New-Item -ItemType Directory -Force results\checkpoint-001 | Out-Null
& "$env:JMETER_HOME\bin\jmeter.bat" -n `
-t chapter01-checkpoint.jmx `
-l results\checkpoint-001\results.jtl `
-j results\checkpoint-001\jmeter.log
While it runs, observe generator CPU/memory and keep the fixture terminal visible. If the machine becomes unstable or any guard fails, stop rather than “pushing through.”
8. Verify independently
After completion, query target state:
curl --fail --silent http://127.0.0.1:8000/stats
Expected from a fresh fixture is a total of 20 requests. Then adapt
the Lesson 2 analyzer path to
results/checkpoint-001/results.jtl and record sample
count, error count, p50, p95, and approximate samples/second. Treat
the p95 as a description of this tiny sample set only.
Also inspect jmeter.log for unexpected errors. A
successful process exit is not a substitute for checking sample
correctness and the target counter.
9. Build the evidence packet
| Artifact | What it proves | What it does not prove |
|---|---|---|
chapter01-checkpoint.jmx |
Configured workload and target. | That the workload was achieved exactly as intended. |
| CLI command/run record | Exact execution inputs and artifact locations. | Sample correctness. |
results.jtl |
Recorded sample outcomes/timing fields. | Root cause of any slowdown. |
jmeter.log |
JMeter engine/runtime diagnostics. | Server health by itself. |
Fixture /stats |
Independent count of requests reaching the local service. | A production business transaction count. |
| Generator resource snapshot | Whether the injector obviously lacked headroom. | Complete low-level performance diagnosis. |
| Version record | Java/JMeter environment identity. | Future compatibility with different releases. |
| Validity note | Scope of the conclusion and known limitations. | A replacement for raw evidence. |
10. Write the validity note
A strong checkpoint note is explicit:
The sentence about what the run does not prove is part of professional performance engineering, not a disclaimer to hide weak results.
11. Cleanup and rollback
- Stop the fixture with Ctrl+C.
- Confirm no local fixture process remains listening on port 8000.
-
Keep
results/checkpoint-001until review is complete. - Do not delete the failed or surprising run if diagnosis is still open.
- No certificates, accounts, databases, remote engines, plugins, or system-wide settings were changed by this lab.
12. Checkpoint completion criteria
- The target remained loopback-only and authorized.
- The configured ceiling was not increased.
- Predicted sample count and target counter were checked.
- JTL and
jmeter.logwere preserved. - Generator health was observed rather than assumed.
- Percentiles were reported with the tiny-sample limitation.
- The validity note clearly separates evidence from unsupported capacity claims.
Knowledge check
Why must the checkpoint begin with a fresh fixture process?
So the /stats total starts from a known state and can independently verify how many requests this run delivered.
If the JTL has 20 rows but the fixture reports 18 requests, is the checkpoint complete?
No. The evidence sources disagree. Preserve the artifacts and investigate sample failures, target reachability, and exact run inputs.
Can a zero-error 20-sample run prove production capacity?
No. It is a deliberately tiny loopback baseline with insufficient workload, duration, environment fidelity, repetition, and server telemetry for a production-capacity claim.
Why record generator health in such a small lab?
To establish the operating habit that injector headroom is part of test validity. The same requirement becomes critical at larger loads.
What is the bridge from this chapter to Chapter 02?
Chapter 01 defined the experiment and evidence contract. Chapter 02 now explains JMeter architecture, Java setup, installation, CLI entry points, and how to author the first test plan reproducibly.
Official references and version notes
- Apache JMeter downloads — current production-release and Java requirement baseline.
- Getting Started — GUI authoring, CLI load execution, Java requirements, CLI flags, and operational guidance.
- Best Practices — load-generation and result-collection practices.
- Component Reference — Thread Group and current Open Model Thread Group status.
- HTML Dashboard Report — result-report terminology and percentile-oriented reporting.
Version-sensitive statements were rechecked against current Apache
JMeter primary documentation on 2026-09-04. The production
download is Apache JMeter 5.6.3 and requires Java 8+. The
mandatory Chapter 01 executable lab uses Java 17 as a pinned local
lab choice, no third-party plugins, no distributed engines, and
only the loopback target 127.0.0.1:8000. Apache
guidance requires CLI mode for actual load execution; GUI mode is
limited to construction and bounded debugging. The Open Model
Thread Group is discussed conceptually only and remains marked
experimental in the current Component Reference.
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.