Skip to content

Instantly share code, notes, and snippets.

@CTimmerman
Last active November 16, 2025 12:28
Show Gist options
  • Select an option

  • Save CTimmerman/6d1fc20c3fb61ef0ba3e2d6de2c582ce to your computer and use it in GitHub Desktop.

Select an option

Save CTimmerman/6d1fc20c3fb61ef0ba3e2d6de2c582ce to your computer and use it in GitHub Desktop.
Out Of Memory (OOM) Killer to save your SSD and other processes.
pylint:
disable:
- line-too-long
- multiple-imports
- pointless-string-statement
- too-many-nested-blocks
"""Out Of Memory (OOM) Killer by Cees Timmerman, 2012-2025."""
import ctypes, ctypes.wintypes, os, subprocess, sys, time
import psutil # python -m pip install psutil
MIN_BYTES_FREE = 1.1e9 # Windows 10 and/or Chrome appear to fill the disk rather than put free RAM below 20 MB of 12 GB. 128 MB is also hard to hit. 250e6 works. 404e6 for Mint Cinnamon. 550e6 for Windows 10 when MsMpEng is hogging all resources. 1e9 for temporary blackouts with 100% pagefile use by memcompression instead of nonresponsive Chrome with 16 - 1.1 GB RAM.
# Windows 11 needs 1+e9 to not grow the pagefile.
KILL_UNTIL_BYTES_FREE = 1.2e9 # Kill until
MAX_BYTES_PROCESS = 100e6 # Kills tabs bigger than this. TODO: Don't kill focused tab.
# Lowercase list of process names that don't need confirmation to be killed.
HIT_LIST = [
"procmon64",
"chrome",
"chromium",
"firefox",
"web content",
"isolated web co",
"microsoftedgecp",
"msedge",
"msmpeng",
"node",
# "python",
# "python3",
]
def in_visible_windows(pid) -> bool:
"https://stackoverflow.com/a/71844662/819417"
if sys.platform == "win32":
import win32gui # pip install pywin32
class TITLEBARINFO(ctypes.Structure):
_fields_ = [
("cbSize", ctypes.wintypes.DWORD),
("rcTitleBar", ctypes.wintypes.RECT),
("rgstate", ctypes.wintypes.DWORD * 6),
]
# visible_windows = []
pid_visible = {}
def callback(hwnd, _):
nonlocal pid_visible
title_info = TITLEBARINFO()
title_info.cbSize = ctypes.sizeof(title_info)
ctypes.windll.user32.GetTitleBarInfo(hwnd, ctypes.byref(title_info))
cloaked = ctypes.c_int(0)
ctypes.WinDLL("dwmapi").DwmGetWindowAttribute(
hwnd, 14, ctypes.byref(cloaked), ctypes.sizeof(cloaked)
)
title = win32gui.GetWindowText(hwnd)
if (
not win32gui.IsIconic(hwnd)
and win32gui.IsWindowVisible(hwnd)
and title != ""
and cloaked.value == 0
and not (title_info.rgstate[0] & 0x00008000) # STATE_SYSTEM_INVISIBLE
):
cpid = ctypes.wintypes.DWORD()
ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(cpid))
# visible_windows.append({
# "title": title,
# "pid": cpid,
# "hwnd": hwnd
# })
print(cpid.value, title)
pid_visible[pid] = pid == cpid.value
print("enum")
win32gui.EnumWindows(callback, None)
# TODO
"""
import ctypes
EnumWindows = ctypes.windll.user32.EnumWindows
GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, types.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
IsWindowEnabled = ctypes.windll.user32.IsWindowEnabled
"""
print(f"visible pid {pid}?", pid_visible)
return pid_visible[pid]
elif sys.platform == "linux":
pass
return False
def kill(pid):
if os.name == "nt":
print(
subprocess.check_output(["TASKKILL", "/PID", str(pid), "/T", "/F"]) # nosec
) # /Tree /Force. /IM image didn't work though all child processes were named the same.
else:
# 15 is SIGTERM according to https://en.wikipedia.org/wiki/Signal_(IPC) which is friendlier than SIGKILL (9). On Windows this is just the exit code for the process.
os.kill(pid, 15)
def main(verbose=True):
lines = __doc__.splitlines()
print(lines[0], lines[-1])
print(
f"If free RAM < {MIN_BYTES_FREE/1e9:,.2f} GB, kill {MAX_BYTES_PROCESS/1e9:,.2f} GB {HIT_LIST} until {KILL_UNTIL_BYTES_FREE/1e9:,.2f} GB free."
)
proc_data = []
while True:
time.sleep(4)
try:
ram_free = psutil.virtual_memory().available
if verbose or ram_free < MIN_BYTES_FREE:
print(f"\n{time.ctime()} {ram_free / 1e9:,.2f} GB free:")
if psutil.disk_usage("/").free < 1e9:
print("WARNING: Less than 1 GB HDD free. Check downloads.")
try:
proc_data = [
(
p.memory_info().rss,
p.pid,
p.name().split(".")[0].lower(),
p.parent()
and p.parent().name().split(".")[0].lower()
or None,
p.parent() and p.parent().pid or None,
)
for p in psutil.process_iter()
]
except Exception as ex: # OOM. [WinError 1455] The paging file is too small for this operation to complete
print(ex, ". Using last known process data.")
proc_data.sort(reverse=True)
print(" ".join(f"{p[2]} {p[0] / 1e6:,.2f} MB" for p in proc_data[:5]))
if ram_free >= MIN_BYTES_FREE:
continue
names = " ".join(p[2] for p in proc_data)
# if "memcompression" in names:
# print("Allowing memcompression.")
# continue
if "pcdrmemory" in names:
print("Allowing pcdrmemory test.")
continue
skipped = []
for rss, pid, name, parent, parent_id in proc_data:
if (
rss > MAX_BYTES_PROCESS
and ram_free < KILL_UNTIL_BYTES_FREE
and name in HIT_LIST
and True # not in_visible_windows(parent_id)
):
if (
parent != name and name not in skipped
): # and name in ("chrome", "firefox", "msedge"):
print("Skipping main", name)
skipped.append(name)
continue
print(
f"\7Killing {rss/1e6:,.2f} MB {name} {pid} of {parent} {parent_id} on {time.ctime()}"
)
kill(pid)
time.sleep(4)
ram_free = psutil.virtual_memory().available
print(f"Free RAM: {ram_free / 1e9:,.2f} GB")
except Exception as ex:
print(type(ex).__name__, ex, f"on line {sys.exc_info()[2].tb_lineno}")
import traceback
traceback.print_stack()
if __name__ == "__main__":
try:
main("verbose" in sys.argv)
except KeyboardInterrupt:
pass # Normal user exit by Ctrl+Break or Ctrl+C.
[tool.pylint.messages_control]
disable = [
"bad-continuation",
"broad-except",
"invalid-name",
"line-too-long",
"missing-function-docstring",
"mixed-indentation",
"multiple-imports",
"multiple-statements",
"pointless-string-statement",
"too-many-nested-blocks",
]
[bandit]
skips = B101,B311,B404,B603,B607
[flake8]
ignore = E266,E401,E402,E501,E701,W503,W191
[mypy]
ignore_missing_imports = True
ignore_missing_imports_per_module = True
[pycodestyle]
ignore = E265,E266,E401,E402,E501,E701,W191,W503,W504
[pydocstyle]
ignore = D100,D103,D105,D107,D203,D213,D400,D415
[pylama]
ignore = C901
@CTimmerman
Copy link
Author

On Windows 10, you can open C:\Users\[username]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup by entering shell:startup after pressing Win+R. Put a .bat file there containing python.exe -u "C:\Users\[username]\Documents\code\OOM_killer\OOM_killer.py" to run it on startup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment