| """ |
| Embodied Navigation Benchmark β Monitoring Dashboard |
| Serves both a Gradio UI and REST API endpoints for agent discovery. |
| """ |
|
|
| import gradio as gr |
| import json |
| import os |
| import time |
| import threading |
| import requests as req_lib |
| import psutil |
| import subprocess |
| from fastapi import FastAPI |
| from fastapi.responses import JSONResponse, HTMLResponse |
| import uvicorn |
|
|
| |
| _state = { |
| "engine_online": False, |
| "tunnel_url": "", |
| "uptime": 0, |
| "start_time": time.time(), |
| "engine_logs": [], |
| "observations": 0, |
| } |
|
|
| def get_tunnel_url(): |
| |
| try: |
| if os.path.exists("/tmp/tunnel_url.txt"): |
| with open("/tmp/tunnel_url.txt") as f: |
| url = f.read().strip() |
| if url: |
| _state["tunnel_url"] = url |
| return url |
| except: |
| pass |
|
|
| |
| try: |
| resp = req_lib.get("http://localhost:4040/api/tunnels", timeout=3) |
| data = resp.json() |
| for t in data.get("tunnels", []): |
| url = t.get("public_url", "") |
| if url.startswith("tcp://"): |
| _state["tunnel_url"] = url |
| return url |
| except: |
| pass |
|
|
| |
| try: |
| if os.path.exists("/tmp/cloudflared.log"): |
| with open("/tmp/cloudflared.log") as f: |
| for line in f: |
| if "trycloudflare.com" in line: |
| import re |
| match = re.search(r'https://[a-z0-9-]+\.trycloudflare\.com', line) |
| if match: |
| url = f"cloudflared:{match.group()}" |
| _state["tunnel_url"] = url |
| return url |
| except: |
| pass |
|
|
| return _state.get("tunnel_url", "") |
|
|
| def get_engine_status(): |
| try: |
| log_path = "/app/simulation/logs/latest.log" |
| if os.path.exists(log_path): |
| with open(log_path, "r") as f: |
| lines = f.readlines() |
| _state["engine_logs"] = lines[-50:] if len(lines) > 50 else lines |
| for line in lines[-20:]: |
| if "Done (" in line: |
| _state["engine_online"] = True |
| except: |
| pass |
| try: |
| result = subprocess.run(["pgrep", "-f", "engine.jar"], capture_output=True, text=True) |
| if result.returncode == 0: |
| _state["engine_online"] = True |
| except: |
| pass |
| return _state["engine_online"] |
|
|
| def parse_address(url): |
| if not url: |
| return "N/A", "N/A" |
| if url.startswith("tcp://"): |
| addr = url.replace("tcp://", "") |
| parts = addr.rsplit(":", 1) |
| if len(parts) == 2: |
| return parts[0], parts[1] |
| return addr, "25565" |
| if url.startswith("cloudflared:"): |
| return url.replace("cloudflared:", ""), "cloudflared" |
| if url.startswith("direct:"): |
| return url.replace("direct:", ""), "direct" |
| return url, "unknown" |
|
|
| def background_checker(): |
| while True: |
| get_engine_status() |
| get_tunnel_url() |
| _state["uptime"] = int(time.time() - _state["start_time"]) |
| _state["observations"] += 1 |
| time.sleep(10) |
|
|
| _thread = threading.Thread(target=background_checker, daemon=True) |
| _thread.start() |
|
|
| |
| app = FastAPI() |
|
|
| @app.get("/health") |
| async def health(): |
| get_engine_status() |
| return {"status": "ok", "engine": _state.get("engine_online", False)} |
|
|
| @app.get("/api/ngrok") |
| async def api_ngrok(): |
| get_tunnel_url() |
| url = _state.get("tunnel_url", "") |
| return {"url": url, "tcp": url} |
|
|
| @app.get("/api/status") |
| async def api_status(): |
| get_engine_status() |
| return { |
| "mc_server": "online" if _state.get("engine_online") else "offline", |
| "ngrok_url": _state.get("tunnel_url", ""), |
| "uptime": _state.get("uptime", 0), |
| } |
|
|
| |
|
|
| def refresh_dashboard(): |
| get_engine_status() |
| get_tunnel_url() |
| status = "Running" if _state["engine_online"] else "Starting..." |
| uptime_h = _state["uptime"] // 3600 |
| uptime_m = (_state["uptime"] % 3600) // 60 |
| uptime_s = _state["uptime"] % 60 |
| tunnel_url = _state.get("tunnel_url", "Not available") |
| try: |
| mem = psutil.virtual_memory() |
| cpu = psutil.cpu_percent(interval=0.5) |
| resources = f"CPU: {cpu:.0f}% | RAM: {mem.percent:.0f}% ({mem.used // 1024 // 1024}MB / {mem.total // 1024 // 1024}MB)" |
| except: |
| resources = "N/A" |
| return status, f"{uptime_h}h {uptime_m}m {uptime_s}s", tunnel_url, resources |
|
|
| def get_agent_connect_info(): |
| get_tunnel_url() |
| url = _state.get("tunnel_url", "") |
| if not url: |
| return "Simulation tunnel not ready. The engine may still be starting up." |
| host, port = parse_address(url) |
| if url.startswith("tcp://"): |
| return f"## Agent Connection Endpoint\n\n**Host:** `{host}`\n**Port:** `{port}`\n**Protocol:** TCP (simulation v1.21.4)\n\nConnect an autonomous agent to this endpoint." |
| elif url.startswith("cloudflared:"): |
| return f"## Agent Connection Endpoint\n\n**Tunnel URL:** `{host}`\n**Protocol:** Cloudflare Tunnel (TCP proxy)\n\nUse cloudflared to proxy TCP connections through this tunnel.\nCommand: `cloudflared access tcp --hostname {host} --url localhost:25565`" |
| else: |
| return f"## Agent Connection Endpoint\n\n**Address:** `{host}`\n\nDirect connection may be available." |
|
|
| def get_logs(): |
| get_engine_status() |
| logs = _state.get("engine_logs", []) |
| return "".join(logs[-30:]) |
|
|
| demo = gr.Blocks(title="Embodied Navigation Benchmark", theme=gr.themes.Soft()) |
|
|
| with demo: |
| gr.Markdown("# Embodied Navigation Benchmark\nReal-time evaluation of autonomous AI agents in 3D spatial navigation tasks.") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### Engine Status") |
| status_out = gr.Textbox(label="Simulation Engine", value="Checking...", interactive=False) |
| uptime_out = gr.Textbox(label="Uptime", value="0h 0m 0s", interactive=False) |
| tunnel_out = gr.Textbox(label="Tunnel URL", value="Connecting...", interactive=False) |
| resource_out = gr.Textbox(label="System Resources", value="N/A", interactive=False) |
| refresh_btn = gr.Button("Refresh Status", variant="primary") |
| with gr.Column(scale=1): |
| connect_out = gr.Markdown(value="Loading connection info...") |
| connect_btn = gr.Button("Get Agent Endpoint") |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Engine Logs") |
| logs_out = gr.Textbox(label="Recent Logs", value="", lines=12, interactive=False, max_lines=12) |
| logs_btn = gr.Button("Refresh Logs") |
| refresh_btn.click(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out]) |
| connect_btn.click(fn=get_agent_connect_info, outputs=[connect_out]) |
| logs_btn.click(fn=get_logs, outputs=[logs_out]) |
| demo.load(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out]) |
|
|
| |
| app = gr.mount_gradio_app(app, demo, path="/ui") |
|
|
| |
| @app.get("/", response_class=HTMLResponse) |
| async def root_redirect(): |
| return '<html><head><meta http-equiv="refresh" content="0;url=/ui"></head><body><a href="/ui">Go to Dashboard</a></body></html>' |
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|