Files
FunguyBot/plugins/sysinfo.py

443 lines
18 KiB
Python

"""
This plugin provides comprehensive system information and resource monitoring.
"""
import logging
import platform
import os
import psutil
import socket
import datetime
import simplematrixbotlib as botlib
import subprocess
import sys
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle !sysinfo command for system information.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("sysinfo"):
logging.info("Received !sysinfo command")
args = match.args()
if len(args) > 0 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 def get_system_info(room, bot):
"""Collect and display comprehensive system information."""
try:
await bot.api.send_text_message(room.room_id, "🔍 Gathering system information...")
sysinfo = {
'system': await get_system_info_basic(),
'cpu': await get_cpu_info(),
'memory': await get_memory_info(),
'storage': await get_storage_info(),
'network': await get_network_info(),
'processes': await get_process_info(),
'docker': await get_docker_info(),
'sensors': await get_sensor_info(),
'gpu': await get_gpu_info()
}
output = await format_system_info(sysinfo)
await bot.api.send_markdown_message(room.room_id, output)
logging.info("Sent system information")
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error gathering system info: {str(e)}")
logging.error(f"Error in get_system_info: {e}")
async def get_system_info_basic():
"""Get basic system information."""
try:
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=psutil.boot_time() - datetime.datetime.now().timestamp())).split('.')[0],
'users': len(psutil.users())
}
except Exception as e:
return {'error': str(e)}
async def get_cpu_info():
"""Get CPU information and usage."""
try:
cpu_times = psutil.cpu_times_percent(interval=1)
cpu_freq = psutil.cpu_freq()
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': os.getloadavg() if hasattr(os, 'getloadavg') else "N/A"
}
except Exception as e:
return {'error': str(e)}
async def get_memory_info():
"""Get memory and swap information."""
try:
memory = psutil.virtual_memory()
swap = psutil.swap_memory()
return {
'total': f"{memory.total / (1024**3):.2f} GB",
'available': f"{memory.available / (1024**3):.2f} GB",
'used': f"{memory.used / (1024**3):.2f} GB",
'usage_percent': memory.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
}
except Exception as e:
return {'error': str(e)}
async def get_storage_info():
"""Get storage device information."""
try:
partitions = psutil.disk_partitions()
storage_info = []
for partition in partitions:
try:
usage = psutil.disk_usage(partition.mountpoint)
storage_info.append({
'device': partition.device,
'mountpoint': partition.mountpoint,
'fstype': partition.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
})
except:
continue
# Get disk I/O statistics
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_info,
'io_stats': io_info
}
except Exception as e:
return {'error': str(e)}
async def get_network_info():
"""Get network interface information."""
try:
interfaces = psutil.net_if_addrs()
io_counters = psutil.net_io_counters(pernic=True)
network_info = []
for interface, addrs in interfaces.items():
if interface not in ['lo']: # Skip loopback
interface_io = io_counters.get(interface, None)
network_info.append({
'interface': interface,
'ipv4': next((addr.address for addr in addrs if addr.family == socket.AF_INET), 'N/A'),
'ipv6': next((addr.address for addr in addrs if addr.family == socket.AF_INET6), 'N/A'),
'mac': next((addr.address for addr in addrs if addr.family == psutil.AF_LINK), 'N/A'),
'bytes_sent': f"{interface_io.bytes_sent / (1024**2):.2f} MB" if interface_io else "N/A",
'bytes_recv': f"{interface_io.bytes_recv / (1024**2):.2f} MB" if interface_io else "N/A"
})
return network_info
except Exception as e:
return {'error': str(e)}
async def get_process_info():
"""Get process and system load information."""
try:
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
try:
processes.append(proc.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# Sort by CPU usage and get top 5
top_processes = sorted(processes, key=lambda x: x['cpu_percent'] or 0, reverse=True)[:5]
return {
'total_processes': len(processes),
'top_cpu': top_processes
}
except Exception as e:
return {'error': str(e)}
async def get_docker_info():
"""Get Docker container information if available."""
try:
# Check if docker is available
result = subprocess.run(['docker', '--version'], capture_output=True, text=True)
if result.returncode != 0:
return {'available': False}
# Get running containers
result = subprocess.run(['docker', 'ps', '--format', '{{.Names}}|{{.Status}}|{{.Ports}}'],
capture_output=True, text=True)
containers = []
for line in result.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)
}
except Exception as e:
return {'available': False, 'error': str(e)}
async def get_sensor_info():
"""Get hardware sensor information."""
try:
temperatures = psutil.sensors_temperatures()
fans = psutil.sensors_fans()
battery = psutil.sensors_battery()
sensor_info = {
'temperatures': {},
'fans': {},
'battery': {}
}
# Temperature sensors
if temperatures:
for name, entries in temperatures.items():
sensor_info['temperatures'][name] = [
f"{entry.current}°C" for entry in entries[:2] # Show first 2 sensors per type
]
# Fan speeds
if fans:
for name, entries in fans.items():
sensor_info['fans'][name] = [
f"{entry.current} RPM" for entry in entries[:2]
]
# Battery information
if battery:
sensor_info['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_info
except Exception as e:
return {'error': str(e)}
async def get_gpu_info():
"""Get GPU information using various methods."""
try:
gpu_info = {}
# Try nvidia-smi first
try:
result = 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 result.returncode == 0:
nvidia_gpus = []
for line in result.stdout.strip().split('\n'):
if line:
parts = [part.strip() for part in line.split(',')]
if len(parts) >= 6:
nvidia_gpus.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]}%"
})
gpu_info['nvidia'] = nvidia_gpus
except:
pass
# Try lspci for generic GPU detection
try:
result = subprocess.run(['lspci'], capture_output=True, text=True)
if result.returncode == 0:
gpu_lines = [line for line in result.stdout.split('\n') if 'VGA' in line or '3D' in line]
gpu_info['detected'] = gpu_lines[:3] # Show first 3 GPUs
except:
pass
return gpu_info
except Exception as e:
return {'error': str(e)}
async def format_system_info(sysinfo):
"""Format system information for display."""
output = "<strong>💻 System Information</strong><br><br>"
# System Overview
system = sysinfo.get('system', {})
output += "<strong>🖥️ System Overview</strong><br>"
output += f" • <strong>Hostname:</strong> {system.get('hostname', 'N/A')}<br>"
output += f" • <strong>OS:</strong> {system.get('os', 'N/A')} {system.get('os_release', '')}<br>"
output += f" • <strong>Architecture:</strong> {system.get('architecture', 'N/A')}<br>"
output += f" • <strong>Uptime:</strong> {system.get('uptime', 'N/A')}<br>"
output += f" • <strong>Boot Time:</strong> {system.get('boot_time', 'N/A')}<br>"
output += f" • <strong>Users:</strong> {system.get('users', 'N/A')}<br>"
output += "<br>"
# CPU Information
cpu = sysinfo.get('cpu', {})
if 'error' not in cpu:
output += "<strong>⚡ CPU Information</strong><br>"
output += f" • <strong>Cores:</strong> {cpu.get('physical_cores', 'N/A')} physical, {cpu.get('total_cores', 'N/A')} logical<br>"
output += f" • <strong>Frequency:</strong> {cpu.get('current_frequency', 'N/A')} (max: {cpu.get('max_frequency', 'N/A')})<br>"
output += f" • <strong>Usage:</strong> {cpu.get('usage_percent', 'N/A')}%<br>"
if cpu.get('load_avg') != "N/A":
output += f" • <strong>Load Average:</strong> {', '.join([f'{load:.2f}' for load in cpu.get('load_avg', [0,0,0])])}<br>"
output += "<br>"
# Memory Information
memory = sysinfo.get('memory', {})
if 'error' not in memory:
output += "<strong>🧠 Memory Information</strong><br>"
output += f" • <strong>Total:</strong> {memory.get('total', 'N/A')}<br>"
output += f" • <strong>Used:</strong> {memory.get('used', 'N/A')} ({memory.get('usage_percent', 'N/A')}%)<br>"
output += f" • <strong>Available:</strong> {memory.get('available', 'N/A')}<br>"
output += f" • <strong>Swap:</strong> {memory.get('swap_used', 'N/A')} / {memory.get('swap_total', 'N/A')} ({memory.get('swap_percent', 'N/A')}%)<br>"
output += "<br>"
# Storage Information
storage = sysinfo.get('storage', {})
if 'error' not in storage:
output += "<strong>💾 Storage Information</strong><br>"
partitions = storage.get('partitions', [])
for partition in partitions[:3]: # Show first 3 partitions
output += f" • <strong>{partition.get('device', 'N/A')}:</strong> {partition.get('used', 'N/A')} / {partition.get('total', 'N/A')} ({partition.get('percent', 'N/A')}%)<br>"
output += "<br>"
# GPU Information
gpu = sysinfo.get('gpu', {})
if gpu.get('nvidia'):
output += "<strong>🎮 GPU Information (NVIDIA)</strong><br>"
for gpu_info in gpu['nvidia']:
output += f" • <strong>{gpu_info.get('name', 'N/A')}:</strong> {gpu_info.get('utilization', 'N/A')} usage, {gpu_info.get('temperature', 'N/A')}<br>"
output += "<br>"
elif gpu.get('detected'):
output += "<strong>🎮 GPU Information</strong><br>"
for gpu_line in gpu['detected'][:2]:
output += f"{gpu_line}<br>"
output += "<br>"
# Network Information
network = sysinfo.get('network', [])
if network and 'error' not in network:
output += "<strong>🌐 Network Information</strong><br>"
for interface in network[:2]: # Show first 2 interfaces
output += f" • <strong>{interface.get('interface', 'N/A')}:</strong> {interface.get('ipv4', 'N/A')}<br>"
output += "<br>"
# Process Information
processes = sysinfo.get('processes', {})
if 'error' not in processes:
output += "<strong>🔄 Top Processes (by CPU)</strong><br>"
for proc in processes.get('top_cpu', [])[:3]:
output += f" • <strong>{proc.get('name', 'N/A')}:</strong> {proc.get('cpu_percent', 0):.1f}% CPU, {proc.get('memory_percent', 0):.1f}% RAM<br>"
output += f" • <strong>Total Processes:</strong> {processes.get('total_processes', 'N/A')}<br>"
output += "<br>"
# Docker Information
docker = sysinfo.get('docker', {})
if docker.get('available'):
output += "<strong>🐳 Docker Containers</strong><br>"
for container in docker.get('containers', [])[:3]:
output += f" • <strong>{container.get('name', 'N/A')}:</strong> {container.get('status', 'N/A')}<br>"
output += f" • <strong>Total Running:</strong> {docker.get('total_running', 'N/A')}<br>"
output += "<br>"
# Sensor Information
sensors = sysinfo.get('sensors', {})
if 'error' not in sensors:
if sensors.get('temperatures'):
output += "<strong>🌡️ Temperature Sensors</strong><br>"
for sensor, temps in list(sensors['temperatures'].items())[:2]:
output += f" • <strong>{sensor}:</strong> {', '.join(temps[:2])}<br>"
output += "<br>"
if sensors.get('battery'):
battery = sensors['battery']
output += "<strong>🔋 Battery Information</strong><br>"
output += f" • <strong>Charge:</strong> {battery.get('percent', 'N/A')}%<br>"
output += f" • <strong>Plugged In:</strong> {'Yes' if battery.get('power_plugged') else 'No'}<br>"
if battery.get('time_left'):
output += f" • <strong>Time Left:</strong> {battery.get('time_left', 'N/A')}<br>"
output += "<br>"
# Add timestamp
output += f"<em>Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</em>"
# Wrap in collapsible due to comprehensive output
output = f"<details><summary><strong>💻 System Information - {system.get('hostname', 'Unknown')}</strong></summary>{output}</details>"
return output