From fd7c2364c959633c3a6ae710208af204d5d794d3 Mon Sep 17 00:00:00 2001 From: nnbcccscdscdsc <2709767634@qq.com> Date: Thu, 26 Mar 2026 23:48:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=96=B0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E6=B5=8B=E8=AF=95kcp=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + scripts/run-kcp-batch-test.sh | 868 ++++++++++++++++++++++++++++++++++ 2 files changed, 873 insertions(+) create mode 100755 scripts/run-kcp-batch-test.sh diff --git a/.gitignore b/.gitignore index e3ab713..da5db40 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ inbox/* *.html peer-b-latency.* + +*.bin +.vscode/settings.json +*.log +root@117.78.11.244 diff --git a/scripts/run-kcp-batch-test.sh b/scripts/run-kcp-batch-test.sh new file mode 100755 index 0000000..5481699 --- /dev/null +++ b/scripts/run-kcp-batch-test.sh @@ -0,0 +1,868 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_dir="$(cd "$script_dir/.." && pwd)" +script_name="$(basename "$0")" + +server_ssh="" +peerb_ssh="" +server_addr="" +log_prefix="" +listen_addr="0.0.0.0:10909" +server_workdir="$repo_dir" +peerb_workdir="$repo_dir" +local_workdir="$repo_dir" +ready_timeout=60 +send_interval=1 +drain_wait=5 +repeat_count=1 + +declare -a peerb_files=() + +server_started=0 +peer_b_started=0 +peer_a_pid="" + +usage() { + printf 'Usage:\n' + printf ' %s --server-ssh --peerb-ssh --server-addr \\\n' "$script_name" + printf ' --log-prefix --file [--file ...] [options]\n' + printf '\n' + printf 'Required arguments:\n' + printf ' --server-ssh SSH target for the server machine\n' + printf ' --peerb-ssh SSH target for the peer-b machine\n' + printf ' --server-addr Server address used by peer-a and peer-b\n' + printf ' --log-prefix Log directory prefix; logs go under logs/\n' + printf ' --file Existing file path on peer-b; repeat for multiple files\n' + printf '\n' + printf 'Options:\n' + printf ' --listen-addr Server listen address (default: %s)\n' "$listen_addr" + printf ' --server-workdir Server-side workdir (default: %s)\n' "$server_workdir" + printf ' --peerb-workdir Peer-b-side workdir (default: %s)\n' "$peerb_workdir" + printf ' --local-workdir Local peer-a workdir (default: %s)\n' "$local_workdir" + printf ' --ready-timeout Startup wait timeout (default: %s)\n' "$ready_timeout" + printf ' --repeat Repeat the full --file list this many rounds (default: %s)\n' "$repeat_count" + printf ' --send-interval Delay between file commands (default: %s)\n' "$send_interval" + printf ' --drain-wait Wait after the last file before quit (default: %s)\n' "$drain_wait" + printf ' -h, --help Show this help\n' + printf '\n' + printf 'Example:\n' + printf ' %s \\\n' "$script_name" + printf ' --server-ssh root@server-host \\\n' + printf ' --peerb-ssh root@peer-b-host \\\n' + printf ' --server-addr 203.0.113.10:10909 \\\n' + printf ' --log-prefix case01- \\\n' + printf ' --repeat 30 \\\n' + printf ' --file /tmp/test125.bin \\\n' + printf ' --file /tmp/test5.bin\n' +} + +log() { + printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" +} + +die() { + printf >&2 '[%s] error: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" + exit 1 +} + +join_path() { + local base="${1%/}" + printf '%s/%s' "$base" "$2" +} + +build_quoted_command() { + local out_var="$1" + shift + + local command="" + local part="" + local quoted="" + for part in "$@"; do + printf -v quoted '%q' "$part" + if [[ -n "$command" ]]; then + command+=" " + fi + command+="$quoted" + done + + printf -v "$out_var" '%s' "$command" +} + +run_remote_script() { + local target="$1" + local script="$2" + shift 2 + + local parts=("env") + local assignment="" + for assignment in "$@"; do + parts+=("$assignment") + done + parts+=("bash" "-s" "--") + + local remote_cmd="" + build_quoted_command remote_cmd "${parts[@]}" + ssh -T "$target" "$remote_cmd" <<<"$script" +} + +validate_positive_integer() { + local name="$1" + local value="$2" + + if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then + die "$name must be a positive integer, got: $value" + fi +} + +validate_sleep_value() { + local name="$1" + local value="$2" + + if [[ ! "$value" =~ ^([0-9]+([.][0-9]+)?|[.][0-9]+)$ ]]; then + die "$name must be a non-negative number understood by sleep, got: $value" + fi +} + +dump_local_log_head() { + local path="$1" + + if [[ -f "$path" ]]; then + sed -n '1,120p' "$path" >&2 || true + fi +} + +dump_remote_log_head() { + local target="$1" + local log_file="$2" + local label="$3" + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ -f "$LOG_FILE" ]]; then + sed -n '1,120p' "$LOG_FILE" +fi +EOF +)" + + log "showing $label log head from $target" + run_remote_script "$target" "$script" "LOG_FILE=$log_file" || true +} + +check_local_dependencies() { + command -v ssh >/dev/null 2>&1 || die "ssh is required" + command -v scp >/dev/null 2>&1 || die "scp is required" + command -v go >/dev/null 2>&1 || die "go is required for local peer-a" +} + +copy_remote_file_to_local() { + local remote_source="$1" + local local_dest="$2" + local local_dir="" + local local_tmp="" + + local_dir="$(dirname "$local_dest")" + mkdir -p "$local_dir" + local_tmp="$(mktemp "$local_dir/.copy.tmp.XXXXXX")" + + if scp "$remote_source" "$local_tmp"; then + mv -f "$local_tmp" "$local_dest" + else + local status=$? + rm -f "$local_tmp" + return "$status" + fi +} + +remove_local_log_dir() { + if [[ -e "$local_log_dir" ]]; then + log "removing local log dir: $local_log_dir" + rm -rf "$local_log_dir" + fi +} + +remove_remote_log_dir() { + local target="$1" + local log_dir="$2" + local label="$3" + local pid_file="${4:-}" + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ -n "${PID_FILE:-}" && -f "$PID_FILE" ]]; then + existing_pid="$(<"$PID_FILE")" + if [[ -n "$existing_pid" ]] && kill -0 "$existing_pid" 2>/dev/null; then + printf >&2 'refusing to remove log dir while process %s is still running\n' "$existing_pid" + exit 1 + fi +fi + +rm -rf "$LOG_DIR" +EOF +)" + + log "removing $label log dir on $target: $log_dir" + run_remote_script "$target" "$script" \ + "LOG_DIR=$log_dir" \ + "PID_FILE=$pid_file" +} + +clean_log_directories() { + remove_local_log_dir + remove_remote_log_dir "$server_ssh" "$server_log_dir" "server" "$server_pid_file" + remove_remote_log_dir "$peerb_ssh" "$peerb_log_dir" "peer-b" +} + +fetch_remote_peer_b_logs() { + log "copying peer-b logs from $peerb_ssh:$peerb_log_dir to $local_log_dir" + copy_remote_file_to_local "$peerb_ssh:$peerb_stdout_log" "$local_peer_b_stdout_log" + copy_remote_file_to_local "$peerb_ssh:$peerb_latency_log" "$local_peer_b_latency_log" + copy_remote_file_to_local "$peerb_ssh:$peerb_ts_debug_log" "$local_peer_b_ts_debug_log" + copy_remote_file_to_local "$peerb_ssh:$peerb_session_stats_log" "$local_peer_b_session_stats_log" +} + +run_local_latency_summary() { + [[ -f "$local_peer_a_latency_log" ]] || die "local peer-a latency log not found: $local_peer_a_latency_log" + [[ -f "$local_peer_b_latency_log" ]] || die "local peer-b latency log not found: $local_peer_b_latency_log" + + log "generating local latency summary: $local_kcp_latency_summary_log" + ( + cd "$repo_dir" + exec go run ./cmd/latencysummary \ + -input "$local_peer_a_latency_log" \ + -input "$local_peer_b_latency_log" \ + -output "$local_kcp_latency_summary_log" + ) +} + +check_remote_peerb_files() { + local script="" + local file="" + + script="$(cat <<'EOF' +set -euo pipefail + +cd "$PEERB_WORKDIR" +if [[ ! -f "$FILE_PATH" ]]; then + printf >&2 'peer-b file not found: %s\n' "$FILE_PATH" + exit 1 +fi +EOF +)" + + for file in "${peerb_files[@]}"; do + log "checking peer-b file exists: $file" + run_remote_script "$peerb_ssh" "$script" \ + "PEERB_WORKDIR=$peerb_workdir" \ + "FILE_PATH=$file" + done +} + +start_remote_server() { + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +cd "$SERVER_WORKDIR" +mkdir -p "$LOG_DIR" + +if [[ -f "$PID_FILE" ]]; then + existing_pid="$(<"$PID_FILE")" + if [[ -n "$existing_pid" ]] && kill -0 "$existing_pid" 2>/dev/null; then + printf >&2 'server already running with pid %s\n' "$existing_pid" + exit 1 + fi +fi + +: > "$STDOUT_LOG" +nohup ./kcpserver \ + -listen "$LISTEN_ADDR" \ + >>"$STDOUT_LOG" 2>&1 "$PID_FILE" +EOF +)" + + log "starting remote kcpserver on $server_ssh" + run_remote_script "$server_ssh" "$script" \ + "SERVER_WORKDIR=$server_workdir" \ + "LOG_DIR=$server_log_dir" \ + "PID_FILE=$server_pid_file" \ + "STDOUT_LOG=$server_stdout_log" \ + "LISTEN_ADDR=$listen_addr" + + server_started=1 +} + +wait_for_remote_server_ready() { + local pattern="kcp server listening" + local script="" + local start_time="$SECONDS" + local status=0 + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ -f "$LOG_FILE" ]] && grep -Fq -- "$READY_PATTERN" "$LOG_FILE"; then + exit 0 +fi + +if [[ -f "$PID_FILE" ]]; then + pid="$(<"$PID_FILE")" + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + exit 10 + fi +fi + +exit 20 +EOF +)" + + while (( SECONDS - start_time < ready_timeout )); do + if run_remote_script "$server_ssh" "$script" \ + "LOG_FILE=$server_stdout_log" \ + "READY_PATTERN=$pattern" \ + "PID_FILE=$server_pid_file"; then + log "remote server is ready" + return 0 + fi + + status=$? + case "$status" in + 10) + sleep 1 + ;; + 20) + log "remote server exited before readiness" + dump_remote_log_head "$server_ssh" "$server_stdout_log" "server" + return 1 + ;; + *) + log "remote server readiness check failed with status $status" + dump_remote_log_head "$server_ssh" "$server_stdout_log" "server" + return 1 + ;; + esac + done + + log "timed out waiting for remote server readiness after ${ready_timeout}s" + dump_remote_log_head "$server_ssh" "$server_stdout_log" "server" + return 1 +} + +stop_remote_server() { + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ ! -f "$PID_FILE" ]]; then + exit 0 +fi + +pid="$(<"$PID_FILE")" +if [[ -z "$pid" ]]; then + rm -f "$PID_FILE" + exit 0 +fi + +if ! kill -0 "$pid" 2>/dev/null; then + rm -f "$PID_FILE" + exit 0 +fi + +kill "$pid" 2>/dev/null || true +for _ in 1 2 3 4 5; do + if ! kill -0 "$pid" 2>/dev/null; then + rm -f "$PID_FILE" + exit 0 + fi + sleep 1 +done + +kill -9 "$pid" 2>/dev/null || true +rm -f "$PID_FILE" +EOF +)" + + run_remote_script "$server_ssh" "$script" "PID_FILE=$server_pid_file" +} + +start_local_peer_a() { + log "starting local peer-a" + mkdir -p "$local_log_dir" "$local_peer_a_inbox" + : > "$local_peer_a_stdout_log" + + ( + cd "$local_workdir" + exec go run ./cmd/kcppeer \ + -id peer-a \ + -server "$server_addr" \ + -inbox-dir "$local_peer_a_inbox" \ + -latency-log "$local_peer_a_latency_log" \ + -kcp-ts-debug-log "$local_peer_a_ts_debug_log" \ + -kcp-session-stats-log "$local_peer_a_session_stats_log" \ + -interactive=false \ + >>"$local_peer_a_stdout_log" 2>&1 + ) & + + peer_a_pid="$!" +} + +wait_for_local_peer_a_ready() { + local pattern="connected to $server_addr as peer-a (KCP)" + local start_time="$SECONDS" + + while (( SECONDS - start_time < ready_timeout )); do + if [[ -f "$local_peer_a_stdout_log" ]] && grep -Fq -- "$pattern" "$local_peer_a_stdout_log"; then + log "local peer-a is ready" + return 0 + fi + + if [[ -n "$peer_a_pid" ]] && ! kill -0 "$peer_a_pid" 2>/dev/null; then + log "local peer-a exited before readiness" + dump_local_log_head "$local_peer_a_stdout_log" + return 1 + fi + + sleep 1 + done + + log "timed out waiting for local peer-a readiness after ${ready_timeout}s" + dump_local_log_head "$local_peer_a_stdout_log" + return 1 +} + +stop_local_peer_a() { + if [[ -z "$peer_a_pid" ]]; then + return 0 + fi + + if kill -0 "$peer_a_pid" 2>/dev/null; then + kill "$peer_a_pid" 2>/dev/null || true + wait "$peer_a_pid" 2>/dev/null || true + else + wait "$peer_a_pid" 2>/dev/null || true + fi + + peer_a_pid="" +} + +start_remote_peer_b() { + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +cd "$PEERB_WORKDIR" +mkdir -p "$LOG_DIR" "$INBOX_DIR" + +if [[ -f "$PID_FILE" ]]; then + existing_pid="$(<"$PID_FILE")" + if [[ -n "$existing_pid" ]] && kill -0 "$existing_pid" 2>/dev/null; then + printf >&2 'peer-b already running with pid %s\n' "$existing_pid" + exit 1 + fi +fi + +: > "$STDOUT_LOG" +: > "$COMMAND_FILE" + + peer_b_cmd="$(cat <<'INNER' + tail -n +1 -f "$COMMAND_FILE" | exec ./bin/kcppeer \ + -id peer-b \ + -server "$SERVER_ADDR" \ + -inbox-dir "$INBOX_DIR" \ + -latency-log "$LATENCY_LOG" \ + -kcp-ts-debug-log "$TS_DEBUG_LOG" \ + -kcp-session-stats-log "$SESSION_STATS_LOG" +INNER +)" + +nohup bash -lc "$peer_b_cmd" >>"$STDOUT_LOG" 2>&1 "$PID_FILE" +EOF +)" + + log "starting remote peer-b on $peerb_ssh" + run_remote_script "$peerb_ssh" "$script" \ + "PEERB_WORKDIR=$peerb_workdir" \ + "LOG_DIR=$peerb_log_dir" \ + "INBOX_DIR=$peerb_inbox_dir" \ + "STDOUT_LOG=$peerb_stdout_log" \ + "COMMAND_FILE=$peerb_command_file" \ + "PID_FILE=$peerb_pid_file" \ + "SERVER_ADDR=$server_addr" \ + "LATENCY_LOG=$peerb_latency_log" \ + "TS_DEBUG_LOG=$peerb_ts_debug_log" \ + "SESSION_STATS_LOG=$peerb_session_stats_log" + + peer_b_started=1 +} + +wait_for_remote_peer_b_ready() { + local pattern="connected to $server_addr as peer-b (KCP)" + local script="" + local start_time="$SECONDS" + local status=0 + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ -f "$LOG_FILE" ]] && grep -Fq -- "$READY_PATTERN" "$LOG_FILE"; then + exit 0 +fi + +if [[ -f "$PID_FILE" ]]; then + pid="$(<"$PID_FILE")" + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + exit 10 + fi +fi + +exit 20 +EOF +)" + + while (( SECONDS - start_time < ready_timeout )); do + if run_remote_script "$peerb_ssh" "$script" \ + "LOG_FILE=$peerb_stdout_log" \ + "READY_PATTERN=$pattern" \ + "PID_FILE=$peerb_pid_file"; then + log "remote peer-b is ready" + return 0 + fi + + status=$? + case "$status" in + 10) + sleep 1 + ;; + 20) + log "remote peer-b exited before readiness" + dump_remote_log_head "$peerb_ssh" "$peerb_stdout_log" "peer-b" + return 1 + ;; + *) + log "remote peer-b readiness check failed with status $status" + dump_remote_log_head "$peerb_ssh" "$peerb_stdout_log" "peer-b" + return 1 + ;; + esac + done + + log "timed out waiting for remote peer-b readiness after ${ready_timeout}s" + dump_remote_log_head "$peerb_ssh" "$peerb_stdout_log" "peer-b" + return 1 +} + +run_remote_peer_b_batch() { + local script="" + local batch_commands="" + local round=0 + local i=0 + local send_index=0 + local total_sends=$(( ${#peerb_files[@]} * repeat_count )) + local file="" + local command_line="" + local quoted_command="" + local quoted_sleep="" + + for (( round = 1; round <= repeat_count; round++ )); do + for (( i = 0; i < ${#peerb_files[@]}; i++ )); do + file="${peerb_files[$i]}" + send_index=$(( send_index + 1 )) + log "queueing peer-b -> peer-a file (round $round/$repeat_count, send $send_index/$total_sends): $file" + printf -v command_line 'file peer-a %s' "$file" + printf -v quoted_command '%q' "$command_line" + batch_commands+="printf '%s\n' ${quoted_command} >> \"\$COMMAND_FILE\""$'\n' + if (( send_index < total_sends )); then + printf -v quoted_sleep '%q' "$send_interval" + batch_commands+="sleep ${quoted_sleep}"$'\n' + fi + done + done + printf -v quoted_sleep '%q' "$drain_wait" + batch_commands+="sleep ${quoted_sleep}"$'\n' + batch_commands+="printf '%s\n' quit >> \"\$COMMAND_FILE\""$'\n' + + script="$(cat <&2 'peer-b pid file not found: %s\n' "\$PID_FILE" + exit 1 +fi + +pid="\$(<"\$PID_FILE")" +if [[ -z "\$pid" ]] || ! kill -0 "\$pid" 2>/dev/null; then + printf >&2 'peer-b is not running\n' + exit 1 +fi + +$batch_commands + +for (( i = 0; i < READY_TIMEOUT; i++ )); do + if ! kill -0 "\$pid" 2>/dev/null; then + rm -f "\$PID_FILE" "\$COMMAND_FILE" + exit 0 + fi + sleep 1 +done + +printf >&2 'peer-b did not exit after quit within %s seconds\n' "\$READY_TIMEOUT" +exit 1 +EOF +)" + + log "sending ${#peerb_files[@]} files across $repeat_count rounds ($total_sends sends total) from peer-b" + run_remote_script "$peerb_ssh" "$script" \ + "PID_FILE=$peerb_pid_file" \ + "COMMAND_FILE=$peerb_command_file" \ + "READY_TIMEOUT=$ready_timeout" + + peer_b_started=0 +} + +stop_remote_peer_b() { + local script="" + + script="$(cat <<'EOF' +set -euo pipefail + +if [[ ! -f "$PID_FILE" ]]; then + rm -f "$COMMAND_FILE" + exit 0 +fi + +pid="$(<"$PID_FILE")" +if [[ -z "$pid" ]]; then + rm -f "$PID_FILE" "$COMMAND_FILE" + exit 0 +fi + +if kill -0 "$pid" 2>/dev/null; then + printf 'quit\n' >> "$COMMAND_FILE" 2>/dev/null || true + for _ in 1 2 3 4 5; do + if ! kill -0 "$pid" 2>/dev/null; then + rm -f "$PID_FILE" "$COMMAND_FILE" + exit 0 + fi + sleep 1 + done + kill "$pid" 2>/dev/null || true + for _ in 1 2 3 4 5; do + if ! kill -0 "$pid" 2>/dev/null; then + rm -f "$PID_FILE" "$COMMAND_FILE" + exit 0 + fi + sleep 1 + done + kill -9 "$pid" 2>/dev/null || true +fi + +rm -f "$PID_FILE" "$COMMAND_FILE" +EOF +)" + + run_remote_script "$peerb_ssh" "$script" \ + "PID_FILE=$peerb_pid_file" \ + "COMMAND_FILE=$peerb_command_file" +} + +cleanup() { + local exit_code="$?" + + trap - EXIT INT TERM + + if [[ -n "$peer_a_pid" ]]; then + log "stopping local peer-a" + stop_local_peer_a + fi + + if (( peer_b_started == 1 )); then + log "stopping remote peer-b on $peerb_ssh" + stop_remote_peer_b || true + fi + + if (( server_started == 1 )); then + log "stopping remote server on $server_ssh" + stop_remote_server || true + fi + + exit "$exit_code" +} + +handle_interrupt() { + log "received interrupt signal" + exit 130 +} + +handle_terminate() { + log "received terminate signal" + exit 143 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --server-ssh) + [[ $# -ge 2 ]] || die "--server-ssh requires a value" + server_ssh="$2" + shift 2 + ;; + --peerb-ssh) + [[ $# -ge 2 ]] || die "--peerb-ssh requires a value" + peerb_ssh="$2" + shift 2 + ;; + --server-addr) + [[ $# -ge 2 ]] || die "--server-addr requires a value" + server_addr="$2" + shift 2 + ;; + --log-prefix) + [[ $# -ge 2 ]] || die "--log-prefix requires a value" + log_prefix="$2" + shift 2 + ;; + --listen-addr) + [[ $# -ge 2 ]] || die "--listen-addr requires a value" + listen_addr="$2" + shift 2 + ;; + --server-workdir) + [[ $# -ge 2 ]] || die "--server-workdir requires a value" + server_workdir="$2" + shift 2 + ;; + --peerb-workdir) + [[ $# -ge 2 ]] || die "--peerb-workdir requires a value" + peerb_workdir="$2" + shift 2 + ;; + --local-workdir) + [[ $# -ge 2 ]] || die "--local-workdir requires a value" + local_workdir="$2" + shift 2 + ;; + --ready-timeout) + [[ $# -ge 2 ]] || die "--ready-timeout requires a value" + ready_timeout="$2" + shift 2 + ;; + --repeat) + [[ $# -ge 2 ]] || die "--repeat requires a value" + repeat_count="$2" + shift 2 + ;; + --send-interval) + [[ $# -ge 2 ]] || die "--send-interval requires a value" + send_interval="$2" + shift 2 + ;; + --drain-wait) + [[ $# -ge 2 ]] || die "--drain-wait requires a value" + drain_wait="$2" + shift 2 + ;; + --file) + [[ $# -ge 2 ]] || die "--file requires a value" + peerb_files+=("$2") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac +done + +[[ -n "$server_ssh" ]] || die "--server-ssh is required" +[[ -n "$peerb_ssh" ]] || die "--peerb-ssh is required" +[[ -n "$server_addr" ]] || die "--server-addr is required" +[[ -n "$log_prefix" ]] || die "--log-prefix is required" +(( ${#peerb_files[@]} > 0 )) || die "at least one --file is required" + +validate_positive_integer "--ready-timeout" "$ready_timeout" +validate_positive_integer "--repeat" "$repeat_count" +validate_sleep_value "--send-interval" "$send_interval" +validate_sleep_value "--drain-wait" "$drain_wait" + +check_local_dependencies + +log_dir_name="${log_prefix}logs" +inbox_dir_name="${log_prefix}inbox" + +local_log_dir="$(join_path "$local_workdir" "$log_dir_name")" +local_peer_a_inbox="$(join_path "$local_workdir" "$inbox_dir_name/peer-a")" +local_peer_a_stdout_log="$(join_path "$local_log_dir" "peer-a.stdout.log")" +local_peer_a_latency_log="$(join_path "$local_log_dir" "peer-a-kcp-latency.jsonl")" +local_peer_a_ts_debug_log="$(join_path "$local_log_dir" "peer-a-kcp-packet-debug.jsonl")" +local_peer_a_session_stats_log="$(join_path "$local_log_dir" "peer-a-kcp-session-stats.jsonl")" +local_peer_b_stdout_log="$(join_path "$local_log_dir" "peer-b.stdout.log")" +local_peer_b_latency_log="$(join_path "$local_log_dir" "peer-b-kcp-latency.jsonl")" +local_peer_b_ts_debug_log="$(join_path "$local_log_dir" "peer-b-kcp-packet-debug.jsonl")" +local_peer_b_session_stats_log="$(join_path "$local_log_dir" "peer-b-kcp-session-stats.jsonl")" +local_kcp_latency_summary_log="$(join_path "$local_log_dir" "kcp-latency-summary.jsonl")" + +server_log_dir="$(join_path "$server_workdir" "$log_dir_name")" +server_pid_file="$(join_path "$server_log_dir" "server.pid")" +server_stdout_log="$(join_path "$server_log_dir" "server.stdout.log")" + +peerb_log_dir="$(join_path "$peerb_workdir" "$log_dir_name")" +peerb_inbox_dir="$(join_path "$peerb_workdir" "$inbox_dir_name/peer-b")" +peerb_stdout_log="$(join_path "$peerb_log_dir" "peer-b.stdout.log")" +peerb_latency_log="$(join_path "$peerb_log_dir" "peer-b-kcp-latency.jsonl")" +peerb_ts_debug_log="$(join_path "$peerb_log_dir" "peer-b-kcp-packet-debug.jsonl")" +peerb_session_stats_log="$(join_path "$peerb_log_dir" "peer-b-kcp-session-stats.jsonl")" +peerb_pid_file="$(join_path "$peerb_log_dir" "peer-b.pid")" +peerb_command_file="$(join_path "$peerb_log_dir" "peer-b.commands")" + +trap cleanup EXIT +trap handle_interrupt INT +trap handle_terminate TERM + +clean_log_directories + +mkdir -p "$local_log_dir" "$local_peer_a_inbox" + +log "local peer-a logs: $local_log_dir" +log "remote server logs: $server_log_dir" +log "remote peer-b logs: $peerb_log_dir" + +check_remote_peerb_files +start_remote_server +wait_for_remote_server_ready +start_local_peer_a +start_remote_peer_b +wait_for_local_peer_a_ready +wait_for_remote_peer_b_ready +run_remote_peer_b_batch + +log "batch send completed" + +if [[ -n "$peer_a_pid" ]]; then + log "stopping local peer-a after batch" + stop_local_peer_a +fi + +if (( server_started == 1 )); then + log "stopping remote server on $server_ssh after batch" + if stop_remote_server; then + server_started=0 + else + log "failed to stop remote server cleanly; cleanup will retry" + fi +fi + +fetch_remote_peer_b_logs +run_local_latency_summary