""" pyserve top - Live monitoring dashboard """ import asyncio import time from pathlib import Path from typing import Optional import click @click.command("top") @click.argument("services", nargs=-1) @click.option( "--refresh", "refresh_interval", default=2, type=float, help="Refresh interval in seconds", ) @click.option( "--no-color", is_flag=True, help="Disable colored output", ) @click.pass_obj def top_cmd(ctx, services: tuple, refresh_interval: float, no_color: bool): """ Live monitoring dashboard for services. Shows real-time CPU, memory usage, and request metrics. \b Examples: pyserve top # Monitor all services pyserve top api admin # Monitor specific services pyserve top --refresh 5 # Slower refresh rate """ from ..output import console, print_info from ..state import StateManager state_manager = StateManager(Path(".pyserve"), ctx.project) if not state_manager.is_daemon_running(): print_info("No services running. Start with 'pyserve up -d'") return try: asyncio.run( _run_dashboard( state_manager, list(services) if services else None, refresh_interval, no_color, ) ) except KeyboardInterrupt: console.print("\n") async def _run_dashboard( state_manager, filter_services: Optional[list], refresh_interval: float, no_color: bool, ): from rich.live import Live from rich.table import Table from rich.panel import Panel from rich.layout import Layout from rich.text import Text from ..output import console, format_uptime, format_bytes try: import psutil except ImportError: console.print("[yellow]psutil not installed. Install with: pip install psutil[/yellow]") return start_time = time.time() def make_dashboard(): all_services = state_manager.get_all_services() if filter_services: all_services = {k: v for k, v in all_services.items() if k in filter_services} table = Table( title=None, show_header=True, header_style="bold", border_style="dim", expand=True, ) table.add_column("SERVICE", style="cyan", no_wrap=True) table.add_column("STATUS", no_wrap=True) table.add_column("CPU%", justify="right") table.add_column("MEM", justify="right") table.add_column("PID", style="dim") table.add_column("UPTIME", style="dim") table.add_column("HEALTH", no_wrap=True) total_cpu = 0.0 total_mem = 0 running_count = 0 total_count = len(all_services) for name, service in sorted(all_services.items()): status_style = { "running": "[green]● RUN[/green]", "stopped": "[dim]○ STOP[/dim]", "failed": "[red]✗ FAIL[/red]", "starting": "[yellow]◐ START[/yellow]", "stopping": "[yellow]◑ STOP[/yellow]", }.get(service.state, service.state) cpu_str = "-" mem_str = "-" if service.pid and service.state == "running": try: proc = psutil.Process(service.pid) cpu = proc.cpu_percent(interval=0.1) mem = proc.memory_info().rss cpu_str = f"{cpu:.1f}%" mem_str = format_bytes(mem) total_cpu += cpu total_mem += mem running_count += 1 except (psutil.NoSuchProcess, psutil.AccessDenied): pass health_style = { "healthy": "[green]✓[/green]", "unhealthy": "[red]✗[/red]", "degraded": "[yellow]⚠[/yellow]", "unknown": "[dim]?[/dim]", }.get(service.health.status, "[dim]-[/dim]") uptime = format_uptime(service.uptime) if service.state == "running" else "-" pid = str(service.pid) if service.pid else "-" table.add_row( name, status_style, cpu_str, mem_str, pid, uptime, health_style, ) elapsed = format_uptime(time.time() - start_time) summary = Text() summary.append(f"Running: {running_count}/{total_count}", style="bold") summary.append(" | ") summary.append(f"CPU: {total_cpu:.1f}%", style="cyan") summary.append(" | ") summary.append(f"MEM: {format_bytes(total_mem)}", style="cyan") summary.append(" | ") summary.append(f"Session: {elapsed}", style="dim") layout = Layout() layout.split_column( Layout( Panel( Text("PyServe Dashboard", style="bold cyan", justify="center"), border_style="cyan", ), size=3, ), Layout(table), Layout(Panel(summary, border_style="dim"), size=3), ) return layout with Live(make_dashboard(), refresh_per_second=1 / refresh_interval, console=console) as live: while True: await asyncio.sleep(refresh_interval) live.update(make_dashboard())