Reverted to 5d746027e2 for funguy.py and loadplugin.py
This commit is contained in:
442
plugins/sysinfo.py
Normal file
442
plugins/sysinfo.py
Normal file
@@ -0,0 +1,442 @@
|
||||
"""
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user