"""Follow chats through the tiny server, step by step, then read its metrics. Run it from this folder: python3 trace.py

It uses the lesson's own functions and adds no rules of its own: serve() runs the server and report() reads the clipboard.
"""
from tiny_server import HEAVY, LIGHT, LIMIT, PAGES, WORKLOAD, bottleneck, intended_times, load_test, pages_for, serve

MORE_PAGES = 480  # the cache of the second heavy run

arrive = intended_times(WORKLOAD["gaps"], HEAVY)
run = serve(WORKLOAD, HEAVY)
print(f"heavy load: {HEAVY} chats a second, a cache of {PAGES} pages, at most {LIMIT} chats per step")
print("\nthe first chats arrive")
for i in range(4):
    prompt, reply = WORKLOAD["prompts"][i], WORKLOAD["replies"][i]
    print(f"  chat {i}: sent at {arrive[i]:.3f} s, prompt {prompt} tokens, reply {reply} tokens, needs {pages_for(prompt + reply)} pages")


def show(k):
    s = run.steps[k]
    wait = f"  next chat waits for: {s.blocked}" if s.blocked else ""
    print(f"  {k + 1:>4}  {s.start:.3f} s  {s.seconds * 1000:>5.1f} ms  {s.stepped:>5}  {s.joined:>3}  {s.queue:>5}  {s.held:>3} + {s.owed:<3}  {PAGES - s.held - s.owed:>3}{wait}")


header = "  step  start     took     chats  new  queue  pages held + owed  left"
print(f"\nthe first steps\n{header}")
for k in range(5):
    show(k)
full = next(k for k, s in enumerate(run.steps) if "cache" in s.blocked)
print(f"\nthe cache fills at step {full + 1}\n{header}")
for k in range(full - 1, full + 3):
    show(k)

print(f"\nthe clipboard: the same {len(WORKLOAD['gaps'])} chats, three runs")
print("  run                  rate  first word p50 / p99   between words p50 / p99   queue  pages  wall")
for name, rate, pages in (("light", LIGHT, PAGES), ("heavy", HEAVY, PAGES), (f"heavy, {MORE_PAGES} pages", HEAVY, MORE_PAGES)):
    m = load_test(rate, pages)
    print(f"  {name:<19} {rate:>4}  {m['ttft_p50']:>8.3f} s {m['ttft_p99']:>7.3f} s  "
          f"{m['tbt_p50'] * 1000:>10.1f} ms {m['tbt_p99'] * 1000:>6.1f} ms  {m['queue_max']:>5}  {m['pages_max']:>5}  {bottleneck(m)}")
