
- Implemented APScheduler for automatic uptime checks - Each monitor runs on its own configured interval (default 5 minutes) - Scheduler starts automatically on application startup - Real-time job management when monitors are created/updated/deleted - Added scheduler status and control endpoints - Background logging of all check results - Updated documentation with scheduling features
135 lines
4.8 KiB
Python
135 lines
4.8 KiB
Python
import logging
|
|
from datetime import datetime, timedelta
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
from sqlalchemy.orm import sessionmaker
|
|
from app.db.session import engine
|
|
from app.models.monitor import Monitor
|
|
from app.services.uptime_checker import UptimeChecker
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class UptimeScheduler:
|
|
def __init__(self):
|
|
self.scheduler = BackgroundScheduler()
|
|
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
self.is_running = False
|
|
|
|
def start(self):
|
|
if not self.is_running:
|
|
self.scheduler.start()
|
|
self.is_running = True
|
|
logger.info("Uptime scheduler started")
|
|
self.schedule_all_monitors()
|
|
|
|
def stop(self):
|
|
if self.is_running:
|
|
self.scheduler.shutdown()
|
|
self.is_running = False
|
|
logger.info("Uptime scheduler stopped")
|
|
|
|
def schedule_all_monitors(self):
|
|
"""Schedule all active monitors based on their individual intervals"""
|
|
db = self.SessionLocal()
|
|
try:
|
|
active_monitors = db.query(Monitor).filter(Monitor.is_active).all()
|
|
|
|
for monitor in active_monitors:
|
|
job_id = f"monitor_{monitor.id}"
|
|
|
|
# Remove existing job if it exists
|
|
if self.scheduler.get_job(job_id):
|
|
self.scheduler.remove_job(job_id)
|
|
|
|
# Add new job with the monitor's interval
|
|
self.scheduler.add_job(
|
|
func=self.check_monitor,
|
|
trigger=IntervalTrigger(seconds=monitor.interval),
|
|
id=job_id,
|
|
args=[monitor.id],
|
|
name=f"Check {monitor.name}",
|
|
replace_existing=True,
|
|
next_run_time=datetime.now()
|
|
+ timedelta(seconds=10), # Start after 10 seconds
|
|
)
|
|
|
|
logger.info(
|
|
f"Scheduled monitor '{monitor.name}' (ID: {monitor.id}) to run every {monitor.interval} seconds"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error scheduling monitors: {e}")
|
|
finally:
|
|
db.close()
|
|
|
|
def check_monitor(self, monitor_id: int):
|
|
"""Run uptime check for a specific monitor"""
|
|
db = self.SessionLocal()
|
|
try:
|
|
monitor = db.query(Monitor).filter(Monitor.id == monitor_id).first()
|
|
if monitor and monitor.is_active:
|
|
checker = UptimeChecker(db)
|
|
result = checker.check_monitor(monitor)
|
|
|
|
status = "UP" if result["is_up"] else "DOWN"
|
|
logger.info(
|
|
f"Monitor '{monitor.name}' (ID: {monitor_id}): {status} - Response time: {result['response_time']}ms"
|
|
)
|
|
else:
|
|
# Monitor was deleted or deactivated, remove the job
|
|
job_id = f"monitor_{monitor_id}"
|
|
if self.scheduler.get_job(job_id):
|
|
self.scheduler.remove_job(job_id)
|
|
logger.info(
|
|
f"Removed job for inactive/deleted monitor ID: {monitor_id}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking monitor {monitor_id}: {e}")
|
|
finally:
|
|
db.close()
|
|
|
|
def add_monitor_job(self, monitor_id: int, interval: int, name: str):
|
|
"""Add a job for a new monitor"""
|
|
job_id = f"monitor_{monitor_id}"
|
|
|
|
# Remove existing job if it exists
|
|
if self.scheduler.get_job(job_id):
|
|
self.scheduler.remove_job(job_id)
|
|
|
|
# Add new job
|
|
self.scheduler.add_job(
|
|
func=self.check_monitor,
|
|
trigger=IntervalTrigger(seconds=interval),
|
|
id=job_id,
|
|
args=[monitor_id],
|
|
name=f"Check {name}",
|
|
replace_existing=True,
|
|
next_run_time=datetime.now() + timedelta(seconds=10),
|
|
)
|
|
|
|
logger.info(
|
|
f"Added scheduler job for monitor '{name}' (ID: {monitor_id}) with {interval} second interval"
|
|
)
|
|
|
|
def remove_monitor_job(self, monitor_id: int):
|
|
"""Remove a job for a deleted monitor"""
|
|
job_id = f"monitor_{monitor_id}"
|
|
if self.scheduler.get_job(job_id):
|
|
self.scheduler.remove_job(job_id)
|
|
logger.info(f"Removed scheduler job for monitor ID: {monitor_id}")
|
|
|
|
def update_monitor_job(
|
|
self, monitor_id: int, interval: int, name: str, is_active: bool
|
|
):
|
|
"""Update a job for a modified monitor"""
|
|
if is_active:
|
|
self.add_monitor_job(monitor_id, interval, name)
|
|
else:
|
|
self.remove_monitor_job(monitor_id)
|
|
|
|
|
|
# Global scheduler instance
|
|
uptime_scheduler = UptimeScheduler()
|