Skip to content

Commit 07cda33

Browse files
committed
Fix pixi commands
1 parent 1cb1b97 commit 07cda33

File tree

8 files changed

+713
-283
lines changed

8 files changed

+713
-283
lines changed

README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,16 @@ For Ethernet benchmarks, the simplest setup is:
8585

8686
```sh
8787
export ARENA_ETH_IP=192.168.10.104
88-
pixi run bench -- --stream-path patterns/pat0004.pat --json-out bench_results.jsonl
88+
pixi run bench-full -- --json-out bench_results.jsonl
8989
```
9090

9191
Useful pre-defined tasks:
9292

9393
- `pixi run all-on`: turn all LEDs on as a communication sanity check
9494
- `pixi run all-off`: turn all LEDs off as a communication sanity check
95-
- `pixi run bench`: default host benchmark suite
95+
- `pixi run bench`: default host-side suite (`command_rtt` + `spf_updates`)
96+
- `pixi run bench-full`: default host-side suite plus `stream_frames` using `patterns/pat0004.pat`
97+
- `pixi run bench-smoke`: shorter full run for quick confidence checks
9698
- `pixi run bench-persistent`: force persistent TCP sockets for small-command RTT
9799
- `pixi run bench-new-connection`: open a new TCP connection per command
98100
- `pixi run bench-no-quickack`: disable Linux `TCP_QUICKACK` but keep `TCP_NODELAY`
@@ -103,6 +105,70 @@ Useful pre-defined tasks:
103105
Extra arguments after the task are forwarded to the CLI or script, so you can
104106
still customize labels, durations, rates, and pattern paths.
105107

108+
Examples:
109+
110+
```sh
111+
pixi run bench -- --json-out host_only.jsonl
112+
pixi run bench-full -- --json-out host_plus_stream.jsonl
113+
pixi run bench-full -- --stream-rate 250 --stream-seconds 8 --json-out stream_250hz.jsonl
114+
```
115+
116+
## Benchmark progress, timeouts, and failure reporting
117+
118+
The benchmark command now prints phase start and finish lines as it runs, along
119+
with throttled in-phase progress for the long loops. That makes it much easier
120+
to tell whether a run is healthy, slow, or stuck.
121+
122+
By default, the benchmark suite applies a temporary per-operation I/O timeout of
123+
`5.0` seconds. This avoids the old behavior where a missing reply could block a
124+
run forever.
125+
126+
You can override that timeout from the CLI:
127+
128+
```sh
129+
pixi run bench -- --io-timeout 10
130+
pixi run bench-full -- --io-timeout 0
131+
```
132+
133+
Use `--io-timeout 0` to disable the temporary benchmark timeout and fall back to
134+
blocking I/O.
135+
136+
If a phase fails, the suite now:
137+
138+
- records `status=error`
139+
- records the failed phase name and exception in the JSON result
140+
- attempts a best-effort `ALL_OFF` cleanup before returning
141+
- exits the CLI with a nonzero status
142+
143+
This makes it much easier to automate benchmarks in CI-like shell scripts or
144+
lab orchestration scripts.
145+
146+
## Host-only benchmarks versus QS logs
147+
148+
The Python benchmark suite is enough to compare host-visible and end-to-end
149+
behavior across:
150+
151+
- operating systems
152+
- host machines
153+
- NICs, switches, and cables
154+
- socket-option policies such as `TCP_NODELAY` and `TCP_QUICKACK`
155+
156+
The JSON output is therefore a good default artifact for broad comparisons
157+
across rigs.
158+
159+
QS logs are still important when you need firmware-internal detail, including:
160+
161+
- `PERF_NET` poll cadence and command processing cost
162+
- `PERF_UPD` receive / process / commit / applied / coalesced counts
163+
- display-transfer and SPI bottlenecks
164+
- confirmation that the rig applied what the host sent
165+
166+
A practical workflow is:
167+
168+
1. Run Python-only benchmarks everywhere for broad comparison.
169+
2. Capture QS logs on representative runs or anomalous runs.
170+
3. Use the QS logs to explain why two host-visible results differ.
171+
106172
## Socket latency tuning
107173

108174
The host code exposes both `TCP_NODELAY` and `TCP_QUICKACK` as explicit options.

pixi.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ check = [{ task = "lint" }, { task = "test" }]
8686
build = "python -m build"
8787
archive = "git archive --format=zip --output=../arena_interface_python.zip HEAD"
8888
bench = "arena-interface bench"
89+
bench-full = "arena-interface bench --stream-path patterns/pat0004.pat"
90+
bench-smoke = "arena-interface bench --cmd-iters 250 --spf-seconds 2 --stream-path patterns/pat0004.pat --stream-seconds 2"
8991
bench-persistent = "arena-interface bench --cmd-connect-mode persistent"
9092
bench-new-connection = "arena-interface bench --cmd-connect-mode new_connection"
9193
bench-no-quickack = "arena-interface --no-tcp-quickack bench"

scripts/bench_matrix.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
sys.path.insert(0, str(src_root))
1414

1515
from arena_interface import ArenaInterface
16-
from arena_interface.arena_interface import SERIAL_BAUDRATE
16+
from arena_interface.arena_interface import BENCH_IO_TIMEOUT_S, SERIAL_BAUDRATE
1717

1818
VARIANTS: dict[str, dict[str, bool]] = {
1919
"default": {"tcp_nodelay": True, "tcp_quickack": True},
@@ -59,6 +59,12 @@ def build_parser() -> argparse.ArgumentParser:
5959
parser.add_argument("--stream-seconds", type=float, default=5.0)
6060
parser.add_argument("--stream-coalesced", action=argparse.BooleanOptionalAction, default=True)
6161
parser.add_argument("--progress-interval", type=float, default=1.0)
62+
parser.add_argument(
63+
"--io-timeout",
64+
type=float,
65+
default=BENCH_IO_TIMEOUT_S,
66+
help="Temporary per-read/connect timeout for each suite run in seconds. Use 0 to disable.",
67+
)
6268
return parser
6369

6470

@@ -67,11 +73,20 @@ def variant_label(base_label: str | None, variant_name: str) -> str:
6773

6874

6975
def print_summary(variant_name: str, suite: dict) -> None:
76+
meta = suite.get("meta", {})
77+
quickack = meta.get("tcp_quickack_supported") and meta.get("tcp_quickack_requested")
78+
status = suite.get("status", "unknown")
79+
80+
if status != "ok":
81+
error = suite.get("error") or {}
82+
print(
83+
f"{variant_name:>18} | FAILED {error.get('phase')} {error.get('type')}: {error.get('message')}"
84+
)
85+
return
86+
7087
cmd = suite["command_rtt"]
7188
spf = suite["spf_updates"]
7289
stream = suite.get("stream_frames")
73-
meta = suite.get("meta", {})
74-
quickack = meta.get("tcp_quickack_supported") and meta.get("tcp_quickack_requested")
7590

7691
line = (
7792
f"{variant_name:>18} | cmd mean={cmd['mean_ms']:.3f} ms p99={cmd['p99_ms']:.3f} | "
@@ -96,6 +111,7 @@ def configure_transport(ai: ArenaInterface, args: argparse.Namespace) -> None:
96111

97112
def main() -> int:
98113
args = build_parser().parse_args()
114+
exit_code = 0
99115

100116
print("variant | command RTT | SPF | socket policy")
101117
print("----------------------+---------------------------+------------+-------------------------------")
@@ -125,12 +141,16 @@ def main() -> int:
125141
stream_seconds=float(args.stream_seconds),
126142
stream_coalesced=bool(args.stream_coalesced),
127143
progress_interval_s=float(args.progress_interval),
144+
bench_io_timeout_s=float(args.io_timeout),
145+
status_callback=print,
128146
)
129147
if args.json_out is not None:
130148
ArenaInterface.write_bench_jsonl(str(args.json_out), suite)
131149
print_summary(variant_name, suite)
150+
if suite.get("status") != "ok":
151+
exit_code = 1
132152

133-
return 0
153+
return exit_code
134154

135155

136156
if __name__ == "__main__":

0 commit comments

Comments
 (0)