Add host memory introspection utilities - #756
Conversation
psutil.virtual_memory().available reads /proc/meminfo, which inside a container reports the HOST's memory rather than the container's limit, so a memory budget check that trusts it alone can pass immediately before the container is killed. This module reads the cgroup limit alongside meminfo and believes the smaller. It resolves the process's own cgroup path rather than assuming the hierarchy root, translates that path through each mount's root, tries v2 then v1, and selects the cgroup with the least headroom, since every ancestor constrains the process and allocations compete with every other descendant of it. cgroup_memory_breakdown separates anonymous memory from reclaimable page cache, which is what decides whether sitting near the limit is survivable; for v1 it reads the hierarchical total_* fields so the breakdown describes the same population as the usage figure beside it. log_stage_memory logs that split at pipeline phase boundaries. Adds a module and its tests only; no existing code path changes.
| self.assertLessEqual(available, host_memory.psutil.virtual_memory().total) | ||
|
|
||
|
|
||
| class MemoryBreakdownTest(CgroupResolutionTest): |
There was a problem hiding this comment.
nit: inheriting from a concrete test class will inherits its test methods. consider refactor it to move test methods out of the base class
| limit, current = limits | ||
| parts.append( | ||
| f"cgroup {current / 2**30:.1f}/{limit / 2**30:.1f} GiB " | ||
| f"({100.0 * current / limit:.0f}%) headroom {(limit - current) / 2**30:.1f} GiB" |
There was a problem hiding this comment.
nit: guard against division by 0?
| """How much host memory this process may still allocate. | ||
|
|
||
| ``psutil.virtual_memory().available`` reads ``/proc/meminfo``, which inside a container reports the | ||
| HOST's memory rather than the container's limit, so a budget check trusting it alone can pass | ||
| immediately before the container is killed. The cgroup knows the real limit; read both and believe | ||
| the smaller. |
There was a problem hiding this comment.
nit. Can we update this docstring for recommended usage of the module? The public functions are all at the bottom which can make it hard for human readers to discover
| fields = line.strip().split(":", 2) | ||
| if len(fields) != 3: | ||
| continue |
There was a problem hiding this comment.
nit, can we leave the expected output here?
| parts = [part for part in path.split("/") if part] | ||
| while True: | ||
| candidate = "/" + "/".join(parts) | ||
| if candidate not in expanded: |
There was a problem hiding this comment.
BTW do we call this often? Do we suspect that using a set here and t hen converting to list later (if needed) would be faster?
There was a problem hiding this comment.
On the perf note (and if if this isn't expensive it's ok to ignore), can we do the following:
- have `parts: tuple[str, ...] = tuple(part for part in path.split("/") if part)
for i in range(len(parts)- if
parts[:len(parts-i) in ... - Add the sub-tuple to expanded (as a set)
- Covert all to str at the end.
Adds
gigl/utils/host_memory.pyand its tests. Two new files; no existing code path changes.Why
psutil.virtual_memory().availablereads/proc/meminfo, which inside a container reports the host's memory rather than the container's limit. A memory budget check that trusts it alone can pass moments before the container is OOM-killed. This module reads the cgroup limit alongside meminfo and believes the smaller.What it handles
cgroup_memory_breakdownseparates anonymous memory from reclaimable page cache, which is what decides whether sitting near the limit is survivable.log_stage_memorylogs that split at pipeline phase boundaries.Testing
45 tests pass;
tyandruffclean. The tests build real cgroup hierarchies in temporary directories and read them through the real code path, mocking only cgroup discovery andpsutil.