Repository navigation
process.memoryUsage: read heapUsed through Bun::heapUsage() and remove the observer that copied it - #43925
process.memoryUsage: read heapUsed through Bun::heapUsage() and remove the observer that copied it#43925robobun wants to merge 3 commits into
Conversation
…on() through Bun::heapUsage()
Bun::heapUsage(vm) returns { total, used }: the allocated block bytes, and the
size JSC stored at the end of the most recent collection with total as the
fallback until the first collection finishes. Process_functionMemoryUsage
reports both from it.
The HeapSizeAfterLastCollection observer copied the same counter in
didGarbageCollect(). JSC has an accessor for it now, so the observer, its
member in JSVMClientData and the HeapObserver.h include are removed.
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
|
Updated 7:54 PM PT - Sep 24th, 2026
✅ @robobun, your commit 6c627f21ac11713f201ab303b1c2f0d0afa556d9 passed in 🧪 To try this PR locally: bunx bun-pr 43925That installs a local version of the PR into your bun-43925 --bun |
|
Status: ready for review. CI is green. How it was checked: release builds of main (73df7bb) and of this branch. CI: build 120504 of 6c627f2 passed on all 181 jobs. 6c627f2 is an empty commit: build 120455 ran during an outage of gitlab.com and was red in tests that clone from it. Against current main: the branch merges into main (5866b5f) with no conflict. Main took two upstream WebKit merges (#44615, #45303). The collector moved into its own class, and the stored size now sums the bytes marked, not the bytes visited. PRs: #43925 (this PR) and #43926 (the node:v8 change that reads the new helper, stacked on #30596). Open #44548 (from a fork) keeps the observer that this PR removes. The Notes of this PR say how the two compose. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe change replaces the per-VM heap-size observer with a ChangesHeap usage reporting
Suggested reviewers: Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to
|
Behaviour change: none
Groundwork for #43926, the node:v8 follow-up to #30596. It does not depend on #30596.
Problem
process.memoryUsage().heapUsedreports the size JSC stores at the end of each collection. JSC had no accessor for it when process.memoryUsage: report heapUsed from the most recent collection #39593 landed, soBun::HeapSizeAfterLastCollection(src/jsc/bindings/BunClientData.h) copies it from a heap observer on every VM.Heap::sizeAfterLastCollection(). The observer is redundant, and its comment ("has no accessor") is false.heapTotal/heapUsedrule is inline inProcess_functionMemoryUsage, so a second reader must copy it.Fix
Bun::heapUsage(vm)(BunProcess.h,BunProcess.cpp) returns{ total, used }:blockBytesAllocated(), andsizeAfterLastCollection()withtotalas the fallback before the first collection.Process_functionMemoryUsagereports both from it.HeapObserver.hinclude.BunProcess.cppwas the only reader.Heap::updateAllocationLimits()stores one value in both counters before the heap notifies observers. The copy always equalled the accessor.process.test.js(describe "process.memoryUsage().heapUsed reports the most recent collection"),v8-module.test.ts,test-v8-collect-gc-profile*.js,worker.test.ts,worker_threads.test.ts.Background
JSC::HeapObserverreceives two virtual calls in every collection.JSVMClientDatais per VM, so each Worker registered one too.Downsides
sizeof(JSVMClientData)6160 -> 6136 B, observer calls per collection 2 -> 0.process.memoryUsage()keeps four relations to the collector's own numbers in 30 of 30 runs of each build.Notes
Co-authored-bytrailer. [JSC] Less memory per function that never runs, and code that can be dropped and decoded again WebKit#628 added the accessor.Heap::m_sizeAfterLastCollecthas one writer (Heap.cpp:3181). It stores the samecurrentHeapSizeasm_sizeAfterLastFullCollect(:3157) orm_sizeAfterLastEdenCollect(:3167), insideupdateAllocationLimits().didFinishCollection()calls the observers later (:3225). The observer read the counter of the scope it was given, so it always stored the value the accessor returns.BUN_GC_TIMER_DISABLE=1, 30 runs per build. All four relations hold in 60 of 60 runs, and both builds print the same values:heapUsed === heapTotal > 0(851968).Bun.gc(true),heapUsedequals its return value (254404).bun:jscedenGC(),heapUsedequals its return value (254516).ArrayBuffer,heapUsedequals theusedHeapSizeof the last collection that av8.GCProfilerrecorded (54792651).heapUsedfollows it.nm -Sonbun-profile):Process_functionMemoryUsage1311 -> 1312 B.Bun::heapUsageis inlined. The fourHeapSizeAfterLastCollectionfunctions (250 B) and the vtable (48 B) are gone.sizeof(WebCore::JSVMClientData)comes from astatic_assertprobe compiled with the release flags ofBunClientData.cpp. The 24 B are the vtable pointer, theHeap&and the counter.HeapSizeAfterLastCollection::didGarbageCollectfires 1000 times over 1000edenGC()calls. The heap callswillGarbageCollect()on the same observers when a collection starts (Heap.cpp:3052). On this PR the symbols do not exist.Heap::m_observershas no inline capacity, so the firstaddObserver()allocates 16 slots (128 B). Duringbun -e 'console.log(1)'the foldedVector<T*, 0, CrashOnOverflow, 16>::expandCapacityruns 12 times on main and 11 times here. No always-on observer remains:grep -rn addObserver src/jsc/bindingsfinds onlyGCProfilerObserver.JSC::HeapObserversubclass toBunClientData.hand a member next tom_heapSizeAfterLastCollection. Whichever PR lands second resolves one hunk there, and worker_threads: answer worker.cpuUsage() and worker.getHeapStatistics() on the parent thread #42360 then needs its ownHeapObserver.hinclude.Heap::totalBytesAllocated()at the last collection, so thatheapUsedfollows allocation between collections. If that value is wanted, the observer stays, or a JSC accessor for the bytes allocated in the current cycle replaces it. The sum then goes intoBun::heapUsage(). Whichever PR lands second needs a rebase.process.test.js180 pass and 5 skip (it needsUSERin the environment).v8-module.test.ts8 pass.test-v8-collect-gc-profile.js,-using.js,-exit-before-stop.jsandtest-v8-stats.jsexit 0.worker.test.ts42 pass.worker_threads.test.ts142 pass.bun-jsc.test.ts41 pass.globals.test.js20 pass.no test proof · iteration 1 · the description declares no behaviour change, so there is no failing test to prove; the existing suite in CI is the check