In k6, what changes when you add p(99) to summaryTrendStats, and what does not?
answer
- a column, not an assertion
- the values were already retained
- verdict and exit code untouched
- thresholds compute their own percentile
- replacement, so restate the rest
basics
~10 sAdding p(99) adds one column to every Trend row in k6's end-of-test summary and nothing else. It creates no threshold, leaves the verdict and exit code untouched, and costs no extra memory.
solid answer
~40 sAdding `p(99)` to `summaryTrendStats` is a reporting change only. Every `Trend` row in the metrics table gains a `p(99)=...` pair in the position you listed it, computed from the same retained samples the other columns use. It creates no threshold, so the run's pass/fail verdict and exit code are unaffected, and it costs no extra retention because the sink already held every value. The independence runs the other way too: a `p(99)` threshold is evaluated whether or not the column is listed, and k6 will attach that percentile to the metric's entry in the thresholds section anyway. The practical trap is writing `summaryTrendStats: ['p(99)']` and silently losing the other six columns, since assignment replaces the default.
code
javascript · 10 linesexport const options = {
// Reporting: adds one column to every Trend row.
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)'],
// Verdict: declared separately, and evaluated whether or not
// p(99) appears in the list above.
thresholds: {
http_req_duration: ['p(99)<800'],
},
};go deeper
Remember that summaryTrendStats only decides what the report prints, and that adding a token means restating the whole list rather than appending to it.
Explain that the value comes from samples already retained, so extra columns cost nothing at runtime, and that no threshold is implied by listing a statistic.
Show the independence in both directions and name the quiet failure: a replacement that drops columns nobody notices because the run still passes.
Judge whether a wide shared column set is worth the report noise on every run, versus keeping the script narrow and widening it per investigation from the command line.
## The change itself Suppose a script currently runs on the default columns and you want the 99th percentile in the report. The edit is one line: ```javascript export const options = { summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)'], }; ``` The `p(99)` token has to be added to a list that already restates the other six, because assigning to `summaryTrendStats` replaces the default rather than extending it. ## What changes - Every `Trend` row in the end-of-test metrics table gains one more `label=value` pair, printed as `p(99)=...` in the position you listed it. Column order follows list order. - The change is uniform: `http_req_duration`, `iteration_duration`, `group_duration` and every custom `Trend` all get the column, because the option is one test-wide list. - The value is read off the same retained samples the other columns come from, using the same percentile routine with `0.99`. ## What does not change This is the part interviewers are actually probing, because the option looks more powerful than it is. | you might expect | what actually happens | |---|---| | the run's pass/fail verdict shifts | unchanged — the column list is not an assertion | | the process exit code changes | unchanged — only a breached threshold moves it | | a `p(99)` threshold now exists | none is created; thresholds are declared separately | | more memory is retained | none — the sink already held every value | | the extra percentile costs a second pass | no — the slice is sorted once and reused | | some metrics opt out | no — every `Trend` gets the same columns | The single most useful thing to know is the last row of the "expect" column made explicit: **adding a statistic to the report is not adding an assertion**. `summaryTrendStats` populates a table. Nothing about a column makes k6 compare it to anything. ## The independence runs both ways The converse is just as true and slightly more surprising. A threshold that names a percentile is evaluated from the same sink whether or not that percentile is a summary column. k6 goes further: when it renders the thresholds section, if a threshold names a percentile that is not in `summaryTrendStats`, it computes that value from the sink and attaches it to the metric's entry there anyway, so the number you are being judged on is visible even though you never listed it. So the two settings are genuinely orthogonal: 1. You can print `p(99)` with no `p(99)` threshold anywhere — a report column and nothing more. 2. You can have a `p(99)` threshold with `p(99)` absent from `summaryTrendStats` — evaluated normally, and still shown in the thresholds section. 3. Adding the column to a script that already has the threshold changes only where the number appears, never its value or its effect. ## The failure mode to watch for The realistic mistake is not writing `p(99)` wrongly — a malformed token stops the run before anything else happens, with `invalid trend stat 'p99', unknown format`. The mistake is writing `summaryTrendStats: ['p(99)']`, expecting an addition and getting a replacement. The run succeeds, the report looks tidy, and five columns that people had been reading for months are gone. Because nothing errors and nothing fails, it can survive review and only surface when someone asks where `med` went. A second, milder one: adding several columns to a shared script widens the metrics table for everyone, and that is a readability cost paid on every run, so treat the list as a shared artefact rather than a per-investigation scratchpad. If a wide set is needed only occasionally, `--summary-trend-stats` on the command line changes the columns for one run without touching the script, and `K6_SUMMARY_TREND_STATS` does the same from the environment when the invocation is buried in a job definition you would rather not edit. ## Where the column lands, and what else moves with it Position is worth a moment because it is the only other visible effect. k6 renders the tokens in list order, so appending `p(99)` puts it at the right-hand end of every `Trend` row, while inserting it between `p(95)` and the rest puts it there instead. k6 then pads each column position to the widest rendered value across all metrics, so a new column can shift the horizontal layout of the whole table even for metrics whose numbers did not change. If anything downstream reads the summary text positionally, that is the thing the edit actually breaks — not the numbers. ## How to sanity-check the edit 1. Confirm the list still contains every token the report had before, since the assignment replaced rather than extended it. 2. Run once and check that a `p(99)=` pair now appears on `http_req_duration` and on every other `Trend` row, not just the metric you were interested in — the option is test-wide. 3. Confirm the exit code and the thresholds section are exactly as they were, which is the evidence that the change was a reporting change and nothing more.
- A k6 threshold names p(99) but summaryTrendStats does not list it. Is the value visible?Yes. k6 computes the percentile named by a threshold from the same Trend sink and attaches it to that metric's entry in the thresholds section of the summary, even though it is absent from the metrics table's columns. The threshold itself is evaluated normally either way.
- Could adding p(99) to summaryTrendStats ever change a k6 run's exit code?No. The column list has no bearing on the verdict; only a breached threshold changes the exit code. The one way an edit here stops a run at all is a malformed token, which fails configuration validation before the test starts rather than producing a test result.
saying these in an interview costs you the question
- Thinking a summary column acts as a pass rule
- Expecting extra columns to cost extra memory
- Assuming a threshold percentile must be listed as a column
- Writing only the new token and losing the other columns
- Believing the column list can differ per metric