← back to Ram Sniper
initial scaffold: standalone RAM Sniper tool + README
bed7c85fe5afb013703434bf3ff5f7010108d0a0 · 2026-09-09 10:13:51 -0700 · Steve
Single-file cross-platform RAM/CPU watchdog (Tkinter GUI, psutil-preferred
with stdlib fallback). Navbar live RAM%/CPU% + ON/OFF toggle, RAM-hog list,
kill-selected, optional auto-snipe. Runs on stock Python; --test is headless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2uvgXWs4FjLd6eDBDNUxP
Files touched
A .gitignoreA RAMSniper.pyA README.md
Diff
commit bed7c85fe5afb013703434bf3ff5f7010108d0a0
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 10:13:51 2026 -0700
initial scaffold: standalone RAM Sniper tool + README
Single-file cross-platform RAM/CPU watchdog (Tkinter GUI, psutil-preferred
with stdlib fallback). Navbar live RAM%/CPU% + ON/OFF toggle, RAM-hog list,
kill-selected, optional auto-snipe. Runs on stock Python; --test is headless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2uvgXWs4FjLd6eDBDNUxP
---
.gitignore | 12 ++
RAMSniper.py | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 60 ++++++++
3 files changed, 536 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eaebc11
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+__pycache__/
+*.pyc
+.venv/
+venv/
diff --git a/RAMSniper.py b/RAMSniper.py
new file mode 100644
index 0000000..0c1cbfc
--- /dev/null
+++ b/RAMSniper.py
@@ -0,0 +1,464 @@
+#!/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")
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5159dda
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+# RAM Sniper
+
+A standalone, single-file cross-platform RAM/CPU watchdog with a GUI. Built to hand
+to someone as one file that runs on a stock Python install — no `pip install` required.
+
+## What it does
+
+- **Navbar** with live **RAM %** and **CPU %**, plus an **ON/OFF** toggle button.
+- **Process list** (Treeview) of the biggest RAM hogs — PID / Process / RAM MB / RAM % —
+ sorted largest-first, refreshing every 2s.
+- **🔫 Kill Selected** — terminates the highlighted process (with a confirm dialog).
+- **Auto-Snipe** (OFF by default) — when enabled (and confirmed), auto-kills any process
+ whose RAM usage crosses a threshold (default **15%**).
+
+## Run it
+
+```sh
+python3 RAMSniper.py
+```
+
+That's it. Tkinter ships with the standard Python installer on **macOS** and **Windows**,
+so the GUI works out of the box — genuinely one-file-and-run.
+
+Headless self-test (no window, prints the stats it reads):
+
+```sh
+python3 RAMSniper.py --test
+```
+
+## How the stats are read (psutil-preferred, stdlib fallback)
+
+The tool prefers [`psutil`](https://pypi.org/project/psutil/) when it's installed
+(faster, cleaner numbers), and **falls back to platform stdlib tools** when it isn't —
+so it still works on a bare Python:
+
+| Platform | Fallback source |
+|----------|-----------------|
+| macOS | `sysctl`, `vm_stat`, `ps` |
+| Windows | `ctypes` `GlobalMemoryStatusEx`, `wmic`/`tasklist`/`taskkill` |
+| Linux | `/proc/meminfo`, `/proc/stat`, `/proc/<pid>` |
+
+**Optional speed-up:** for tighter/faster readings, install psutil once —
+
+```sh
+pip install psutil
+```
+
+— but it is *not* required. The whole point of the tool is that it runs without it.
+
+## Safety
+
+- `kill_pid()` refuses PID 0 and refuses to kill the Sniper's own process.
+- Kill and Auto-Snipe-enable both prompt for confirmation before acting.
+- Auto-Snipe is **OFF** on every launch; it never persists an armed state.
+
+## Tunables (top of `RAMSniper.py`)
+
+- `REFRESH_MS = 2000` — refresh cadence in milliseconds.
+- `TOP_N = 20` — how many processes to list.
+- Auto-Snipe threshold default — `15` (% RAM).
(oldest)
·
back to Ram Sniper
·
(newest)