Files
hermes-agent/tests/hermes_cli/test_linux_ram_probe.py
brooklyn! 687ca6ed88 fix(hardware): use MemAvailable for Linux RAM
getconf _AVPHYS_PAGES counts page cache as used, so the Desktop statusbar
overreports Linux RAM. Read MemAvailable from /proc/meminfo, fall back to
MemFree, then the existing getconf probe. A reported 0 is a real available
value, not a missing field.

Fixes #102252
2026-09-24 05:29:08 -05:00

108 lines
3.1 KiB
Python

"""Linux RAM availability must include reclaimable page cache (#102252).
``getconf _AVPHYS_PAGES`` counts only free pages, so the Desktop statusbar
treats page cache as used. ``MemAvailable`` is the reclaimable-aware figure.
A reported ``0`` is a real available value, not a missing field.
"""
from __future__ import annotations
import ctypes
import hermes_cli.local_runtime.hardware as hw
GIB = 1 << 30
def _as_linux(monkeypatch) -> None:
"""Reach the Linux branch on every host without editing the Windows probe."""
monkeypatch.delattr(ctypes, "windll", raising=False)
monkeypatch.setattr(hw.sys, "platform", "linux")
def test_memavailable_is_used_when_present():
text = (
"MemTotal: 67108864 kB\n"
"MemFree: 12582912 kB\n"
"MemAvailable: 56623104 kB\n"
"Cached: 44040192 kB\n"
)
assert hw._linux_ram_from_meminfo(text) == (64 * GIB, 54 * GIB)
def test_zero_memavailable_is_a_real_value():
text = (
"MemTotal: 8388608 kB\n"
"MemFree: 2097152 kB\n"
"MemAvailable: 0 kB\n"
)
assert hw._linux_ram_from_meminfo(text) == (8 * GIB, 0)
def test_missing_memavailable_falls_back_to_memfree():
text = "MemTotal: 8388608 kB\nMemFree: 2097152 kB\n"
assert hw._linux_ram_from_meminfo(text) == (8 * GIB, 2 * GIB)
def test_unusable_meminfo_is_not_a_reading():
assert hw._linux_ram_from_meminfo("MemAvailable: 1048576 kB\n") is None
assert hw._linux_ram_from_meminfo(
"MemTotal: 1048576 kB\nMemAvailable: 2097152 kB\n"
) is None
def test_ram_bytes_uses_injected_meminfo_including_zero(monkeypatch):
_as_linux(monkeypatch)
monkeypatch.setattr(
hw,
"_linux_meminfo_text",
lambda: (
"MemTotal: 8388608 kB\n"
"MemFree: 2097152 kB\n"
"MemAvailable: 0 kB\n"
),
)
def _getconf_must_not_run(*_args, **_kwargs):
raise AssertionError("getconf must not run when MemAvailable is present")
monkeypatch.setattr(hw, "_stdout", _getconf_must_not_run)
assert hw._ram_bytes() == (8 * GIB, 0)
def test_ram_bytes_falls_back_when_memavailable_is_absent(monkeypatch):
_as_linux(monkeypatch)
monkeypatch.setattr(
hw,
"_linux_meminfo_text",
lambda: "MemTotal: 8388608 kB\nMemFree: 2097152 kB\n",
)
def _getconf_must_not_run(*_args, **_kwargs):
raise AssertionError("MemFree is a valid fallback; getconf must not run")
monkeypatch.setattr(hw, "_stdout", _getconf_must_not_run)
assert hw._ram_bytes() == (8 * GIB, 2 * GIB)
def test_ram_bytes_falls_back_to_getconf_when_meminfo_unusable(monkeypatch):
_as_linux(monkeypatch)
monkeypatch.setattr(hw, "_linux_meminfo_text", lambda: None)
values = {
"PAGE_SIZE": "4096\n",
"_PHYS_PAGES": "2097152\n",
"_AVPHYS_PAGES": "524288\n",
}
def fake_stdout(*argv):
return values[argv[-1]]
monkeypatch.setattr(hw, "_stdout", fake_stdout)
assert hw._ram_bytes() == (8 * GIB, 2 * GIB)