"""Process memory as a number — current RSS, peak RSS, and a log formatter (phase 187, task 01, D1, LOCKED A1). **Pure stdlib:** this module imports the standard library only — no FastAPI, no ``app.api``, no third-party package — mirroring ``app/core/response_budget.py``. **The incident (owner, 2026-10-15).** Reconstructing "how much RAM did the sync actually use?" meant reading ``systemd`` unit-teardown lines from three boots (``brain-of-code-app.service: … 3.3G memory peak``), and boot −1 — the run that killed the box — had no teardown record at all because the VM never shut down cleanly. The app itself had no opinion about its own memory: no log line, no status field, nothing to ``grep``. These helpers give the sync's lifecycle lines (task 02) a number so the next incident starts from data instead of archaeology. **The helpers.** * :func:`rss_mb` — the process's CURRENT resident set size in MB. Linux: parse ``VmRSS`` from ``/proc/self/status`` (kB, floored to whole MB). On a platform without ``/proc`` — or on any failure reading it — fall back to ``resource.getrusage(RUSAGE_SELF).ru_maxrss // 1024``, which is the process HIGH-WATER mark, not a current value: the best available proxy. (``ru_maxrss`` is kilobytes on Linux, the deploy target.) * :func:`rss_peak_mb` — ``ru_maxrss // 1024``, the process high-water mark. ``None`` only where the ``resource`` module is unavailable (Windows); the import is guarded so this module still loads there. * :func:`cgroup_peak_mb` — ``/sys/fs/cgroup/memory.peak`` (cgroup v2, bytes, floored to MB) when that file exists and parses, else ``None``. Read it for context, never as the app's number — see the caveat below. * :func:`fmt_mb` — ``"?"`` for ``None``, else ``str(v)``, so a log line renders ``rss_mb=?`` and never ``rss_mb=None`` (task 02 single-sources the rendering through this one function). **The never-raise contract.** Every helper returns ``int`` or ``None``; none may propagate an exception to a caller on the sync or request path. Memory is diagnostics — a failed ``/proc`` read must never fail the turn, the sync, or a status poll that asked for a number, so every read is wrapped and every failure mode degrades to ``None`` (or to the proxy). **The container/cgroup caveat (written ONCE, here, so no future reader conflates the numbers).** Inside a container ``/sys/fs/cgroup/memory.peak`` is usually absent (the HOST reads it), and the host's number counts PAGE CACHE too: the incident box's app cgroup showed ~2.16 GB of ``file`` against ~211 MB of ``anon`` while idle, and ``systemd`` reported ``3.3G memory peak`` for the unit while the uvicorn process was a fraction of that. This module therefore reports PROCESS numbers (LOCKED A1); an operator dashboard may legitimately show a larger cgroup number — neither is wrong, and the log says which one it is. """ from __future__ import annotations import contextlib from types import ModuleType #: Procfs file carrying ``VmRSS`` (current RSS in kB) among the other #: ``/proc/self`` identity fields. Module-level so tests can point it at a #: stub file (or a nonexistent path) without patching ``open`` globally. PROC_STATUS_PATH = "/proc/self/status" #: cgroup v2 high-water mark, in BYTES. Usually absent inside a container — #: see the module docstring's caveat; ``None`` is the normal answer there. CGROUP_PEAK_PATH = "/sys/fs/cgroup/memory.peak" #: ``resource`` is Unix-only. Guarded so the module loads (and every helper #: answers ``None`` where it cannot know) on platforms without it. _resource: ModuleType | None try: import resource as _resource except ImportError: _resource = None #: The ``/proc/self/status`` label holding current RSS, in kB. _VM_RSS_LABEL = "VmRSS:" def rss_mb() -> int | None: """Current process RSS in whole MB, or ``None`` if unknowable. Linux path: the ``VmRSS:`` line of ``/proc/self/status`` ("kB" is implied by the kernel; the value is floored with integer division). Any failure — no ``/proc`` (non-Linux), unreadable file, a file without the line, a line whose value is not an integer — falls back to :func:`rss_peak_mb`, the high-water proxy. ``KeyError`` is caught defensively per the phase contract; the line-scanning form below itself cannot raise it (the absent-line case is a fall-through). """ with contextlib.suppress(OSError, KeyError, ValueError, IndexError), open( PROC_STATUS_PATH, encoding="utf-8" ) as fh: for line in fh: if line.startswith(_VM_RSS_LABEL): return int(line.split()[1]) // 1024 return rss_peak_mb() def rss_peak_mb() -> int | None: """Process high-water RSS in whole MB, or ``None`` without ``resource``. ``ru_maxrss`` is the high-water mark, never a current value (kilobytes on Linux, the deploy target), floored to whole MB. The ``getrusage`` call itself is wrapped to honor the never-raise contract even though ``RUSAGE_SELF`` cannot realistically fail. **One honest edge, measured here (so nobody "fixes" this helper):** the kernel refreshes its high-water LAZILY and the value FLOORs to whole MB, so a growing process can report a CURRENT ``VmRSS`` ABOVE its reported ``ru_maxrss`` — observed on this repo's own suite at ``VmRSS`` 217,288 kB against ``ru_maxrss`` 216,832 kB, and again as ``rss_mb=164`` against ``rss_peak_mb=162``. This helper stays the RAW kernel reading (it is a diagnostic, and a diagnostic that quietly edits the OS is worse than the imprecision). The CALLER that publishes the pair is what normalizes it: :func:`app.api.sync._sample_rss` — the single sampling point behind both sync lifecycle lines, ``GET /api/sync/status`` and the Sources page's clause — lifts a peak that sits below the current reading up to that reading, because a high-water BELOW the process's own current set is not a smaller number but a false one. So the RUN's numbers carry ``peak >= current``, while a raw ``rss_peak_mb()`` call may still sit under a raw ``rss_mb()``. """ if _resource is None: return None try: return int(_resource.getrusage(_resource.RUSAGE_SELF).ru_maxrss) // 1024 except (OSError, AttributeError, TypeError, ValueError): return None def cgroup_peak_mb() -> int | None: """The cgroup v2 ``memory.peak`` in whole MB, or ``None``. Context for operators, NOT the app's number: inside a container this file is usually absent (the host reads it), and the host's number counts PAGE CACHE too (~2.16 GB ``file`` against ~211 MB ``anon`` on the idle incident box; ``systemd`` said ``3.3G memory peak`` for the unit while the uvicorn process was a fraction of that). PROCESS numbers are what the sync logs — module docstring has the full caveat, LOCKED A1. """ try: with open(CGROUP_PEAK_PATH, encoding="utf-8") as fh: return int(fh.read().strip()) // (1024 * 1024) except (OSError, ValueError): return None def fmt_mb(value: int | None) -> str: """Render an MB reading for a log line: ``?`` when unknown, digits when not. ``0`` is a VALUE, not missing — it renders ``"0"``. This is the single place the ``rss_mb=?`` convention lives (task 02 formats every line through here). """ return "?" if value is None else str(value)