Files
James MaesandClaude Sonnet 5.5 63245b14e0
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 5s
maven / build (push) Failing after 42s
build / maven (push) Failing after 42s
feat(collaboration): integrate presence, Redis presence, virtual-field and fixture work on the attachment base
One reviewable integration on current develop 7d61f29e. It starts from
feat/record-files-standalone (attachment lifecycle, upload journal, retention
SPI; descends from develop) and adds the owner stack content from
feature/collab, feature/collab-int-20261029, feature/field-values-1196,
feature/presence-recovery-1300, test/presence-capacity-1302 and the
fix/attachment-transactions-1289 / fix/mywork-query-bounds-1291 work, plus
KOF22 adaptations: CollaborationQBitConfig.attachmentLifecycle,
CollaborationRecords.insertActivity/query(transaction), ActivityWriter source
feature routing, and the Redis/fixture tests.

Build: develop CI files are preserved. pom.xml only adds the Redis API
guardrail allow-list and an optional qbit-redis dependency; the core and
qbit-redis versions stay on the canonical SNAPSHOT lines with markers to repin
after the core and redis integration PRs are published (order CORE -> REDIS ->
COLLAB). No PRIVATE or feature-snapshot coordinate is introduced. Not yet
compiled: it needs the core integration artifact (virtual-field providers).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 17:19:49 -06:00

344 lines
21 KiB
Python

"""Bounded HTTP presence workload across independent, disposable JVMs.
This measures loopback HTTP service latency, not browser propagation or a
production capacity limit. Run test-compile and dependency:build-classpath with
test scope and output target/capacity-classpath.txt before invoking this script.
"""
import argparse
from concurrent.futures import ThreadPoolExecutor
import json
import math
import os
from pathlib import Path
import shutil
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request
import uuid
def port():
"""Choose a loopback port; child startup detects a competing bind."""
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
return listener.getsockname()[1]
def request(node, user, path, body=None, tab=0, method=None, expected=200):
"""Use real HTTP and the explicit mock identity; no tenant parameter."""
separator = "&" if "?" in path else "?"
req = urllib.request.Request(
f"http://127.0.0.1:{node}/qqq/v1/qbit/collaboration{path}{separator}user={user}",
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"Content-Type": "application/json",
"Cookie": f"sessionUUID={user}-capacity-{tab}"})
try:
response = urllib.request.urlopen(req, timeout=10)
except urllib.error.HTTPError as error:
if error.code == expected:
with error:
return json.load(error)
with error:
detail = error.read(4096).decode(errors="replace")
raise RuntimeError(f"HTTP {error.code} node={node} user={user} path={path}: {detail}") from error
with response:
if response.status != expected:
raise AssertionError(f"Unexpected status {response.status}")
return json.load(response)
def distribution(values):
"""Nearest-rank milliseconds, with no discarded slow or failed samples."""
ordered = sorted(values)
return {"samples": len(ordered), **{
name: ordered[max(0, math.ceil(len(ordered) * fraction) - 1)]
for name, fraction in (("p50_ms", .5), ("p95_ms", .95), ("p99_ms", .99), ("max_ms", 1))}}
def main():
"""Own child JVMs/container and retain results even when a check fails."""
parser = argparse.ArgumentParser()
parser.add_argument("--java", required=True)
parser.add_argument("--node-image", help="Optional Linux Java21 image; isolate Redis traffic on an owned Docker network")
parser.add_argument("--nodes", type=int, default=3)
parser.add_argument("--tenants", type=int, default=2)
parser.add_argument("--users", type=int, default=6)
parser.add_argument("--tabs", type=int, default=4)
parser.add_argument("--rounds", type=int, default=10)
parser.add_argument("--workers", type=int, default=8)
parser.add_argument("--crash-round", type=int, default=0, help="Kill the first node before this round; zero disables")
parser.add_argument("--churn-every", type=int, default=0, help="Leave/rejoin the last user's tabs every N rounds; zero disables")
parser.add_argument("--pause-redis", action="store_true", help="After the workload, freeze owned Redis and verify bounded failures/recovery")
args = parser.parse_args()
for name, lower, upper in (("nodes", 2, 8), ("tenants", 2, 8), ("users", 2, 100),
("tabs", 2, 8), ("rounds", 1, 1000), ("workers", 1, 64)):
if not lower <= getattr(args, name) <= upper:
parser.error(f"{name} must be {lower}..{upper}")
if args.crash_round and not 2 <= args.crash_round <= args.rounds:
parser.error("crash-round must be zero or 2..rounds")
if args.churn_every and not 1 <= args.churn_every <= args.rounds:
parser.error("churn-every must be zero or 1..rounds")
repo = Path(__file__).resolve().parents[2]
output = repo / "target" / ("capacity-nodes-" + uuid.uuid4().hex[:10])
output.mkdir(parents=True)
classpath = os.pathsep.join([str(repo / "target/test-classes"), str(repo / "target/classes"),
(repo / "target/capacity-classpath.txt").read_text().strip()])
processes, logs = [], []
container = "qqq-capacity-" + uuid.uuid4().hex[:12]
network = container + "-net"
node_containers = []
network_created = False
result = {"status": "FAIL", "parameters": vars(args), "scope": "loopback HTTP, mock identities, memory records"}
try:
network_args = []
if args.node_image:
result["node_image_id"] = subprocess.check_output(
["docker", "image", "inspect", args.node_image, "--format", "{{.Id}}"], text=True).strip()
subprocess.run(["docker", "network", "create", network], check=True, capture_output=True)
network_created = True
network_args = ["--network", network]
dependencies = output / "dependencies"
dependencies.mkdir()
linux_classpath = ["/fixture/target/test-classes", "/fixture/target/classes"]
for index, dependency in enumerate((repo / "target/capacity-classpath.txt").read_text().strip().split(os.pathsep)):
destination = dependencies / f"{index}.jar"
shutil.copyfile(dependency, destination)
linux_classpath.append("/fixture/" + destination.relative_to(repo).as_posix())
classpath = ":".join(linux_classpath)
subprocess.run(["docker", "run", "-d", "--name", container, "-p", "127.0.0.1::6379",
*network_args,
"redis:7.4-alpine", "redis-server", "--slowlog-log-slower-than", "1000",
"--slowlog-max-len", "128", "--latency-monitor-threshold", "1"], check=True, capture_output=True)
redis_port = int(subprocess.check_output(["docker", "port", container, "6379"], text=True).strip().rsplit(":", 1)[1])
nodes = [port() for _ in range(args.nodes)]
if len(set(nodes)) != len(nodes):
raise RuntimeError("Port allocation collision; no nodes started")
for node in nodes:
argfile = output / f"node-{node}.args"
connection_properties = (f"-Dcollab.capacity.redisHost={container}\n-Dcollab.dev.bindAddress=0.0.0.0\n"
if args.node_image else "")
argfile.write_text(f'-Xmx384m\n-Dcollab.capacity.tenants={args.tenants}\n-Dcollab.capacity.users={args.users}\n'
+ connection_properties +
'-cp\n"' + classpath.replace("\\", "/") + '"\n'
'com.kingsrook.qbits.collaboration.devserver.CapacityPresenceDevServer\n'
+ (f'8080\n6379\n' if args.node_image else f'{node}\n{redis_port}\n'), encoding="utf-8")
log = (output / f"node-{node}.log").open("w")
logs.append(log)
command = [args.java, "@" + str(argfile)]
if args.node_image:
node_container = f"{container}-node-{node}"
node_containers.append(node_container)
command = ["docker", "run", "--rm", "--name", node_container, "--network", network,
"-p", f"127.0.0.1:{node}:8080", "--mount", f"type=bind,source={repo},target=/fixture,readonly",
args.node_image, "java", "@/fixture/" + argfile.relative_to(repo).as_posix()]
processes.append(subprocess.Popen(command, cwd=repo,
stdout=log, stderr=subprocess.STDOUT,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)))
deadline = time.monotonic() + 120
for node, process in zip(nodes, processes):
while "CAPACITY_PRESENCE_READY " not in (output / f"node-{node}.log").read_text(errors="replace"):
if process.poll() is not None or time.monotonic() > deadline:
raise RuntimeError(f"Node {node} failed startup; inspect {output}")
time.sleep(.2)
actors = []
for tenant in range(args.tenants):
for user in range(args.users):
for tab in range(args.tabs):
identity = f"load-t{tenant}-u{user}"
record = str(1 + tab % 2)
node = nodes[len(actors) % len(nodes)]
lease = request(node, identity, "/presence/leases", {"table": "person", "pk": record}, tab)["connectionId"]
actors.append((identity, record, tab, lease, user))
renewals, snapshots = [], []
def check_rooms(excluded=None):
"""Check every tenant/record on every surviving node, including absence."""
for tenant in range(args.tenants):
expected_users = {f"load-t{tenant}-u{user}" for user in range(args.users) if user != excluded}
for node in nodes:
for record in ("1", "2"):
before = time.perf_counter()
viewers = request(node, f"load-t{tenant}-u0", f"/presence?table=person&pk={record}")["viewers"]
snapshots.append((time.perf_counter() - before) * 1000)
actual = {viewer["userId"] for viewer in viewers}
if actual != expected_users or len(viewers) != len(expected_users):
raise AssertionError(f"Tenant isolation/deduplication mismatch: {actual} != {expected_users}")
if sum(viewer["mode"] == "EDITING" for viewer in viewers) != sum(user != excluded for user in range(2)):
raise AssertionError("Unexpected editor count")
def renew(item, sequence):
"""Keep each tab sequential while moving its writes between nodes."""
index, (identity, record, tab, lease, user) = item
before = time.perf_counter()
request(nodes[(index + sequence) % len(nodes)], identity, "/presence/leases/renew",
{"connectionId": lease, "table": "person", "pk": record,
"mode": "EDITING" if user < 2 else "VIEWING", "sequence": sequence}, tab)
return (time.perf_counter() - before) * 1000
started = time.perf_counter()
with ThreadPoolExecutor(max_workers=args.workers) as workers:
for sequence in range(1, args.rounds + 1):
result["current_round"] = sequence
if sequence == args.crash_round:
crashed = nodes.pop(0)
if args.node_image:
subprocess.run(["docker", "kill", node_containers[0]], check=True, capture_output=True)
else:
processes[0].kill()
processes[0].wait(timeout=10)
result["crash"] = {"round": sequence, "http_port": crashed,
"surviving_nodes": len(nodes), "process_exit": processes[0].returncode}
renewals.extend(workers.map(lambda item: renew(item, sequence), enumerate(actors)))
check_rooms()
if args.churn_every and sequence % args.churn_every == 0:
departing = [(index, actor) for index, actor in enumerate(actors) if actor[4] == args.users - 1]
for index, (identity, record, tab, lease, user) in departing:
request(nodes[index % len(nodes)], identity, "/presence",
{"connectionId": lease, "table": "person", "pk": record, "sequence": sequence + 1}, tab, method="DELETE")
response = request(nodes[(index + 1) % len(nodes)], identity, "/presence/leases/renew",
{"connectionId": lease, "table": "person", "pk": record,
"mode": "VIEWING", "sequence": sequence + 2}, tab, expected=409)
if response.get("code") != "PRESENCE_LEASE_EXPIRED":
raise AssertionError("Departed lease was not rejected as expired")
check_rooms(excluded=args.users - 1)
for index, (identity, record, tab, lease, user) in departing:
replacement = request(nodes[(index + 1) % len(nodes)], identity, "/presence/leases",
{"table": "person", "pk": record}, tab)["connectionId"]
if replacement == lease:
raise AssertionError("Rejoin reused departed authority")
actors[index] = (identity, record, tab, replacement, user)
renew((index, actors[index]), sequence)
check_rooms()
result["churn_cycles"] = result.get("churn_cycles", 0) + 1
result["departed_leases_rejected"] = result.get("departed_leases_rejected", 0) + len(departing)
result["completed_rounds"] = sequence
if args.pause_redis:
failures = []
def stalled_renew(item):
"""A frozen authority must fail explicitly, never acknowledge a write."""
index, (identity, record, tab, lease, user) = item
before = time.perf_counter()
response = request(nodes[index % len(nodes)], identity, "/presence/leases/renew",
{"connectionId": lease, "table": "person", "pk": record,
"mode": "EDITING" if user < 2 else "VIEWING", "sequence": args.rounds + 1},
tab, expected=503)
if "viewers" in response or "temporarily unavailable" not in response.get("message", ""):
raise AssertionError("Stalled authority did not return the expected unavailable response")
duration = (time.perf_counter() - before) * 1000
if duration > 8000:
raise AssertionError("Unavailable response exceeded the eight-second fixture bound")
return duration
subprocess.run(["docker", "pause", container], check=True, capture_output=True)
try:
with ThreadPoolExecutor(max_workers=64) as burst:
failures.extend(burst.map(stalled_renew, enumerate(actors)))
for node in nodes:
response = request(node, "load-t0-u0", "/presence?table=person&pk=1", expected=503)
if "viewers" in response:
raise AssertionError("Stalled authority was presented as a room snapshot")
finally:
subprocess.run(["docker", "unpause", container], check=True, capture_output=True)
subprocess.run(["docker", "exec", container, "redis-cli", "PING"], check=True, capture_output=True, timeout=10)
for item in enumerate(actors):
renew(item, args.rounds + 2)
check_rooms()
result["redis_stall"] = {"rejected_renewals": len(failures), "failed_snapshot_nodes": len(nodes),
"response_times": distribution(failures), "same_leases_recovered": True}
elapsed = time.perf_counter() - started
result.update(status="PASS", seconds=elapsed, renewals=distribution(renewals),
snapshots=distribution(snapshots), renewal_throughput=len(renewals) / elapsed,
node_pids=[process.pid for process in processes],
pid_kind="docker-client" if args.node_image else "JVM", node_containers=node_containers)
except Exception as error:
result["error"] = str(error)
raise
finally:
failure_active = sys.exc_info()[0] is not None
try:
slow = json.loads(subprocess.check_output(
["docker", "exec", container, "redis-cli", "--json", "SLOWLOG", "GET", "128"], text=True, timeout=10))
result["redis_slow_commands"] = [{"unix_time": entry[1], "duration_us": entry[2], "command": entry[3][0]}
for entry in slow]
result["redis_latency_events"] = json.loads(subprocess.check_output(
["docker", "exec", container, "redis-cli", "--json", "LATENCY", "LATEST"], text=True, timeout=10))
for section in ("cpu", "memory", "commandstats", "stats"):
value = subprocess.check_output(["docker", "exec", container, "redis-cli", "INFO", section], text=True, timeout=10)
(output / f"redis-{section}.txt").write_text(value)
parsed = dict(line.split(":", 1) for line in value.splitlines() if ":" in line and not line.startswith("#"))
if section == "memory":
result["redis_memory"] = {key: int(parsed[key]) for key in
("used_memory", "used_memory_peak", "used_memory_rss")}
if section == "cpu":
result["redis_cpu_seconds_since_start"] = {key: float(parsed[key]) for key in
("used_cpu_sys", "used_cpu_user")}
except (subprocess.SubprocessError, ValueError, KeyError) as error:
result["redis_diagnostic_error"] = type(error).__name__
node_cleanup = True
for node_container in node_containers:
removed = subprocess.run(["docker", "rm", "-f", node_container], capture_output=True)
if removed.returncode != 0:
remaining = subprocess.run(["docker", "ps", "-aq", "--filter", f"name=^{node_container}$"], capture_output=True, text=True)
node_cleanup = node_cleanup and remaining.returncode == 0 and not remaining.stdout.strip()
for process in processes:
if process.poll() is None:
process.terminate()
for process in processes:
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=10)
for log in logs:
log.close()
resources = []
for node_log in sorted(output.glob("node-*.log")):
samples = [json.loads(line.split("CAPACITY_METRIC ", 1)[1])
for line in node_log.read_text(errors="replace").splitlines()
if line.startswith("CAPACITY_METRIC ")]
if samples:
first, last = samples[0], samples[-1]
wall_ms = last["uptimeMillis"] - first["uptimeMillis"]
cpu_ns = last["cpuNanos"] - first["cpuNanos"]
resources.append({"node_log": node_log.name, "samples": len(samples),
"observed_wall_ms": wall_ms,
"cpu_seconds": cpu_ns / 1e9 if first["cpuNanos"] >= 0 else None,
"cpu_core_equivalents": cpu_ns / (wall_ms * 1e6) if wall_ms > 0 and first["cpuNanos"] >= 0 else None,
**{key + "_sampled_max": max(sample[key] for sample in samples)
for key in ("heapUsedBytes", "heapCommittedBytes", "poolActive", "poolIdle", "poolWaiting", "poolMaximum")},
"residentBytes_sampled_max": max(sample["residentBytes"] for sample in samples)
if all(sample.get("residentBytes", -1) >= 0 for sample in samples) else None})
if any(sample["poolActive"] > sample["poolMaximum"] for sample in samples):
result.update(status="FAIL", error="Sampled pool active count exceeded configured maximum")
result["resources"] = resources
if result["status"] == "PASS" and args.node_image and any(node["residentBytes_sampled_max"] is None for node in resources):
result.update(status="FAIL", error="Linux resident-memory samples missing")
if result["status"] == "PASS" and any(key not in result for key in ("redis_memory", "redis_cpu_seconds_since_start")):
result.update(status="FAIL", error="Redis memory/CPU diagnostics missing")
if result["status"] == "PASS" and len(resources) != args.nodes:
result.update(status="FAIL", error="Missing resource samples for a node")
if result["status"] == "PASS" and args.pause_redis and not any(node["poolWaiting_sampled_max"] > 0 for node in resources):
result.update(status="FAIL", error="Redis stall did not demonstrate pool waiters")
cleanup = subprocess.run(["docker", "rm", "-f", container], capture_output=True)
network_cleanup = True
if network_created:
network_cleanup = subprocess.run(["docker", "network", "rm", network], capture_output=True).returncode == 0
result["cleanup"] = cleanup.returncode == 0 and node_cleanup and network_cleanup and all(process.poll() is not None for process in processes)
(output / "result.json").write_text(json.dumps(result, indent=2))
print(json.dumps(result, indent=2))
print(output)
if result["status"] == "PASS" and not result["cleanup"]:
raise RuntimeError("Workload passed but owned resource cleanup failed")
if result["status"] == "FAIL" and not failure_active:
raise RuntimeError(result.get("error", "Capacity resource validation failed"))
if __name__ == "__main__":
main()