238 lines
6.1 KiB
Python
Executable File
238 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
sysmon - System monitor
|
|
|
|
Quick system status and resource tracking.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
|
HISTORY_FILE = WORKSPACE / "data" / "sysmon_history.json"
|
|
|
|
def get_cpu_usage():
|
|
"""Get CPU usage percentage."""
|
|
try:
|
|
# Read /proc/stat for CPU usage
|
|
with open('/proc/stat') as f:
|
|
line = f.readline()
|
|
parts = line.split()
|
|
idle = int(parts[4])
|
|
total = sum(int(p) for p in parts[1:])
|
|
|
|
# Read again after a moment
|
|
import time
|
|
time.sleep(0.1)
|
|
|
|
with open('/proc/stat') as f:
|
|
line = f.readline()
|
|
parts = line.split()
|
|
idle2 = int(parts[4])
|
|
total2 = sum(int(p) for p in parts[1:])
|
|
|
|
idle_delta = idle2 - idle
|
|
total_delta = total2 - total
|
|
|
|
if total_delta == 0:
|
|
return 0
|
|
|
|
usage = 100 * (1 - idle_delta / total_delta)
|
|
return round(usage, 1)
|
|
except:
|
|
return None
|
|
|
|
def get_memory():
|
|
"""Get memory usage."""
|
|
try:
|
|
with open('/proc/meminfo') as f:
|
|
lines = f.readlines()
|
|
|
|
mem = {}
|
|
for line in lines:
|
|
parts = line.split()
|
|
key = parts[0].rstrip(':')
|
|
value = int(parts[1]) # in KB
|
|
mem[key] = value
|
|
|
|
total = mem.get('MemTotal', 0) / 1024 / 1024 # GB
|
|
available = mem.get('MemAvailable', 0) / 1024 / 1024 # GB
|
|
used = total - available
|
|
pct = (used / total * 100) if total > 0 else 0
|
|
|
|
return {
|
|
'total_gb': round(total, 1),
|
|
'used_gb': round(used, 1),
|
|
'available_gb': round(available, 1),
|
|
'percent': round(pct, 1),
|
|
}
|
|
except:
|
|
return None
|
|
|
|
def get_disk():
|
|
"""Get disk usage."""
|
|
try:
|
|
stat = os.statvfs('/')
|
|
total = stat.f_blocks * stat.f_frsize / 1024 / 1024 / 1024 # GB
|
|
free = stat.f_bavail * stat.f_frsize / 1024 / 1024 / 1024 # GB
|
|
used = total - free
|
|
pct = (used / total * 100) if total > 0 else 0
|
|
|
|
return {
|
|
'total_gb': round(total, 1),
|
|
'used_gb': round(used, 1),
|
|
'free_gb': round(free, 1),
|
|
'percent': round(pct, 1),
|
|
}
|
|
except:
|
|
return None
|
|
|
|
def get_load():
|
|
"""Get system load average."""
|
|
try:
|
|
with open('/proc/loadavg') as f:
|
|
parts = f.read().split()
|
|
return {
|
|
'load1': float(parts[0]),
|
|
'load5': float(parts[1]),
|
|
'load15': float(parts[2]),
|
|
}
|
|
except:
|
|
return None
|
|
|
|
def get_uptime():
|
|
"""Get system uptime."""
|
|
try:
|
|
with open('/proc/uptime') as f:
|
|
uptime_seconds = float(f.read().split()[0])
|
|
|
|
days = int(uptime_seconds // 86400)
|
|
hours = int((uptime_seconds % 86400) // 3600)
|
|
mins = int((uptime_seconds % 3600) // 60)
|
|
|
|
if days > 0:
|
|
return f"{days}d {hours}h {mins}m"
|
|
elif hours > 0:
|
|
return f"{hours}h {mins}m"
|
|
else:
|
|
return f"{mins}m"
|
|
except:
|
|
return None
|
|
|
|
def get_processes():
|
|
"""Get top processes by memory/CPU."""
|
|
try:
|
|
result = subprocess.run(
|
|
['ps', 'aux', '--sort=-rss'],
|
|
capture_output=True, text=True
|
|
)
|
|
lines = result.stdout.strip().split('\n')[1:6] # Skip header, top 5
|
|
|
|
processes = []
|
|
for line in lines:
|
|
parts = line.split(None, 10)
|
|
if len(parts) >= 11:
|
|
processes.append({
|
|
'user': parts[0],
|
|
'cpu': float(parts[2]),
|
|
'mem': float(parts[3]),
|
|
'cmd': parts[10][:30],
|
|
})
|
|
return processes
|
|
except:
|
|
return []
|
|
|
|
def bar(pct: float, width: int = 20) -> str:
|
|
"""Create a progress bar."""
|
|
filled = int(pct / 100 * width)
|
|
return "█" * filled + "░" * (width - filled)
|
|
|
|
def show_status():
|
|
"""Show current system status."""
|
|
print()
|
|
print("╔" + "═" * 40 + "╗")
|
|
print(f"║ 🖥️ System Monitor {' ' * 20}║")
|
|
print("╚" + "═" * 40 + "╝")
|
|
|
|
# Uptime
|
|
uptime = get_uptime()
|
|
if uptime:
|
|
print(f"\n⏰ Uptime: {uptime}")
|
|
|
|
# CPU
|
|
cpu = get_cpu_usage()
|
|
if cpu is not None:
|
|
print(f"\n🔲 CPU: {bar(cpu)} {cpu}%")
|
|
|
|
# Memory
|
|
mem = get_memory()
|
|
if mem:
|
|
print(f"🔲 Memory: {bar(mem['percent'])} {mem['percent']}%")
|
|
print(f" {mem['used_gb']:.1f}GB / {mem['total_gb']:.1f}GB")
|
|
|
|
# Disk
|
|
disk = get_disk()
|
|
if disk:
|
|
print(f"🔲 Disk: {bar(disk['percent'])} {disk['percent']}%")
|
|
print(f" {disk['used_gb']:.0f}GB / {disk['total_gb']:.0f}GB")
|
|
|
|
# Load
|
|
load = get_load()
|
|
if load:
|
|
print(f"\n📊 Load: {load['load1']:.2f} / {load['load5']:.2f} / {load['load15']:.2f}")
|
|
|
|
# Top processes
|
|
procs = get_processes()
|
|
if procs:
|
|
print(f"\n🔝 Top Processes:")
|
|
for p in procs[:3]:
|
|
print(f" {p['mem']:.1f}% {p['cmd']}")
|
|
|
|
print()
|
|
|
|
def record():
|
|
"""Record current stats to history."""
|
|
HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
if HISTORY_FILE.exists():
|
|
with open(HISTORY_FILE) as f:
|
|
history = json.load(f)
|
|
else:
|
|
history = []
|
|
|
|
entry = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'cpu': get_cpu_usage(),
|
|
'memory': get_memory(),
|
|
'disk': get_disk(),
|
|
'load': get_load(),
|
|
}
|
|
|
|
history.append(entry)
|
|
|
|
# Keep last 1000 entries
|
|
history = history[-1000:]
|
|
|
|
with open(HISTORY_FILE, 'w') as f:
|
|
json.dump(history, f)
|
|
|
|
print(f"✓ Recorded at {entry['timestamp'][:19]}")
|
|
|
|
def main():
|
|
import sys
|
|
|
|
if len(sys.argv) > 1:
|
|
cmd = sys.argv[1]
|
|
if cmd == 'record':
|
|
record()
|
|
else:
|
|
print("Usage: sysmon [record]")
|
|
else:
|
|
show_status()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|