#!/usr/bin/env bash
# Real demos for P019 "Why Python Is Slow (and When It Doesn't Matter)". Runs on render01 (Docker).
# One lab "laptop" (python:3.13-slim + gcc, NumPy and PyPy; hostname laptop, user ana, no network after
# the build). Every command is typed into one real interactive bash (a pty). Timings are best of 3 runs
# inside each script; demo_output.json keeps every line, and build_script.py reads the numbers from it.
set -euo pipefail
cd "$(dirname "$0")"
P=ce-p019
W=/tmp/$P
cleanup() { docker rm -f $P-laptop >/dev/null 2>&1 || true; }
cleanup
trap cleanup EXIT
rm -rf $W && mkdir -p $W/img
cat > $W/img/Dockerfile <<'DOCKER'
FROM python:3.13-slim
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends gcc libc6-dev pypy3 >/dev/null \
 && rm -rf /var/lib/apt/lists/* && pip install -q --no-cache-dir --root-user-action=ignore numpy==2.3.4 \
 && useradd -m -s /bin/bash ana
DOCKER
docker build -q -t $P-lab $W/img >/dev/null
docker run -d --name $P-laptop --hostname laptop --network none --cpus 4 $P-lab sleep infinity >/dev/null
docker exec $P-laptop mkdir -p /home/ana/shop
docker cp lab/. $P-laptop:/home/ana/shop/
docker exec $P-laptop chown -R ana:ana /home/ana

python3 - "$P" <<'PY'
import fcntl, json, os, pty, re, select, struct, subprocess, sys, termios, time
P = sys.argv[1]
READY = re.compile(rb"__READY__:(.*?):__ ")
steps = {}
class Shell:
    def __init__(self):
        pid, fd = pty.fork()
        if pid == 0:
            fcntl.ioctl(0, termios.TIOCSWINSZ, struct.pack("HHHH", 50, 100, 0, 0))
            os.execvp("docker", ["docker", "exec", "-it", "-u", "ana", "-w", "/home/ana/shop", "-e", "HOME=/home/ana",
                                 "-e", "TERM=dumb", f"{P}-laptop", "bash", "-i"])
        self.fd, self.where = fd, "~/shop"
        os.write(fd, b"stty -echo; stty cols 100 rows 50; PS1='__REA''DY__:\\w:__ '\n")
        self.read()
    def read(self, timeout=600):
        buf, end = b"", time.time() + timeout
        while time.time() < end:
            if select.select([self.fd], [], [], 0.3)[0]:
                buf += os.read(self.fd, 65536)
                m = None
                for m in READY.finditer(buf):
                    pass
                if m:
                    self.where = m.group(1).decode()
                    out = buf[:m.start()].replace(b"\r\r\n", b"\n").replace(b"\r\n", b"\n").decode()
                    return "\n".join(l.rstrip() for l in out.split("\n")).strip("\n")
        raise SystemExit(f"timed out; last output: {buf[-600:]!r}")
    def run(self, key, cmd):
        shown = f"ana@laptop:{self.where}$ {cmd}"
        os.write(self.fd, cmd.encode() + b"\n")
        out = self.read()
        steps[key] = {"cmd": shown, "out": out}
        print(f"--- {key}\n{shown}\n{out}\n", flush=True)
s = Shell()
s.run("loop", "python3 loop.py")
s.run("builtin", "python3 builtin.py")
s.run("numpy", "python3 numpy_sum.py")
s.run("cc", "gcc -O2 -o loop loop.c")
s.run("c", "./loop")
s.run("sizes", "python3 sizes.py")
s.run("bytecode", "python3 bytecode.py")
s.run("pypy", "pypy3 loop.py")
s.run("threads", "python3 threads.py")
s.run("web", "python3 web_request.py")
ver = subprocess.run(["docker", "exec", f"{P}-laptop", "sh", "-c",
    "python3 --version; pypy3 --version | tail -1; gcc --version | head -1; python3 -c 'import numpy; print(\"numpy\", numpy.__version__)'; nproc; lscpu | grep 'Model name'"],
    capture_output=True, text=True).stdout.strip()
steps["_meta"] = {"cmd": "", "out": json.dumps({"versions": ver.splitlines(), "run_at": time.strftime("%Y-%m-%d %H:%M %Z")})}
json.dump(steps, open("demo_output.json", "w"), indent=1)
print("saved demo_output.json")
PY
