import csv
import json
import statistics
import time
import urllib.request
import random

URL = "http://127.0.0.1:11434/api/generate"
MODEL = "qwen3:4b-instruct-2507-q4_K_M"
REPEATS = 5

OUTPUT_TASK = (
    "请从 1 开始输出连续递增的整数，只能用空格分隔，不要解释，也不要主动停止。"
)

WORDS = [
    "request",
    "token",
    "model",
    "cache",
    "memory",
    "scheduler",
    "prefill",
    "decode",
    "storage",
    "latency",
    "throughput",
    "attention",
]


def generate(prompt, num_predict):
    payload = {
        "model": MODEL,
        "prompt": prompt,
        "stream": False,
        "keep_alive": "10m",
        "options": {
            "temperature": 0,
            "seed": 42,
            "num_predict": num_predict,
            "num_ctx": 4096,
        },
    }

    request = urllib.request.Request(
        URL,
        data=json.dumps(payload).encode("utf-8"),
        headers={"Content-Type": "application/json"},
    )

    start = time.perf_counter()
    with urllib.request.urlopen(request, timeout=300) as response:
        result = json.load(response)
    client_ms = (time.perf_counter() - start) * 1000

    return result, client_ms


def measure(experiment, target, run, prompt, num_predict):
    result, client_ms = generate(prompt, num_predict)

    prompt_tokens = result["prompt_eval_count"]
    output_tokens = result["eval_count"]
    prefill_ms = result["prompt_eval_duration"] / 1e6
    decode_ms = result["eval_duration"] / 1e6

    return {
        "experiment": experiment,
        "target": target,
        "run": run,
        "prompt_tokens": prompt_tokens,
        "output_tokens": output_tokens,
        "load_ms": round(result["load_duration"] / 1e6, 3),
        "prefill_ms": round(prefill_ms, 3),
        "decode_ms": round(decode_ms, 3),
        "approx_tpot_ms": round(decode_ms / output_tokens, 3),
        "decode_tokens_per_s": round(output_tokens / (decode_ms / 1000), 2),
        "total_ms": round(result["total_duration"] / 1e6, 3),
        "client_ms": round(client_ms, 3),
        "done_reason": result["done_reason"],
    }


print("Warming up model...")
generate("热身请求。请只回答：好。", 8)

rows = []

# 实验 A：只改变输入长度，输出上限固定为 32 token。
for input_words in (64, 512, 2048):
    for run in range(1, REPEATS + 1):
        nonce = time.time_ns()
        rng = random.Random(nonce)

        body = " ".join(rng.choice(WORDS) for _ in range(input_words))

        prompt = f"experiment-{nonce} {body}\n{OUTPUT_TASK}"

        row = measure(
            experiment="input_length_v2",
            target=input_words,
            run=run,
            prompt=prompt,
            num_predict=32,
        )
        rows.append(row)
        print(row)

# 实验 B：只改变输出上限，输入长度保持不变。
# for output_cap in (32, 128, 512):
#     for run in range(1, REPEATS + 1):
#         nonce = f"实验编号 {time.time_ns()}。"
#         prompt = nonce + ("系统 " * 64) + OUTPUT_TASK
#
#         row = measure(
#             experiment="output_length",
#             target=output_cap,
#             run=run,
#             prompt=prompt,
#             num_predict=output_cap,
#         )
#         rows.append(row)
#         print(row)

with open("input-length-results-v2.csv", "w", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=rows[0].keys())
    writer.writeheader()
    writer.writerows(rows)

print("\nMedian results:")
for experiment in ("input_length", "output_length"):
    targets = sorted({row["target"] for row in rows if row["experiment"] == experiment})

    for target in targets:
        group = [
            row
            for row in rows
            if row["experiment"] == experiment and row["target"] == target
        ]

        print(
            experiment,
            f"target={target}",
            f"prompt_tokens={statistics.median(r['prompt_tokens'] for r in group):.0f}",
            f"output_tokens={statistics.median(r['output_tokens'] for r in group):.0f}",
            f"prefill_ms={statistics.median(r['prefill_ms'] for r in group):.3f}",
            f"decode_ms={statistics.median(r['decode_ms'] for r in group):.3f}",
            f"tpot_ms={statistics.median(r['approx_tpot_ms'] for r in group):.3f}",
            f"total_ms={statistics.median(r['total_ms'] for r in group):.3f}",
        )

print("\nRaw results written to length-results.csv")
