456 lines
18 KiB
Python
456 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
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Plugin Metadata
|
||
# ---------------------------------------------------------------------------
|
||
|
||
__version__ = "1.0.0"
|
||
__author__ = "Funguy Bot"
|
||
__description__ = "System information and monitoring"
|
||
__help__ = """
|
||
<details>
|
||
<summary><strong>!sysinfo</strong> – System information</summary>
|
||
<p>Displays CPU, RAM, storage, network, Docker, GPU, sensors, and top processes.</p>
|
||
</details>
|
||
"""
|