Skip to content

fix(analyze): judge soak memory growth on a Theil–Sen slope after a settle period - #31

Merged
psanders merged 2 commits into
mainfrom
fix/soak-memory-second-half
Nov 10, 2026
Merged

psanders merged 2 commits into
mainfrom
fix/soak-memory-second-half

Conversation

@psanders

@psanders psanders commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Why

In Routr run 42699126885, RoutrOne's RSS climbed during start-up and levelled off around minute 25. The least-squares slope over the whole window after the warm-up gave +13.7 MB/h and failed the 5 MB/h limit, although the process was not leaking. The analyzer also counted samples taken after the load ended, while RSS fell as the load wound down.

The first commit on this branch judged growth on the second half of the window. On the real data that made RoutrOne worse (+23.8 MB/h), because a dip at minutes 60–80 sat at the start of the second half. Decision (Pedro, 2026-11-10): use a settle period and a robust slope instead.

What

  • Settle period: a new optional soak field, settle. It defaults to 30m, or to half of duration when that is shorter, so 20-minute smoke soaks still work. An explicit settle must be shorter than duration. Only memory growth skips it; the other rules count from the end of the warm-up.
  • Theil–Sen slope (theilSenSlope): the median of the slopes between every pair of samples. Past 600 samples it uses an evenly spaced subset of 600, about 180,000 pairs.
  • Window (memoryGrowthWindow): from the end of the warm-up plus settle, to the end of the load (meta.endedAt). Samples taken after the load ends are left out.
  • Scope: processes and Kubernetes pods both use this rule. The live page uses it too, and shows growth once there is a full hour of samples after the settle period.
  • report.json v1, additive: each memoryGrowthMBPerHour rule gets an optional window: { from, to }, and a measured soak gets an optional settleS. Older reports stay valid. schemas/*.json are regenerated.
  • Page: "Memory growth: Theil–Sen slope from 15:00 to 16:30 UTC, after a 30 min settle". The live page's note says the same.
  • Docs: README "How verdicts are computed" (Soak), docs/perfkit-yaml.md (settle), docs/report-v1.md.

Run 42699126885, recomputed

Warm-up 10 min, settle 30 min. The window runs from 23:32 or 23:33 to 01:02 or 01:03 UTC.

Soak Process Before (least squares, warm-up to last sample) This PR (Theil–Sen)
one-soak one +13.7 MB/h, failed −0.3 MB/h, passed
k8s-soak edgeport +15.8 MB/h, failed +9.8 MB/h, failed
k8s-soak dispatcher +2.9 +3.3
k8s-soak connect, location, data, postgres, redis ≤ 1.1 ≤ 0.3

K8s edgeport climbs steadily, from 2,761 to 2,809 MB over 2 h, so it still fails.

Tests

  • RSS that rises for 15 min after the warm-up, then stays flat with ±25 MB noise, passes 5 MB/h.
  • A steady 10 MB/h rise fails.
  • Real dip: RoutrOne's real per-minute RSS from run 42699126885 (fixture test/fixtures/soak/routr-42699126885-one-rss.json) passes, with |slope| < 1 MB/h. A least-squares fit from the warm-up on fails it.
  • Samples after endedAt (rising 500 MB/h) are left out.
  • Settle: a soak's own settle moves the window. The default is half of a short duration. A settle that is not shorter than duration is rejected.
  • Unit tests: theilSenSlope (median, a dip, subsampling, edge cases), memoryGrowthWindow, settleSeconds, the live builder, and the page note.

npm test (835 passing), npm run typecheck, npm run lint (ESLint and Prettier).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WSt25UoKGj2mgDNQ4dHVrB

A soak's memoryGrowthMBPerHour is now the least-squares slope over the
second half of the window after the warm-up, for processes and Kubernetes
pods alike, and the live page uses the same window. A heap that climbs while
it settles after start-up and then levels off no longer fails the limit; a
real leak still shows in the second half.

Each memoryGrowthMBPerHour rule in report.json gets an optional window
({ from, to }) and the soak's checks card says which window the slope covers.
@psanders

Copy link
Copy Markdown
Member Author

Recomputed on the raw samples of Routr run 42699126885 (10 min warm-up, 2 h measured). This branch does not make that run pass:

Soak Process Whole window (main) Second half (this PR)
one-soak one 13.7 MB/h 23.8 MB/h
k8s-soak edgeport 15.8 MB/h 11.4 MB/h
k8s-soak dispatcher, connect, location, data, postgres, redis ≤ 2.9 ≤ 2.0

Why one gets worse: RSS dipped to 2,281–2,297 MB around minutes 60–80, then went back to 2,310–2,352 MB, so the second half starts in the dip and slopes upward. Other windows for one: minutes 25–130 give 2.9 MB/h, minutes 40–130 give −0.4 MB/h.

A separate issue: the analyzer also counts samples taken after meta.endedAt, while the load winds down and RSS drops to about 2,200 MB. Ending the second half at endedAt gives 33.1 MB/h for one.

k8s edgeport climbs slowly but steadily (2,761 to 2,809 MB over 2 h), so it fails under either rule.

A maintainer should decide whether the second-half rule stays, or whether a fixed settle period after the warm-up (or a longer warm-up) fits better.

…ettle period

Replace the second-half rule: memory growth is now the Theil-Sen slope
(median of pairwise slopes, an evenly spaced subset of 600 samples on long
runs) from the end of the warm-up plus a settle period to the end of the
load. Samples taken after meta.endedAt, while the load winds down, no longer
count. The settle period is a new optional soak field, settle (default 30m,
or half of duration when that is shorter); the report records settleS.
Processes, Kubernetes pods and the live page all use the same window.
@psanders psanders changed the title fix(analyze): judge soak memory growth on the second half of the window fix(analyze): judge soak memory growth on a Theil–Sen slope after a settle period Nov 10, 2026
@psanders
psanders merged commit 731358f into main Nov 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant