Chapter 04Lesson 05~180 minutes

Checkpoint Lab — Thread Groups, Virtual Users, Ramp-Up, Loops, and Workload Modeling

Complete the chapter by comparing two different workload questions against the same local service: a bounded population of concurrent users and a stable rate-oriented sampler schedule. The exercise is intentionally small so the evidence—not the traffic volume—is the focus.

CheckpointConcurrency profileRate profileActive threadsRun manifest

Learning objectives

  • Design a concurrency-oriented classic Thread Group profile from a bounded-user question.
  • Design a stable rate-oriented profile using classic threads plus Precise Throughput Timer.
  • Predict sample count/rate, active threads, target max concurrency, and timer effects before execution.
  • Run both profiles from CLI with thread-count JTL fields and unique artifacts.
  • Compare achieved throughput, p50/p95 elapsed time, errors, fixture concurrency, and generator headroom.
  • State why neither tiny local profile proves production capacity and bridge to Chapter 05 HTTP behavior.

1. Checkpoint assumptions and hard limits

Item Baseline
JMeter Apache JMeter 5.6.3.
Java Java 17 JDK lab baseline; JMeter 5.6.3 requires Java 8+.
Plugins None.
Target http://127.0.0.1:8000 only.
Fixture delay 100 ms per /work request.
Concurrency profile ceiling 3 threads × 4 loops = at most 12 samples.
Rate profile ceiling 4 classic threads, 6-second scheduler window, target ~1 sample/sec via Precise Throughput Timer; bounded duration.
Result contract Unique JTL + jmeter.log + run manifest + fixture stats + generator observation.
Abort conditions: target mismatch, fixture unhealthy, unexpected errors, rate/profile settings above the stated ceilings, or unsafe generator pressure. Do not convert this checkpoint into a stress test.

2. Start a fresh fixture

Use the same workload_fixture.py from Lesson 2. Restart it before each profile so total=0 and max_active=0. Verify /health and record the initial /stats.

3. Profile A — concurrency-oriented users

Question: “How does this synthetic endpoint behave when up to three interactive users repeat the same request with think time?”

Configure a classic Thread Group:

  • Threads: 3
  • Ramp-up: 3 seconds
  • Loops: 4
  • Constant Timer: 200 ms
  • Sampler: GET /work?delay_ms=100
  • Connect timeout: 1000 ms; response timeout: 2000 ms

Configured maximum: 12 samples. This profile controls the user population; it does not target a fixed samples/second value.

4. Predict Profile A

  1. Exactly 12 samples are expected if all users complete four loops with no early stop/error.
  2. JTL grpThreads should show the classic population ramping up and later ending.
  3. Fixture max_active may be below 3 because the 200 ms think time and 100 ms service windows do not guarantee simultaneous requests.
  4. Achieved throughput emerges from the users' closed loops; it is not configured directly.

5. Run Profile A

mkdir -p results/profile-a-concurrency
jmeter -n   -t plans/profile-a-concurrency.jmx   -l results/profile-a-concurrency/results.jtl   -j results/profile-a-concurrency/jmeter.log   -Jjmeter.save.saveservice.print_field_names=true   -Jjmeter.save.saveservice.thread_counts=true   -Jsampleresult.timestamp.start=false
python tools/analyze_jtl.py results/profile-a-concurrency/results.jtl
curl --fail --silent http://127.0.0.1:8000/stats

PowerShell uses the same JMeter flags with jmeter.bat and backtick line continuation. Record generator CPU/memory during the run.

6. Profile B — stable rate-oriented fallback

Question: “Can a bounded thread pool issue roughly one sample per second for a short window, and what active concurrency is required at this target delay?”

Use a classic Thread Group plus a Precise Throughput Timer scoped to the single Work sampler:

  • Threads: 4 (headroom for serving timer departures)
  • Ramp-up: 0
  • Loop Count: 100 (high ceiling; scheduler should end first)
  • Specify Thread lifetime: enabled
  • Duration: 6 seconds
  • Sampler: GET /work?delay_ms=100
  • Precise Throughput Timer target: 6 samples per 6 seconds
  • Timer test duration: 6 seconds
  • Batch size: 1
  • Batch delay: 0 ms
  • Random seed: 4242 (repeatable schedule)

This is a rate-oriented sampler schedule over a classic pool, not an assertion that four users equal one RPS and not a semantic substitute for the experimental Open Model Thread Group.

7. Predict Profile B

  1. The configured target is about one sample per second over the six-second window.
  2. Achieved sample count/rate must be measured; scheduler boundaries and timer schedule placement can make a tiny edge-window run differ from a perfect integer target.
  3. Because service time is ~100 ms and target rate is low, target max_active should normally remain low even though four JMeter threads exist.
  4. JTL thread counts may show several JMeter threads alive while only one request at a time is active at the fixture. Thread liveness and target request concurrency are different states.

8. Run Profile B

Restart the fixture first, then:

mkdir -p results/profile-b-rate
jmeter -n   -t plans/profile-b-rate.jmx   -l results/profile-b-rate/results.jtl   -j results/profile-b-rate/jmeter.log   -Jjmeter.save.saveservice.print_field_names=true   -Jjmeter.save.saveservice.thread_counts=true   -Jsampleresult.timestamp.start=false
python tools/analyze_jtl.py results/profile-b-rate/results.jtl
curl --fail --silent http://127.0.0.1:8000/stats

If the configured target is missed, do not add threads automatically. First inspect whether the six-second boundary clipped a scheduled departure, whether the timer/config is correct, whether the injector had headroom, and whether the target response time permitted the schedule.

9. Compare the two workload models

Evidence Profile A: concurrency Profile B: rate-oriented
Primary control 3 classic users. Timer target ~1 sample/sec over 6 sec.
Thread population Ramps over 3 sec. Up to 4 helper threads available.
Sample count Expected 12 if loops complete. Observed count is measured around the short scheduled window.
Throughput Outcome of user loops + timer + target. Schedule target, still subject to achievement limits.
Target max concurrency Emerges from overlapping user requests. Expected low at 1/sec and 100 ms service, but verify.
Best question Bounded-user concurrency behavior. Rate/departure behavior with a stable built-in timer.

10. Active-thread series versus target concurrency

Use JTL grpThreads/allThreads to inspect how many JMeter threads are alive when samples are recorded. Use fixture max_active to inspect simultaneous target requests. These numbers need not match. Profile B can keep several helper threads alive while the timer allows only sparse request departures.

11. Percentiles, errors, and validity

Compare p50/p95 elapsed and error rate only as local checkpoint evidence. Sample populations are tiny, schedules differ intentionally, and there is no production network or server stack. A lower p95 in one profile does not prove one workload model is “better”; it means the endpoint was observed under a different demand pattern.

12. Optional Open Model comparison

If you want to see true scenario-arrival syntax under the current experimental component, create a third optional profile with one sampler:

rate(1/sec) random_arrivals(6 sec) pause(2 sec)

Record JMeter 5.6.3 explicitly and label the run experimental. Do not replace the stable checkpoint requirement with this plan, and do not assume the syntax/termination behavior remains unchanged after an upgrade.

13. Required evidence packet

Artifact Required content
Profile A JMX + settings note Threads, ramp, loops, timer, sampler, safety ceiling.
Profile B JMX + settings note Threads, duration, Precise Throughput Timer target/period/seed.
Two run manifests JMeter/Java, target, schedule, expected ceiling, run ID.
Two JTLs Raw samples including thread-count fields.
Two jmeter.log files Engine/runtime diagnostics.
Analyzer output Samples, achieved rate, p50/p95, errors, max observed JMeter thread counts.
Fixture stats Independent total/max-active target evidence.
Generator observation CPU/memory/network note showing whether injector headroom looked safe.
Validity statement What each profile demonstrates and what it cannot establish.

14. Validity statement

Example: “Using JMeter 5.6.3 with Java 17 against the loopback-only fixture, Profile A controlled a three-user closed workload and Profile B controlled a low-rate sampler schedule over a bounded classic pool. The recorded JTL, fixture concurrency, errors, percentiles, and generator observations describe those exact local profiles. The comparison demonstrates the difference between user-concurrency control and rate-oriented departure control; it does not establish production capacity, a production SLO, or the semantics of the experimental Open Model component in future JMeter releases.”

15. Cleanup and rollback

  1. Stop the fixture.
  2. Keep both JMXs, manifests, JTLs, logs, analyzer output, and stats until review is complete.
  3. Do not carry GUI listeners into later load profiles.
  4. No production system, credential, certificate, database, remote engine, container, plugin, or system-wide tuning was changed.

16. What Chapter 04 adds to the operating model

Chapter 01 defined the experiment, Chapter 02 made the toolchain reproducible, Chapter 03 made the tree semantics reviewable, and Chapter 04 adds a workload schedule contract: who/what arrives, how users repeat, how demand ramps, how long it lasts, what rate is configured, what rate is actually achieved, and whether generator headroom makes the result valid.

Chapter 05 now moves into HTTP Request Samplers, defaults, headers, cookies, and cache state so the workload schedule can drive realistic HTTP session behavior rather than a single synthetic endpoint.

Knowledge check

What is the primary control variable in Profile A?

What is the primary control variable in Profile B?

Why can Profile B have four JMeter threads but fixture max_active near one?

If Profile B misses the configured rate, why is adding more threads not the first automatic fix?

Why can't the checkpoint declare production capacity?

Next chapter

HTTP Request Samplers, Defaults, Headers, Cookies, and Cache Managers

The workload schedule is now explicit. Chapter 05 teaches how each scheduled virtual user creates realistic HTTP requests and maintains protocol/session state without confusing JMeter's protocol client with a real browser.

Official references and version notes

  • Component Reference — current classic Thread Group, Open Model Thread Group, Precise Throughput Timer, and timer semantics.
  • Elements of a Test Plan — sampler ordering, Thread Groups, timer purpose/scope, and listener guidance.
  • Properties Reference — CSV result-save settings including thread-count fields and timestamp behavior.
  • Getting Started — GUI authoring versus CLI load execution and command-line behavior.
  • Best Practices — generator/listener practices for load execution.
  • Apache JMeter downloads — current production release and release-specific Java requirement.
Version and compatibility note

Version-sensitive statements were rechecked against current Apache JMeter primary documentation on 2026-09-04. The production baseline remains Apache JMeter 5.6.3; the release requires Java 8+, while mandatory labs use a Java 17 JDK. The classic Thread Group controls user/thread count, ramp-up, loops, and optional lifetime; with its scheduler, JMeter stops when loops finish or duration/end-time is reached, whichever occurs first, but the check happens between samples and an in-flight sampler waiting for a response is not forcibly ended by that scheduler boundary. The current Open Model Thread Group is explicitly marked experimental and may change; its schedule is evaluated at test start and its schedule end terminates/intercepts active scenario threads unless a tail pause(...) leaves time to finish. The stable mandatory rate-oriented path in this chapter uses the classic Thread Group plus the built-in Precise Throughput Timer. That timer does not create threads and cannot guarantee achieved throughput when too few threads, generator limits, target limits, or other delays prevent the schedule from being served.

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.