141 lines
4.2 KiB
Python
141 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Memory Sentinel Script
|
|
|
|
This script monitors the system's RAM usage and kills the process with the highest
|
|
memory consumption if available RAM drops below 5GB.
|
|
|
|
Usage:
|
|
python memory_sentinel.py
|
|
|
|
The script runs continuously, checking memory usage every second.
|
|
|
|
Script was generated by Claude 3.7 with the following prompt:
|
|
|
|
I'm working on a server and I have one script that sometimes consumes so much memory that the entire server becomes unresponsive. This is a hue problem because then I cannot log in to it anymore.
|
|
|
|
Could you write a sentinel script in python that does the following:
|
|
|
|
Check the total available RAM of the system (disregarding swap)
|
|
Check the currently used RAM of the system
|
|
If we have more than 5G left, do nothing
|
|
Else, find the process with the highest RAM consumption and kill it. Note: Use the SIGKILL command
|
|
Check every second
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import signal
|
|
import time
|
|
|
|
import psutil
|
|
|
|
# Set up logging
|
|
logging.basicConfig(
|
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", filename="memory_sentinel.log", filemode="a"
|
|
)
|
|
|
|
# Memory threshold in GB
|
|
MEMORY_THRESHOLD_GB = 5
|
|
# Convert to bytes for comparison
|
|
MEMORY_THRESHOLD_BYTES = MEMORY_THRESHOLD_GB * 1024 * 1024 * 1024
|
|
|
|
|
|
def get_available_ram():
|
|
"""Get the available RAM in bytes, excluding swap."""
|
|
return psutil.virtual_memory().available
|
|
|
|
|
|
def get_total_ram():
|
|
"""Get the total RAM in bytes, excluding swap."""
|
|
return psutil.virtual_memory().total
|
|
|
|
|
|
def get_used_ram():
|
|
"""Get the used RAM in bytes."""
|
|
return psutil.virtual_memory().used
|
|
|
|
|
|
def get_process_with_highest_memory():
|
|
"""Find the process with the highest memory consumption."""
|
|
processes = []
|
|
for proc in psutil.process_iter(["pid", "name", "memory_info"]):
|
|
try:
|
|
processes.append((proc.info["pid"], proc.info["name"], proc.info["memory_info"].rss))
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
pass
|
|
|
|
# Sort by memory usage (descending)
|
|
processes.sort(key=lambda x: x[2], reverse=True)
|
|
|
|
if processes:
|
|
return processes[0]
|
|
return None
|
|
|
|
|
|
def kill_process(pid):
|
|
"""Kill a process using SIGKILL."""
|
|
try:
|
|
os.kill(pid, signal.SIGKILL)
|
|
return True
|
|
except OSError as e:
|
|
logging.error(f"Failed to kill process {pid}: {e}")
|
|
return False
|
|
|
|
|
|
def format_bytes(bytes_value):
|
|
"""Format bytes to a human-readable string."""
|
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
if bytes_value < 1024.0:
|
|
return f"{bytes_value:.2f} {unit}"
|
|
bytes_value /= 1024.0
|
|
return f"{bytes_value:.2f} PB"
|
|
|
|
|
|
def main():
|
|
"""Main function that runs the memory monitoring loop."""
|
|
logging.info("Memory Sentinel started")
|
|
logging.info(f"Memory threshold set to {MEMORY_THRESHOLD_GB}GB")
|
|
|
|
try:
|
|
while True:
|
|
available_ram = get_available_ram()
|
|
total_ram = get_total_ram()
|
|
used_ram = get_used_ram()
|
|
|
|
logging.debug(
|
|
f"Total RAM: {format_bytes(total_ram)}, "
|
|
+ f"Used RAM: {format_bytes(used_ram)}, "
|
|
+ f"Available RAM: {format_bytes(available_ram)}"
|
|
)
|
|
|
|
if available_ram < MEMORY_THRESHOLD_BYTES:
|
|
logging.warning(
|
|
f"Available RAM ({format_bytes(available_ram)}) " + f"below threshold of {MEMORY_THRESHOLD_GB}GB"
|
|
)
|
|
|
|
process = get_process_with_highest_memory()
|
|
if process:
|
|
pid, name, memory = process
|
|
logging.warning(f"Killing process {pid} ({name}) " + f"using {format_bytes(memory)}")
|
|
|
|
if kill_process(pid):
|
|
logging.info(f"Successfully killed process {pid} ({name})")
|
|
else:
|
|
logging.error(f"Failed to kill process {pid} ({name})")
|
|
else:
|
|
logging.warning("No process found to kill")
|
|
|
|
# Sleep for 1 second
|
|
time.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
logging.info("Memory Sentinel stopped by user")
|
|
except Exception as e:
|
|
logging.error(f"Unexpected error: {e}")
|
|
raise
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|