""" 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 = """ 💻 System Information Plugin !sysinfo - Display comprehensive system information !sysinfo help - Show this help message Information Provided: • 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 = "💻 System Information

" # System Overview system = sysinfo.get('system', {}) output += "🖥️ System Overview
" output += f" • Hostname: {system.get('hostname', 'N/A')}
" output += f" • OS: {system.get('os', 'N/A')} {system.get('os_release', '')}
" output += f" • Architecture: {system.get('architecture', 'N/A')}
" output += f" • Uptime: {system.get('uptime', 'N/A')}
" output += f" • Boot Time: {system.get('boot_time', 'N/A')}
" output += f" • Users: {system.get('users', 'N/A')}
" output += "
" # CPU Information cpu = sysinfo.get('cpu', {}) if 'error' not in cpu: output += "⚡ CPU Information
" output += f" • Cores: {cpu.get('physical_cores', 'N/A')} physical, {cpu.get('total_cores', 'N/A')} logical
" output += f" • Frequency: {cpu.get('current_frequency', 'N/A')} (max: {cpu.get('max_frequency', 'N/A')})
" output += f" • Usage: {cpu.get('usage_percent', 'N/A')}%
" if cpu.get('load_avg') != "N/A": output += f" • Load Average: {', '.join([f'{load:.2f}' for load in cpu.get('load_avg', [0,0,0])])}
" output += "
" # Memory Information memory = sysinfo.get('memory', {}) if 'error' not in memory: output += "🧠 Memory Information
" output += f" • Total: {memory.get('total', 'N/A')}
" output += f" • Used: {memory.get('used', 'N/A')} ({memory.get('usage_percent', 'N/A')}%)
" output += f" • Available: {memory.get('available', 'N/A')}
" output += f" • Swap: {memory.get('swap_used', 'N/A')} / {memory.get('swap_total', 'N/A')} ({memory.get('swap_percent', 'N/A')}%)
" output += "
" # Storage Information storage = sysinfo.get('storage', {}) if 'error' not in storage: output += "💾 Storage Information
" partitions = storage.get('partitions', []) for partition in partitions[:3]: # Show first 3 partitions output += f" • {partition.get('device', 'N/A')}: {partition.get('used', 'N/A')} / {partition.get('total', 'N/A')} ({partition.get('percent', 'N/A')}%)
" output += "
" # GPU Information gpu = sysinfo.get('gpu', {}) if gpu.get('nvidia'): output += "🎮 GPU Information (NVIDIA)
" for gpu_info in gpu['nvidia']: output += f" • {gpu_info.get('name', 'N/A')}: {gpu_info.get('utilization', 'N/A')} usage, {gpu_info.get('temperature', 'N/A')}
" output += "
" elif gpu.get('detected'): output += "🎮 GPU Information
" for gpu_line in gpu['detected'][:2]: output += f" • {gpu_line}
" output += "
" # Network Information network = sysinfo.get('network', []) if network and 'error' not in network: output += "🌐 Network Information
" for interface in network[:2]: # Show first 2 interfaces output += f" • {interface.get('interface', 'N/A')}: {interface.get('ipv4', 'N/A')}
" output += "
" # Process Information processes = sysinfo.get('processes', {}) if 'error' not in processes: output += "🔄 Top Processes (by CPU)
" for proc in processes.get('top_cpu', [])[:3]: output += f" • {proc.get('name', 'N/A')}: {proc.get('cpu_percent', 0):.1f}% CPU, {proc.get('memory_percent', 0):.1f}% RAM
" output += f" • Total Processes: {processes.get('total_processes', 'N/A')}
" output += "
" # Docker Information docker = sysinfo.get('docker', {}) if docker.get('available'): output += "🐳 Docker Containers
" for container in docker.get('containers', [])[:3]: output += f" • {container.get('name', 'N/A')}: {container.get('status', 'N/A')}
" output += f" • Total Running: {docker.get('total_running', 'N/A')}
" output += "
" # Sensor Information sensors = sysinfo.get('sensors', {}) if 'error' not in sensors: if sensors.get('temperatures'): output += "🌡️ Temperature Sensors
" for sensor, temps in list(sensors['temperatures'].items())[:2]: output += f" • {sensor}: {', '.join(temps[:2])}
" output += "
" if sensors.get('battery'): battery = sensors['battery'] output += "🔋 Battery Information
" output += f" • Charge: {battery.get('percent', 'N/A')}%
" output += f" • Plugged In: {'Yes' if battery.get('power_plugged') else 'No'}
" if battery.get('time_left'): output += f" • Time Left: {battery.get('time_left', 'N/A')}
" output += "
" # Add timestamp output += f"Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" # Wrap in collapsible due to comprehensive output output = f"
💻 System Information - {system.get('hostname', 'Unknown')}{output}
" return output