""" pyserve health - Check health of services """ import asyncio from pathlib import Path from typing import Any import click @click.command("health") @click.argument("services", nargs=-1) @click.option( "--timeout", "timeout", default=5, type=int, help="Health check timeout in seconds", ) @click.option( "--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format", ) @click.pass_obj def health_cmd(ctx: Any, services: tuple[str, ...], timeout: int, output_format: str) -> None: """ Check health of services. Performs active health checks on running services. \b Examples: pyserve health # Check all services pyserve health api admin # Check specific services pyserve health --format json # JSON output """ from ..output import console, print_error, print_info from ..state import StateManager state_manager = StateManager(Path(".pyserve"), ctx.project) all_services = state_manager.get_all_services() if services: all_services = {k: v for k, v in all_services.items() if k in services} if not all_services: print_info("No services to check") return results = asyncio.run(_check_health(all_services, timeout)) if output_format == "json": import json console.print(json.dumps(results, indent=2)) return from rich.table import Table from ..output import format_health table = Table(show_header=True, header_style="bold") table.add_column("SERVICE", style="cyan") table.add_column("HEALTH") table.add_column("CHECKS", justify="right") table.add_column("LAST CHECK", style="dim") table.add_column("RESPONSE TIME", justify="right") for name, result in results.items(): health_str = format_health(result["status"]) checks = f"{result['successes']}/{result['total']}" last_check = result.get("last_check", "-") response_time = f"{result['response_time_ms']:.0f}ms" if result.get("response_time_ms") else "-" table.add_row(name, health_str, checks, last_check, response_time) console.print() console.print(table) console.print() healthy = sum(1 for r in results.values() if r["status"] == "healthy") unhealthy = sum(1 for r in results.values() if r["status"] == "unhealthy") if unhealthy: print_error(f"{unhealthy} service(s) unhealthy") raise SystemExit(1) else: from ..output import print_success print_success(f"All {healthy} service(s) healthy") async def _check_health(services: dict, timeout: int) -> dict: import time try: import httpx except ImportError: return {name: {"status": "unknown", "error": "httpx not installed"} for name in services} results = {} for name, service in services.items(): if service.state != "running" or not service.port: results[name] = { "status": "unknown", "successes": 0, "total": 0, "error": "Service not running", } continue health_path = "/health" url = f"http://127.0.0.1:{service.port}{health_path}" start_time = time.time() try: async with httpx.AsyncClient(timeout=timeout) as client: resp = await client.get(url) response_time = (time.time() - start_time) * 1000 if resp.status_code < 500: results[name] = { "status": "healthy", "successes": 1, "total": 1, "response_time_ms": response_time, "last_check": "just now", "status_code": resp.status_code, } else: results[name] = { "status": "unhealthy", "successes": 0, "total": 1, "response_time_ms": response_time, "last_check": "just now", "status_code": resp.status_code, } except httpx.TimeoutException: results[name] = { "status": "unhealthy", "successes": 0, "total": 1, "error": "timeout", "last_check": "just now", } except Exception as e: results[name] = { "status": "unhealthy", "successes": 0, "total": 1, "error": str(e), "last_check": "just now", } return results