Files
default/telegram-agy-bot/sys_monitor.py
T

279 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import time
import shutil
import platform
import logging
from pathlib import Path
from typing import Dict, Any, Tuple
try:
import psutil
except ImportError:
psutil = None
logger = logging.getLogger("AGYSysMonitor")
def make_progress_bar(percent: float, length: int = 10) -> str:
"""Generates a visual emoji progress bar based on percentage."""
percent = max(0.0, min(100.0, float(percent)))
filled = int(round((percent / 100.0) * length))
filled = max(0, min(length, filled))
if percent < 60:
fill_char = "🟩"
elif percent < 85:
fill_char = "🟨"
else:
fill_char = "🟥"
empty_char = "⬜"
return f"{fill_char * filled}{empty_char * (length - filled)}"
def make_load_bar(load_val: float, cpu_cores: int, length: int = 10) -> Tuple[str, float]:
"""Calculates load percentage based on core count and returns (bar, percent)."""
cores = max(1, cpu_cores)
percent = round((float(load_val) / cores) * 100.0, 1)
bar = make_progress_bar(percent, length)
return bar, percent
def format_bytes(bytes_val: int) -> str:
"""Converts bytes to human readable format (MB, GB, TB)."""
if bytes_val < 1024 * 1024:
return f"{bytes_val / 1024:.1f} KB"
elif bytes_val < 1024 * 1024 * 1024:
return f"{bytes_val / (1024 * 1024):.1f} MB"
elif bytes_val < 1024 * 1024 * 1024 * 1024:
return f"{bytes_val / (1024 * 1024 * 1024):.2f} GB"
else:
return f"{bytes_val / (1024 * 1024 * 1024 * 1024):.2f} TB"
def get_server_uptime_str(is_fa: bool = True) -> str:
"""Returns server uptime string."""
try:
if psutil:
boot_time = psutil.boot_time()
uptime_seconds = int(time.time() - boot_time)
else:
with open("/proc/uptime", "r") as f:
uptime_seconds = int(float(f.readline().split()[0]))
days = uptime_seconds // 86400
hours = (uptime_seconds % 86400) // 3600
minutes = (uptime_seconds % 3600) // 60
if is_fa:
parts = []
if days > 0:
parts.append(f"{days} روز")
if hours > 0 or days > 0:
parts.append(f"{hours} ساعت")
parts.append(f"{minutes} دقیقه")
return " و ".join(parts)
else:
return f"{days}d {hours}h {minutes}m"
except Exception:
return "N/A"
def get_system_hardware_stats() -> Dict[str, Any]:
"""Gathers detailed real-time RAM, CPU, Disk, and process statistics."""
stats: Dict[str, Any] = {}
# 1. RAM & Swap
if psutil:
mem = psutil.virtual_memory()
stats["ram_total"] = mem.total
stats["ram_used"] = mem.used
stats["ram_free"] = mem.available
stats["ram_percent"] = mem.percent
swap = psutil.swap_memory()
stats["swap_total"] = swap.total
stats["swap_used"] = swap.used
stats["swap_free"] = swap.free
stats["swap_percent"] = swap.percent
else:
# Fallback reading /proc/meminfo
try:
meminfo = {}
with open("/proc/meminfo", "r") as f:
for line in f:
parts = line.split(":")
if len(parts) == 2:
key = parts[0].strip()
val = int(parts[1].split()[0]) * 1024 # kB to bytes
meminfo[key] = val
total = meminfo.get("MemTotal", 1)
free = meminfo.get("MemAvailable", meminfo.get("MemFree", 0))
used = total - free
stats["ram_total"] = total
stats["ram_used"] = used
stats["ram_free"] = free
stats["ram_percent"] = round((used / total) * 100, 1)
stats["swap_total"] = meminfo.get("SwapTotal", 0)
stats["swap_free"] = meminfo.get("SwapFree", 0)
stats["swap_used"] = stats["swap_total"] - stats["swap_free"]
stats["swap_percent"] = round((stats["swap_used"] / stats["swap_total"] * 100), 1) if stats["swap_total"] > 0 else 0.0
except Exception as e:
logger.error(f"Error reading meminfo: {e}")
# 2. CPU & Load
if psutil:
stats["cpu_percent"] = psutil.cpu_percent(interval=0.2)
stats["cpu_count_logical"] = psutil.cpu_count(logical=True) or 1
stats["cpu_count_physical"] = psutil.cpu_count(logical=False) or stats["cpu_count_logical"]
else:
stats["cpu_percent"] = 0.0
stats["cpu_count_logical"] = os.cpu_count() or 1
stats["cpu_count_physical"] = stats["cpu_count_logical"]
try:
load_1, load_5, load_15 = os.getloadavg()
stats["load_avg"] = (round(load_1, 2), round(load_5, 2), round(load_15, 2))
except Exception:
stats["load_avg"] = (0.0, 0.0, 0.0)
# 3. Disk Storage
try:
root_usage = shutil.disk_usage("/")
stats["disk_total"] = root_usage.total
stats["disk_used"] = root_usage.used
stats["disk_free"] = root_usage.free
stats["disk_percent"] = round((root_usage.used / root_usage.total) * 100, 1)
except Exception as e:
logger.error(f"Error reading root disk usage: {e}")
# 4. Bot Process Memory / CPU
try:
if psutil:
proc = psutil.Process(os.getpid())
proc_mem = proc.memory_info().rss
stats["bot_mem_rss"] = proc_mem
stats["bot_cpu_percent"] = proc.cpu_percent(interval=None)
else:
stats["bot_mem_rss"] = 0
stats["bot_cpu_percent"] = 0.0
except Exception:
stats["bot_mem_rss"] = 0
stats["bot_cpu_percent"] = 0.0
stats["uptime_fa"] = get_server_uptime_str(is_fa=True)
stats["uptime_en"] = get_server_uptime_str(is_fa=False)
stats["os_info"] = f"{platform.system()} {platform.release()} ({platform.machine()})"
return stats
def render_server_hardware_report(is_fa: bool = True) -> str:
"""Renders formatted HTML hardware status report with progress bars for RAM, CPU, Load Avg, and Disk."""
st = get_system_hardware_stats()
# RAM
ram_pct = st.get("ram_percent", 0.0)
ram_bar = make_progress_bar(ram_pct)
ram_used_str = format_bytes(st.get("ram_used", 0))
ram_total_str = format_bytes(st.get("ram_total", 0))
ram_free_str = format_bytes(st.get("ram_free", 0))
# CPU & Load Average
cpu_pct = st.get("cpu_percent", 0.0)
cpu_bar = make_progress_bar(cpu_pct)
cpu_cores = st.get("cpu_count_logical", 1)
load_1, load_5, load_15 = st.get("load_avg", (0.0, 0.0, 0.0))
load_1_bar, load_1_pct = make_load_bar(load_1, cpu_cores)
load_5_bar, load_5_pct = make_load_bar(load_5, cpu_cores)
load_15_bar, load_15_pct = make_load_bar(load_15, cpu_cores)
# Disk
disk_pct = st.get("disk_percent", 0.0)
disk_bar = make_progress_bar(disk_pct)
disk_used_str = format_bytes(st.get("disk_used", 0))
disk_total_str = format_bytes(st.get("disk_total", 0))
disk_free_str = format_bytes(st.get("disk_free", 0))
# Swap
swap_total = st.get("swap_total", 0)
swap_pct = st.get("swap_percent", 0.0)
swap_bar = make_progress_bar(swap_pct) if swap_total > 0 else ""
bot_mem_str = format_bytes(st.get("bot_mem_rss", 0))
uptime_str = st.get("uptime_fa") if is_fa else st.get("uptime_en")
os_info = st.get("os_info", "Linux")
if is_fa:
lines = [
"🖥 <b>گزارش لحظه‌ای وضعیت سخت‌افزار و سرور</b>",
"",
"🧠 <b>حافظه رم (RAM Usage):</b>",
f" {ram_bar} <b>{ram_pct}%</b>",
f" • <b>مصرف شده:</b> <code>{ram_used_str}</code> از <code>{ram_total_str}</code>",
f" • <b>حافظه آزاد:</b> <code>{ram_free_str}</code>",
]
if swap_total > 0:
lines.extend([
"",
"🔄 <b>حافظه مجازی (Swap):</b>",
f" {swap_bar} <b>{swap_pct}%</b> ({format_bytes(st.get('swap_used', 0))} / {format_bytes(swap_total)})",
])
lines.extend([
"",
"⚡ <b>میانگین بار پردازشی پردازنده (CPU Load Average):</b>",
f" • <b>میانگین بار ۱ دقیقه:</b>",
f" {load_1_bar} <b>{load_1_pct}%</b> (بار: <code>{load_1}</code>)",
f" • <b>میانگین بار ۵ دقیقه:</b>",
f" {load_5_bar} <b>{load_5_pct}%</b> (بار: <code>{load_5}</code>)",
f" • <b>میانگین بار ۱۵ دقیقه:</b>",
f" {load_15_bar} <b>{load_15_pct}%</b> (بار: <code>{load_15}</code>)",
f" • <b>تعداد هسته‌ها:</b> <code>{cpu_cores} Cores / Threads</code>",
"",
"💾 <b>حافظه داخلی و دیسک (Disk Storage):</b>",
f" {disk_bar} <b>{disk_pct}%</b>",
f" • <b>فضای مصرف شده:</b> <code>{disk_used_str}</code> از <code>{disk_total_str}</code>",
f" • <b>فضای خالی باقیمانده:</b> <code>{disk_free_str}</code>",
"",
"⚙️ <b>اطلاعات سیستم و ربات:</b>",
f" • 🤖 <b>رم مصرفی پردازش ربات:</b> <code>{bot_mem_str}</code>",
f" • ⏱ <b>مدت زمان روشن بودن سرور (Uptime):</b> <code>{uptime_str}</code>",
f" • 🐧 <b>سیستم‌عامل سرور:</b> <code>{os_info}</code>",
])
else:
lines = [
"🖥 <b>Real-Time Server Hardware Status</b>",
"",
"🧠 <b>Memory (RAM Usage):</b>",
f" {ram_bar} <b>{ram_pct}%</b>",
f" • <b>Used:</b> <code>{ram_used_str}</code> / <code>{ram_total_str}</code>",
f" • <b>Available:</b> <code>{ram_free_str}</code>",
]
if swap_total > 0:
lines.extend([
"",
"🔄 <b>Swap Memory:</b>",
f" {swap_bar} <b>{swap_pct}%</b> ({format_bytes(st.get('swap_used', 0))} / {format_bytes(swap_total)})",
])
lines.extend([
"",
"⚡ <b>Processor Load Average (CPU Load):</b>",
f" • <b>1-Minute Load:</b>",
f" {load_1_bar} <b>{load_1_pct}%</b> (Load: <code>{load_1}</code>)",
f" • <b>5-Minute Load:</b>",
f" {load_5_bar} <b>{load_5_pct}%</b> (Load: <code>{load_5}</code>)",
f" • <b>15-Minute Load:</b>",
f" {load_15_bar} <b>{load_15_pct}%</b> (Load: <code>{load_15}</code>)",
f" • <b>Cores:</b> <code>{cpu_cores} Cores / Threads</code>",
"",
"💾 <b>Disk Storage:</b>",
f" {disk_bar} <b>{disk_pct}%</b>",
f" • <b>Used Space:</b> <code>{disk_used_str}</code> / <code>{disk_total_str}</code>",
f" • <b>Free Space:</b> <code>{disk_free_str}</code>",
"",
"⚙️ <b>Process & System Info:</b>",
f" • 🤖 <b>Bot Process RAM:</b> <code>{bot_mem_str}</code>",
f" • ⏱ <b>Server Uptime:</b> <code>{uptime_str}</code>",
f" • 🐧 <b>OS / Kernel:</b> <code>{os_info}</code>",
])
return "\n".join(lines)