#!/usr/bin/env bash
# Real demos for P068 "Why Golang Uses Goroutines Instead of Threads". Runs in Docker (Docker Desktop on the Mac:
# a Linux VM with 10 cores and 8 GB). One lab "laptop": the official golang:1.27 image (it ships gcc too), hostname
# laptop, user ana, no network. lab/c/ holds the same jobs written with plain OS threads in C (POSIX threads), lab/
# the Go versions. Everything is built off screen, so the commands on camera print only the programs' own output.
# Every command is typed into one real interactive bash (a pty); demo_output.json keeps every line, and
# build_script.py reads every number from it.
set -euo pipefail
cd "$(dirname "$0")"
P=ce-p068
cleanup() { docker rm -f $P-laptop >/dev/null 2>&1 || true; }
cleanup
trap cleanup EXIT
docker run -d --name $P-laptop --hostname laptop --network none golang:1.27 sleep infinity >/dev/null
docker exec $P-laptop useradd -m -s /bin/bash ana
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
# off screen: build every program once
docker exec -u ana -w /home/ana/shop -e HOME=/home/ana $P-laptop sh -c '
  go build ./... &&
  gcc -O2 -pthread -o c/threads c/threads.c &&
  gcc -O2 -pthread -o c/switch c/switch.c &&
  gcc -O0 -o c/grow c/grow.c'

python3 - "$P" <<'PY'
import fcntl, json, os, pty, re, select, struct, 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("machine", "nproc && grep MemTotal /proc/meminfo")
s.run("t10k", "c/threads 10000")
s.run("tmax", "cat /proc/sys/kernel/threads-max")
s.run("t100k", "c/threads 100000")
s.run("g10k", "go run ./goroutines 10000")
s.run("g100k", "go run ./goroutines 100000")
s.run("g1m", "go run ./goroutines 1000000")
s.run("ulimit", "ulimit -s")
s.run("grow_c", "c/grow")
s.run("grow_go", "go run ./grow")
s.run("switch_c", "c/switch")
s.run("switch_go", "go run ./switch")
s.run("blocking", "go run ./blocking")
s.run("version", "go version && gcc --version | head -1")
steps["_meta"] = {"cmd": "", "out": json.dumps({"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
