Dev 6 #9

Merged
KerradKerridi merged 13 commits from dev-6 into master 2025-08-30 11:58:45 +00:00
2 changed files with 195 additions and 25 deletions
Showing only changes of commit dc0e5d788c - Show all commits

View File

@@ -2,6 +2,7 @@ import asyncio
import os
import psutil
import time
import platform
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple
import logging
@@ -15,6 +16,10 @@ class ServerMonitor:
self.group_for_logs = group_for_logs
self.important_logs = important_logs
# Определяем ОС
self.os_type = self._detect_os()
logger.info(f"Обнаружена ОС: {self.os_type}")
# Пороговые значения для алертов
self.threshold = 80.0
self.recovery_threshold = 75.0
@@ -39,6 +44,119 @@ class ServerMonitor:
self.last_disk_io = None
self.last_disk_io_time = None
# Время запуска бота для расчета uptime
self.bot_start_time = time.time()
def _detect_os(self) -> str:
"""Определение типа операционной системы"""
system = platform.system().lower()
if system == "darwin":
return "macos"
elif system == "linux":
return "ubuntu"
else:
return "unknown"
def _get_disk_path(self) -> str:
"""Получение пути к диску в зависимости от ОС"""
if self.os_type == "macos":
return "/"
elif self.os_type == "ubuntu":
return "/"
else:
return "/"
def _get_disk_usage(self) -> Optional[object]:
"""Получение информации о диске с учетом ОС"""
try:
if self.os_type == "macos":
# На macOS используем diskutil для получения реального использования диска
return self._get_macos_disk_usage()
else:
disk_path = self._get_disk_path()
return psutil.disk_usage(disk_path)
except Exception as e:
logger.error(f"Ошибка при получении информации о диске: {e}")
return None
def _get_macos_disk_usage(self) -> Optional[object]:
"""Получение информации о диске на macOS через diskutil"""
try:
import subprocess
import re
# Получаем информацию о диске через diskutil
result = subprocess.run(['diskutil', 'info', '/'], capture_output=True, text=True)
if result.returncode != 0:
# Fallback к psutil
return psutil.disk_usage('/')
output = result.stdout
# Извлекаем размеры из вывода diskutil
total_match = re.search(r'Container Total Space:\s+(\d+\.\d+)\s+GB', output)
free_match = re.search(r'Container Free Space:\s+(\d+\.\d+)\s+GB', output)
if total_match and free_match:
total_gb = float(total_match.group(1))
free_gb = float(free_match.group(1))
used_gb = total_gb - free_gb
# Создаем объект, похожий на результат psutil.disk_usage
class DiskUsage:
def __init__(self, total, used, free):
self.total = total * (1024**3) # Конвертируем в байты
self.used = used * (1024**3)
self.free = free * (1024**3)
return DiskUsage(total_gb, used_gb, free_gb)
else:
# Fallback к psutil
return psutil.disk_usage('/')
except Exception as e:
logger.error(f"Ошибка при получении информации о диске macOS: {e}")
# Fallback к psutil
return psutil.disk_usage('/')
def _get_disk_io_counters(self):
"""Получение статистики диска с учетом ОС"""
try:
if self.os_type == "macos":
# На macOS может быть несколько дисков, берем основной
return psutil.disk_io_counters(perdisk=False)
elif self.os_type == "ubuntu":
# На Ubuntu обычно один диск
return psutil.disk_io_counters(perdisk=False)
else:
return psutil.disk_io_counters()
except Exception as e:
logger.error(f"Ошибка при получении статистики диска: {e}")
return None
def _get_system_uptime(self) -> float:
"""Получение uptime системы с учетом ОС"""
try:
if self.os_type == "macos":
# На macOS используем boot_time
boot_time = psutil.boot_time()
return time.time() - boot_time
elif self.os_type == "ubuntu":
# На Ubuntu также используем boot_time
boot_time = psutil.boot_time()
return time.time() - boot_time
else:
boot_time = psutil.boot_time()
return time.time() - boot_time
except Exception as e:
logger.error(f"Ошибка при получении uptime системы: {e}")
return 0.0
def get_bot_uptime(self) -> str:
"""Получение uptime бота"""
uptime_seconds = time.time() - self.bot_start_time
return self._format_uptime(uptime_seconds)
def get_system_info(self) -> Dict:
"""Получение информации о системе"""
try:
@@ -51,16 +169,31 @@ class ServerMonitor:
memory = psutil.virtual_memory()
swap = psutil.swap_memory()
# Используем единый расчет для всех ОС: used / total для получения процента занятой памяти
# Это обеспечивает консистентность между macOS и Ubuntu
ram_percent = (memory.used / memory.total) * 100
# Диск
disk = psutil.disk_usage('/')
disk_io = psutil.disk_io_counters()
disk = self._get_disk_usage()
disk_io = self._get_disk_io_counters()
if disk is None:
logger.error("Не удалось получить информацию о диске")
return {}
# Расчет скорости диска
disk_read_speed, disk_write_speed = self._calculate_disk_speed(disk_io)
# Система
boot_time = psutil.boot_time()
uptime = time.time() - boot_time
system_uptime = self._get_system_uptime()
# Получаем имя хоста в зависимости от ОС
if self.os_type == "macos":
hostname = os.uname().nodename
elif self.os_type == "ubuntu":
hostname = os.uname().nodename
else:
hostname = "unknown"
return {
'cpu_percent': cpu_percent,
@@ -70,25 +203,35 @@ class ServerMonitor:
'cpu_count': cpu_count,
'ram_used': round(memory.used / (1024**3), 2),
'ram_total': round(memory.total / (1024**3), 2),
'ram_percent': round(memory.percent, 1),
'ram_percent': round(ram_percent, 1), # Исправленный процент занятой памяти
'swap_used': round(swap.used / (1024**3), 2),
'swap_total': round(swap.total / (1024**3), 2),
'swap_percent': swap.percent,
'disk_used': round(disk.used / (1024**3), 2),
'disk_total': round(disk.total / (1024**3), 2),
'disk_percent': round((disk.used / disk.total) * 100, 2),
'disk_percent': round((disk.used / disk.total) * 100, 1),
'disk_free': round(disk.free / (1024**3), 2),
'disk_read_speed': disk_read_speed,
'disk_write_speed': disk_write_speed,
'disk_io_percent': self._calculate_disk_io_percent(),
'system_uptime': self._format_uptime(uptime),
'server_hostname': psutil.os.uname().nodename,
'system_uptime': self._format_uptime(system_uptime),
'bot_uptime': self.get_bot_uptime(),
'server_hostname': hostname,
'current_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
logger.error(f"Ошибка при получении информации о системе: {e}")
return {}
def _get_disk_space_emoji(self, disk_percent: float) -> str:
"""Получение эмодзи для дискового пространства"""
if disk_percent < 60:
return "🟢"
elif disk_percent < 90:
return "⚠️"
else:
return "🚨"
def _format_bytes(self, bytes_value: int) -> str:
"""Форматирование байтов в человекочитаемый вид"""
if bytes_value == 0:
@@ -115,8 +258,8 @@ class ServerMonitor:
else:
return f"{minutes}м"
def check_process_status(self, process_name: str) -> str:
"""Проверка статуса процесса"""
def check_process_status(self, process_name: str) -> Tuple[str, str]:
"""Проверка статуса процесса и возврат статуса с uptime"""
try:
# Сначала проверяем по PID файлу
pid_file = self.pid_files.get(process_name)
@@ -127,7 +270,14 @@ class ServerMonitor:
if content and content != '# Этот файл будет автоматически обновляться при запуске бота':
pid = int(content)
if psutil.pid_exists(pid):
return ""
# Получаем uptime процесса
try:
proc = psutil.Process(pid)
proc_uptime = time.time() - proc.create_time()
uptime_str = self._format_uptime(proc_uptime)
return "", f"Uptime {uptime_str}"
except:
return "", "Uptime неизвестно"
except (ValueError, FileNotFoundError):
pass
@@ -143,21 +293,33 @@ class ServerMonitor:
if ('voice_bot' in proc_name or
'voice_bot' in cmdline or
'voice_bot_v2.py' in cmdline):
return ""
# Получаем uptime процесса
try:
proc_uptime = time.time() - proc.create_time()
uptime_str = self._format_uptime(proc_uptime)
return "", f"Uptime {uptime_str}"
except:
return "", "Uptime неизвестно"
elif process_name == 'helper_bot':
# Проверяем helper_bot
if ('helper_bot' in proc_name or
'helper_bot' in cmdline or
'run_helper.py' in cmdline or
'python' in proc_name and 'helper_bot' in cmdline):
return ""
# Получаем uptime процесса
try:
proc_uptime = time.time() - proc.create_time()
uptime_str = self._format_uptime(proc_uptime)
return "", f"Uptime {uptime_str}"
except:
return "", "Uptime неизвестно"
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
return ""
return "", "Выключен"
except Exception as e:
logger.error(f"Ошибка при проверке процесса {process_name}: {e}")
return ""
return "", "Выключен"
def should_send_status(self) -> bool:
"""Проверка, нужно ли отправить статус (каждые 30 минут в 00 и 30 минут часа)"""
@@ -203,7 +365,7 @@ class ServerMonitor:
"""Расчет процента загрузки диска на основе IOPS"""
try:
# Получаем статистику диска
disk_io = psutil.disk_io_counters()
disk_io = self._get_disk_io_counters()
if disk_io is None:
return 0
@@ -237,6 +399,7 @@ class ServerMonitor:
**Время запуска:** <code>{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</code>
**Сервер:** `{psutil.os.uname().nodename}`
**Система:** {psutil.os.uname().sysname} {psutil.os.uname().release}
**ОС:** {self.os_type.upper()}
✅ Мониторинг сервера активирован
✅ Статус будет отправляться каждые 30 минут (в 00 и 30 минут часа)
@@ -324,8 +487,11 @@ class ServerMonitor:
async def send_status_message(self, system_info: Dict):
"""Отправка сообщения со статусом сервера"""
try:
voice_bot_status = self.check_process_status('voice_bot')
helper_bot_status = self.check_process_status('helper_bot')
voice_bot_status, voice_bot_uptime = self.check_process_status('voice_bot')
helper_bot_status, helper_bot_uptime = self.check_process_status('helper_bot')
# Получаем эмодзи для дискового пространства
disk_emoji = self._get_disk_space_emoji(system_info['disk_percent'])
message = f"""🖥 **Статус Сервера** | <code>{system_info['current_time']}</code>
---------------------------------
@@ -336,14 +502,18 @@ CPU: <b>{system_info['cpu_percent']}%</b> | LA: <b>{system_info['load_avg_1m']}
RAM: <b>{system_info['ram_used']}/{system_info['ram_total']} GB</b> ({system_info['ram_percent']}%)
Swap: <b>{system_info['swap_used']}/{system_info['swap_total']} GB</b> ({system_info['swap_percent']}%)
**🗂️ Дисковое пространство:**
Диск (/): <b>{system_info['disk_used']}/{system_info['disk_total']} GB</b> ({system_info['disk_percent']}%) {disk_emoji}
**💿 Диск I/O:**
Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_write_speed']}</b>
Диск загружен: <b>{system_info['disk_io_percent']}%</b>
**🤖 Процессы:**
{voice_bot_status} voice-bot | {helper_bot_status} helper-bot
{voice_bot_status} voice-bot - {voice_bot_uptime}
{helper_bot_status} helper-bot - {helper_bot_uptime}
---------------------------------
⏰ Uptime: <code>{system_info['system_uptime']}</code>"""
⏰ Uptime сервера: {system_info['system_uptime']}"""
await self.bot.send_message(
chat_id=self.group_for_logs,
@@ -406,7 +576,7 @@ Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_wri
async def monitor_loop(self):
"""Основной цикл мониторинга"""
logger.info("Модуль мониторинга сервера запущен")
logger.info(f"Модуль мониторинга сервера запущен на {self.os_type.upper()}")
# Отправляем сообщение о запуске при первом запуске
if self.should_send_startup_status():

View File

@@ -49,10 +49,10 @@ async def test_monitor():
print(f"Uptime: {system_info['system_uptime']}")
print("\n🤖 Проверка статуса процессов...")
voice_status = monitor.check_process_status('voice_bot')
helper_status = monitor.check_process_status('helper_bot')
print(f"Voice Bot: {voice_status}")
print(f"Helper Bot: {helper_status}")
voice_status, voice_uptime = monitor.check_process_status('voice_bot')
helper_status, helper_uptime = monitor.check_process_status('helper_bot')
print(f"Voice Bot: {voice_status} - {voice_uptime}")
print(f"Helper Bot: {helper_status} - {helper_uptime}")
print("\n📝 Тестирование отправки статуса...")
await monitor.send_status_message(system_info)