#!/usr/bin/env python3 """Report Linux page-cache residency for files without reading their contents.""" from __future__ import annotations import argparse import ctypes import json import os def residency(path: str) -> dict[str, int | float | str]: page_size = os.sysconf("SC_PAGE_SIZE") size = os.stat(path).st_size pages = (size + page_size - 1) // page_size fd = os.open(path, os.O_RDONLY) libc = ctypes.CDLL(None, use_errno=True) libc.mmap.restype = ctypes.c_void_p libc.mmap.argtypes = [ ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_long, ] libc.mincore.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p] libc.munmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t] address = libc.mmap(None, size, 0, 1, fd, 0) # PROT_NONE, MAP_SHARED os.close(fd) if address == ctypes.c_void_p(-1).value: error = ctypes.get_errno() raise OSError(error, os.strerror(error), path) vector = (ctypes.c_ubyte * pages)() try: if libc.mincore(address, size, vector) != 0: error = ctypes.get_errno() raise OSError(error, os.strerror(error), path) resident = sum(1 for value in vector if value & 1) finally: libc.munmap(address, size) return { "path": path, "size_bytes": size, "page_size": page_size, "pages": pages, "resident_pages": resident, "resident_bytes": resident * page_size, "resident_percent": round(100 * resident / pages, 2) if pages else 100.0, } def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("paths", nargs="+") args = parser.parse_args() print(json.dumps([residency(path) for path in args.paths], separators=(",", ":"))) if __name__ == "__main__": main()