forked from Shifty/pyserveX
161 lines
4.7 KiB
Python
161 lines
4.7 KiB
Python
"""
|
|
pyserve health - Check health of services
|
|
"""
|
|
|
|
import asyncio
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
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, services: tuple, timeout: int, output_format: str):
|
|
"""
|
|
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
|