Files
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / Resolve release version (push) Waiting to run
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / Distro compat summary (push) Blocked by required conditions
CI: Nix Linux Rust source / Nix / compositor build (push) Waiting to run
CI: Nix Linux Rust source / Nix / driver package (push) Waiting to run
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Waiting to run
CI: Rust Linux unit / Rust Linux unit and compile (push) Waiting to run
CI: Rust Windows unit / Rust Windows unit and compile (push) Waiting to run
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Waiting to run
CD: Docs MCP Server / build (linux/amd64) (push) Waiting to run
CD: Docs MCP Server / build (linux/arm64) (push) Waiting to run
CD: Docs MCP Server / merge (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

158 lines
5.5 KiB
Python

"""
Demo 1: Fleet Throughput
Connect to 99 cloud VMs sequentially, then send tap+screenshot concurrently.
Reports total throughput as steps/hour at the end.
Usage:
CUA_API_KEY=sk_... python samples/python/1_fleet_throughput.py
"""
import argparse
import asyncio
import time
from pathlib import Path
from cua_sandbox import Sandbox
STATE_FILE = "/tmp/android_bench_sandboxes.txt"
CONCURRENCY = 20 # parallel commands at once
ROUNDS = 3 # tap+screenshot rounds per VM
CONNECT_TIMEOUT = 30 # seconds per connect attempt
OUT_DIR = Path(__file__).parent / "out"
# ── helpers ──────────────────────────────────────────────────────────────────
def load_names(n: int | None = None) -> list[str]:
with open(STATE_FILE) as f:
names = [line.strip() for line in f if line.strip()]
return names[:n] if n else names
async def connect_all(names: list[str]) -> list[tuple[str, Sandbox]]:
"""Connect to each VM sequentially so output is ordered and easy to read."""
print(f"Connecting to {len(names)} VMs (sequential, timeout={CONNECT_TIMEOUT}s each)...\n")
sandboxes = []
connect_times: list[float] = []
for i, name in enumerate(names, 1):
print(f" [{i:>3}/{len(names)}] {name} ...", end=" ", flush=True)
t0 = time.monotonic()
try:
sb = await asyncio.wait_for(Sandbox.connect(name=name), timeout=CONNECT_TIMEOUT)
elapsed = time.monotonic() - t0
connect_times.append(elapsed)
print(f"✓ {elapsed*1000:.0f}ms", flush=True)
sandboxes.append((name, sb))
except asyncio.TimeoutError:
print("✗ timeout", flush=True)
except Exception as e:
print(f"✗ {e}", flush=True)
if connect_times:
connect_times.sort()
p50 = connect_times[len(connect_times) // 2] * 1000
p95 = connect_times[int(len(connect_times) * 0.95)] * 1000
avg = sum(connect_times) / len(connect_times) * 1000
print(f"\n Connect avg={avg:.0f}ms p50={p50:.0f}ms p95={p95:.0f}ms")
return sandboxes
async def run_step(name: str, sb: Sandbox) -> float:
"""Tap center + screenshot. Returns elapsed seconds."""
t0 = time.monotonic()
w, h = await sb.screen.size()
await sb.mouse.click(w // 2, h // 2)
await sb.screenshot(format="jpeg")
return time.monotonic() - t0
# ── main ─────────────────────────────────────────────────────────────────────
async def main(n: int | None = None, rounds: int = ROUNDS, concurrency: int = CONCURRENCY):
OUT_DIR.mkdir(exist_ok=True)
names = load_names(n)
print(f"\n{'='*50}")
print(" Fleet Throughput Demo")
print(f" {len(names)} VMs | {rounds} rounds each | concurrency={concurrency}")
print(f"{'='*50}\n")
sandboxes = await connect_all(names)
print(f"\nConnected: {len(sandboxes)}/{len(names)} VMs\n")
if not sandboxes:
print("No VMs connected — exiting.")
return
# Run rounds of tap+screenshot across all VMs concurrently
sem = asyncio.Semaphore(concurrency)
total_steps = 0
latencies: list[float] = []
print(f"Running {rounds} rounds of tap+screenshot (concurrency={concurrency})...\n")
t_start = time.monotonic()
for round_num in range(1, rounds + 1):
print(f"── Round {round_num}/{rounds} ──")
async def step(name: str, sb: Sandbox):
async with sem:
try:
elapsed = await run_step(name, sb)
print(f" {name} {elapsed * 1000:.0f}ms")
return elapsed
except Exception as e:
print(f" {name} ERROR: {e}")
return None
round_results = await asyncio.gather(*[step(n, sb) for n, sb in sandboxes])
ok = [r for r in round_results if r is not None]
total_steps += len(ok)
latencies.extend(ok)
t_elapsed = time.monotonic() - t_start
# Disconnect
await asyncio.gather(*[sb.disconnect() for _, sb in sandboxes], return_exceptions=True)
# Save results
steps_per_hour = total_steps / t_elapsed * 3600
latencies.sort()
p50 = latencies[len(latencies) // 2] * 1000
p95 = latencies[int(len(latencies) * 0.95)] * 1000
results = (
f"VMs active : {len(sandboxes)}\n"
f"Total steps : {total_steps}\n"
f"Elapsed : {t_elapsed:.1f}s\n"
f"Throughput : {steps_per_hour:,.0f} steps/hour\n"
f"Latency p50 : {p50:.0f}ms\n"
f"Latency p95 : {p95:.0f}ms\n"
)
(OUT_DIR / "fleet_results.txt").write_text(results)
print(f"\n{'='*50}")
print(" Results")
print(f"{'='*50}")
print(results, end="")
print(f" Saved to : {OUT_DIR}/fleet_results.txt")
print(f"{'='*50}\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--n", type=int, default=None, help="Number of VMs to use (default: all)")
parser.add_argument(
"--rounds", type=int, default=ROUNDS, help=f"Rounds per VM (default: {ROUNDS})"
)
parser.add_argument(
"--concurrency",
type=int,
default=CONCURRENCY,
help=f"Parallel commands (default: {CONCURRENCY})",
)
args = parser.parse_args()
asyncio.run(main(n=args.n, rounds=args.rounds, concurrency=args.concurrency))