from __future__ import annotations import asyncio import logging import time from sqlalchemy.ext.asyncio import AsyncSession from roundtable.core.alerts import record_metric from roundtable.models.hosts import Host, ProbeType from roundtable.models.monitors import PingResult, PingStatus logger = logging.getLogger(__name__) TCP_TIMEOUT = 5.0 DEFAULT_TCP_PORT = 80 async def ping_check(host: Host, session: AsyncSession) -> None: """Probe a single host and write ping_result + metrics.""" if host.probe_type == ProbeType.icmp: result = await _icmp_ping(host.address) else: result = await _tcp_ping(host.address, host.probe_port or DEFAULT_TCP_PORT) status = PingStatus.up if result["up"] else PingStatus.down session.add(PingResult( host_id=host.id, status=status, response_time_ms=result.get("response_time_ms"), )) await session.flush() await record_metric(session, "ping", host.name, "up", 1.0 if result["up"] else 0.0) await record_metric( session, "ping", host.name, "response_time_ms", result.get("response_time_ms") or 0.0, ) async def _tcp_ping(address: str, port: int) -> dict: start = time.monotonic() try: reader, writer = await asyncio.wait_for( asyncio.open_connection(address, port), timeout=TCP_TIMEOUT, ) writer.close() await writer.wait_closed() return {"up": True, "response_time_ms": round((time.monotonic() - start) * 1000, 2)} except Exception: return {"up": False, "response_time_ms": None} async def _icmp_ping(address: str) -> dict: """Use system ping command (setuid binary; no raw socket privilege needed in Python).""" start = time.monotonic() try: proc = await asyncio.create_subprocess_exec( "ping", "-c", "1", "-W", "2", address, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL, ) await asyncio.wait_for(proc.wait(), timeout=5.0) if proc.returncode == 0: return {"up": True, "response_time_ms": round((time.monotonic() - start) * 1000, 2)} return {"up": False, "response_time_ms": None} except Exception as exc: logger.warning(f"ICMP ping failed for {address}: {exc}, falling back to TCP") return await _tcp_ping(address, DEFAULT_TCP_PORT)