Skip to content

Report OS memory pressure in TurboMalloc and trace samples#92939

Closed
sokra wants to merge 2 commits intocanaryfrom
sokra/memory-pressure
Closed

Report OS memory pressure in TurboMalloc and trace samples#92939
sokra wants to merge 2 commits intocanaryfrom
sokra/memory-pressure

Conversation

@sokra
Copy link
Copy Markdown
Member

@sokra sokra commented Apr 17, 2026

What?

Adds a new TurboMalloc::memory_pressure() method that returns a normalized OS-level memory pressure value in the range 0..=100 as Option<u8>. That value is attached to every memory sample in the tracing layer (TraceRow::MemorySample) and propagated through the trace-server so that span queries return a memory_pressure_samples vector next to the existing memory_samples.

Why?

Our current tracing only records the in-process allocator usage (TurboMalloc::memory_usage()), which does not tell us when the operating system is actually under memory pressure. We want that signal in the trace output to:

  1. Surface real OS memory pressure in trace dashboards alongside our own allocation totals.
  2. Eventually use it as input to task-eviction decisions in turbo-tasks (see branch description). This PR lands the plumbing; the eviction heuristic is not part of this change.

How?

TurboMalloc::memory_pressure() -> Option<u8> — new, in turbopack/crates/turbo-tasks-malloc/. Values are normalized so that 0 = no pressure, 100 = maximum pressure. Platform-specific backends:

Platform Source Notes
Linux /proc/pressure/memory (some avg10), fallback to (MemTotal - MemAvailable) / MemTotal from /proc/meminfo PSI is not available on all kernels (< 4.20, without CONFIG_PSI, restricted containers). The meminfo fallback keeps the signal meaningful on any standard Linux system and matches the semantics of Windows' dwMemoryLoad.
macOS kern.memorystatus_level sysctl (% free memory) Pressure = 100 - level, read via libc::sysctlbyname.
Windows MEMORYSTATUSEX::dwMemoryLoad via GlobalMemoryStatusEx (windows-sys) Already a 0–100 percentage of physical memory in use.
Other / wasm Returns None.

All runtime failures (missing file, sysctl error, failed API call, unparseable content) silently yield None rather than panicking.

Wiring into tracing:

  • TraceRow::MemorySample gains a memory_pressure: u8 field. 0 is used when memory_pressure() returns None on unsupported platforms.
  • RawTraceLayer::maybe_report_memory_sample populates it on every sample (sampling cadence unchanged).
  • This is a breaking change to the postcard wire format of MemorySample; old trace files cannot be read by the new turbopack-trace-server. Given the dev-only nature of this data that seemed acceptable — let me know if a migration is desired.

Wiring into the trace-server:

  • Store::memory_samples is now Vec<(Timestamp, u64, u8)> and add_memory_sample(ts, memory, memory_pressure).
  • A new Store::memory_pressure_samples_for_range(start, end) -> Vec<u8> mirrors memory_samples_for_range: same MAX_MEMORY_SAMPLES = 200 cap and same group-and-max downsampling, so both vectors align index-by-index for a given span query.
  • ServerToClientMessage::QueryResult gains a memory_pressure_samples: Vec<u8> field next to memory_samples.

Dependencies: libc (macOS only, cfg-gated), windows-sys with the Win32_System_SystemInformation feature (Windows only, cfg-gated). No new deps on Linux.

Verification

  • cargo build -p turbo-tasks-malloc -p turbopack-trace-utils -p turbopack-trace-server
  • cargo clippy -p turbo-tasks-malloc -p turbopack-trace-utils -p turbopack-trace-server --all-targets -- -D warnings
  • cargo test -p turbo-tasks-malloc — 7 tests pass, including:
    • memory_pressure_is_in_range: asserts Some(_) and ≤ 100 on Linux, macOS and Windows (via cfg-gated .expect()), and allows None elsewhere.
    • Parser tests for both PSI and /proc/meminfo code paths (typical content, malformed input, clamping).
  • Runtime sanity check on the Linux sandbox: /proc/pressure/memory is absent (kernel 5.10 without CONFIG_PSI); the /proc/meminfo fallback returned Some(3) as expected.

@nextjs-bot nextjs-bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Apr 17, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 17, 2026

Merging this PR will improve performance by 3.76%

⚡ 1 improved benchmark
✅ 16 untouched benchmarks
⏩ 3 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation app-page-turbo.runtime.prod.js[full] 641.3 ms 618.1 ms +3.76%

Comparing sokra/memory-pressure (24d0c6a) with canary (c526347)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@nextjs-bot
Copy link
Copy Markdown
Contributor

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms ▁▁▁▁▁
Cold (Ready in log) 441ms 442ms ▂▁▁▁▂
Cold (First Request) 818ms 828ms ███▁▁
Warm (Listen) 456ms 456ms ▁▁▁▁▁
Warm (Ready in log) 440ms 440ms ▂▂▄▁▁
Warm (First Request) 339ms 341ms ▆▇█▂▆
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 456ms 456ms █▁▁▁▅
Cold (Ready in log) 442ms 441ms █▃▁▁▂
Cold (First Request) 1.940s 1.940s █▃▁▁▅
Warm (Listen) 457ms 456ms █▅▁▅▅
Warm (Ready in log) 441ms 441ms █▃▁▁▁
Warm (First Request) 1.974s 1.971s █▂▁▁▄

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 3.928s 3.920s ▂▂▁▃▃
Cached Build 3.958s 3.955s ▂▁▁▂▃
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 14.876s 14.712s █▃▁▁▁
Cached Build 14.811s 14.819s █▃▁▁▁
node_modules Size 494 MB 494 MB ███▁█
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
07rxhp_1_g4mu.js gzip 13.1 kB N/A -
08avva-dy02e7.js gzip 10.4 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0fli3_wppnim5.js gzip 12.9 kB N/A -
0k09jwjeb-tki.js gzip 13.8 kB N/A -
0kb7_ep3r1z0_.js gzip 10.1 kB N/A -
0khj6l1dkbztz.js gzip 158 B N/A -
0kw8xgqdrilf6.js gzip 8.56 kB N/A -
0ojkk2e654xsc.js gzip 8.59 kB N/A -
0r13y5wryy_rb.js gzip 156 B N/A -
0wxpyd8r-vipl.js gzip 1.47 kB N/A -
0xy2fhla48_rd.js gzip 9.24 kB N/A -
10wqsvi2mgfmi.js gzip 9.82 kB N/A -
10x5yjviaxcoo.js gzip 155 B N/A -
16c9-6ahplfxj.js gzip 157 B N/A -
16lhqjoqbznyg.js gzip 220 B 220 B
16vepdkipri3r.js gzip 8.51 kB N/A -
17n96uu6y1pxq.js gzip 8.6 kB N/A -
18y4_8-9or0mn.js gzip 8.51 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1gq145j3kps-h.js gzip 8.62 kB N/A -
1nsh-mbn0e-se.js gzip 8.56 kB N/A -
1tsrrp1tdngti.js gzip 13.3 kB N/A -
2__-e_ym8n788.js gzip 450 B N/A -
22o6xd9_ywdu6.js gzip 233 B N/A -
25l6e629itu5j.js gzip 153 B N/A -
26ui6d5bv607a.js gzip 49.3 kB N/A -
27niq-zweblzs.js gzip 154 B N/A -
29bzq8qn1wyxo.js gzip 156 B N/A -
2jrge3u8y0nbz.js gzip 155 B N/A -
2kvj8yrfznmwx.js gzip 5.69 kB N/A -
2nrhepcp9ve2o.js gzip 70.8 kB N/A -
2qv7m7xjnokgr.js gzip 8.58 kB N/A -
2sewu64d_awot.js gzip 157 B N/A -
3_otef55yxm6_.js gzip 160 B N/A -
342ijzvrpe53h.js gzip 2.29 kB N/A -
3hiogkn179_b_.js gzip 169 B N/A -
3j78vioukb8ay.js gzip 156 B N/A -
3mm89bcl7qq09.js gzip 65.5 kB N/A -
3u73li4mpeclh.js gzip 157 B N/A -
44un3--wmqiyh.js gzip 7.61 kB N/A -
turbopack-0-..yjzq.js gzip 4.19 kB N/A -
turbopack-0h..4zvl.js gzip 4.19 kB N/A -
turbopack-0n..aaf6.js gzip 4.19 kB N/A -
turbopack-0u..8fol.js gzip 4.17 kB N/A -
turbopack-0x..iazv.js gzip 4.2 kB N/A -
turbopack-1x..5vxm.js gzip 4.19 kB N/A -
turbopack-2b..016f.js gzip 4.19 kB N/A -
turbopack-2h..-wl2.js gzip 4.19 kB N/A -
turbopack-2j..-y6j.js gzip 4.19 kB N/A -
turbopack-34..u2y9.js gzip 4.19 kB N/A -
turbopack-36..95b2.js gzip 4.19 kB N/A -
turbopack-3g..v35j.js gzip 4.19 kB N/A -
turbopack-3j..wswm.js gzip 4.19 kB N/A -
turbopack-3q..bf9c.js gzip 4.19 kB N/A -
00h4u194bb8c7.js gzip N/A 155 B -
06s2a1-sw8s8o.js gzip N/A 158 B -
0arkbdqpxc37i.js gzip N/A 8.6 kB -
0bz-xifewa17d.js gzip N/A 8.63 kB -
0em4a2sxerhz4.js gzip N/A 168 B -
0fbm505yboynb.js gzip N/A 49.3 kB -
0pmle13jdhdu0.js gzip N/A 65.5 kB -
0tvekitj587fh.js gzip N/A 8.51 kB -
0u3coesgskysq.js gzip N/A 161 B -
0yvk6-wi8e9wh.js gzip N/A 13.3 kB -
0z83a1om5rvtt.js gzip N/A 7.61 kB -
0zthaynjuxk58.js gzip N/A 157 B -
1-jqyfc89tixo.js gzip N/A 1.46 kB -
13q7l6-nygmmo.js gzip N/A 155 B -
14t1kneseb8th.js gzip N/A 2.3 kB -
15sb1-dsqfk_j.js gzip N/A 8.59 kB -
19mzuj3yh9is8.js gzip N/A 70.8 kB -
1ab2xruymo-oj.js gzip N/A 449 B -
1fx2d-glwbnlk.js gzip N/A 155 B -
1h55541zppmdw.js gzip N/A 156 B -
1tu25qtsmfhar.js gzip N/A 9.82 kB -
1vein_gnv3mwr.js gzip N/A 8.56 kB -
1wzrm0xjjbzn5.js gzip N/A 10.1 kB -
1z3g0uaqtv9_3.js gzip N/A 8.56 kB -
25a1yz7zua29z.js gzip N/A 13.8 kB -
2bi5hx402juv-.js gzip N/A 8.58 kB -
2hy56297fog9u.js gzip N/A 8.52 kB -
2u_rpxq3tzytl.js gzip N/A 233 B -
2wyls4kpqcn48.js gzip N/A 155 B -
3-a4k89t_92ej.js gzip N/A 153 B -
368lim5wq0o0r.js gzip N/A 12.9 kB -
3dj55jtmtuus3.js gzip N/A 155 B -
3drqjohogojbw.js gzip N/A 5.69 kB -
3g8l1m2-o-ewi.js gzip N/A 13.1 kB -
3jmkxsnxg0nrh.js gzip N/A 10.4 kB -
3v_4mi6gvc1jd.js gzip N/A 152 B -
3wpp8nvyoj121.js gzip N/A 9.24 kB -
42eqqiw5x99_v.js gzip N/A 158 B -
turbopack-0g..siep.js gzip N/A 4.19 kB -
turbopack-0n..58uq.js gzip N/A 4.19 kB -
turbopack-16..ev3s.js gzip N/A 4.19 kB -
turbopack-18..3_jf.js gzip N/A 4.19 kB -
turbopack-1d..i_fs.js gzip N/A 4.21 kB -
turbopack-1e..4m2e.js gzip N/A 4.19 kB -
turbopack-1e..tlsj.js gzip N/A 4.19 kB -
turbopack-1g..nrg9.js gzip N/A 4.19 kB -
turbopack-2a..b-mj.js gzip N/A 4.19 kB -
turbopack-2s.._bhv.js gzip N/A 4.19 kB -
turbopack-2y..bi9m.js gzip N/A 4.17 kB -
turbopack-32..dufm.js gzip N/A 4.19 kB -
turbopack-3b..a3aj.js gzip N/A 4.19 kB -
turbopack-3i..xnar.js gzip N/A 4.19 kB -
Total 465 kB 465 kB ⚠️ +12 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 719 B 718 B
Total 719 B 718 B ✅ -1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 433 B 436 B
Total 433 B 436 B ⚠️ +3 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2637-HASH.js gzip 4.63 kB N/A -
7724.HASH.js gzip 169 B N/A -
8274-HASH.js gzip 61.4 kB N/A -
8817-HASH.js gzip 5.59 kB N/A -
c3500254-HASH.js gzip 62.8 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 254 B 255 B
main-HASH.js gzip 39.4 kB 39.3 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
5887-HASH.js gzip N/A 5.61 kB -
6522-HASH.js gzip N/A 60.8 kB -
6779-HASH.js gzip N/A 4.63 kB -
8854.HASH.js gzip N/A 169 B -
eab920f9-HASH.js gzip N/A 62.8 kB -
Total 236 kB 235 kB ✅ -644 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 182 B 182 B
css-HASH.js gzip 333 B 334 B
dynamic-HASH.js gzip 1.81 kB 1.8 kB
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 353 B 349 B 🟢 4 B (-1%)
hooks-HASH.js gzip 384 B 382 B
image-HASH.js gzip 581 B 581 B
index-HASH.js gzip 260 B 259 B
link-HASH.js gzip 2.51 kB 2.51 kB
routerDirect..HASH.js gzip 316 B 318 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 313 B 314 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.97 kB ✅ -10 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 273 kB 273 kB
Total 399 kB 399 kB ✅ -390 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 616 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 44 kB 44.4 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.6 kB 46 kB ⚠️ +384 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 721 B 720 B
Total 721 B 720 B ✅ -1 B
Build Cache
Canary PR Change
0.pack gzip 4.38 MB 4.38 MB
index.pack gzip 115 kB 112 kB 🟢 2.91 kB (-3%)
index.pack.old gzip 114 kB 112 kB 🟢 1.83 kB (-2%)
Total 4.61 MB 4.6 MB ✅ -6.63 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 347 kB 347 kB
app-page-exp..prod.js gzip 192 kB 192 kB
app-page-tur...dev.js gzip 346 kB 346 kB
app-page-tur..prod.js gzip 192 kB 192 kB
app-page-tur...dev.js gzip 343 kB 343 kB
app-page-tur..prod.js gzip 190 kB 190 kB
app-page.run...dev.js gzip 343 kB 343 kB
app-page.run..prod.js gzip 190 kB 190 kB
app-route-ex...dev.js gzip 77 kB 77 kB
app-route-ex..prod.js gzip 52.5 kB 52.5 kB
app-route-tu...dev.js gzip 77.1 kB 77.1 kB
app-route-tu..prod.js gzip 52.6 kB 52.6 kB
app-route-tu...dev.js gzip 76.7 kB 76.7 kB
app-route-tu..prod.js gzip 52.3 kB 52.3 kB
app-route.ru...dev.js gzip 76.6 kB 76.6 kB
app-route.ru..prod.js gzip 52.3 kB 52.3 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.9 kB 43.9 kB
pages-api-tu..prod.js gzip 33.5 kB 33.5 kB
pages-api.ru...dev.js gzip 43.9 kB 43.9 kB
pages-api.ru..prod.js gzip 33.5 kB 33.5 kB
pages-turbo....dev.js gzip 53.3 kB 53.3 kB
pages-turbo...prod.js gzip 39.1 kB 39.1 kB
pages.runtim...dev.js gzip 53.3 kB 53.3 kB
pages.runtim..prod.js gzip 39.1 kB 39.1 kB
server.runti..prod.js gzip 63 kB 63 kB
Total 3.06 MB 3.06 MB ⚠️ +1 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/9acc099e907c97171e4867bc947b89815f7ca595/next

sokra and others added 2 commits April 21, 2026 19:45
Adds `TurboMalloc::memory_pressure()` returning a normalized `Option<u8>`
in `0..=100` backed by `/proc/pressure/memory` (Linux), the
`kern.memorystatus_level` sysctl (macOS) and `GlobalMemoryStatusEx`
(Windows). Unsupported targets return `None`.

The value is attached to `TraceRow::MemorySample` and propagated through
the trace-server store, so that span queries return a parallel
`memory_pressure_samples` vector alongside `memory_samples`, enabling
future task-eviction heuristics based on real OS pressure.

Co-Authored-By: Claude <noreply@anthropic.com>
PSI (`/proc/pressure/memory`) is not available on all Linux kernels (older
than 4.20, built without `CONFIG_PSI`, or in restricted containers).
Falling back to `(MemTotal - MemAvailable) / MemTotal` from
`/proc/meminfo` ensures `TurboMalloc::memory_pressure()` returns a sane
value on any standard Linux system, matching the semantics of
Windows' `dwMemoryLoad`.

The unit test is tightened to assert `Some(_)` on every supported
platform (Linux, macOS, Windows).

Co-Authored-By: Claude <noreply@anthropic.com>
@sokra sokra force-pushed the sokra/memory-pressure branch from e7f22c1 to 24d0c6a Compare April 21, 2026 19:46
@sokra sokra closed this Apr 28, 2026
@sokra sokra deleted the sokra/memory-pressure branch April 28, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants