#!/usr/bin/env python3
"""Parse H200 SGLang baseline benchmark JSONL outputs.

Reads the single-line summary JSON produced by
`sglang.bench_serving --output-file --output-details` and generates:
    - results.json  (appended scenarios)
    - report.md     (human-readable summary)

Usage:
    python3 parse_results.py <result_root>
"""

import json
import sys
from pathlib import Path


def parse_jsonl(path: Path) -> dict | None:
    """Read the first (and usually only) JSON object from the JSONL file."""
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                return json.loads(line)
            except json.JSONDecodeError:
                continue
    return None


def compute_metrics(data: dict) -> dict:
    completed = data.get("completed", 0)
    total = len(data.get("input_lens", []))
    failed = total - completed if total > 0 else 0
    duration_s = data.get("duration", 0.0)

    return {
        "success": completed,
        "failed": failed,
        "duration_s": duration_s,
        "request_throughput": data.get("request_throughput", 0.0),
        "input_token_throughput": data.get("input_throughput", 0.0),
        "output_token_throughput": data.get("output_throughput", 0.0),
        "total_token_throughput": data.get("total_throughput", 0.0),
        "total_input_tokens": data.get("total_input_tokens", 0),
        "total_output_tokens": data.get("total_output_tokens", 0),
        "e2e_ms": {
            "mean": data.get("mean_e2e_latency_ms", 0.0),
            "p50": data.get("median_e2e_latency_ms", 0.0),
            "p90": data.get("p90_e2e_latency_ms", 0.0),
            "p95": data.get("p95_e2e_latency_ms", 0.0),
            "p99": data.get("p99_e2e_latency_ms", 0.0),
        },
        "ttft_ms": {
            "mean": data.get("mean_ttft_ms", 0.0),
            "p50": data.get("median_ttft_ms", 0.0),
            "p90": data.get("p90_ttft_ms", 0.0),
            "p95": data.get("p95_ttft_ms", 0.0),
            "p99": data.get("p99_ttft_ms", 0.0),
        },
        "tpot_ms": {
            "mean": data.get("mean_tpot_ms", 0.0),
            "p50": data.get("median_tpot_ms", 0.0),
            "p90": data.get("p90_tpot_ms", 0.0),
            "p95": data.get("p95_tpot_ms", 0.0),
            "p99": data.get("p99_tpot_ms", 0.0),
        },
        "itl_ms": {
            "mean": data.get("mean_itl_ms", 0.0),
            "p50": data.get("median_itl_ms", 0.0),
            "p90": data.get("p90_itl_ms", 0.0),
            "p95": data.get("p95_itl_ms", 0.0),
            "p99": data.get("p99_itl_ms", 0.0),
        },
    }


def scenario_name(concurrency: int, input_len: int, output_len: int) -> str:
    return f"c{concurrency}_i{input_len}_o{output_len}"


def append_scenario(results_json: Path, scenario: dict) -> None:
    with open(results_json, "r", encoding="utf-8") as f:
        data = json.load(f)
    data["scenarios"].append(scenario)
    with open(results_json, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


def generate_report(result_root: Path, scenarios: list[dict]) -> None:
    report_path = result_root / "report.md"
    with open(report_path, "w", encoding="utf-8") as f:
        f.write("# H200 SGLang Baseline Benchmark Report\n\n")
        f.write(f"- Result root: `{result_root}`\n")
        f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n")
        f.write("- Backend: SGLang (TP=8, moe-runner-backend=marlin, no speculative decoding)\n")
        f.write("- Benchmark client: `sglang.bench_serving --backend sglang`\n\n")

        f.write("## Results\n\n")
        f.write("| Scenario | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) |\n")
        f.write("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")

        for s in scenarios:
            cfg = s["config"]
            m = s["metrics"]
            f.write(
                f"| {s['name']} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
                f"{m['duration_s']:.2f} | {m['success']} | {m['request_throughput']:.2f} | "
                f"{m['input_token_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
                f"{m['total_token_throughput']:.2f} | "
                f"{m['ttft_ms']['mean']:.2f} | {m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
                f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
                f"{m['e2e_ms']['mean']:.2f} | {m['e2e_ms']['p95']:.2f} | {m['e2e_ms']['p99']:.2f} |\n"
            )
        f.write("\n")


def main() -> None:
    result_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("results")
    raw_dir = result_root / "raw_outputs"
    results_json = result_root / "results.json"

    if not raw_dir.exists():
        raise SystemExit(f"raw_outputs directory not found: {raw_dir}")

    scenarios = []
    for jsonl_path in sorted(raw_dir.glob("sglang_*.jsonl")):
        # Filename: sglang_MMDD_concurrency_inputlen_outputlen.jsonl
        parts = jsonl_path.stem.split("_")
        if len(parts) < 5:
            continue
        concurrency, input_len, output_len = int(parts[2]), int(parts[3]), int(parts[4])

        data = parse_jsonl(jsonl_path)
        if data is None:
            continue

        metrics = compute_metrics(data)
        scenario = {
            "name": scenario_name(concurrency, input_len, output_len),
            "config": {
                "concurrency": concurrency,
                "input_len": input_len,
                "output_len": output_len,
                "dataset": "random",
                "num_prompts": metrics["success"] + metrics["failed"],
            },
            "metrics": metrics,
            "raw_file": str(jsonl_path),
        }
        scenarios.append(scenario)

    if not scenarios:
        print("No benchmark outputs found to parse")
        return

    if results_json.exists():
        for s in scenarios:
            append_scenario(results_json, s)

    generate_report(result_root, scenarios)
    print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md")


if __name__ == "__main__":
    main()
