"""Single scheduled task that runs every due Monitor of any type.""" from __future__ import annotations import asyncio import logging from datetime import datetime, timezone from sqlalchemy import select from steward.core.alerts import record_metric from steward.models.monitors import Monitor, MonitorResult from .runner import run_monitor logger = logging.getLogger(__name__) async def run_due_monitors(app) -> None: """Check every enabled Monitor whose interval has elapsed, persist results. Per-monitor `check_interval_seconds` overrides the global MONITORS_POLL_INTERVAL (0 = use global), mirroring how the scheduler fires on the global cadence but each monitor decides if it is actually due. """ global_interval = int(app.config.get("MONITORS_POLL_INTERVAL", 60)) now = datetime.now(timezone.utc) async with app.db_sessionmaker() as session: monitors = list((await session.execute( select(Monitor).where(Monitor.enabled.is_(True)) )).scalars()) due: list[Monitor] = [] for m in monitors: interval = m.check_interval_seconds or global_interval if m.last_checked_at is None or (now - m.last_checked_at).total_seconds() >= interval: due.append(m) if not due: return pairs = await asyncio.gather( *[run_monitor(m) for m in due], return_exceptions=True ) check_time = datetime.now(timezone.utc) async with app.db_sessionmaker() as session: async with session.begin(): for monitor, res in zip(due, pairs): if isinstance(res, Exception): logger.error("Monitor %r check errored: %s", monitor.name, res) continue session.add(MonitorResult( monitor_id=monitor.id, checked_at=check_time, is_up=res["is_up"], response_ms=res["response_ms"], status_code=res["status_code"], resolved_ip=res["resolved_ip"], content_matched=res["content_matched"], tls_expires_at=res["tls_expires_at"], error_msg=res["error_msg"], )) db_monitor = await session.get(Monitor, monitor.id) if db_monitor: db_monitor.last_checked_at = check_time # Alert pipeline — keyed by monitor type (source) + name. await record_metric( session=session, source_module=monitor.type, resource_name=monitor.name, metric_name="is_up", value=1.0 if res["is_up"] else 0.0, ) if res["response_ms"] is not None: await record_metric( session=session, source_module=monitor.type, resource_name=monitor.name, metric_name="response_ms", value=res["response_ms"], )