Thread Groups, Virtual Users, Ramp-Up, Loops, and Workload Modeling: Core Concepts and Mental Model
Thread Group fields are not arbitrary “load knobs.” They describe how virtual users are created and allowed to repeat a scenario. The target request rate that actually occurs is an outcome of that schedule, the scenario, timers, response time, errors, and generator/target limits.
Learning objectives
- Separate configured threads, active threads, iteration rate, request rate, arrival rate, and achieved throughput.
- Trace one classic Thread Group user from thread start through repeated scenario loops and termination.
- Explain exact ramp-up behavior and why a thread count is not an RPS target.
- Distinguish loop-bound and duration-bound termination, including in-flight sampler behavior.
- Contrast closed/concurrency-oriented and open/arrival-oriented workload questions.
- Identify generator and target evidence required before interpreting queueing or capacity.
1. From tree semantics to workload semantics
Chapter 03 answered “what does one thread execute?” Chapter 04 asks “how many scenario executions exist over time, when do they start, how often do they repeat, and what rate does that produce?” This is the point where an otherwise correct JMX can become an invalid performance experiment.
http://127.0.0.1:8000. Chapter labs use at most 4
classic threads, short durations, deliberate timers, and small
sample counts. Do not translate these examples into higher traffic
or external targets without a new authorization/capacity envelope.
2. Mental model: workload objective to achieved rate
Use this diagram as a workload-state model, not as a promise that configured values become achieved values. The prose below explains where feedback from response time, timers, generator limits, and target limits enters.
flowchart TD Q[Performance question] --> M[Workload model] M --> TG[Thread Group / schedule] TG --> A[Thread creation or scenario arrivals] A --> L[Per-thread scenario loop] L --> T[Think time / pacing timers] T --> S[Sampler(s)] S --> R[Response time + correctness] R --> L S --> O[Observed throughput / active threads / errors] G[Generator CPU / heap / sockets / network] --> O U[Target CPU / queues / pools / downstreams] --> O O --> V[Validity judgment]
Classic GUI vocabulary: the field named Number of Threads (users) is the configured classic-user count. It is not a request-rate field.
A workload objective is the demand pattern the experiment is trying to represent. The Thread Group turns that intent into thread creation, ramp-up, loops, or a bounded lifetime. Each thread executes the JMX scenario. Timers add think time/pacing before samplers. Sampler response time then feeds back into how quickly a closed user can begin the next iteration. Generator and target limits can slow or prevent the intended schedule from being achieved.
3. Vocabulary: user lifecycle, arrivals, and rates
| Term | Precise meaning here | Do not substitute |
|---|---|---|
| Configured threads | Maximum user threads requested by a classic Thread Group. | Requests/second. |
| Active threads | Threads currently alive at a particular sample observation. | Configured threads; ramp-up/termination make them differ. |
| Iteration | One traversal of the Thread Group's test case/loop body. | One request when the scenario has multiple samplers. |
| Iteration rate | Completed scenario iterations per time unit. | Request rate. |
| Request/sample rate | Sampler executions/results per time unit. | User arrivals. |
| Arrival rate | Rate at which new independent scenarios/users are started in an open model. | Classic active-user count. |
| Ramp-up | Classic Thread Group interval over which configured threads are started. | Warm-up or request-rate ramp by itself. |
| Think time | Intentional user wait, usually modeled with a Timer. | Server latency. |
| Duration | Thread Group lifetime bound when scheduler/lifetime is enabled. | A guarantee that an in-flight sampler ends exactly at that second. |
| Achieved throughput | Observed completed samples/transactions per time unit. | Configured demand. |
4. Classic Thread Group is naturally concurrency-oriented
In a closed user model, a fixed or ramped pool of threads repeatedly runs the scenario. A thread cannot begin its next iteration while it is still waiting for the current sampler and think time. If the target slows down, the same users complete work less often and achieved throughput may fall. That feedback is a defining property of a closed workload.
This is often appropriate for bounded populations such as support agents or logged-in interactive users. It is not automatically appropriate for independent external events that keep arriving even while earlier work is slow.
5. Ramp-up controls thread starts, not a request-rate slope
The current classic Thread Group documentation defines Ramp-up Period as how long JMeter should take to get all threads started. With 10 threads and 100 seconds, starts are spaced about 10 seconds apart. The first thread starts immediately, so the tenth starts around 90 seconds rather than at second 100.
A fast ramp can intentionally create a startup burst, but it can also accidentally spike the target or the injector. A very long ramp can mean the early part of the test never reaches the intended concurrent population. Choose ramp from the business scenario and experiment design, not from a generic “one second per user” folklore rule.
6. Loop Count controls scenario repetition
Loop Count says how many times each classic thread performs its test case. If one thread has five loops and the scenario has one sampler, the maximum configured sample count is five. If the same scenario contains three samplers, the maximum becomes fifteen samples—assuming no controller logic, failures, duration bounds, or early stop changes the path.
“Infinite” is a valid Thread Group option, but it is operationally dangerous without an independent lifetime bound. A bounded course or CI profile should make its stop condition explicit before starting.
7. Scheduler duration and loops race: whichever ends first
When Thread Group lifetime/scheduler is enabled, JMeter runs until the loop condition finishes or the duration/end-time boundary is reached, whichever comes first. The boundary is checked between samples. An HTTP sampler already waiting for a response is not automatically interrupted merely because the nominal duration has elapsed.
8. Open arrival models answer a different question
An open model schedules new scenario arrivals independently of how long earlier scenarios take. If the target slows, active concurrency can grow because new work continues arriving. This is useful for demands such as incoming messages, externally scheduled API jobs, or traffic that does not politely wait for previous users to finish.
JMeter 5.6.3 includes an Open Model Thread Group,
but the current Component Reference explicitly marks it experimental
and warns that it may change. Its schedule syntax uses constructs
such as
rate(1/sec) random_arrivals(30 sec) rate(2/sec). Treat
those as scenario/user arrivals; when a scenario has multiple
samplers, arrival rate is not identical to raw request rate.
9. Stable rate-oriented fallback: throttle departures, measure achievement
The built-in Precise Throughput Timer can create a rate-oriented departure schedule for samples. It does not create threads, so enough classic threads must exist to serve the schedule. If the target slows, timers are too large, or the generator lacks capacity, achieved throughput can be lower than the configured target.
This makes it useful as a stable teaching fallback for “try to issue this many samples over this period,” while preserving an important limitation: it is not the same semantic model as independent open user arrivals.
10. Queueing symptoms require both sides of the experiment
When offered work exceeds a target's ability to complete it, response time, active concurrency, queue depth, errors, or timeouts may rise. But similar symptoms can come from the injector: CPU saturation, GC, sockets, DNS, result writing, or heavy listeners.
| Evidence | What it tells you | What it cannot prove alone |
|---|---|---|
JTL grpThreads/allThreads |
Thread counts observed when samples are recorded. | Continuous thread history between samples. |
| JTL throughput/timestamps | Achieved sample completions over time. | Configured demand was fully achieved. |
| Fixture/server active/max-active | Observed server-side concurrency. | Production queue behavior in another system. |
| Generator CPU/heap/network | Whether injector headroom looks healthy. | Root cause in the SUT. |
| SUT queue/pool/CPU telemetry | Server-side saturation clues. | Whether the generator issued the intended workload. |
11. Read-only workload inspection before changing Thread Group fields
- Record Thread Group type, thread count, ramp-up, loop count, scheduler/lifetime, and startup delay.
- Count samplers/transactions in one scenario iteration and note controller branches.
- List timers and their scopes; separate user think time from target response time.
- Inspect existing JTL headers. JMeter's current property reference says thread counts are saved by default.
- Record generator and target version/environment identity before comparison.
- Predict configured maximum samples and likely active-user shape before running.
12. Why this matters in DevOps
Preserve the JTL and jmeter.log beside the workload
manifest so a reviewer can distinguish sample behavior from
engine/runtime diagnostics.
A CI gate that says “500 threads passed” is meaningless unless those threads represent an operational demand model and the achieved workload is measured. Release evidence should name the workload class, schedule, target build/environment, timers, achieved throughput, errors, percentiles, generator headroom, and validity window.
Knowledge check
Why does 20 classic JMeter threads not mean 20 requests per second?
Threads are concurrent scenario executors. Request rate also depends on sampler count, response time, timers/pacing, loop behavior, errors, and generator/target capacity.
With 10 threads and a 100-second ramp-up, when does the tenth thread approximately start?
Around 90 seconds: the first starts immediately and subsequent starts are spaced by roughly 10 seconds.
Why can a duration-bounded Thread Group finish after its nominal duration?
The duration condition is checked between samples; JMeter does not automatically interrupt a sampler already waiting for a response at the boundary.
What changes when a target slows under a closed user model?
Each fixed user completes iterations more slowly, so achieved iteration/request throughput can fall without increasing the configured user count.
Why is Open Model Thread Group not the mandatory course baseline?
The current JMeter Component Reference still marks it experimental and warns that it may change; the course provides a stable built-in fallback and teaches open-model semantics explicitly.
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-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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.