docs(findings): attribute the native bucket at peak, and record two disproven theories
Closes the last unexplained part of the peak footprint. The 118.8 MiB 'other
anonymous' smaps bucket is now mostly accounted for: V8 large-object space 34.5,
external buffers 24.6, and ~38 MiB of V8 parser/compiler zone memory that was freed
back to malloc but never returned to the kernel by musl - peak_malloced_memory goes
4.6 -> 42.2 MiB at board load while current malloced_memory stays at 1.0.
Two theories tested and rejected rather than asserted:
- 'Fewer loaded files means less compiler zone memory, so shipping less server JS
pays twice.' Wrong. peak_malloced_memory is identical (42.1-42.5) across the
preloadEntriesOnStart A/B despite 310 fewer files. The spike is a fixed cost of
the board route, not a per-file one. Claim withdrawn.
- Orphaned zlib streams. Next documents this leak class and the fix is absent from
this build, and a peak snapshot showed 15 Gzip objects still present at idle -
which read as accumulation. getActiveResourcesInfo() reports Zlib=0 both at
baseline and after 300 deliberately aborted gzip requests, RSS flat. Snapshot
objects are not live native handles, and nginx buffers responses so Next never
sees the mid-response disconnect that orphans a stream.
Adds a /handles endpoint to the probe, which is what settled the second one.
This commit is contained in:
2 files changed
+65
No files matched your search
@@ -202,6 +202,19 @@ globalThis.__homarrProbe.snapshot = (destination) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Live libuv/Node handles by kind.
|
||||
*
|
||||
* Native handles are invisible to both the JS heap and the allocation profiler, yet a zlib
|
||||
* stream left open by a client disconnect is pinned by its handle, survives GC, and holds
|
||||
* ~256 KiB of deflate state. Counting handles is the only cheap way to watch that accumulate.
|
||||
*/
|
||||
globalThis.__homarrProbe.handles = () => {
|
||||
const counts = {};
|
||||
for (const kind of process.getActiveResourcesInfo()) counts[kind] = (counts[kind] ?? 0) + 1;
|
||||
return counts;
|
||||
};
|
||||
|
||||
/** Address-space breakdown at the moment of capture; only /proc shows native vs JS vs code. */
|
||||
globalThis.__homarrProbe.maps = () => {
|
||||
try {
|
||||
@@ -410,6 +423,8 @@ if (probePort > 0) {
|
||||
return probe.allocations ? await probe.allocations() : { disabled: true };
|
||||
case "/maps":
|
||||
return probe.maps();
|
||||
case "/handles":
|
||||
return probe.handles();
|
||||
case "/snapshot":
|
||||
return await probe.snapshot(new URLSearchParams(query).get("path") ?? "/tmp/peak.heapsnapshot");
|
||||
default:
|
||||
|
||||
Reference in new issue
Block a user