← back to Ram Sniper
RAMSniper.py
465 lines
#!/usr/bin/env python3
"""
RAM Sniper — a tiny standalone memory/CPU watchdog.
WHAT IT DOES
• A top "navbar" showing live RAM % and CPU % (updates every 2s when ON).
• A list of the biggest RAM-hogging processes, largest first.
• "Kill Selected" snipes the process you pick (asks you first).
• "Auto-Snipe" (optional, OFF by default) auto-kills anything whose RAM
goes over the threshold you set — the real "sniper" mode.
HOW TO RUN
Mac: open Terminal, then: python3 RAMSniper.py
(or double-click if .py is set to open with Python Launcher)
Windows: double-click the file, OR: py RAMSniper.py
(rename to RAMSniper.pyw to hide the black console window)
It needs nothing installed — Python's built-in Tkinter draws the window.
For the most accurate numbers, optionally: pip install psutil
(works fine without it; falls back to the OS's own tools.)
Sanity check without the window: python3 RAMSniper.py --test
Single file. Nothing phones home. Nothing is written to disk.
"""
import os
import sys
import signal
import subprocess
import platform
APP_TITLE = "RAM Sniper"
REFRESH_MS = 2000 # how often to poll while ON
TOP_N = 20 # how many processes to list
DEFAULT_THRESHOLD = 15.0 # auto-snipe RAM % per-process trigger
IS_WIN = platform.system() == "Windows"
IS_MAC = platform.system() == "Darwin"
# ---------------------------------------------------------------------------
# Stats layer: prefer psutil (accurate), else per-OS fallbacks (best-effort).
# Every function returns simple numbers so the UI never has to know the source.
# ---------------------------------------------------------------------------
try:
import psutil # optional
HAVE_PSUTIL = True
except Exception:
HAVE_PSUTIL = False
def _run(cmd):
"""Run a shell command, return stdout text (empty string on any failure)."""
try:
return subprocess.run(cmd, capture_output=True, text=True, timeout=4).stdout
except Exception:
return ""
def total_ram_bytes():
if HAVE_PSUTIL:
return psutil.virtual_memory().total
if IS_MAC:
out = _run(["sysctl", "-n", "hw.memsize"])
return int(out.strip() or 0)
if IS_WIN:
import ctypes
class MS(ctypes.Structure):
_fields_ = [("dwLength", ctypes.c_ulong),
("dwMemoryLoad", ctypes.c_ulong),
("ullTotalPhys", ctypes.c_ulonglong),
("ullAvailPhys", ctypes.c_ulonglong),
("ullTotalPageFile", ctypes.c_ulonglong),
("ullAvailPageFile", ctypes.c_ulonglong),
("ullTotalVirtual", ctypes.c_ulonglong),
("ullAvailVirtual", ctypes.c_ulonglong),
("ullAvailExtendedVirtual", ctypes.c_ulonglong)]
m = MS(); m.dwLength = ctypes.sizeof(MS)
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(m))
return m.ullTotalPhys
# Linux
try:
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("MemTotal:"):
return int(line.split()[1]) * 1024
except Exception:
pass
return 0
def ram_used_percent():
"""Return system RAM used, as a percent 0..100."""
if HAVE_PSUTIL:
return psutil.virtual_memory().percent
if IS_WIN:
import ctypes
class MS(ctypes.Structure):
_fields_ = [("dwLength", ctypes.c_ulong),
("dwMemoryLoad", ctypes.c_ulong),
("ullTotalPhys", ctypes.c_ulonglong),
("ullAvailPhys", ctypes.c_ulonglong),
("ullTotalPageFile", ctypes.c_ulonglong),
("ullAvailPageFile", ctypes.c_ulonglong),
("ullTotalVirtual", ctypes.c_ulonglong),
("ullAvailVirtual", ctypes.c_ulonglong),
("ullAvailExtendedVirtual", ctypes.c_ulonglong)]
m = MS(); m.dwLength = ctypes.sizeof(MS)
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(m))
return float(m.dwMemoryLoad)
if IS_MAC:
# Derive used% from vm_stat page counts.
page = 4096
ps = _run(["sysctl", "-n", "vm.pagesize"]).strip()
if ps.isdigit():
page = int(ps)
out = _run(["vm_stat"])
pages = {}
for line in out.splitlines():
if ":" in line:
k, _, v = line.partition(":")
v = v.strip().rstrip(".")
if v.isdigit():
pages[k.strip()] = int(v)
total = total_ram_bytes()
if total and pages:
free = (pages.get("Pages free", 0) +
pages.get("Pages inactive", 0) +
pages.get("Pages speculative", 0) +
pages.get("Pages purgeable", 0)) * page
used = max(0, total - free)
return round(100.0 * used / total, 1)
return 0.0
# Linux
try:
info = {}
with open("/proc/meminfo") as f:
for line in f:
k, _, v = line.partition(":")
info[k.strip()] = int(v.split()[0]) * 1024
total = info.get("MemTotal", 0)
avail = info.get("MemAvailable", info.get("MemFree", 0))
if total:
return round(100.0 * (total - avail) / total, 1)
except Exception:
pass
return 0.0
_last_cpu = {"idle": None, "total": None}
def cpu_used_percent():
"""Return system CPU used, as a percent 0..100 (non-blocking / instant)."""
if HAVE_PSUTIL:
# interval=None => since the previous call; primed at startup.
return psutil.cpu_percent(interval=None)
if IS_WIN:
out = _run(["wmic", "cpu", "get", "loadpercentage"])
for tok in out.split():
if tok.strip().isdigit():
return float(tok.strip())
return 0.0
if IS_MAC:
# Sum recent %cpu across processes, normalise by core count. Approximate.
out = _run(["ps", "-A", "-o", "%cpu"])
vals = []
for line in out.splitlines()[1:]:
line = line.strip()
try:
vals.append(float(line))
except ValueError:
pass
ncpu = os.cpu_count() or 1
return round(min(100.0, sum(vals) / ncpu), 1)
# Linux: delta of /proc/stat between calls.
try:
with open("/proc/stat") as f:
parts = f.readline().split()[1:]
nums = list(map(int, parts))
idle = nums[3] + (nums[4] if len(nums) > 4 else 0)
total = sum(nums)
pi, pt = _last_cpu["idle"], _last_cpu["total"]
_last_cpu["idle"], _last_cpu["total"] = idle, total
if pi is None:
return 0.0
dt = total - pt
di = idle - pi
if dt <= 0:
return 0.0
return round(100.0 * (dt - di) / dt, 1)
except Exception:
return 0.0
def top_processes(n=TOP_N):
"""Return list of dicts: pid, name, rss_mb, ram_pct — biggest RAM first."""
total = total_ram_bytes() or 1
rows = []
if HAVE_PSUTIL:
for p in psutil.process_iter(["pid", "name", "memory_info"]):
try:
rss = p.info["memory_info"].rss
rows.append({
"pid": p.info["pid"],
"name": p.info["name"] or "?",
"rss_mb": rss / (1024 * 1024),
"ram_pct": 100.0 * rss / total,
})
except Exception:
continue
elif IS_WIN:
out = _run(["tasklist", "/fo", "csv", "/nh"])
for line in out.splitlines():
cols = [c.strip('"') for c in line.split('","')]
if len(cols) >= 5:
name = cols[0].strip('"')
try:
pid = int(cols[1])
mem_kb = int(cols[4].replace(",", "").replace(".", "")
.replace(" K", "").replace("K", "").strip() or 0)
except ValueError:
continue
rss = mem_kb * 1024
rows.append({"pid": pid, "name": name,
"rss_mb": rss / (1024 * 1024),
"ram_pct": 100.0 * rss / total})
else: # Mac / Linux
out = _run(["ps", "-axo", "pid=,rss=,comm="])
for line in out.splitlines():
line = line.strip()
if not line:
continue
try:
pid_s, rss_s, comm = line.split(None, 2)
pid = int(pid_s)
rss = int(rss_s) * 1024 # ps rss is in KB
except ValueError:
continue
rows.append({"pid": pid, "name": os.path.basename(comm),
"rss_mb": rss / (1024 * 1024),
"ram_pct": 100.0 * rss / total})
rows.sort(key=lambda r: r["rss_mb"], reverse=True)
return rows[:n]
def kill_pid(pid):
"""Terminate a process. Returns (ok, message)."""
if pid in (0, os.getpid()):
return False, "Refusing to kill an invalid PID or myself."
try:
if IS_WIN:
r = subprocess.run(["taskkill", "/PID", str(pid), "/F"],
capture_output=True, text=True)
return (r.returncode == 0), (r.stdout or r.stderr).strip()
os.kill(pid, signal.SIGTERM)
return True, f"Sent SIGTERM to {pid}."
except PermissionError:
return False, f"Permission denied killing {pid} (needs admin/root)."
except ProcessLookupError:
return False, f"Process {pid} already gone."
except Exception as e:
return False, f"{type(e).__name__}: {e}"
# ---------------------------------------------------------------------------
# Headless self-test: python3 RAMSniper.py --test
# ---------------------------------------------------------------------------
def selftest():
if HAVE_PSUTIL:
cpu_used_percent() # prime
print(f"{APP_TITLE} self-test on {platform.system()} "
f"({'psutil' if HAVE_PSUTIL else 'stdlib fallback'})")
print(f" Total RAM : {total_ram_bytes()/(1024**3):.1f} GB")
print(f" RAM used : {ram_used_percent():.1f} %")
print(f" CPU used : {cpu_used_percent():.1f} %")
print(" Top 5 RAM hogs:")
for r in top_processes(5):
print(f" {r['pid']:>7} {r['ram_pct']:5.1f}% "
f"{r['rss_mb']:8.1f} MB {r['name']}")
# ---------------------------------------------------------------------------
# GUI
# ---------------------------------------------------------------------------
def launch_gui():
import tkinter as tk
from tkinter import ttk, messagebox
if HAVE_PSUTIL:
cpu_used_percent() # prime the psutil delta
root = tk.Tk()
root.title(APP_TITLE)
root.geometry("720x520")
root.minsize(620, 420)
BG = "#0f1216"; FG = "#e7edf3"; ACCENT = "#00b4d8"
HOT = "#ff5c5c"; OKC = "#3ddc84"; MUTE = "#8a97a3"
root.configure(bg=BG)
state = {"on": False, "auto": tk.BooleanVar(value=False),
"threshold": tk.DoubleVar(value=DEFAULT_THRESHOLD),
"job": None}
# ---- NAVBAR ----------------------------------------------------------
nav = tk.Frame(root, bg="#151b22", height=64)
nav.pack(fill="x", side="top")
nav.pack_propagate(False)
tk.Label(nav, text="🎯 RAM SNIPER", bg="#151b22", fg=ACCENT,
font=("Helvetica", 15, "bold")).pack(side="left", padx=16)
ram_lbl = tk.Label(nav, text="RAM --%", bg="#151b22", fg=FG,
font=("Helvetica", 14, "bold"))
ram_lbl.pack(side="left", padx=14)
cpu_lbl = tk.Label(nav, text="CPU --%", bg="#151b22", fg=FG,
font=("Helvetica", 14, "bold"))
cpu_lbl.pack(side="left", padx=14)
def toggle():
state["on"] = not state["on"]
if state["on"]:
power_btn.config(text="● ON", bg=OKC, fg="#08210f")
tick()
else:
power_btn.config(text="○ OFF", bg="#2a333d", fg=FG)
if state["job"]:
root.after_cancel(state["job"]); state["job"] = None
status.config(text="Paused.")
power_btn = tk.Button(nav, text="○ OFF", command=toggle, bg="#2a333d",
fg=FG, font=("Helvetica", 12, "bold"),
relief="flat", padx=16, pady=4, cursor="hand2")
power_btn.pack(side="right", padx=16)
src = "psutil" if HAVE_PSUTIL else "built-in"
tk.Label(nav, text=src, bg="#151b22", fg=MUTE,
font=("Helvetica", 9)).pack(side="right", padx=6)
# ---- CONTROLS --------------------------------------------------------
ctl = tk.Frame(root, bg=BG); ctl.pack(fill="x", padx=14, pady=(10, 4))
tk.Checkbutton(ctl, text="Auto-snipe processes over",
variable=state["auto"], bg=BG, fg=FG, selectcolor="#151b22",
activebackground=BG, activeforeground=FG,
font=("Helvetica", 11),
command=lambda: _confirm_auto()).pack(side="left")
tk.Entry(ctl, textvariable=state["threshold"], width=5,
bg="#151b22", fg=FG, insertbackground=FG,
relief="flat").pack(side="left", padx=(6, 2))
tk.Label(ctl, text="% RAM", bg=BG, fg=FG,
font=("Helvetica", 11)).pack(side="left")
def _confirm_auto():
if state["auto"].get():
if not messagebox.askyesno(
"Enable Auto-Snipe?",
"Auto-Snipe will automatically KILL any process whose RAM "
"goes over the threshold, with no further prompts.\n\n"
"Turn it on?"):
state["auto"].set(False)
kill_btn = tk.Button(ctl, text="🔫 Kill Selected",
command=lambda: do_kill(), bg=HOT, fg="white",
font=("Helvetica", 11, "bold"), relief="flat",
padx=14, pady=3, cursor="hand2")
kill_btn.pack(side="right")
# ---- PROCESS LIST ----------------------------------------------------
style = ttk.Style()
try:
style.theme_use("clam")
except Exception:
pass
style.configure("Treeview", background="#12171d", fieldbackground="#12171d",
foreground=FG, rowheight=24, borderwidth=0)
style.configure("Treeview.Heading", background="#151b22", foreground=ACCENT,
font=("Helvetica", 10, "bold"))
style.map("Treeview", background=[("selected", ACCENT)],
foreground=[("selected", "#04121a")])
cols = ("pid", "name", "ram_mb", "ram_pct")
tree = ttk.Treeview(root, columns=cols, show="headings")
tree.heading("pid", text="PID")
tree.heading("name", text="Process")
tree.heading("ram_mb", text="RAM (MB)")
tree.heading("ram_pct", text="RAM %")
tree.column("pid", width=80, anchor="e")
tree.column("name", width=320, anchor="w")
tree.column("ram_mb", width=120, anchor="e")
tree.column("ram_pct", width=90, anchor="e")
tree.tag_configure("hot", foreground=HOT)
tree.pack(fill="both", expand=True, padx=14, pady=8)
status = tk.Label(root, text="Press ON to start.", bg=BG, fg=MUTE,
anchor="w", font=("Helvetica", 10))
status.pack(fill="x", padx=14, pady=(0, 8))
def do_kill():
sel = tree.selection()
if not sel:
status.config(text="Select a process first."); return
item = tree.item(sel[0])["values"]
pid, name = int(item[0]), item[1]
if messagebox.askyesno("Snipe process?",
f"Kill {name} (PID {pid}) ?"):
ok, msg = kill_pid(pid)
status.config(text=msg, fg=OKC if ok else HOT)
refresh()
def refresh():
procs = top_processes()
thr = 0.0
try:
thr = float(state["threshold"].get())
except Exception:
thr = DEFAULT_THRESHOLD
auto_hits = []
tree.delete(*tree.get_children())
for r in procs:
hot = r["ram_pct"] >= thr
tree.insert("", "end",
values=(r["pid"], r["name"],
f"{r['rss_mb']:.0f}", f"{r['ram_pct']:.1f}"),
tags=("hot",) if hot else ())
if hot:
auto_hits.append(r)
return auto_hits
def tick():
ram = ram_used_percent()
cpu = cpu_used_percent()
ram_lbl.config(text=f"RAM {ram:.0f}%",
fg=HOT if ram >= 85 else FG)
cpu_lbl.config(text=f"CPU {cpu:.0f}%",
fg=HOT if cpu >= 85 else FG)
hits = refresh()
if state["auto"].get() and hits:
for r in hits:
if r["pid"] == os.getpid():
continue
ok, msg = kill_pid(r["pid"])
status.config(text=f"AUTO-SNIPED {r['name']} ({r['pid']}): {msg}",
fg=OKC if ok else HOT)
else:
status.config(text=f"Watching {TOP_N} processes · "
f"threshold {state['threshold'].get():.0f}% · "
f"{'AUTO-SNIPE ON' if state['auto'].get() else 'manual'}",
fg=MUTE)
if state["on"]:
state["job"] = root.after(REFRESH_MS, tick)
root.mainloop()
if __name__ == "__main__":
if "--test" in sys.argv:
selftest()
else:
try:
launch_gui()
except Exception as e:
print(f"Could not open the window: {e}")
print("Try the headless check: python3 RAMSniper.py --test")