Commit Graph
8 Commits
Author SHA1 Message Date
m4air 06681a3300 test(crash-reporting): gate the live-kernel test on a readable PSI file and a non-root cgroup
Also says in the attribution doc that oom_kill steps for a host-wide kill too,
and that an unchanged counter is inconclusive when the pre-gone sample is young.
2026-09-22 07:48:15 -07:00
m4air bda3202997 fix(crash-reporting): say whether the cgroup chain reached the machine root
An absent `memory.max` was documented as proof that nothing above us caps us:
"`systemMemoryCgroupCurrentMB` ... Present means the chain was read and the
missing ceiling really is unlimited — over the ancestors too". The code cannot
establish that. `CgroupCurrentMB` only proves OUR cgroup resolved, and inside a
cgroup namespace — the container/Flatpak case the mount-root fallback exists
for — the walked chain is one level and every ancestor is structurally
unreadable. A `memory.max` on a Kubernetes pod cgroup or on the slice hosting
the container is enforced on us and invisible, so the doc cleared check 2 on
exactly the sandboxes the ancestor walk was added to cover.

`memory.current` exists on non-root cgroups only — the invariant this module
already relies on to reject the host root — so a readable one at the mount root
means the mount root is itself a cgroup, i.e. a namespace root. Report that as
`systemMemoryCgroupChainReachesRoot`, and rewrite the doc's "tell" and check 2
to require it before no-ceiling clears the cgroup.

The flag is excluded from the "nothing was measurable" guard: it always
resolves, and counting it would ship a `Cgroup*` row on every Linux host with
no v2 memory controller at all.
2026-09-22 07:10:09 -07:00
m4air 205cb038b2 fix(crash-reporting): read the cgroup ceiling the kernel enforces, not just ours
`memory.max` and `memory.high` are enforced as the MINIMUM over our cgroup and
its ancestors, but only our own level was read. A `snap set-quota --memory`
slice, a `MemoryMax=` on `user.slice` and a Kubernetes pod cgroup all sit above
the unit we run in, so our own file reads `max` under every one of them: the
report showed no ceiling, the label stayed `mem-available`, and the doc told the
reader that a missing ceiling means unlimited — clearing check 2 on exactly the
sandboxes this module names as its reason to exist.

Walk the chain to the mount root and take the lowest for each file. Where the
binding ceiling is an ancestor's it is shared with our siblings, so our own
`memory.current` is not comparable to it; `systemMemoryCgroupCeilingCurrentMB`
carries the usage that ceiling actually counts against, and appears only in that
case. `oom_kill` needed nothing: the kernel credits it to the victim's cgroup
and every cgroup above, whichever level's limit fired. The `max`/`high` event
counters stay at our level and the doc now says a zero there clears nothing.

Also closes four mutations that no test could falsify: the 30% stall threshold
(only a self-referential boundary case pinned it, so 5% or 29% stayed green),
`someAvg60` in both stall scopes (deletable and mislabelable), the pseudo-file
read's error swallow (the test double answers what the swallow produces, so it
could never exercise it), and PSI's non-numeric field guard.
2026-09-22 07:10:09 -07:00
m4air d8475152ec docs(crash-reporting): stop excluding the cgroup-scoped kernel OOM killer
Host-wide headroom falsifies only the host-wide OOM killer; the cgroup-scoped
one fires with the machine's spare gigabytes untouched, which is what the new
oom_kill counter exists to read.
2026-09-22 07:10:09 -07:00
Claude af3bfe0522 fix(crash-reporting): pin the empty-ceiling-file term and correct the uptime claim
An empty memory.max reads as Number('') = 0 without the empty-string term, so a
sandbox that stubs /sys/fs/cgroup with zero-length files would ship a 0 MB
ceiling and a false mem-available-cgroup-capped label. No test held that term.

The doc said the main process started 'hours earlier' in all three reports;
2ea53f9c is 42 m 55 s. Replaced with the measured per-report uptimes.
2026-09-22 07:10:09 -07:00
m4air bc411fb851 docs(crash-reporting): correct the falsified 181e8e36 premise, and pin the unknown-total term
The doc claimed 181e8e36 showed a renderer and a GPU dying together "in the same
tree", and read that as a whole-cgroup kill. Measured from the payload they are
9m 04.9s apart — GPU process_gone_suppressed at 22:29:32.397Z, renderer report
Created at 22:38:37.276Z — so they are two independent single-process kills.

systemd-oomd is likewise demoted from explanation to candidate: it kills the
whole cgroup, and Orca's main process survived and authored the report in all
three cases (processMetricsBrowserCount: 1 post-death). These fields exist to
DISTINGUISH the killers, not to ratify one chosen in advance. The same claim is
scrubbed from the PSI reader header and the PSI test's field-report note.

Mutation testing also found `total === undefined` in cgroupCeilingBelowHostRam
unfalsifiable on its own: deleting only that term left the suite green, while
deleting it together with `lowest < total` was caught. The new test fixes a
readable ceiling beside an unreadable MemTotal, which kills the isolated mutant.
2026-09-22 07:10:09 -07:00
Claude f3514ef1e8 test(crash-reporting): cover the cgroup/PSI sysfs layer and unpin it from the CI host
The reader test doubles skipped every line that actually opens a file: with
resolveCgroupV2MemoryDir, readLinuxCgroupMemoryLimitFromSysfs and
readLinuxMemoryPressureStallFromProcfs each throwing on entry, all 306
crash-reporting tests still passed. Adds a pseudo-file seam (shared by both
modules, as the single reader already was) and tests the namespace mount-root
fallback, the root-cgroup rejection, the all-undefined silence and PSI's
absence.

pre-gone-host-memory.test.ts calls getSystemMemoryDetails('linux') with the real
readers live, so on a memory-capped or busy Linux runner its label assertion
reads the runner's own cgroup and fails; seams it like the new files do.

Corrects the doc's blanket "an absent row means could not measure": memory.max
reading `max` is also reported as an absent field, and CgroupCurrentMB is what
separates the two.
2026-09-22 07:10:08 -07:00
m4air 3c83a6fc14 feat(crash-reporting): attribute Linux SIGKILLs with cgroup and PSI readings
Three v1.4.200 field reports (2ea53f9c, ad185d76, 181e8e36) are Arch renderer
SIGKILLs with ~100% free swap and gigabytes of MemAvailable, and 181e8e36 lost
the GPU process to exit 9 in the same tree. The kernel OOM killer does not fire
with that headroom; systemd-oomd, default-enabled on Arch, kills a whole cgroup
on PSI memory stall instead. Nothing in the report could tell those apart, or
tell either from an outside kill, because /proc/meminfo is the entire Linux
memory story a report carried.

Read cgroup v2 memory.max/high/current and memory.events (oom_kill, max, high),
plus PSI some/full avg10/avg60 from /proc/pressure/memory and the cgroup's own
memory.pressure. Both flow through getSystemMemoryDetails, so the existing
pre-gone sampler carries them as systemMemoryPreGone* automatically: an oom_kill
counter stepping across the death proves the kernel did it, and an unchanged one
rules the cgroup out.

The Linux pressure signal no longer says 'mem-available' unconditionally — it
refines to 'mem-available-cgroup-capped' or 'mem-available-stalled'. Both keep
the family prefix, so no existing value changes meaning.

Both readers are platform-guarded, swallow every read failure, and stay silent
rather than emit a row of zeroes that would read as "measured, and unlimited".
2026-09-22 07:10:08 -07:00