"""1000 SSE readers on one hub: delivery latency per event to all readers,
the server's CPU and thread count. Numbers printed, nothing assumed."""
import json, os, resource, selectors, socket, sqlite3, sys, time
N = int(sys.argv[1]); PID = int(sys.argv[2]); DB = sys.argv[3]
resource.setrlimit(resource.RLIMIT_NOFILE, (8192, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))
def cpu(pid):
    f = open(f"/proc/{pid}/stat").read().rsplit(")", 1)[1].split()
    return (int(f[11]) + int(f[12])) / os.sysconf("SC_CLK_TCK")
def threads(pid): return len(os.listdir(f"/proc/{pid}/task"))
t0 = time.monotonic(); socks = []
for i in range(N):
    s = socket.create_connection(("127.0.0.1", 8199))
    s.sendall(b"GET /api/events/stream HTTP/1.1\r\nHost: t\r\n\r\n")
    socks.append(s)
sel = selectors.DefaultSelector(); buf = {}
for s in socks:
    s.setblocking(False); sel.register(s, selectors.EVENT_READ); buf[s] = b""
def pump(until_pred, timeout):
    end = time.monotonic() + timeout
    while time.monotonic() < end:
        for k, _ in sel.select(0.2):
            try: d = k.fileobj.recv(65536)
            except BlockingIOError: continue
            buf[k.fileobj] += d
        if until_pred(): return True
    return False
ok = pump(lambda: all(b"retry:" in b for b in buf.values()), 60)
opened = time.monotonic() - t0
print(json.dumps({"readers": N, "all_opened": ok, "open_s": round(opened, 2),
                  "server_threads_with_all_open": threads(PID)}))
conn = sqlite3.connect(DB, isolation_level=None)
lat = []; c0 = cpu(PID); w0 = time.monotonic()
for k in range(10):
    for s in socks: buf[s] = b""
    n = conn.execute("SELECT COALESCE(MAX(seq),0)+1 FROM events").fetchone()[0]
    conn.execute("INSERT INTO events (event_id, turn, phase, seq, round, type, actor_player, x, y, data_json, visibility) VALUES (?,2,'t',?,1,'moved','p_one',3,3,?,'public')",
                 ("lt%05d" % n, n, json.dumps({"from": [2, 3], "to": [3, 3]})))
    ti = time.monotonic()
    got = pump(lambda: all(b"data:" in b for b in buf.values()), 10)
    lat.append((round(time.monotonic() - ti, 3), got, sum(b"data:" in b for b in buf.values())))
    time.sleep(0.5)
c1 = cpu(PID); w1 = time.monotonic()
print(json.dumps({"per_event_all_readers_s": [l[0] for l in lat], "all_received": [l[1] for l in lat],
                  "readers_received_last": lat[-1][2],
                  "server_cpu_s": round(c1 - c0, 3), "wall_s": round(w1 - w0, 2),
                  "server_cpu_pct_of_one_core": round(100 * (c1 - c0) / (w1 - w0), 2),
                  "server_threads_end": threads(PID)}))
# quiet: CPU with 1000 idle readers for 20 s
c0 = cpu(PID); time.sleep(20); c1 = cpu(PID)
print(json.dumps({"idle_20s_cpu_s": round(c1 - c0, 3), "idle_cpu_pct": round(100 * (c1 - c0) / 20, 2)}))
