#!/usr/bin/env python3 """simple.py — check whether a list of sites is reachable and how fast they respond. Usage: python simple.py [sites.txt] The config file is a plain-text list of URLs, one per line. Lines starting with '#' and blank lines are ignored. Exits 0 if all sites responded, 1 if any failed. """ import sys import time import urllib.error import urllib.request _TIMEOUT = 10 # seconds def load_sites(path: str) -> list[str]: """Return URLs from a plain-text config file (one per line, # comments).""" urls = [] with open(path) as f: for raw in f: line = raw.strip() if not line or line.startswith("#"): continue # Take only the first token so future 'url key=value' annotations # (e.g. budget=200ms) don't break the URL. urls.append(line.split()[0]) return urls def check_site(url: str) -> tuple[bool, float, str]: """Probe url and return (ok, elapsed_ms, error_message). The body is fully read so the elapsed time includes transfer, not just TTFB. """ t0 = time.perf_counter() try: with urllib.request.urlopen(url, timeout=_TIMEOUT) as resp: resp.read() elapsed = (time.perf_counter() - t0) * 1000 return True, elapsed, "" except urllib.error.HTTPError as exc: elapsed = (time.perf_counter() - t0) * 1000 # Show the full "HTTP Error CODE: REASON" so the status code is visible. return False, elapsed, str(exc) except urllib.error.URLError as exc: elapsed = (time.perf_counter() - t0) * 1000 # URLError.reason is either a string or another exception. reason = str(exc.reason) if exc.reason else str(exc) return False, elapsed, reason except Exception as exc: # noqa: BLE001 — catch-all for unexpected errors elapsed = (time.perf_counter() - t0) * 1000 return False, elapsed, str(exc) def main(argv: list[str]) -> int: config = argv[0] if argv else "sites.txt" try: urls = load_sites(config) except FileNotFoundError: print(f"error: config file not found: {config}", file=sys.stderr) return 1 except OSError as exc: print(f"error: cannot read config file: {exc}", file=sys.stderr) return 1 if not urls: print("error: no URLs found in config file", file=sys.stderr) return 1 any_failed = False for url in urls: ok, elapsed_ms, err = check_site(url) if ok: print(f"OK {elapsed_ms:8.2f} ms {url}") else: any_failed = True print(f"FAIL ({err}) {url}") return 1 if any_failed else 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))