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()