Files
onion-mc/analysis/client-perf/harness/rcon.py
T
Onion Agent e5e369a693 chore(bench): add client bench harness and Phase 2 handoff
The client now boots cleanly on the bench machine (the CAC spawn-key
crash was fixed in 8e8483d). Add the harness used to drive bench scenes
and capture frametimes, the bench-world generation workflow, and a
self-contained handoff brief. All files live under analysis/ (packwizignored),
so nothing player-facing changes.

- harness/rcon.py: minimal RCON client for the temp worldgen server
- harness/run_scene.py: apply variant -> launch Prism under MangoHud ->
  xdotool scene commands -> p50/p95/p99/1%-low from the CSV
- HANDOFF.md: state, gotchas, next steps
- build.py: disable DH autoUpdater for bench variants (suppress modal)
2026-10-14 23:16:31 -04:00

77 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Minimal Minecraft RCON client (stdlib only).
Usage: python3 rcon.py <host> <port> <password> <command> [command ...]
Exit non-zero if the server reports a failure.
"""
import socket
import struct
import sys
class Rcon:
def __init__(self, host: str, port: int, password: str, timeout: float = 10.0):
self.host, self.port, self.password = host, port, password
self.timeout = timeout
self._id = 0
def _send(self, sock: socket.socket, ptype: int, body: str) -> int:
self._id += 1
rid = self._id
payload = struct.pack("<ii", rid, ptype) + body.encode("utf-8") + b"\x00\x00"
sock.sendall(struct.pack("<i", len(payload)) + payload)
return rid
def _recv(self, sock: socket.socket):
raw = b""
while len(raw) < 4:
chunk = sock.recv(4 - len(raw))
if not chunk:
raise ConnectionError("RCON connection closed")
raw += chunk
(length,) = struct.unpack("<i", raw)
body = b""
while len(body) < length:
chunk = sock.recv(length - len(body))
if not chunk:
raise ConnectionError("RCON connection closed")
body += chunk
rid, ptype = struct.unpack("<ii", body[:8])
return rid, ptype, body[8:-2].decode("utf-8", "replace")
def command(self, cmd: str) -> str:
with socket.create_connection((self.host, self.port), timeout=self.timeout) as sock:
sock.settimeout(self.timeout)
rid = self._send(sock, 3, self.password)
rid2, _, _ = self._recv(sock)
if rid2 == -1:
raise PermissionError("RCON auth failed")
self._send(sock, 2, cmd)
_, _, text = self._recv(sock)
return text
def main(argv):
if len(argv) < 5:
print(__doc__)
return 2
host, port, password = argv[1], int(argv[2]), argv[3]
rc = 0
for cmd in argv[4:]:
try:
out = Rcon(host, port, password).command(cmd)
except Exception as exc: # noqa: BLE001 - CLI surface
print(f"! {cmd}: {exc}", file=sys.stderr)
rc = 1
continue
if out.strip():
print(out.strip())
lowered = out.lower()
if "unknown" in lowered or "failed" in lowered or "error" in lowered:
rc = 1
return rc
if __name__ == "__main__":
raise SystemExit(main(sys.argv))