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 os
import psutil import psutil
import time import time
import platform
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
import logging import logging
@@ -15,6 +16,10 @@ class ServerMonitor:
self.group_for_logs = group_for_logs self.group_for_logs = group_for_logs
self.important_logs = important_logs self.important_logs = important_logs
# Определяем ОС
self.os_type = self._detect_os()
logger.info(f"Обнаружена ОС: {self.os_type}")
# Пороговые значения для алертов # Пороговые значения для алертов
self.threshold = 80.0 self.threshold = 80.0
self.recovery_threshold = 75.0 self.recovery_threshold = 75.0
@@ -39,6 +44,119 @@ class ServerMonitor:
self.last_disk_io = None self.last_disk_io = None
self.last_disk_io_time = 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: def get_system_info(self) -> Dict:
"""Получение информации о системе""" """Получение информации о системе"""
try: try:
@@ -51,16 +169,31 @@ class ServerMonitor:
memory = psutil.virtual_memory() memory = psutil.virtual_memory()
swap = psutil.swap_memory() swap = psutil.swap_memory()
# Используем единый расчет для всех ОС: used / total для получения процента занятой памяти
# Это обеспечивает консистентность между macOS и Ubuntu
ram_percent = (memory.used / memory.total) * 100
# Диск # Диск
disk = psutil.disk_usage('/') disk = self._get_disk_usage()
disk_io = psutil.disk_io_counters() 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) disk_read_speed, disk_write_speed = self._calculate_disk_speed(disk_io)
# Система # Система
boot_time = psutil.boot_time() system_uptime = self._get_system_uptime()
uptime = time.time() - boot_time
# Получаем имя хоста в зависимости от ОС
if self.os_type == "macos":
hostname = os.uname().nodename
elif self.os_type == "ubuntu":
hostname = os.uname().nodename
else:
hostname = "unknown"
return { return {
'cpu_percent': cpu_percent, 'cpu_percent': cpu_percent,
@@ -70,25 +203,35 @@ class ServerMonitor:
'cpu_count': cpu_count, 'cpu_count': cpu_count,
'ram_used': round(memory.used / (1024**3), 2), 'ram_used': round(memory.used / (1024**3), 2),
'ram_total': round(memory.total / (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_used': round(swap.used / (1024**3), 2),
'swap_total': round(swap.total / (1024**3), 2), 'swap_total': round(swap.total / (1024**3), 2),
'swap_percent': swap.percent, 'swap_percent': swap.percent,
'disk_used': round(disk.used / (1024**3), 2), 'disk_used': round(disk.used / (1024**3), 2),
'disk_total': round(disk.total / (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_free': round(disk.free / (1024**3), 2),
'disk_read_speed': disk_read_speed, 'disk_read_speed': disk_read_speed,
'disk_write_speed': disk_write_speed, 'disk_write_speed': disk_write_speed,
'disk_io_percent': self._calculate_disk_io_percent(), 'disk_io_percent': self._calculate_disk_io_percent(),
'system_uptime': self._format_uptime(uptime), 'system_uptime': self._format_uptime(system_uptime),
'server_hostname': psutil.os.uname().nodename, 'bot_uptime': self.get_bot_uptime(),
'server_hostname': hostname,
'current_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') 'current_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
} }
except Exception as e: except Exception as e:
logger.error(f"Ошибка при получении информации о системе: {e}") logger.error(f"Ошибка при получении информации о системе: {e}")
return {} 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: def _format_bytes(self, bytes_value: int) -> str:
"""Форматирование байтов в человекочитаемый вид""" """Форматирование байтов в человекочитаемый вид"""
if bytes_value == 0: if bytes_value == 0:
@@ -115,8 +258,8 @@ class ServerMonitor:
else: else:
return f"{minutes}м" 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: try:
# Сначала проверяем по PID файлу # Сначала проверяем по PID файлу
pid_file = self.pid_files.get(process_name) pid_file = self.pid_files.get(process_name)
@@ -127,7 +270,14 @@ class ServerMonitor:
if content and content != '# Этот файл будет автоматически обновляться при запуске бота': if content and content != '# Этот файл будет автоматически обновляться при запуске бота':
pid = int(content) pid = int(content)
if psutil.pid_exists(pid): 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): except (ValueError, FileNotFoundError):
pass pass
@@ -143,21 +293,33 @@ class ServerMonitor:
if ('voice_bot' in proc_name or if ('voice_bot' in proc_name or
'voice_bot' in cmdline or 'voice_bot' in cmdline or
'voice_bot_v2.py' in cmdline): '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': elif process_name == 'helper_bot':
# Проверяем helper_bot # Проверяем helper_bot
if ('helper_bot' in proc_name or if ('helper_bot' in proc_name or
'helper_bot' in cmdline or 'helper_bot' in cmdline or
'run_helper.py' in cmdline or 'run_helper.py' in cmdline or
'python' in proc_name and 'helper_bot' in cmdline): '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): except (psutil.NoSuchProcess, psutil.AccessDenied):
continue continue
return "" return "", "Выключен"
except Exception as e: except Exception as e:
logger.error(f"Ошибка при проверке процесса {process_name}: {e}") logger.error(f"Ошибка при проверке процесса {process_name}: {e}")
return "" return "", "Выключен"
def should_send_status(self) -> bool: def should_send_status(self) -> bool:
"""Проверка, нужно ли отправить статус (каждые 30 минут в 00 и 30 минут часа)""" """Проверка, нужно ли отправить статус (каждые 30 минут в 00 и 30 минут часа)"""
@@ -203,7 +365,7 @@ class ServerMonitor:
"""Расчет процента загрузки диска на основе IOPS""" """Расчет процента загрузки диска на основе IOPS"""
try: try:
# Получаем статистику диска # Получаем статистику диска
disk_io = psutil.disk_io_counters() disk_io = self._get_disk_io_counters()
if disk_io is None: if disk_io is None:
return 0 return 0
@@ -237,6 +399,7 @@ class ServerMonitor:
**Время запуска:** <code>{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</code> **Время запуска:** <code>{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</code>
**Сервер:** `{psutil.os.uname().nodename}` **Сервер:** `{psutil.os.uname().nodename}`
**Система:** {psutil.os.uname().sysname} {psutil.os.uname().release} **Система:** {psutil.os.uname().sysname} {psutil.os.uname().release}
**ОС:** {self.os_type.upper()}
✅ Мониторинг сервера активирован ✅ Мониторинг сервера активирован
✅ Статус будет отправляться каждые 30 минут (в 00 и 30 минут часа) ✅ Статус будет отправляться каждые 30 минут (в 00 и 30 минут часа)
@@ -324,8 +487,11 @@ class ServerMonitor:
async def send_status_message(self, system_info: Dict): async def send_status_message(self, system_info: Dict):
"""Отправка сообщения со статусом сервера""" """Отправка сообщения со статусом сервера"""
try: try:
voice_bot_status = self.check_process_status('voice_bot') voice_bot_status, voice_bot_uptime = self.check_process_status('voice_bot')
helper_bot_status = self.check_process_status('helper_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> 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']}%) 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']}%) 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:** **💿 Диск I/O:**
Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_write_speed']}</b> Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_write_speed']}</b>
Диск загружен: <b>{system_info['disk_io_percent']}%</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( await self.bot.send_message(
chat_id=self.group_for_logs, 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): async def monitor_loop(self):
"""Основной цикл мониторинга""" """Основной цикл мониторинга"""
logger.info("Модуль мониторинга сервера запущен") logger.info(f"Модуль мониторинга сервера запущен на {self.os_type.upper()}")
# Отправляем сообщение о запуске при первом запуске # Отправляем сообщение о запуске при первом запуске
if self.should_send_startup_status(): if self.should_send_startup_status():

View File

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