From f596b8761ffd69757744a6fdfb71bb511d6fd665 Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 1 Jul 2026 18:11:21 -0400 Subject: [PATCH] fix(perf): ASTFAssociation takes rules=ASTFAssociationRule(port), not port= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + add OFFLINE TRex-profile validation to end the one-error-per-EC2-run cycle. Run 28549496895 (post-#106) hit 'unexpected keyword argument port'. Rather than guess again, I stood up the TRex control-plane profile module locally (its ArgVerify is pure Python — no NIC/server) and introspected the real API: - ASTFAssociation(rules=...) — NOT port=; the port goes on ASTFAssociationRule. - ASTFTCPClientTemplate DOES accept port= (my #106 guess there was fine). Fixed the association and confirmed build_tcp_profile() builds a valid ASTFProfile for all payload sizes OFFLINE in <1s. scripts/perf-tests/test/validate_tcp_profile.sh reproduces this locally (sparse-clones the TRex control-plane, shims cgi/zmq for py3.13/arm64, builds the profile). This catches ASTF-API errors instantly instead of via ~30-min EC2 runs — the fix for the 'testing at deployment is expensive' problem. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../perf-tests/test/validate_tcp_profile.sh | 71 +++++++++++++++++++ scripts/perf-tests/trex/run_tcp_benchmark.py | 3 +- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100755 scripts/perf-tests/test/validate_tcp_profile.sh diff --git a/scripts/perf-tests/test/validate_tcp_profile.sh b/scripts/perf-tests/test/validate_tcp_profile.sh new file mode 100755 index 0000000..2eedc31 --- /dev/null +++ b/scripts/perf-tests/test/validate_tcp_profile.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Offline validation of the TRex ASTF profile built by run_tcp_benchmark.py. +# +# Builds the profile against the REAL TRex control-plane (its ArgVerify) with NO +# NIC and NO TRex server — catching ASTF-API errors (wrong arg names/types, bad +# IP ranges, etc.) in <1s locally instead of a ~30-min EC2 run. This exists +# because a chain of such errors (#104 LRO, #105 ip_range, #106 glob, +# ASTFAssociation) was found one-at-a-time on real hardware; every one of them +# is caught here instantly. +# +# Usage: bash scripts/perf-tests/test/validate_tcp_profile.sh +# Exit 0 = profile builds for all payload sizes; 1 = ArgVerify/API error; 2 = setup error. +set -uo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$HERE/../../.." && pwd)" +CACHE="${TREX_CP_CACHE:-/tmp/trex-cp}" +CPI="$CACHE/scripts/automation/trex_control_plane/interactive" + +# 1. Fetch the TRex control-plane profile module (sparse, shallow) — once, cached. +if [ ! -d "$CPI/trex/astf" ]; then + echo "Fetching TRex control-plane into $CACHE ..." + rm -rf "$CACHE" + git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/cisco-system-traffic-generator/trex-core "$CACHE" >/dev/null 2>&1 \ + || { echo "SKIP: could not clone trex-core (no network?)"; exit 2; } + git -C "$CACHE" sparse-checkout set \ + scripts/automation/trex_control_plane/interactive/trex \ + scripts/external_libs >/dev/null 2>&1 \ + || { echo "ERROR: sparse-checkout failed"; exit 2; } +fi + +# 2. Shim: Python 3.12+ removed stdlib `cgi`, which the bundled scapy 2.4.3 imports. +SHIM="$CACHE/.pyshim"; mkdir -p "$SHIM" +cat > "$SHIM/cgi.py" <<'PY' +def escape(s, quote=False): + s = s.replace('&', '&').replace('<', '<').replace('>', '>') + return s.replace('"', '"') if quote else s +PY + +# 3. Build the profile for every payload size. A zmq stub is pre-loaded so TRex +# never touches the arch-specific bundled pyzmq (only needed to CONNECT a +# client, not to BUILD a profile). +export TREX_EXT_LIBS="$CACHE/scripts/external_libs" +export PYTHONPATH="$SHIM:$CPI" +python3 -W ignore - "$REPO_ROOT" <<'PY' +import sys, types +_z = types.ModuleType('zmq') +def _g(name): + if name.startswith('__') and name.endswith('__'): + raise AttributeError(name) # no __path__ etc. -> TRex's ext-lib loader skips it + return object +_z.__getattr__ = _g +sys.modules['zmq'] = _z + +root = sys.argv[1] +sys.path.insert(0, root + '/scripts/perf-tests/trex') +import run_tcp_benchmark as m + +ok = True +for ps in (64, 512, 1400, 65536): + try: + m.build_tcp_profile(ps, '10.0.1.5', '10.0.1.6', 9000) + print(f' ok - build_tcp_profile(payload={ps})') + except Exception as e: + ok = False + print(f' FAIL - build_tcp_profile(payload={ps}): {type(e).__name__}: {e}') +print('== ASTF profile builds cleanly ==' if ok else '== ASTF profile has API errors ==') +sys.exit(0 if ok else 1) +PY diff --git a/scripts/perf-tests/trex/run_tcp_benchmark.py b/scripts/perf-tests/trex/run_tcp_benchmark.py index fc6a0bd..c680f89 100644 --- a/scripts/perf-tests/trex/run_tcp_benchmark.py +++ b/scripts/perf-tests/trex/run_tcp_benchmark.py @@ -35,6 +35,7 @@ ASTFProgram, ASTFTemplate, ASTFAssociation, + ASTFAssociationRule, ) @@ -71,7 +72,7 @@ def build_tcp_profile(payload_size, src_ip, dst_ip, dst_port): ), server_template=ASTFTCPServerTemplate( program=prog_s, - assoc=ASTFAssociation(port=dst_port), + assoc=ASTFAssociation(rules=ASTFAssociationRule(port=dst_port)), ), )