various plugin refactors and fixes
This commit is contained in:
+236
-277
@@ -1,354 +1,313 @@
|
||||
"""
|
||||
Comprehensive system information and resource monitoring.
|
||||
All blocking calls (psutil, subprocess) run in a thread pool.
|
||||
Comprehensive system information – code block with emoji + aligned columns.
|
||||
All blocking calls run in thread pool.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import os
|
||||
import asyncio
|
||||
import psutil
|
||||
import socket
|
||||
import datetime
|
||||
import subprocess
|
||||
import logging, platform, os, asyncio, psutil, socket, datetime, subprocess
|
||||
import simplematrixbotlib as botlib
|
||||
from plugins.common import collapsible_summary, html_escape
|
||||
from plugins.common import collapsible_summary, html_escape, code_block
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Handle !sysinfo command for system information.
|
||||
"""
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("sysinfo"):
|
||||
args = match.args()
|
||||
if args and args[0].lower() == 'help':
|
||||
await show_usage(room, bot)
|
||||
return
|
||||
await get_system_info(room, bot)
|
||||
|
||||
async def show_usage(room, bot):
|
||||
"""Display sysinfo command usage."""
|
||||
usage = """
|
||||
<strong>💻 System Information Plugin</strong>
|
||||
|
||||
<strong>!sysinfo</strong> - Display comprehensive system information
|
||||
<strong>!sysinfo help</strong> - Show this help message
|
||||
|
||||
<strong>Information Provided:</strong>
|
||||
• System hardware (CPU, RAM, storage, GPU)
|
||||
• Operating system and kernel details
|
||||
• Network configuration and interfaces
|
||||
• Running processes and resource usage
|
||||
• Temperature and hardware sensors
|
||||
• System load and performance metrics
|
||||
• Docker container status (if available)
|
||||
"""
|
||||
await bot.api.send_markdown_message(room.room_id, usage)
|
||||
|
||||
# ----- Async wrappers for blocking functions -----
|
||||
async def _run_blocking(func, *args, **kwargs):
|
||||
loop = asyncio.get_running_loop()
|
||||
return await loop.run_in_executor(None, lambda: func(*args, **kwargs))
|
||||
|
||||
# ----- Individual data collectors (all sync, run in thread) -----
|
||||
# ---------- Data collectors (unchanged) ----------
|
||||
def _system_overview():
|
||||
boot = datetime.datetime.fromtimestamp(psutil.boot_time())
|
||||
uptime_delta = datetime.datetime.now() - boot
|
||||
uptime_str = str(datetime.timedelta(seconds=int(uptime_delta.total_seconds())))
|
||||
return {
|
||||
'hostname': socket.gethostname(),
|
||||
'os': platform.system(),
|
||||
'os_release': platform.release(),
|
||||
'os_version': platform.version(),
|
||||
'architecture': platform.architecture()[0],
|
||||
'machine': platform.machine(),
|
||||
'processor': platform.processor(),
|
||||
'boot_time': datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'uptime': str(datetime.timedelta(seconds=int((datetime.datetime.now() - datetime.datetime.fromtimestamp(psutil.boot_time())).total_seconds()))),
|
||||
'users': len(psutil.users())
|
||||
"hostname": socket.gethostname(),
|
||||
"os": f"{platform.system()} {platform.release()}",
|
||||
"architecture": platform.architecture()[0],
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"boot_time": boot.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"uptime": uptime_str,
|
||||
"users": len(psutil.users())
|
||||
}
|
||||
|
||||
def _cpu_info():
|
||||
cpu_times = psutil.cpu_times_percent(interval=1)
|
||||
cpu_freq = psutil.cpu_freq()
|
||||
load_avg = os.getloadavg() if hasattr(os, 'getloadavg') else (0,0,0)
|
||||
load = os.getloadavg() if hasattr(os, "getloadavg") else (0,0,0)
|
||||
return {
|
||||
'physical_cores': psutil.cpu_count(logical=False),
|
||||
'total_cores': psutil.cpu_count(logical=True),
|
||||
'max_frequency': f"{cpu_freq.max:.1f} MHz" if cpu_freq else "N/A",
|
||||
'current_frequency': f"{cpu_freq.current:.1f} MHz" if cpu_freq else "N/A",
|
||||
'usage_percent': psutil.cpu_percent(interval=1),
|
||||
'user_time': cpu_times.user,
|
||||
'system_time': cpu_times.system,
|
||||
'idle_time': cpu_times.idle,
|
||||
'load_avg': ", ".join(f"{l:.2f}" for l in load_avg)
|
||||
"physical_cores": psutil.cpu_count(logical=False),
|
||||
"logical_cores": psutil.cpu_count(logical=True),
|
||||
"max_freq": f"{cpu_freq.max:.0f} MHz" if cpu_freq else "N/A",
|
||||
"current_freq": f"{cpu_freq.current:.0f} MHz" if cpu_freq else "N/A",
|
||||
"usage": f"{psutil.cpu_percent(interval=1)}%",
|
||||
"load_avg": f"{load[0]:.2f} {load[1]:.2f} {load[2]:.2f}"
|
||||
}
|
||||
|
||||
def _memory_info():
|
||||
mem = psutil.virtual_memory()
|
||||
swap = psutil.swap_memory()
|
||||
return {
|
||||
'total': f"{mem.total / (1024**3):.2f} GB",
|
||||
'available': f"{mem.available / (1024**3):.2f} GB",
|
||||
'used': f"{mem.used / (1024**3):.2f} GB",
|
||||
'usage_percent': mem.percent,
|
||||
'swap_total': f"{swap.total / (1024**3):.2f} GB",
|
||||
'swap_used': f"{swap.used / (1024**3):.2f} GB",
|
||||
'swap_free': f"{swap.free / (1024**3):.2f} GB",
|
||||
'swap_percent': swap.percent
|
||||
"total_ram": f"{mem.total / (1024**3):.1f} GB",
|
||||
"used_ram": f"{mem.used / (1024**3):.1f} GB",
|
||||
"ram_percent": f"{mem.percent}%",
|
||||
"available_ram": f"{mem.available / (1024**3):.1f} GB",
|
||||
"total_swap": f"{swap.total / (1024**3):.1f} GB" if swap.total > 0 else "N/A",
|
||||
"used_swap": f"{swap.used / (1024**3):.1f} GB" if swap.total > 0 else "N/A",
|
||||
"swap_percent": f"{swap.percent}%" if swap.total > 0 else "N/A"
|
||||
}
|
||||
|
||||
def _storage_info():
|
||||
def _disk_info():
|
||||
partitions = psutil.disk_partitions()
|
||||
storage_list = []
|
||||
for part in partitions:
|
||||
mounted = []
|
||||
for p in partitions:
|
||||
try:
|
||||
usage = psutil.disk_usage(part.mountpoint)
|
||||
storage_list.append({
|
||||
'device': part.device,
|
||||
'mountpoint': part.mountpoint,
|
||||
'fstype': part.fstype,
|
||||
'total': f"{usage.total / (1024**3):.2f} GB",
|
||||
'used': f"{usage.used / (1024**3):.2f} GB",
|
||||
'free': f"{usage.free / (1024**3):.2f} GB",
|
||||
'percent': usage.percent
|
||||
usage = psutil.disk_usage(p.mountpoint)
|
||||
mounted.append({
|
||||
"mount": p.mountpoint,
|
||||
"used": f"{usage.used / (1024**3):.1f} GB",
|
||||
"total": f"{usage.total / (1024**3):.1f} GB",
|
||||
"percent": usage.percent
|
||||
})
|
||||
except:
|
||||
pass
|
||||
disk_io = psutil.disk_io_counters()
|
||||
io_info = {
|
||||
'read_count': disk_io.read_count if disk_io else 0,
|
||||
'write_count': disk_io.write_count if disk_io else 0,
|
||||
'read_bytes': f"{disk_io.read_bytes / (1024**3):.2f} GB" if disk_io else "0 GB",
|
||||
'write_bytes': f"{disk_io.write_bytes / (1024**3):.2f} GB" if disk_io else "0 GB"
|
||||
}
|
||||
return {'partitions': storage_list, 'io_stats': io_info}
|
||||
io = psutil.disk_io_counters()
|
||||
io_read = f"{io.read_bytes / (1024**3):.2f} GB" if io else "0 GB"
|
||||
io_write = f"{io.write_bytes / (1024**3):.2f} GB" if io else "0 GB"
|
||||
return mounted, io_read, io_write
|
||||
|
||||
def _network_info():
|
||||
interfaces = psutil.net_if_addrs()
|
||||
ifaces = psutil.net_if_addrs()
|
||||
io_counters = psutil.net_io_counters(pernic=True)
|
||||
net_list = []
|
||||
for iface, addrs in interfaces.items():
|
||||
if iface == 'lo':
|
||||
net = []
|
||||
for name, addrs in ifaces.items():
|
||||
if name == "lo":
|
||||
continue
|
||||
info = {
|
||||
'interface': iface,
|
||||
'ipv4': next((a.address for a in addrs if a.family == socket.AF_INET), 'N/A'),
|
||||
'ipv6': next((a.address for a in addrs if a.family == socket.AF_INET6), 'N/A'),
|
||||
'mac': next((a.address for a in addrs if a.family == psutil.AF_LINK), 'N/A'),
|
||||
}
|
||||
io = io_counters.get(iface)
|
||||
if io:
|
||||
info['bytes_sent'] = f"{io.bytes_sent / (1024**2):.2f} MB"
|
||||
info['bytes_recv'] = f"{io.bytes_recv / (1024**2):.2f} MB"
|
||||
else:
|
||||
info['bytes_sent'] = 'N/A'
|
||||
info['bytes_recv'] = 'N/A'
|
||||
net_list.append(info)
|
||||
return net_list
|
||||
ip4 = next((a.address for a in addrs if a.family == socket.AF_INET), None)
|
||||
if ip4:
|
||||
stats = io_counters.get(name)
|
||||
sent = f"{stats.bytes_sent / (1024**2):.1f} MB" if stats else "0 MB"
|
||||
recv = f"{stats.bytes_recv / (1024**2):.1f} MB" if stats else "0 MB"
|
||||
net.append((name, ip4, sent, recv))
|
||||
return net
|
||||
|
||||
def _process_info():
|
||||
def _top_processes():
|
||||
procs = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
|
||||
for p in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
|
||||
try:
|
||||
procs.append(proc.info)
|
||||
procs.append(p.info)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
top_cpu = sorted(procs, key=lambda x: x['cpu_percent'] or 0, reverse=True)[:5]
|
||||
return {'total_processes': len(procs), 'top_cpu': top_cpu}
|
||||
return top_cpu, len(procs)
|
||||
|
||||
def _gpu_info():
|
||||
info = {}
|
||||
try:
|
||||
res = subprocess.run(
|
||||
['nvidia-smi', '--query-gpu=name,memory.used,memory.total,temperature.gpu,utilization.gpu',
|
||||
'--format=csv,noheader,nounits'],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if res.returncode == 0:
|
||||
gpus = []
|
||||
for line in res.stdout.strip().split('\n'):
|
||||
parts = [p.strip() for p in line.split(',')]
|
||||
if len(parts) >= 5:
|
||||
gpus.append({
|
||||
"name": parts[0],
|
||||
"mem_used": f"{parts[1]} MB",
|
||||
"mem_total": f"{parts[2]} MB",
|
||||
"temp": f"{parts[3]}°C",
|
||||
"usage": f"{parts[4]}%"
|
||||
})
|
||||
if gpus:
|
||||
info["nvidia"] = gpus
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
res = subprocess.run(['lspci'], capture_output=True, text=True)
|
||||
if res.returncode == 0:
|
||||
lines = [l for l in res.stdout.split('\n') if 'VGA' in l or '3D' in l]
|
||||
if lines:
|
||||
info["detected"] = lines[:2]
|
||||
except:
|
||||
pass
|
||||
return info
|
||||
|
||||
def _docker_info():
|
||||
try:
|
||||
result = subprocess.run(['docker', '--version'], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return {'available': False}
|
||||
result = subprocess.run(['docker', 'ps', '--format', '{{.Names}}|{{.Status}}|{{.Ports}}'],
|
||||
capture_output=True, text=True)
|
||||
ver = subprocess.run(['docker', '--version'], capture_output=True, text=True)
|
||||
if ver.returncode != 0:
|
||||
return None
|
||||
ps_res = subprocess.run(
|
||||
['docker', 'ps', '--format', '{{.Names}}|{{.Status}}'],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
containers = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
for line in ps_res.stdout.strip().split('\n'):
|
||||
if line:
|
||||
parts = line.split('|')
|
||||
if len(parts) >= 2:
|
||||
containers.append({'name': parts[0], 'status': parts[1], 'ports': parts[2] if len(parts)>2 else 'N/A'})
|
||||
return {'available': True, 'containers': containers, 'total_running': len(containers)}
|
||||
containers.append({"name": parts[0], "status": parts[1]})
|
||||
return containers
|
||||
except:
|
||||
return {'available': False}
|
||||
return None
|
||||
|
||||
def _sensor_info():
|
||||
temps = psutil.sensors_temperatures()
|
||||
fans = psutil.sensors_fans()
|
||||
battery = psutil.sensors_battery()
|
||||
sensor = {'temperatures': {}, 'fans': {}, 'battery': {}}
|
||||
data = {"temps": [], "fans": [], "battery": None}
|
||||
if temps:
|
||||
for name, entries in temps.items():
|
||||
sensor['temperatures'][name] = [f"{e.current}°C" for e in entries[:2]]
|
||||
for chip, entries in temps.items():
|
||||
for e in entries[:2]:
|
||||
data["temps"].append(f"{e.label or chip}: {e.current}°C")
|
||||
if fans:
|
||||
for name, entries in fans.items():
|
||||
sensor['fans'][name] = [f"{e.current} RPM" for e in entries[:2]]
|
||||
for chip, entries in fans.items():
|
||||
for e in entries[:2]:
|
||||
data["fans"].append(f"{e.label or chip}: {e.current} RPM")
|
||||
if battery:
|
||||
sensor['battery'] = {
|
||||
'percent': battery.percent,
|
||||
'power_plugged': battery.power_plugged,
|
||||
'time_left': f"{battery.secsleft // 3600}h {(battery.secsleft % 3600) // 60}m" if battery.secsleft != psutil.POWER_TIME_UNLIMITED else "Unknown"
|
||||
}
|
||||
return sensor
|
||||
rem = ""
|
||||
if battery.secsleft != psutil.POWER_TIME_UNLIMITED and battery.secsleft > 0:
|
||||
h = battery.secsleft // 3600
|
||||
m = (battery.secsleft % 3600) // 60
|
||||
rem = f" ({h}h {m}m left)"
|
||||
plugged = " 🔌" if battery.power_plugged else ""
|
||||
data["battery"] = f"{battery.percent}%{plugged}{rem}"
|
||||
return data
|
||||
|
||||
def _gpu_info():
|
||||
gpu_data = {}
|
||||
# NVIDIA
|
||||
try:
|
||||
res = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu',
|
||||
'--format=csv,noheader,nounits'], capture_output=True, text=True)
|
||||
if res.returncode == 0:
|
||||
nvidia = []
|
||||
for line in res.stdout.strip().split('\n'):
|
||||
parts = [p.strip() for p in line.split(',')]
|
||||
if len(parts) >= 6:
|
||||
nvidia.append({
|
||||
'name': parts[0],
|
||||
'memory_total': f"{parts[1]} MB",
|
||||
'memory_used': f"{parts[2]} MB",
|
||||
'memory_free': f"{parts[3]} MB",
|
||||
'temperature': f"{parts[4]}°C",
|
||||
'utilization': f"{parts[5]}%"
|
||||
})
|
||||
if nvidia:
|
||||
gpu_data['nvidia'] = nvidia
|
||||
except:
|
||||
pass
|
||||
# lspci fallback
|
||||
try:
|
||||
res = subprocess.run(['lspci'], capture_output=True, text=True)
|
||||
if res.returncode == 0:
|
||||
gpu_lines = [l for l in res.stdout.split('\n') if 'VGA' in l or '3D' in l]
|
||||
if gpu_lines:
|
||||
gpu_data['detected'] = gpu_lines[:3]
|
||||
except:
|
||||
pass
|
||||
return gpu_data
|
||||
|
||||
# ----- Main info gatherer -----
|
||||
# -------------------------------------------------------------------
|
||||
# Main builder
|
||||
# -------------------------------------------------------------------
|
||||
async def get_system_info(room, bot):
|
||||
await bot.api.send_text_message(room.room_id, "🔍 Gathering system information...")
|
||||
|
||||
# Run all blocking collectors concurrently
|
||||
system = await _run_blocking(_system_overview)
|
||||
cpu = await _run_blocking(_cpu_info)
|
||||
memory = await _run_blocking(_memory_info)
|
||||
storage = await _run_blocking(_storage_info)
|
||||
network = await _run_blocking(_network_info)
|
||||
processes = await _run_blocking(_process_info)
|
||||
mem = await _run_blocking(_memory_info)
|
||||
disks, io_read, io_write = await _run_blocking(_disk_info)
|
||||
net = await _run_blocking(_network_info)
|
||||
top_procs, total_procs = await _run_blocking(_top_processes)
|
||||
gpu = await _run_blocking(_gpu_info)
|
||||
docker = await _run_blocking(_docker_info)
|
||||
sensors = await _run_blocking(_sensor_info)
|
||||
gpu = await _run_blocking(_gpu_info)
|
||||
|
||||
# Build output HTML
|
||||
output = await format_system_info(system, cpu, memory, storage, network, processes, docker, sensors, gpu)
|
||||
sections = []
|
||||
|
||||
# System Overview
|
||||
sys_rows = [
|
||||
("💻", "Hostname", system["hostname"]),
|
||||
("🖥️", "OS", system["os"]),
|
||||
("📐", "Architecture", system["architecture"]),
|
||||
("⚙️", "Machine", system["machine"]),
|
||||
("🔧", "Processor", system["processor"]),
|
||||
("⏰", "Uptime", system["uptime"]),
|
||||
("📅", "Boot Time", system["boot_time"]),
|
||||
("👥", "Users", str(system["users"]))
|
||||
]
|
||||
sections.append({"title": "🖥️ System Overview", "rows": sys_rows})
|
||||
|
||||
# CPU
|
||||
cpu_rows = [
|
||||
("⚡", "CPU Cores", f"{cpu['physical_cores']} physical, {cpu['logical_cores']} logical"),
|
||||
("📈", "Freq (Max/Cur)", f"{cpu['max_freq']} / {cpu['current_freq']}"),
|
||||
("📊", "CPU Usage", cpu["usage"]),
|
||||
("⚖️", "Load Avg", cpu["load_avg"])
|
||||
]
|
||||
sections.append({"title": "⚡ CPU", "rows": cpu_rows})
|
||||
|
||||
# Memory
|
||||
mem_rows = [
|
||||
("🧠", "RAM", f"{mem['used_ram']} / {mem['total_ram']} ({mem['ram_percent']})")
|
||||
]
|
||||
if mem["total_swap"] != "N/A":
|
||||
mem_rows.append(("💾", "Swap", f"{mem['used_swap']} / {mem['total_swap']} ({mem['swap_percent']})"))
|
||||
sections.append({"title": "🧠 Memory", "rows": mem_rows})
|
||||
|
||||
# Storage
|
||||
disk_rows = []
|
||||
for d in disks[:5]:
|
||||
disk_rows.append(("💽", d['mount'], f"{d['used']} / {d['total']} ({d['percent']}%)"))
|
||||
disk_rows.append(("📀", "Disk I/O", f"Read {io_read} / Write {io_write}"))
|
||||
sections.append({"title": "💾 Storage", "rows": disk_rows})
|
||||
|
||||
# Network
|
||||
net_rows = []
|
||||
if net:
|
||||
for idx, (name, ip, sent, recv) in enumerate(net[:3]):
|
||||
emoji = "🌐" if idx == 0 else ""
|
||||
label = "Network" if idx == 0 else ""
|
||||
net_rows.append((emoji, label, f"{name} - {ip} | ↓{recv} ↑{sent}"))
|
||||
else:
|
||||
net_rows.append(("🌐", "Network", "No active interfaces"))
|
||||
sections.append({"title": "🌐 Network", "rows": net_rows})
|
||||
|
||||
# GPU
|
||||
gpu_rows = []
|
||||
if "nvidia" in gpu:
|
||||
for g in gpu["nvidia"]:
|
||||
gpu_rows.append(("🎮", "GPU", f"{g['name']} | {g['mem_used']}/{g['mem_total']} | {g['temp']} | {g['usage']} util"))
|
||||
elif "detected" in gpu:
|
||||
for line in gpu["detected"]:
|
||||
gpu_rows.append(("🎮", "GPU", line))
|
||||
else:
|
||||
gpu_rows.append(("🎮", "GPU", "No dedicated GPU detected"))
|
||||
sections.append({"title": "🎮 GPU", "rows": gpu_rows})
|
||||
|
||||
# Processes
|
||||
proc_rows = [("🔄", "Processes", f"Total: {total_procs}")]
|
||||
for p in top_procs:
|
||||
name = p.get('name', '?')
|
||||
cpu_p = p.get('cpu_percent') or 0
|
||||
mem_p = p.get('memory_percent') or 0
|
||||
proc_rows.append(("", "", f"{name} - CPU {cpu_p:.1f}% / RAM {mem_p:.1f}%"))
|
||||
sections.append({"title": "🔄 Top Processes", "rows": proc_rows})
|
||||
|
||||
# Docker
|
||||
docker_rows = []
|
||||
if docker is not None:
|
||||
if docker:
|
||||
for c in docker[:5]:
|
||||
docker_rows.append(("🐳", "Docker", f"{c['name']} - {c['status']}"))
|
||||
else:
|
||||
docker_rows.append(("🐳", "Docker", "No containers running"))
|
||||
else:
|
||||
docker_rows.append(("🐳", "Docker", "Docker not available"))
|
||||
sections.append({"title": "🐳 Docker", "rows": docker_rows})
|
||||
|
||||
# Sensors
|
||||
sensor_rows = []
|
||||
if sensors["temps"]:
|
||||
sensor_rows.append(("🌡️", "Temperature", ", ".join(sensors["temps"])))
|
||||
if sensors["fans"]:
|
||||
sensor_rows.append(("🌀", "Fans", ", ".join(sensors["fans"])))
|
||||
if sensors["battery"]:
|
||||
sensor_rows.append(("🔋", "Battery", sensors["battery"]))
|
||||
if sensor_rows:
|
||||
sections.append({"title": "🌡️ Sensors", "rows": sensor_rows})
|
||||
|
||||
block = code_block(f"💻 System Info: {system['hostname']}", sections)
|
||||
output = collapsible_summary(f"💻 System Info – {html_escape(system['hostname'])}", block)
|
||||
await bot.api.send_markdown_message(room.room_id, output)
|
||||
logging.info("Sent system information")
|
||||
|
||||
async def format_system_info(system, cpu, memory, storage, network, processes, docker, sensors, gpu):
|
||||
hostname = html_escape(system.get('hostname', 'Unknown'))
|
||||
body = "<strong>💻 System Information</strong><br><br>"
|
||||
|
||||
# System Overview
|
||||
body += "<strong>🖥️ System Overview</strong><br>"
|
||||
body += f" • <strong>Hostname:</strong> {hostname}<br>"
|
||||
body += f" • <strong>OS:</strong> {html_escape(system['os'])} {html_escape(system['os_release'])}<br>"
|
||||
body += f" • <strong>Architecture:</strong> {html_escape(system['architecture'])}<br>"
|
||||
body += f" • <strong>Uptime:</strong> {html_escape(system['uptime'])}<br>"
|
||||
body += f" • <strong>Boot Time:</strong> {html_escape(system['boot_time'])}<br>"
|
||||
body += f" • <strong>Users:</strong> {system['users']}<br><br>"
|
||||
|
||||
# CPU
|
||||
body += "<strong>⚡ CPU Information</strong><br>"
|
||||
body += f" • <strong>Cores:</strong> {cpu['physical_cores']} physical, {cpu['total_cores']} logical<br>"
|
||||
body += f" • <strong>Frequency:</strong> {html_escape(cpu['current_frequency'])} (max: {html_escape(cpu['max_frequency'])})<br>"
|
||||
body += f" • <strong>Usage:</strong> {cpu['usage_percent']}%<br>"
|
||||
body += f" • <strong>Load Average:</strong> {html_escape(cpu['load_avg'])}<br><br>"
|
||||
|
||||
# Memory
|
||||
body += "<strong>🧠 Memory Information</strong><br>"
|
||||
body += f" • <strong>Total:</strong> {html_escape(memory['total'])}<br>"
|
||||
body += f" • <strong>Used:</strong> {html_escape(memory['used'])} ({memory['usage_percent']}%)<br>"
|
||||
body += f" • <strong>Available:</strong> {html_escape(memory['available'])}<br>"
|
||||
body += f" • <strong>Swap:</strong> {html_escape(memory['swap_used'])} / {html_escape(memory['swap_total'])} ({memory['swap_percent']}%)<br><br>"
|
||||
|
||||
# Storage
|
||||
if storage and 'error' not in storage:
|
||||
body += "<strong>💾 Storage Information</strong><br>"
|
||||
for p in storage['partitions'][:3]:
|
||||
body += f" • <strong>{html_escape(p['device'])}:</strong> {p['used']} / {p['total']} ({p['percent']}%)<br>"
|
||||
# IO stats if wanted
|
||||
io = storage.get('io_stats')
|
||||
if io:
|
||||
body += f" • <strong>Disk I/O:</strong> read {io['read_bytes']}, write {io['write_bytes']}<br>"
|
||||
body += "<br>"
|
||||
|
||||
# GPU
|
||||
if gpu:
|
||||
if 'nvidia' in gpu:
|
||||
body += "<strong>🎮 GPU Information (NVIDIA)</strong><br>"
|
||||
for g in gpu['nvidia']:
|
||||
body += f" • <strong>{html_escape(g['name'])}:</strong> {g['utilization']} usage, {g['temperature']}<br>"
|
||||
body += "<br>"
|
||||
elif 'detected' in gpu:
|
||||
body += "<strong>🎮 GPU Information</strong><br>"
|
||||
for line in gpu['detected'][:2]:
|
||||
body += f" • {html_escape(line)}<br>"
|
||||
body += "<br>"
|
||||
|
||||
# Network
|
||||
if network:
|
||||
body += "<strong>🌐 Network Information</strong><br>"
|
||||
for iface in network[:2]:
|
||||
body += f" • <strong>{html_escape(iface['interface'])}:</strong> {html_escape(iface['ipv4'])}<br>"
|
||||
body += "<br>"
|
||||
|
||||
# Top Processes
|
||||
if processes:
|
||||
body += "<strong>🔄 Top Processes (by CPU)</strong><br>"
|
||||
for proc in processes['top_cpu'][:3]:
|
||||
name = html_escape(proc.get('name', 'N/A'))
|
||||
cpu_p = proc.get('cpu_percent', 0) or 0
|
||||
mem_p = proc.get('memory_percent', 0) or 0
|
||||
body += f" • <strong>{name}:</strong> {cpu_p:.1f}% CPU, {mem_p:.1f}% RAM<br>"
|
||||
body += f" • <strong>Total Processes:</strong> {processes['total_processes']}<br><br>"
|
||||
|
||||
# Docker
|
||||
if docker and docker.get('available'):
|
||||
body += "<strong>🐳 Docker Containers</strong><br>"
|
||||
for c in docker['containers'][:3]:
|
||||
body += f" • <strong>{html_escape(c['name'])}:</strong> {html_escape(c['status'])}<br>"
|
||||
body += f" • <strong>Total Running:</strong> {docker['total_running']}<br><br>"
|
||||
|
||||
# Sensors
|
||||
if sensors and 'error' not in sensors:
|
||||
if sensors.get('temperatures'):
|
||||
body += "<strong>🌡️ Temperature Sensors</strong><br>"
|
||||
for sensor, temps in list(sensors['temperatures'].items())[:2]:
|
||||
body += f" • <strong>{html_escape(sensor)}:</strong> {', '.join(temps[:2])}<br>"
|
||||
body += "<br>"
|
||||
if sensors.get('battery'):
|
||||
bat = sensors['battery']
|
||||
body += "<strong>🔋 Battery Information</strong><br>"
|
||||
body += f" • <strong>Charge:</strong> {bat['percent']}%<br>"
|
||||
body += f" • <strong>Plugged In:</strong> {'Yes' if bat['power_plugged'] else 'No'}<br>"
|
||||
if bat.get('time_left'):
|
||||
body += f" • <strong>Time Left:</strong> {bat['time_left']}<br>"
|
||||
body += "<br>"
|
||||
|
||||
# Timestamp
|
||||
body += f"<em>Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</em>"
|
||||
|
||||
return collapsible_summary(f"💻 System Information - {hostname}", body)
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("sysinfo"):
|
||||
if match.args() and match.args()[0].lower() == 'help':
|
||||
usage = """
|
||||
<strong>💻 System Information</strong>
|
||||
<code>!sysinfo</code> – display comprehensive system info in a clean code block.
|
||||
"""
|
||||
await bot.api.send_markdown_message(room.room_id, usage)
|
||||
return
|
||||
await get_system_info(room, bot)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plugin Metadata
|
||||
# ---------------------------------------------------------------------------
|
||||
__version__ = "1.0.1"
|
||||
__version__ = "1.3.1"
|
||||
__author__ = "Funguy Bot"
|
||||
__description__ = "Comprehensive system information and monitoring"
|
||||
__description__ = "System information plugin"
|
||||
__help__ = """
|
||||
<details>
|
||||
<summary><strong>!sysinfo</strong> – System information</summary>
|
||||
<p>Displays CPU, RAM, storage, network, Docker, GPU, sensors, and top processes.</p>
|
||||
<p>Displays CPU, RAM, storage, network, GPU, sensors, top processes, and more in a clean, aligned code block.</p>
|
||||
</details>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user