skip to content

A JMeter plan runs 500 threads with a View Results Tree still attached. What does that cost?

level: middleimportance: must knowfreq 64%

answer

  1. No listener thread exists in JMeter
  2. One shared collector, many sampling threads
  3. A bounded buffer is not a cheap buffer
  4. The panel redraws twice a second

basics

~20 s

Each of the 500 threads calls the listener inside its own loop and queues behind one shared buffer. In GUI mode that buffer holds up to 500 whole sample results, response bodies included, redrawn twice a second.

solid answer

~40 s

Three costs, in order of how often they are missed. First, listeners are notified **on the sampler's own thread** - JMeter's `ListenerNotifier` processes events in the calling thread - so all 500 threads pay the listener's work inside their own loops. Second, a result collector is marked `NoThreadClone`, so there is **one shared instance**, and the View Results Tree's `add()` synchronizes on its buffer: 500 threads take turns on one lock, once per sample. Third, the buffer keeps whole sample results - response bodies and sub-samples included - which is what stops them being collected. On top of that, a Swing timer rebuilds the entire tree every `jmeter.gui.refresh_period` ms (500 by default). The `view.results.tree.max_results` cap (default 500) bounds the entry count, not the size of an entry.

code

properties · 6 lines
properties
# bin/jmeter.properties
# main samples the View Results Tree stores and displays; 0 means keep every one
view.results.tree.max_results=500

# how often listener panels redraw, in milliseconds
jmeter.gui.refresh_period=500

go deeper

for a junior

Recall the rule before the mechanism: View Results Tree is a scripting tool, not a load tool. Knowing that it retains samples and slows the sampling threads is enough at this level.

for a middle

Explain the three separate costs - the on-thread notification, the shared and synchronized buffer, the retained sample results - and say what the entry cap does and does not bound.

for a senior

Diagnose it: throughput below what the thread count should produce, heap holding response bodies, and a plan that behaves differently unattended because the listener has no panel in a non-GUI run.

for a principal

Decide how the team keeps failure evidence without paying this at load, and who is allowed to turn richer recording back on for a run and for how long.

## Where the work actually happens Three separate costs are hiding behind "a listener is attached", and none of them is the one people usually name. **First, the listener runs on the sampler's own thread.** JMeter's `ListenerNotifier` is documented as processing events *"in the calling thread"*: when a sample finishes, the thread that made the request walks the list of listeners in scope and calls each one before it can move on to its next timer and its next sample. There is no listener thread, no queue, no hand-off. Whatever the listener does, all 500 threads do it, 500 times over, inside their own loops. **Second, they all do it to one object.** A result collector is marked `NoThreadClone`, so unlike the samplers and controllers above it, it is *not* copied per thread — there is a single instance shared by the whole plan. The View Results Tree's `add()` method synchronizes on its internal buffer, so the 500 threads take turns on one monitor for every sample the plan produces. At low rates that is invisible; at 500 threads it is a queue. **Third, the buffer holds real objects.** Each entry is a whole sample result with its response body still attached, kept alive precisely so you can click it and read the response. ## The GUI's own share A Swing timer fires every `jmeter.gui.refresh_period` milliseconds — 500 by default, so twice a second — and the refresh does not append. It clears the tree's children and re-inserts a node for every buffered result, re-applying the expanded and selected state as it goes. That is a full rebuild of a 500-node tree twice a second, on the event dispatch thread, on the same machine that is trying to keep 500 request threads fed. ## What the entry cap does and does not bound Since JMeter 3.2 the View Results Tree is capped by `view.results.tree.max_results`, default 500. It is a genuine bound and it is worth knowing, but read what it bounds: | It bounds | It does not bound | |---|---| | The number of **main** samples stored and displayed | The size of each one — a retained result still holds its response body | | Growth over a long run: the oldest entry is evicted | Sub-samples; a page with 30 embedded resources is one entry carrying 31 results | | The View Results Tree specifically | **View Results in Table**, which has no equivalent cap and appends a row per result | So the cap turns an unbounded leak into a bounded one. It does not make the listener cheap, and it does nothing for the per-sample work on the thread or the contention on the shared buffer. ## The same plan in a non-GUI run The element only behaves this way when a GUI is attached to it. The result collector reaches its panel through a reference that the source itself annotates `// e.g. in non-GUI mode` — outside the GUI it is null, and the notification is dropped on the floor. Run the same `.jmx` from the command line and the View Results Tree in it retains nothing and draws nothing; it is a bare result collector that writes a record only if a filename was configured on it. That is why the same plan can be fine unattended and fall over the moment someone opens it to watch. ## What to do about it 1. **Take it out of the load plan.** Disabled is as good as deleted: disabled elements are removed when the plan is converted, before any thread starts. 2. **If it must stay, make it errors-only.** The *Log/Display Only: Errors* checkbox on the listener panel filters before both the display and the write, so a successful sample costs nothing beyond the check. 3. **Lower the cap rather than raising it.** Setting `view.results.tree.max_results` to `0` restores the pre-3.2 behaviour of keeping everything, which is the opposite of what a 500-thread run needs. 4. **Keep the tree for the scripting phase**, where a handful of threads and full response bodies are exactly what you want.

  • Does the same View Results Tree element cost anything when the plan is run without a GUI?
    Almost nothing. The result collector reaches its panel through a reference the source itself flags as null in non-GUI mode, so the notification is dropped and nothing is retained or drawn. What remains is a bare result collector, which writes a record only if a filename was configured on it. That is why a plan can behave completely differently unattended and with someone watching it.
  • Would raising view.results.tree.max_results to 5000 help you catch a rare failure at load?
    It would make things worse: it multiplies the retained sample results, each still holding its response body, and it lengthens the tree rebuild that runs twice a second. The cheap way to keep failures visible is the Log/Display Only Errors checkbox on the listener, which filters before both the display and the write, so successful samples cost nothing.

It is like asking every cashier to walk to one shared ledger and write the receipt out longhand before calling the next customer. The queue that forms is not at the till, it is at the ledger - and someone is recopying the whole ledger twice a second so it looks tidy.

saying these in an interview costs you the question

  • Thinks listeners run on their own background thread
  • Believes the 500-entry cap makes the tree cheap
  • Says the cost is only heap, never throughput
  • Assumes the tree is safe because the panel looks still
  • Raises the entry cap to catch more failures at load