search for standard sites pub-search.waow.tech
search zig blog atproto
at main 164 lines 4.9 kB view raw
1#!/usr/bin/env python3 2# /// script 3# requires-python = ">=3.11" 4# dependencies = ["httpx", "rich", "plotext", "typer"] 5# /// 6"""pub-search terminal dashboard 7 8usage: 9 uv run scripts/dashboard.py # default view 10 uv run scripts/dashboard.py --days 14 # longer timeline 11""" 12 13import httpx 14import plotext as plt 15import typer 16from rich.console import Console 17from rich.panel import Panel 18from rich.table import Table 19 20API_BASE = "https://leaflet-search-backend.fly.dev" 21console = Console() 22app = typer.Typer(add_completion=False) 23 24 25def fetch_stats() -> dict: 26 """fetch /stats endpoint""" 27 resp = httpx.get(f"{API_BASE}/stats", timeout=30) 28 resp.raise_for_status() 29 return resp.json() 30 31 32def fetch_dashboard() -> dict: 33 """fetch /api/dashboard endpoint""" 34 resp = httpx.get(f"{API_BASE}/api/dashboard", timeout=30) 35 resp.raise_for_status() 36 return resp.json() 37 38 39def display_overview(stats: dict) -> None: 40 """show document/publication counts""" 41 table = Table(show_header=False, box=None, padding=(0, 2), expand=True) 42 table.add_column(style="dim") 43 table.add_column(style="bold green", justify="right") 44 45 table.add_row("documents", f"{stats['documents']:,}") 46 table.add_row("publications", f"{stats['publications']:,}") 47 table.add_row("embeddings", f"{stats['embeddings']:,}") 48 49 embed_pct = (stats['embeddings'] / stats['documents'] * 100) if stats['documents'] > 0 else 0 50 table.add_row("embedded", f"{embed_pct:.0f}%") 51 52 console.print(Panel(table, title="[bold]index[/]", border_style="blue", expand=False)) 53 54 55def display_usage(stats: dict) -> None: 56 """show usage and similarity cache stats""" 57 hits = stats.get('cache_hits', 0) 58 misses = stats.get('cache_misses', 0) 59 total = hits + misses 60 hit_rate = (hits / total * 100) if total > 0 else 0 61 62 table = Table(show_header=False, box=None, padding=(0, 2), expand=True) 63 table.add_column(style="dim") 64 table.add_column(style="bold cyan", justify="right") 65 66 table.add_row("searches", f"{stats.get('searches', 0):,}") 67 table.add_row("errors", f"{stats.get('errors', 0):,}") 68 table.add_row("similar cache hit", f"{hit_rate:.0f}% ({hits}/{total})") 69 70 console.print(Panel(table, title="[bold]usage[/]", border_style="cyan", expand=False)) 71 72 73def display_latency(stats: dict) -> None: 74 """show latency percentiles""" 75 timing = stats.get('timing', {}) 76 if not timing: 77 return 78 79 table = Table(box=None, padding=(0, 1), expand=True) 80 table.add_column("endpoint", style="dim") 81 table.add_column("p50", justify="right", style="green") 82 table.add_column("p95", justify="right", style="yellow") 83 table.add_column("p99", justify="right", style="red") 84 table.add_column("count", justify="right", style="dim") 85 86 for endpoint in ['search', 'similar', 'tags', 'popular']: 87 if endpoint in timing: 88 t = timing[endpoint] 89 table.add_row( 90 endpoint, 91 f"{t['p50_ms']:.0f}ms", 92 f"{t['p95_ms']:.0f}ms", 93 f"{t['p99_ms']:.0f}ms", 94 f"{t['count']:,}", 95 ) 96 97 console.print(Panel(table, title="[bold]latency[/]", border_style="magenta", expand=False)) 98 99 100def display_timeline(dashboard: dict, days: int) -> None: 101 """show indexing activity chart""" 102 timeline = dashboard.get('timeline', [])[:days] 103 if not timeline: 104 return 105 106 timeline = list(reversed(timeline)) # oldest first 107 dates = [d['date'][-5:] for d in timeline] # MM-DD 108 counts = [d['count'] for d in timeline] 109 110 plt.clear_figure() 111 plt.theme("dark") 112 plt.title("documents indexed per day") 113 plt.bar(dates, counts, color="cyan") 114 plt.plotsize(70, 12) 115 plt.show() 116 print() 117 118 119def display_latency_chart(stats: dict) -> None: 120 """bar chart of p50 latencies by endpoint""" 121 timing = stats.get('timing', {}) 122 if not timing: 123 return 124 125 endpoints = [] 126 p50s = [] 127 for endpoint in ['search', 'similar', 'tags', 'popular']: 128 if endpoint in timing: 129 endpoints.append(endpoint) 130 p50s.append(timing[endpoint]['p50_ms']) 131 132 plt.clear_figure() 133 plt.theme("dark") 134 plt.title("p50 latency by endpoint (ms)") 135 plt.bar(endpoints, p50s, color="cyan") 136 plt.plotsize(50, 10) 137 plt.show() 138 print() 139 140 141@app.command() 142def main( 143 days: int = typer.Option(7, "-d", "--days", help="days of timeline to show"), 144) -> None: 145 """pub-search terminal dashboard""" 146 console.print("\n[bold cyan]pub-search[/] dashboard\n") 147 148 try: 149 stats = fetch_stats() 150 dashboard = fetch_dashboard() 151 except httpx.HTTPError as e: 152 console.print(f"[red]error fetching data:[/] {e}") 153 raise typer.Exit(1) 154 155 display_overview(stats) 156 display_usage(stats) 157 display_latency(stats) 158 print() 159 display_timeline(dashboard, days) 160 display_latency_chart(stats) 161 162 163if __name__ == "__main__": 164 app()