skip to content

Why does one JMeter results file give a different 90% Line in the HTML dashboard and in the Aggregate Report?

level: middleimportance: must knowfreq 58%

answer

  1. One file, two report surfaces
  2. Not a filtering difference
  3. One interpolates, one picks a sample
  4. commons-math3 EstimationType, default LEGACY

basics

~20 s

The two surfaces use different percentile estimators. JMeter's HTML dashboard uses the commons-math3 LEGACY type, which interpolates between neighbouring samples; the Aggregate Report listener returns an actually observed elapsed time. Set backend_metrics_percentile_estimator=R_3 to align them.

solid answer

~40 s

JMeter computes the two tables with unrelated code. The dashboard's Statistics table builds each percentile as a `PercentileAggregator`, which wraps a commons-math3 `DescriptiveStatistics` whose estimation type comes from the `backend_metrics_percentile_estimator` property. Its default, `LEGACY`, places the index at `p x (N + 1)` and **interpolates linearly** between the two order statistics either side, so it can print a figure no sample ever recorded. The Aggregate Report listener instead calls JMeter's own `StatCalculator.getPercentPoint`, which rounds `N x p` and walks a sorted map of observed elapsed times, always returning a time some sample actually took. With nine samples from 100 to 180 ms plus one 900 ms outlier, the dashboard prints **828 ms** and the listener prints **180 ms** for the same 90% Line. Setting `backend_metrics_percentile_estimator=R_3` makes the dashboard pick an order statistic too.

code

text · 7 lines
text
elapsed values for one label in results.jtl (ms):
100 110 120 130 140 150 160 170 180 900

HTML dashboard  90% Line = 828   (LEGACY: index 0.9 x 11 = 9.9,
                                  interpolate 180 -> 900)
Aggregate Report 90% Line = 180   (round(10 x 0.9) = 9 -> ninth value)
HTML dashboard  90% Line = 180   after backend_metrics_percentile_estimator=R_3

go deeper

for a junior

Recall that JMeter has two places a 90% Line appears - the HTML dashboard and the Aggregate Report listener - and that the same results file can show different values in them.

for a middle

Explain the mechanics: the dashboard's percentile aggregator interpolates under the LEGACY estimation type, the listener returns an observed elapsed time, and one property switches the dashboard to R_3.

for a senior

Show you would notice. Read both surfaces on a real run, spot the gap before someone quotes a number in a report, and pin the estimator on the machine that generates the report.

for a principal

Own the choice for the team: which estimator every JMeter install carries, where that property file lives, and the fact that the key also moves whatever the Backend Listener streams live.

## Two code paths over the same file A JMeter run writes one JTL. Loading that file into an **Aggregate Report** listener and generating the **HTML dashboard** from it look like two views of one dataset, but the percentile columns are produced by two unrelated pieces of code. - The dashboard's Statistics table comes from `StatisticsSummaryConsumer`. Its median and its three percentile figures are `PercentileAggregator` instances, and `PercentileAggregator` delegates to a commons-math3 `DescriptiveStatistics` built by `DescriptiveStatisticsFactory`, which calls `new Percentile().withEstimationType(...)`. - The Aggregate Report listener (`StatVisualizer`, sharing its table model with `StatGraphVisualizer`) calls `SamplingStatCalculator.getPercentPoint`, which is JMeter's own `org.apache.jorphan.math.StatCalculatorLong`. No commons-math3 is involved on that path at all. ## What the LEGACY estimator does The estimation type is read from the JMeter property `backend_metrics_percentile_estimator`, and its default is `LEGACY` — commons-math3's own historical type. `LEGACY` computes a fractional index of `p x (N + 1)` over the sorted values and interpolates linearly between the order statistics on either side: ``` value = lower + fraction x (upper - lower) ``` Two consequences follow directly: 1. the printed figure need not equal any elapsed time in the file; 2. when the two neighbouring order statistics are far apart — a fast body with one slow tail sample — the interpolation drags the result a long way toward the slow one. That is exactly what the manual means when it warns the divergence is "most observable when the distribution of the timing values is spread too wide" or when too few samples were taken. ## What the Aggregate Report does `StatCalculator` keeps a `TreeMap` of distinct elapsed times to their counts. `getPercentPoint(p)` computes `Math.round(N x p)` and walks that sorted map subtracting counts until the target reaches zero, then returns **that key**. It never interpolates, so the 90% Line is always a time some sample actually took. It is also why the component reference warns that this listener needs extra memory for Median and the 90% Line, and why the Summary Report — which stores no individual samples — offers no percentile columns at all. ## The worked example Nine samples at 100, 110 ... 180 ms and one at 900 ms, all under one label: | Surface | Estimator | 90% Line | |---|---|---| | HTML dashboard, out of the box | `LEGACY` | 828 ms | | HTML dashboard, estimator switched | `R_3` | 180 ms | | Aggregate Report listener | `StatCalculator` | 180 ms | `LEGACY` puts the index at `0.9 x 11 = 9.9` — nine tenths of the way from the ninth value (180 ms) to the tenth (900 ms) — giving 828 ms. The listener rounds `10 x 0.9` to 9 and hands back the ninth value, 180 ms. Same file, same label, same requested percentile. ## Making them agree The manual's own instruction is to change the estimator: - set `backend_metrics_percentile_estimator=R_3` — note there is **no** `jmeter.reportgenerator.` prefix on this key; - `R_3` takes the index as `rint(N x p)`, an integer, so the interpolation weight is zero and an observed order statistic comes back; - the key is absent from the shipped `bin/jmeter.properties`, so you add the line to `user.properties` yourself. ## What switching does not fix - `R_3` rounds half to even while `StatCalculator` uses `Math.round` (half up), so where `N x p` lands exactly on `.5` the two can still land one order statistic apart. The manual says "more or less the same" and means it. - The estimator is read once into a `static final` field, so it is a JVM-wide choice made before the run starts, not something a plan can flip. - The dashboard's percentiles are additionally capped by `jmeter.reportgenerator.statistic_window` (20,000 samples per row by default) while the listener has no window at all — an independent second source of divergence on a large run. - The same key feeds the Backend Listener's `SamplerMetric`, so flipping it also moves the percentiles streamed live.

  • Which of the two figures should a reviewer treat as an elapsed time that really occurred?
    The Aggregate Report's. `StatCalculator.getPercentPoint` returns a key from its map of observed elapsed times, so 180 ms is a time some sample took. The dashboard's default `LEGACY` figure of 828 ms is an interpolation between two neighbouring samples and matched nothing in the file.
  • Does raising the sample count make the two figures converge?
    Usually, yes. The interpolation weight only moves the answer as far as the gap between neighbouring order statistics, and with many samples that gap shrinks. The manual frames the divergence as worst when values are widely spread or too few samples were taken. It is a reduction, not a guarantee.
  • Where does the dashboard's Median column sit in this?
    On the same footing. In `StatisticsSummaryData` the median is a fourth `PercentileAggregator`, so it uses the identical estimator and the identical sliding window as the three percentile columns, and it can diverge from the listener's Median in exactly the same way.

saying these in an interview costs you the question

  • Claims the dashboard and the listener read different result files
  • Calls one of the two numbers a JMeter bug
  • Assumes the dashboard silently drops failed or slow samples
  • Thinks the Aggregate Report can print a time no sample recorded
  • Says aggregate_rpt_pct1 selects the estimation formula