"""Print every number the lesson quotes. Run: python3 show_numbers.py"""
import statistics

import capacity_plan as cp


def main() -> None:
    rate = cp.peak_rate(cp.USERS, cp.ACTIVE, cp.REPLIES)
    print(f"crowd: {cp.USERS:,} users, {cp.ACTIVE}% active in the busiest hour = {cp.USERS * cp.ACTIVE // 100}, {cp.REPLIES} replies each = "
          f"{cp.USERS * cp.ACTIVE * cp.REPLIES // 100:,} replies an hour = {rate:.2f} a second")
    print(f"targets: p99 time between words <= {cp.TARGET_MS} ms, p99 wait before the first word <= {cp.WAIT_TARGET} s; headroom {cp.HEADROOM:.0%}")
    print()
    print("the table for usual chats (server run with a pool that never runs out): chats in progress | replies/s | tokens/s | p99 ms | mean time per reply s")
    rows = cp.step_table("usual")
    for chats, x, tokens, p99 in rows:
        print(f"  {chats:6.1f} | {x:6.3f} | {tokens:6,.0f} | {p99:6.1f} | {chats / x:6.2f}")
    print()
    for mix, m in cp.MIXES.items():
        chats = cp.chats_of(mix)
        mean_p, mean_r = statistics.fmean(chats["prompts"]), statistics.fmean(chats["replies"])
        print(f"{mix}: {m['n']} chats, prompts {m['prompts'][0]:,} to {m['prompts'][1]:,} (mean {mean_p:,.0f}), replies {m['replies'][0]:,} to {m['replies'][1]:,} (mean {mean_r:,.0f}), "
              f"longest {m['longest']:,}; memory holds {cp.memory_limit(mix)} chats of {round(mean_p + mean_r):,} tokens")
        print(f"  the crowd: {m['each']} replies each")
        for target in cp.TARGETS:
            p = cp.plan(cp.USERS, cp.ACTIVE, m["each"], mix, target)
            t = cp.step_table(mix)
            print(f"  p99 <= {target:2} ms: speed limit {p['speed']:6.1f} chats ({cp.x_at(t, p['speed']):5.2f} replies/s), memory limit {p['memory']:3} chats ({cp.x_at(t, p['memory']):5.2f} replies/s); "
                  f"binds: {p['binding']:6}; W {p['stay']:6.3f} s; chats in progress {p['chats']:7.1f}; GPUs: speed {p['n_speed']:3}, memory {p['n_memory']:3}, arithmetic only {p['without']:3}, "
                  f"with headroom {p['n']:3}")
    print()
    print("the opening line:", cp.describe(cp.USERS, cp.ACTIVE, cp.REPLIES, cp.OPENING_MIX, cp.TARGET_MS)["line"])
    print()
    for mix, m in cp.MIXES.items():
        q = cp.plan(cp.USERS, cp.ACTIVE, m["each"], mix, cp.TARGET_MS)
        seconds = round(q["stay"], 1) if q["stay"] < 10 else round(q["stay"])                      # the page writes W to one decimal, whole above 10 s, and the speed limit to one
        chats, speed = q["rate"] * seconds, round(q["speed"], 1)
        print(f"the plain layer, {mix} ({m['each']} replies each), as the page rounds it: {q['rate']:.2f} replies a second x {seconds} s = {chats:.0f} chats in progress (unrounded {q['chats']:.1f}); "
              f"speed {chats:.0f} / ({cp.HEADROOM} x {speed}) = {chats / (cp.HEADROOM * speed):.2f} -> {q['n_speed']}; "
              f"memory {chats:.0f} / ({cp.HEADROOM} x {q['memory']}) = {chats / (cp.HEADROOM * q['memory']):.2f} -> {q['n_memory']}; "
              f"no headroom {chats:.0f} / {min(speed, q['memory'])} = {chats / min(speed, q['memory']):.2f} -> {q['without']}")
    print()
    print("the answers mix at other crowds (replies each | active users mid-reply | replies/s | chats in progress | speed | memory | no headroom | plan):")
    for each in (10, 20, 30, 40):
        q = cp.plan(cp.USERS, cp.ACTIVE, each, "answers", cp.TARGET_MS)
        print(f"  {each:3} | {q['chats'] / (cp.USERS * cp.ACTIVE // 100):4.0%} | {q['rate']:5.2f} | {q['chats']:7.1f} | {q['n_speed']:3} | {q['n_memory']:3} | {q['without']:3} | {q['n']:3}")
    print()
    load = cp.opening_load_test()
    p, cap, requests, busy = load["plan"], load["cap"], load["requests"], load["busiest"]
    print(f"load test: {len(requests):,} random arrivals at {rate:.1f} a second over {cp.LOAD_SECONDS // 60} minutes, cap {cap} chats a GPU; busiest minute {busy['busiest']:,} against "
          f"an average minute of {busy['average']:,.0f}: {busy['ratio'] - 1:+.1%}")
    for count, r in load["runs"].items():
        print(f"  {cp.gpu_word(count)}: p99 between words {r['gap']:5.1f} ms, p99 wait {r['wait']:5.2f} s -> {'passes' if r['passes'] else 'MISSES'}; chats in progress {r['in_progress']:5.1f} "
              f"= {r['rate']:.2f} a second x {r['mean_time']:.2f} s")
    print(f"  the plan said {p['chats']:.1f} chats in progress ({p['rate']:.2f} a second x {p['stay']:.2f} s, each GPU as full as the speed limit {p['speed']:.1f} allows)")
    print(f"  planned {p['n']} (no spare): one down leaves {p['n'] - 1}; planned {p['n'] + 1} (with a spare): one down leaves {p['n']}")
    print()
    for mix in ("documents", "answers"):
        m = cp.plan(cp.USERS, cp.ACTIVE, cp.MIXES[mix]["each"], mix, cp.TARGET_MS)
        print(f"{mix}: {m['n']} GPUs ({m['binding']} binds), {m['chats']:,.0f} chats in progress against {cp.USERS * cp.ACTIVE // 100} active users")
    a = cp.plan(cp.USERS, cp.ACTIVE, cp.MIXES["answers"]["each"], "answers", cp.TARGET_MS)
    t = cp.load_test(a["n"], cp.arrivals(a["rate"], cp.LOAD_SECONDS, "answers"), "answers", cp.cap_for("answers"))
    print(f"answers load test on {cp.gpu_word(a['n'])}: p99 between words {t['gap']:.1f} ms, p99 wait {t['wait']:.2f} s -> {'passes' if t['passes'] else 'MISSES'}; "
          f"chats in progress {t['in_progress']:.1f} = {t['rate']:.2f} a second x {t['mean_time']:.1f} s, against the plan's {a['chats']:.1f} and W {a['stay']:.1f} s")
    print()
    for model in ("8b", "70b", "405b"):
        print(f"model {model}: {cp.gpu_word(cp.L26.least_n(model))} for one copy; {p['n']} copies = {cp.gpu_word(cp.gpus_to_hold(model, p['n']))}")


if __name__ == "__main__":
    main()
