forked from Shifty/pyserveX
184 lines
5.4 KiB
Python
184 lines
5.4 KiB
Python
"""
|
|
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.layout import Layout
|
|
from rich.live import Live
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
from rich.text import Text
|
|
|
|
from ..output import console, format_bytes, format_uptime
|
|
|
|
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())
|