"""Generate benchmark report from evaluation metrics. This script runs policy evaluation on nightly runs and generates a static HTML dashboard for tracking policy performance over time. """ from __future__ import annotations import json import shutil from datetime import datetime from pathlib import Path import tyro import wandb import mjlab from mjlab.tasks.tracking.scripts.evaluate import EvaluateConfig, run_evaluate # Metrics to display: (key, label, unit, scale, higher_is_better) METRICS = [ ("success_rate", "Success Rate", "%", 100, True), ("mpkpe", "MPKPE", "m", 1, False), ("r_mpkpe", "R-MPKPE", "m", 1, False), ("ee_pos_error", "EE Position Error", "m", 1, False), ("ee_ori_error", "EE Orientation Error", "rad", 1, False), ("joint_vel_error", "Joint Velocity Error", "rad/s", 1, False), ] def evaluate_run(run_path: str, num_envs: int = 1024) -> dict: """Evaluate a single run and return metrics with metadata.""" api = wandb.Api() run = api.run(run_path) print(f"Evaluating run: {run.name} ({run.id})") cfg = EvaluateConfig(wandb_run_path=run_path, num_envs=num_envs) metrics = run_evaluate("Mjlab-Tracking-Flat-Unitree-G1", cfg) # Get commit SHA from run metadata. commit = run.commit or run.config.get("commit", "unknown") return { "id": run.id, "name": run.name, "url": run.url, "created_at": run.created_at, "commit": commit[:7] if len(commit) > 7 else commit, "metrics": metrics, } def load_throughput_data(output_dir: Path) -> list[dict]: """Load throughput benchmark data if available.""" data_file = output_dir / "throughput_data.json" if not data_file.exists(): return [] with open(data_file) as f: return json.load(f) def generate_html_report(runs: list[dict], output_dir: Path) -> None: """Generate static HTML dashboard from evaluation data.""" output_dir.mkdir(parents=True, exist_ok=True) # Save raw data. with open(output_dir / "data.json", "w") as f: json.dump(runs, f, indent=2, default=str) # Copy task images for the throughput dashboard. images_src = Path(__file__).parent / "nightly_images" if images_src.is_dir(): images_dst = output_dir / "images" if images_dst.exists(): shutil.rmtree(images_dst) shutil.copytree(images_src, images_dst) # Load throughput data if available. throughput_data = load_throughput_data(output_dir) html = generate_dashboard_html(runs, throughput_data) with open(output_dir / "index.html", "w") as f: f.write(html) print(f"Report generated at {output_dir / 'index.html'}") def generate_dashboard_html(runs: list[dict], throughput_data: list[dict]) -> str: """Generate the HTML dashboard content.""" runs_json = json.dumps(runs, default=str) metrics_json = json.dumps(METRICS) throughput_json = json.dumps(throughput_data, default=str) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") github_repo = "https://github.com/mujocolab/mjlab" return f"""
Nightly motion imitation training and evaluation on Unitree G1 (1024 trials per run).
Physics simulation throughput across tasks (4096 parallel envs, NVIDIA RTX 5090).