Skip to content

Report OS memory pressure in TurboMalloc and trace samples#93333

Merged
sokra merged 2 commits intocanaryfrom
sokra/memory-pressure
Apr 29, 2026
Merged

Report OS memory pressure in TurboMalloc and trace samples#93333
sokra merged 2 commits intocanaryfrom
sokra/memory-pressure

Conversation

@sokra
Copy link
Copy Markdown
Member

@sokra sokra commented Apr 28, 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.

sokra and others added 2 commits April 28, 2026 21:50
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>
@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Apr 28, 2026
@sokra sokra marked this pull request as ready for review April 28, 2026 21:40
@sokra sokra requested a review from lukesandberg April 28, 2026 21:45
@github-actions
Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 90c75d6

@github-actions
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) 811ms 811ms █████
Cold (Ready in log) 791ms 792ms ▇█▇▇█
Cold (First Request) 1.250s 1.245s ▃▆▅▇█
Warm (Listen) 811ms 810ms █████
Warm (Ready in log) 792ms 791ms ▇█▇██
Warm (First Request) 612ms 612ms ▃▇▄▇█
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 811ms 810ms ▅▁█▅█
Cold (Ready in log) 788ms 791ms ▆▇██▂
Cold (First Request) 3.177s 3.194s ▇▇▇█▄
Warm (Listen) 814ms 810ms ▃▃▇▅▇
Warm (Ready in log) 786ms 787ms █▇██▁
Warm (First Request) 3.213s 3.197s █▇▅▆▄

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.020s 4.969s ▆▇▆▇▇
Cached Build 5.034s 4.980s ▅▇▇██
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.670s 23.738s ▅▃▅█▂
Cached Build 23.864s 23.889s ▅▆██▄
node_modules Size 495 MB 495 MB ▁▁▁▁▁
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0_09canb0ezn8.js gzip 153 B N/A -
02k5onwb4z3s6.js gzip 167 B N/A -
03t9nzq88k1pe.js gzip 155 B N/A -
04tqxk-qcsi2f.js gzip 156 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0fli3_wppnim5.js gzip 12.9 kB N/A -
0kb7_ep3r1z0_.js gzip 10.1 kB N/A -
0kw8xgqdrilf6.js gzip 8.56 kB N/A -
0nnx767ck3zz4.js gzip 157 B N/A -
0ojkk2e654xsc.js gzip 8.59 kB N/A -
0wxpyd8r-vipl.js gzip 1.47 kB N/A -
0xrh8l7b4d3s2.js gzip 156 B N/A -
0xy2fhla48_rd.js gzip 9.24 kB N/A -
10wqsvi2mgfmi.js gzip 9.82 kB 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 -
1k3mvlb4-0ngf.js gzip 155 B N/A -
1ke_4s9soy654.js gzip 156 B N/A -
1nsh-mbn0e-se.js gzip 8.56 kB N/A -
1qngoc418rk6i.js gzip 65.6 kB N/A -
1r3s1n8vyb7h6.js gzip 161 B N/A -
1tsrrp1tdngti.js gzip 13.3 kB N/A -
1v-qecyz63-0b.js gzip 154 B N/A -
2__-e_ym8n788.js gzip 450 B N/A -
22o6xd9_ywdu6.js gzip 233 B N/A -
25n272-g99oa1.js gzip 7.61 kB N/A -
2c9mvd-i9rxxl.js gzip 160 B N/A -
2d4njk_907vw4.js gzip 157 B N/A -
2faj3acmavn9n.js gzip 13.1 kB N/A -
2kvj8yrfznmwx.js gzip 5.69 kB N/A -
2qv7m7xjnokgr.js gzip 8.58 kB N/A -
2ue5g3yr_f1ds.js gzip 70.9 kB N/A -
342ijzvrpe53h.js gzip 2.29 kB N/A -
3afk9e9-iuwwd.js gzip 157 B N/A -
3k1k5gtofm6eq.js gzip 10.4 kB N/A -
3xq6of2nocani.js gzip 49.5 kB N/A -
42_02jza_7yny.js gzip 13.8 kB N/A -
turbopack-04..w00-.js gzip 4.19 kB N/A -
turbopack-0g..9a3o.js gzip 4.19 kB N/A -
turbopack-0l..g-ev.js gzip 4.19 kB N/A -
turbopack-0m..2-2t.js gzip 4.18 kB N/A -
turbopack-0t..xwt-.js gzip 4.19 kB N/A -
turbopack-0u..t26z.js gzip 4.2 kB N/A -
turbopack-1k..1uu6.js gzip 4.19 kB N/A -
turbopack-1m..q5n1.js gzip 4.19 kB N/A -
turbopack-2j..87q5.js gzip 4.19 kB N/A -
turbopack-2q..y41_.js gzip 4.19 kB N/A -
turbopack-2y..xjoo.js gzip 4.19 kB N/A -
turbopack-3l..z3of.js gzip 4.17 kB N/A -
turbopack-3s..s0yf.js gzip 4.19 kB N/A -
turbopack-3u..-zq7.js gzip 4.19 kB N/A -
03kysncgx5l7w.js gzip N/A 155 B -
0arkbdqpxc37i.js gzip N/A 8.6 kB -
0bz-xifewa17d.js gzip N/A 8.63 kB -
0efh6erg1kc4c.js gzip N/A 158 B -
0tvekitj587fh.js gzip N/A 8.51 kB -
0yvk6-wi8e9wh.js gzip N/A 13.3 kB -
1-jqyfc89tixo.js gzip N/A 1.46 kB -
10y3h86mnhs_2.js gzip N/A 10.4 kB -
12hxdatac0fxj.js gzip N/A 49.5 kB -
139jydanoq6-d.js gzip N/A 154 B -
14t1kneseb8th.js gzip N/A 2.3 kB -
15sb1-dsqfk_j.js gzip N/A 8.59 kB -
1ab2xruymo-oj.js gzip N/A 449 B -
1b3xo3p2pa8_a.js gzip N/A 70.9 kB -
1dt49_v4y8lxb.js gzip N/A 13.8 kB -
1tu25qtsmfhar.js gzip N/A 9.82 kB -
1v3ftpmn8m_ud.js gzip N/A 161 B -
1vein_gnv3mwr.js gzip N/A 8.56 kB -
1vmibvuhp1gey.js gzip N/A 13.1 kB -
1wzrm0xjjbzn5.js gzip N/A 10.1 kB -
1z1geo4e53wn1.js gzip N/A 156 B -
1z3g0uaqtv9_3.js gzip N/A 8.56 kB -
2-2ld71a0y6d5.js gzip N/A 157 B -
213wdc0nef-no.js gzip N/A 168 B -
248gz0gduuney.js gzip N/A 157 B -
2bi5hx402juv-.js gzip N/A 8.58 kB -
2hy56297fog9u.js gzip N/A 8.52 kB -
2k0exemzm1ral.js gzip N/A 157 B -
2pch5duiz7pl1.js gzip N/A 155 B -
2u_rpxq3tzytl.js gzip N/A 233 B -
2zg0rr542d7qb.js gzip N/A 156 B -
314cbinszt68n.js gzip N/A 155 B -
35nh2lh_i5pyh.js gzip N/A 7.61 kB -
368lim5wq0o0r.js gzip N/A 12.9 kB -
3drqjohogojbw.js gzip N/A 5.69 kB -
3inn3g12k7ggr.js gzip N/A 153 B -
3lx6lyx6jwnsa.js gzip N/A 65.5 kB -
3wpp8nvyoj121.js gzip N/A 9.24 kB -
turbopack-02..h8bk.js gzip N/A 4.19 kB -
turbopack-03..d822.js gzip N/A 4.19 kB -
turbopack-04..-sj1.js gzip N/A 4.19 kB -
turbopack-0d..etty.js gzip N/A 4.19 kB -
turbopack-0e..h9db.js gzip N/A 4.19 kB -
turbopack-0h..um4t.js gzip N/A 4.18 kB -
turbopack-0m.._t4k.js gzip N/A 4.19 kB -
turbopack-0o..jeg1.js gzip N/A 4.19 kB -
turbopack-15..3bjj.js gzip N/A 4.2 kB -
turbopack-18..r3ht.js gzip N/A 4.19 kB -
turbopack-1b..dtt2.js gzip N/A 4.19 kB -
turbopack-3b..mz6c.js gzip N/A 4.19 kB -
turbopack-3n..av5a.js gzip N/A 4.17 kB -
turbopack-3y..9apy.js gzip N/A 4.19 kB -
Total 465 kB 465 kB ⚠️ +48 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 718 B 719 B
Total 718 B 719 B ⚠️ +1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 435 B 433 B
Total 435 B 433 B ✅ -2 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.4 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.7 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 ✅ -652 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.52 kB 2.52 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.99 kB 7.98 kB ✅ -10 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 274 kB 274 kB
Total 400 kB 399 kB ✅ -520 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 616 B 616 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 44.3 kB 44.5 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.9 kB 46.1 kB ⚠️ +206 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 722 B 719 B
Total 722 B 719 B ✅ -3 B
Build Cache
Canary PR Change
0.pack gzip 4.4 MB 4.4 MB 🟢 5.82 kB (0%)
index.pack gzip 114 kB 113 kB
index.pack.old gzip 114 kB 114 kB
Total 4.63 MB 4.63 MB ✅ -6.06 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 348 kB 348 kB
app-page-exp..prod.js gzip 193 kB 193 kB
app-page-tur...dev.js gzip 348 kB 348 kB
app-page-tur..prod.js gzip 193 kB 193 kB
app-page-tur...dev.js gzip 344 kB 344 kB
app-page-tur..prod.js gzip 191 kB 191 kB
app-page.run...dev.js gzip 345 kB 345 kB
app-page.run..prod.js gzip 191 kB 191 kB
app-route-ex...dev.js gzip 77.3 kB 77.3 kB
app-route-ex..prod.js gzip 52.8 kB 52.8 kB
app-route-tu...dev.js gzip 77.4 kB 77.4 kB
app-route-tu..prod.js gzip 52.8 kB 52.8 kB
app-route-tu...dev.js gzip 77 kB 77 kB
app-route-tu..prod.js gzip 52.5 kB 52.5 kB
app-route.ru...dev.js gzip 76.9 kB 76.9 kB
app-route.ru..prod.js gzip 52.5 kB 52.5 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 44.2 kB 44.2 kB
pages-api-tu..prod.js gzip 33.7 kB 33.7 kB
pages-api.ru...dev.js gzip 44.2 kB 44.2 kB
pages-api.ru..prod.js gzip 33.7 kB 33.7 kB
pages-turbo....dev.js gzip 53.7 kB 53.7 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 53.6 kB 53.6 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.1 kB 63.1 kB
Total 3.08 MB 3.08 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/90c75d63c989171cccc06e0269687c593d9c613b/next

Commit: 90c75d6

@sokra sokra merged commit 292a3fb into canary Apr 29, 2026
185 checks passed
@sokra sokra deleted the sokra/memory-pressure branch April 29, 2026 09:39
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