Implement AnonBot integration and monitoring enhancements
- Added AnonBot service to docker-compose with resource limits and environment variables. - Updated Makefile to include commands for AnonBot logs, restart, and dependency checks. - Enhanced Grafana dashboards with AnonBot health metrics and database connection statistics. - Implemented AnonBot status retrieval in the message sender for improved monitoring. - Updated Prometheus configuration to scrape metrics from AnonBot service.
This commit was merged in pull request #2.
This commit is contained in:
@@ -1,188 +0,0 @@
|
||||
# PID Manager - Управление процессами ботов
|
||||
|
||||
## Описание
|
||||
|
||||
`pid_manager.py` - это общий модуль для управления PID файлами всех ботов в проекте. Он обеспечивает создание, отслеживание и очистку PID файлов для мониторинга состояния процессов.
|
||||
|
||||
## Использование
|
||||
|
||||
### Для новых ботов
|
||||
|
||||
Чтобы добавить PID мониторинг в новый бот, выполните следующие шаги:
|
||||
|
||||
1. **Импортируйте PID менеджер в ваш скрипт запуска:**
|
||||
|
||||
```python
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Добавляем путь к инфраструктуре в sys.path
|
||||
infra_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'infra', 'monitoring')
|
||||
if infra_path not in sys.path:
|
||||
sys.path.insert(0, infra_path)
|
||||
|
||||
from pid_manager import get_bot_pid_manager
|
||||
```
|
||||
|
||||
2. **Создайте PID менеджер в начале main функции:**
|
||||
|
||||
```python
|
||||
async def main():
|
||||
# Создаем PID менеджер для отслеживания процесса (если доступен)
|
||||
pid_manager = None
|
||||
if get_bot_pid_manager:
|
||||
pid_manager = get_bot_pid_manager("your_bot_name") # Замените на имя вашего бота
|
||||
if not pid_manager.create_pid_file():
|
||||
logger.error("Не удалось создать PID файл, завершаем работу")
|
||||
return
|
||||
else:
|
||||
logger.info("PID менеджер недоступен, запуск без PID файла")
|
||||
|
||||
# Ваш код запуска бота...
|
||||
```
|
||||
|
||||
3. **Очистите PID файл при завершении:**
|
||||
|
||||
```python
|
||||
try:
|
||||
# Ваш код работы бота...
|
||||
finally:
|
||||
# Очищаем PID файл (если PID менеджер доступен)
|
||||
if pid_manager:
|
||||
pid_manager.cleanup_pid_file()
|
||||
```
|
||||
|
||||
### Для мониторинга
|
||||
|
||||
Чтобы добавить новый бот в систему мониторинга:
|
||||
|
||||
```python
|
||||
from infra.monitoring.metrics_collector import MetricsCollector
|
||||
|
||||
# Создаем экземпляр коллектора метрик
|
||||
collector = MetricsCollector()
|
||||
|
||||
# Добавляем новый бот в мониторинг
|
||||
collector.add_bot_to_monitoring("your_bot_name")
|
||||
|
||||
# Теперь можно проверять статус
|
||||
status, uptime = collector.check_process_status("your_bot_name")
|
||||
```
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
prod/
|
||||
├── infra/
|
||||
│ └── monitoring/
|
||||
│ ├── pid_manager.py # Основной модуль
|
||||
│ ├── metrics_collector.py # Мониторинг процессов
|
||||
│ └── README_PID_MANAGER.md # Эта документация
|
||||
├── bots/
|
||||
│ ├── telegram-helper-bot/
|
||||
│ │ └── run_helper.py # Использует PID менеджер
|
||||
│ └── your-new-bot/
|
||||
│ └── run_your_bot.py # Будет использовать PID менеджер
|
||||
├── helper_bot.pid # PID файл helper_bot
|
||||
├── your_bot.pid # PID файл вашего бота
|
||||
└── .gitignore # Содержит *.pid
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### PIDManager
|
||||
|
||||
- `create_pid_file()` - Создает PID файл
|
||||
- `cleanup_pid_file()` - Удаляет PID файл
|
||||
- `is_running()` - Проверяет, запущен ли процесс
|
||||
- `get_pid()` - Получает PID из файла
|
||||
|
||||
### Функции
|
||||
|
||||
- `get_bot_pid_manager(bot_name)` - Создает PID менеджер для бота
|
||||
- `create_pid_manager(process_name, project_root)` - Создает PID менеджер с настройками
|
||||
|
||||
## Примеры
|
||||
|
||||
### Простой бот
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from pid_manager import get_bot_pid_manager
|
||||
|
||||
async def main():
|
||||
# Создаем PID менеджер
|
||||
pid_manager = get_bot_pid_manager("simple_bot")
|
||||
if not pid_manager.create_pid_file():
|
||||
print("Не удалось создать PID файл")
|
||||
return
|
||||
|
||||
try:
|
||||
# Ваш код бота
|
||||
print("Бот запущен...")
|
||||
await asyncio.sleep(3600) # Работаем час
|
||||
finally:
|
||||
# Очищаем PID файл
|
||||
pid_manager.cleanup_pid_file()
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Бот с обработкой сигналов
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import signal
|
||||
from pid_manager import get_bot_pid_manager
|
||||
|
||||
async def main():
|
||||
pid_manager = get_bot_pid_manager("advanced_bot")
|
||||
if not pid_manager.create_pid_file():
|
||||
return
|
||||
|
||||
# Флаг для корректного завершения
|
||||
shutdown_event = asyncio.Event()
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
print(f"Получен сигнал {signum}, завершаем работу...")
|
||||
shutdown_event.set()
|
||||
|
||||
# Регистрируем обработчики сигналов
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
try:
|
||||
# Ваш код бота
|
||||
await shutdown_event.wait()
|
||||
finally:
|
||||
pid_manager.cleanup_pid_file()
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Примечания
|
||||
|
||||
- PID файлы создаются в корне проекта
|
||||
- Все PID файлы автоматически игнорируются Git (см. `.gitignore`)
|
||||
- PID менеджер автоматически обрабатывает сигналы SIGTERM и SIGINT
|
||||
- При завершении процесса PID файл автоматически удаляется
|
||||
- Система мониторинга автоматически находит PID файлы в корне проекта
|
||||
|
||||
## Изолированный запуск
|
||||
|
||||
При запуске бота изолированно (без доступа к основному проекту):
|
||||
|
||||
- PID менеджер автоматически не создается
|
||||
- Бот запускается без PID файла
|
||||
- В логах появляется сообщение "PID менеджер недоступен (изолированный запуск), PID файл не создается"
|
||||
- Это позволяет запускать бота в любой среде без ошибок
|
||||
|
||||
## Автоматическое определение
|
||||
|
||||
Система автоматически определяет доступность PID менеджера:
|
||||
|
||||
1. **В основном проекте**: PID менеджер доступен, создается PID файл для мониторинга
|
||||
2. **Изолированно**: PID менеджер недоступен, бот работает без PID файла
|
||||
3. **Fallback**: Если PID менеджер недоступен, бот продолжает работать нормально
|
||||
@@ -64,6 +64,39 @@ class MessageSender:
|
||||
logger.error(f"Ошибка при отправке сообщения в Telegram: {e}")
|
||||
return False
|
||||
|
||||
async def get_anonbot_status(self) -> Tuple[str, str]:
|
||||
"""Получение статуса AnonBot через HTTP API"""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# AnonBot доступен через Docker network
|
||||
url = "http://bots_anon_bot:8081/status"
|
||||
|
||||
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
status = data.get('status', 'unknown')
|
||||
uptime = data.get('uptime', 'unknown')
|
||||
|
||||
# Форматируем статус с эмодзи
|
||||
if status == 'running':
|
||||
status_emoji = "✅"
|
||||
elif status == 'stopped':
|
||||
status_emoji = "❌"
|
||||
else:
|
||||
status_emoji = "⚠️"
|
||||
|
||||
return f"{status_emoji}", uptime
|
||||
else:
|
||||
logger.warning(f"AnonBot API вернул статус {response.status}")
|
||||
return "⚠️ AnonBot", "API недоступен"
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
logger.warning(f"Ошибка подключения к AnonBot API: {e}")
|
||||
return "❌", "Недоступен"
|
||||
except Exception as e:
|
||||
logger.error(f"Неожиданная ошибка при получении статуса AnonBot: {e}")
|
||||
return "⚠️", "Ошибка"
|
||||
|
||||
def should_send_status(self) -> bool:
|
||||
"""Проверка, нужно ли отправить статус (каждые N минут)"""
|
||||
now = datetime.now()
|
||||
@@ -147,11 +180,14 @@ class MessageSender:
|
||||
else:
|
||||
return "🚨"
|
||||
|
||||
def get_status_message(self, system_info: Dict) -> str:
|
||||
async def get_status_message(self, system_info: Dict) -> str:
|
||||
"""Формирование сообщения со статусом сервера"""
|
||||
try:
|
||||
helper_bot_status, helper_bot_uptime = self.metrics_collector.check_process_status('helper_bot')
|
||||
|
||||
# Получаем статус AnonBot
|
||||
anonbot_status, anonbot_uptime = await self.get_anonbot_status()
|
||||
|
||||
# Получаем эмодзи для всех метрик
|
||||
cpu_emoji = self._get_cpu_emoji(system_info['cpu_percent'])
|
||||
ram_emoji = self._get_memory_emoji(system_info['ram_percent'])
|
||||
@@ -183,6 +219,7 @@ Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_wri
|
||||
|
||||
**🤖 Процессы:**
|
||||
{helper_bot_status} helper-bot - {helper_bot_uptime}
|
||||
{anonbot_status} AnonBot - {anonbot_uptime}
|
||||
---------------------------------
|
||||
⏰ Uptime сервера: {system_info['system_uptime']}
|
||||
🔍 Уровень мониторинга: {level_text} ({monitoring_level})"""
|
||||
@@ -259,7 +296,7 @@ Read: <b>{system_info['disk_read_speed']}</b> | Write: <b>{system_info['disk_wri
|
||||
logger.error("Не удалось получить информацию о системе")
|
||||
return False
|
||||
|
||||
status_message = self.get_status_message(system_info)
|
||||
status_message = await self.get_status_message(system_info)
|
||||
success = await self.send_telegram_message(self.group_for_logs, status_message)
|
||||
|
||||
# Обновляем время последней отправки только при успешной отправке
|
||||
|
||||
@@ -590,14 +590,17 @@ class MetricsCollector:
|
||||
alerts.append(('cpu', system_info['cpu_percent'], f"Нагрузка за 1 мин: {system_info['load_avg_1m']}"))
|
||||
logger.warning(f"CPU ALERT: {system_info['cpu_percent']:.1f}% > {self.threshold}% (задержка {self.alert_delays['cpu']}s)")
|
||||
else:
|
||||
# CPU ниже порога - сбрасываем состояние
|
||||
# CPU ниже порога - сбрасываем состояние только если был активный алерт
|
||||
if self.alert_states['cpu']:
|
||||
self.alert_states['cpu'] = False
|
||||
recoveries.append(('cpu', system_info['cpu_percent']))
|
||||
logger.info(f"CPU RECOVERY: {system_info['cpu_percent']:.1f}% < {self.recovery_threshold}%")
|
||||
|
||||
# Сбрасываем время начала превышения
|
||||
self.alert_start_times['cpu'] = None
|
||||
# Сбрасываем время начала превышения только после отправки алерта
|
||||
self.alert_start_times['cpu'] = None
|
||||
elif system_info['cpu_percent'] < self.recovery_threshold and self.alert_start_times['cpu'] is not None:
|
||||
# Если CPU опустился значительно ниже порога, сбрасываем время начала превышения
|
||||
logger.debug(f"CPU значительно ниже порога {self.recovery_threshold}%: {system_info['cpu_percent']:.1f}% - сбрасываем время начала превышения")
|
||||
self.alert_start_times['cpu'] = None
|
||||
|
||||
# Проверка RAM с задержкой
|
||||
if system_info['ram_percent'] > self.threshold:
|
||||
@@ -613,14 +616,17 @@ class MetricsCollector:
|
||||
alerts.append(('ram', system_info['ram_percent'], f"Используется: {system_info['ram_used']} GB из {system_info['ram_total']} GB"))
|
||||
logger.warning(f"RAM ALERT: {system_info['ram_percent']:.1f}% > {self.threshold}% (задержка {self.alert_delays['ram']}s)")
|
||||
else:
|
||||
# RAM ниже порога - сбрасываем состояние
|
||||
# RAM ниже порога - сбрасываем состояние только если был активный алерт
|
||||
if self.alert_states['ram']:
|
||||
self.alert_states['ram'] = False
|
||||
recoveries.append(('ram', system_info['ram_percent']))
|
||||
logger.info(f"RAM RECOVERY: {system_info['ram_percent']:.1f}% < {self.recovery_threshold}%")
|
||||
|
||||
# Сбрасываем время начала превышения
|
||||
self.alert_start_times['ram'] = None
|
||||
# Сбрасываем время начала превышения только после отправки алерта
|
||||
self.alert_start_times['ram'] = None
|
||||
elif system_info['ram_percent'] < self.recovery_threshold and self.alert_start_times['ram'] is not None:
|
||||
# Если RAM опустился значительно ниже порога, сбрасываем время начала превышения
|
||||
logger.debug(f"RAM значительно ниже порога {self.recovery_threshold}%: {system_info['ram_percent']:.1f}% - сбрасываем время начала превышения")
|
||||
self.alert_start_times['ram'] = None
|
||||
|
||||
# Проверка диска с задержкой
|
||||
if system_info['disk_percent'] > self.threshold:
|
||||
@@ -636,14 +642,17 @@ class MetricsCollector:
|
||||
alerts.append(('disk', system_info['disk_percent'], f"Свободно: {system_info['disk_free']} GB на /"))
|
||||
logger.warning(f"DISK ALERT: {system_info['disk_percent']:.1f}% > {self.threshold}% (задержка {self.alert_delays['disk']}s)")
|
||||
else:
|
||||
# Диск ниже порога - сбрасываем состояние
|
||||
# Диск ниже порога - сбрасываем состояние только если был активный алерт
|
||||
if self.alert_states['disk']:
|
||||
self.alert_states['disk'] = False
|
||||
recoveries.append(('disk', system_info['disk_percent']))
|
||||
logger.info(f"DISK RECOVERY: {system_info['disk_percent']:.1f}% < {self.recovery_threshold}%")
|
||||
|
||||
# Сбрасываем время начала превышения
|
||||
self.alert_start_times['disk'] = None
|
||||
# Сбрасываем время начала превышения только после отправки алерта
|
||||
self.alert_start_times['disk'] = None
|
||||
elif system_info['disk_percent'] < self.recovery_threshold and self.alert_start_times['disk'] is not None:
|
||||
# Если диск опустился значительно ниже порога, сбрасываем время начала превышения
|
||||
logger.debug(f"Disk значительно ниже порога {self.recovery_threshold}%: {system_info['disk_percent']:.1f}% - сбрасываем время начала превышения")
|
||||
self.alert_start_times['disk'] = None
|
||||
|
||||
return alerts, recoveries
|
||||
|
||||
|
||||
Reference in New Issue
Block a user