#!/usr/bin/env bash # ============================================================================= # benchmark_network.sh — Network Condition Benchmark for fastSyncAI # # Tests fastSyncAI performance under various realistic network conditions: # - LAN (low latency, high bandwidth) # - WAN (high latency, moderate bandwidth) # - WAN with packet loss # - WAN with jitter # # Usage: # ./benchmark_network.sh [SCENARIO] [DATA_SIZE_MB] [RUN_COUNT] # # Scenarios: # --lan : 10ms RTT, no loss (typical LAN) # --wan : 100ms RTT, no loss (typical WAN) # --wan-loss-1 : 100ms RTT, 1% packet loss # --wan-loss-5 : 100ms RTT, 5% packet loss # --wan-jitter : 100ms RTT, +/-50ms jitter # --custom LAT_ms : Custom latency (e.g., --custom 50 for 50ms RTT) # # Examples: # ./benchmark_network.sh --lan # LAN test, 100MB, 3 runs # ./benchmark_network.sh --wan 200 1 # WAN test, 200MB, 1 run # ./benchmark_network.sh --wan-loss-1 50 # WAN with 1% loss, 50MB, 3 runs # ./benchmark_network.sh --wan-jitter 100 5 # WAN with jitter, 100MB, 5 runs # ./benchmark_network.sh --custom 50 100 3 # Custom 50ms RTT, 100MB, 3 runs # ============================================================================= set -euo pipefail # ── Defaults ────────────────────────────────────────────────────────────── DATA_SIZE_MB=${2:-100} RUN_COUNT=${3:-3} # Network scenario configuration SCENARIO="${1:---lan}" # ── Parse scenario ──────────────────────────────────────────────────────── LATENCY_MS=0 PACKET_LOSS="0%" JITTER_MS="0" BANDWIDTH_MBPS="0" # 0 = unlimited case "$SCENARIO" in --lan) LATENCY_MS=10 SCENARIO_NAME="LAN (10ms RTT)" ;; --wan) LATENCY_MS=100 SCENARIO_NAME="WAN (100ms RTT)" ;; --wan-loss-1) LATENCY_MS=100 PACKET_LOSS="1%" SCENARIO_NAME="WAN with 1% loss (100ms RTT)" ;; --wan-loss-5) LATENCY_MS=100 PACKET_LOSS="5%" SCENARIO_NAME="WAN with 5% loss (100ms RTT)" ;; --wan-loss-10) LATENCY_MS=100 PACKET_LOSS="10%" SCENARIO_NAME="WAN with 10% loss (100ms RTT)" ;; --wan-jitter) LATENCY_MS=100 JITTER_MS=50 SCENARIO_NAME="WAN with jitter (100ms +/-50ms RTT)" ;; --custom) if [ -n "${2:-}" ] && [[ "${2}" =~ ^[0-9]+$ ]]; then LATENCY_MS="$2" DATA_SIZE_MB=${3:-100} RUN_COUNT=${4:-3} SCENARIO_NAME="Custom (${LATENCY_MS}ms RTT)" else echo "ERROR: --custom requires latency value in ms" echo "Usage: ./benchmark_network.sh --custom LATENCY_MS [DATA_SIZE_MB] [RUN_COUNT]" exit 1 fi ;; *) echo "ERROR: Unknown scenario '$SCENARIO'" echo "Available scenarios: --lan, --wan, --wan-loss-1, --wan-loss-5, --wan-loss-10, --wan-jitter, --custom" exit 1 ;; esac PORT=19399 HOST="127.0.0.1" SERVER_BIN="./fastsync_server" CLIENT_BIN="./fastsync_client" # ── Directories ──────────────────────────────────────────────────────────── TEST_DIR="network_bench_$$" SRC_DIR="$TEST_DIR/src" DST_DIR="$TEST_DIR/dst" # ── Colours ──────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' # ── Helpers ──────────────────────────────────────────────────────────────── die() { echo -e "${RED}ERROR: $*${RESET}" >&2; exit 1; } info() { echo -e "${CYAN}▶ $*${RESET}"; } ok() { echo -e "${GREEN}✓ $*${RESET}"; } warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } # ── Network simulation (tc netem) ──────────────────────────────────────── setup_network() { info "Setting up network simulation: ${SCENARIO_NAME}..." if ! sudo -n true 2>/dev/null; then echo -e "${YELLOW}⚠ sudo required for tc netem — enter password if prompted${RESET}" fi # Clean existing qdisc sudo tc qdisc del dev lo root 2>/dev/null || true # Build netem command local netem_cmd="delay ${LATENCY_MS}ms" if [ "$JITTER_MS" -gt 0 ]; then netem_cmd="$netem_cmd ${JITTER_MS}ms" fi if [ "$PACKET_LOSS" != "0%" ]; then netem_cmd="$netem_cmd loss ${PACKET_LOSS}" fi # Apply network conditions sudo tc qdisc add dev lo root netem $netem_cmd || \ die "tc netem failed. Install iproute2 or check parameters." ok "Network simulation active: latency=${LATENCY_MS}ms, loss=${PACKET_LOSS}, jitter=±${JITTER_MS}ms" } cleanup_network() { sudo tc qdisc del dev lo root 2>/dev/null || true ok "Network simulation removed" } # ── Sanity checks ────────────────────────────────────────────────────────── [ -x "$SERVER_BIN" ] || die "Server binary not found: $SERVER_BIN — run 'make' first." [ -x "$CLIENT_BIN" ] || die "Client binary not found: $CLIENT_BIN — run 'make' first." cleanup() { cleanup_network pkill -f "fastsync_server" 2>/dev/null || true pkill -f "fastsync_client" 2>/dev/null || true rm -rf "$TEST_DIR" } trap cleanup EXIT # ── Generate test data ────────────────────────────────────────────────────── generate_test_data() { info "Generating ~${DATA_SIZE_MB}MB test data..." rm -rf "$TEST_DIR" mkdir -p "$SRC_DIR/small" "$SRC_DIR/medium" "$SRC_DIR/large" # Small files: many small files to test overhead (~25% of total) local small_count=$(( (DATA_SIZE_MB * 1048576 / 4) / 262144 )) [ "$small_count" -lt 10 ] && small_count=10 [ "$small_count" -gt 100 ] && small_count=100 info " Creating $small_count small files (256KB each)..." for i in $(seq 1 $small_count); do dd if=/dev/urandom bs=256K count=1 of="$SRC_DIR/small/file_$i.bin" 2>/dev/null done # Medium files: 1MB each (~50% of total) local medium_count=$(( (DATA_SIZE_MB * 1048576 / 2) / 1048576 )) [ "$medium_count" -lt 5 ] && medium_count=5 [ "$medium_count" -gt 20 ] && medium_count=20 info " Creating $medium_count medium files (1MB each)..." for i in $(seq 1 $medium_count); do dd if=/dev/urandom bs=1M count=1 of="$SRC_DIR/medium/file_$i.bin" 2>/dev/null done # Large files: remaining ~25% local large_size=$(( DATA_SIZE_MB - (small_count * 256 / 1024) - medium_count )) [ "$large_size" -lt 5 ] && large_size=5 local large_count=3 [ "$large_size" -lt 10 ] && large_count=1 info " Creating $large_count large files (~${large_size}MB total)..." for i in $(seq 1 $large_count); do local this_size=$(( large_size * 1048576 / large_count / 1048576 )) [ "$this_size" -lt 1 ] && this_size=1 dd if=/dev/urandom bs=1M count=$this_size of="$SRC_DIR/large/file_$i.bin" 2>/dev/null done local total_files total_size total_files=$(find "$SRC_DIR" -type f | wc -l) total_size=$(du -sh "$SRC_DIR" | cut -f1) ok "Generated $total_files files, $total_size total" } # ── Verify sync ──────────────────────────────────────────────────────────── verify_sync() { local src_dir=$1 local dst_dir=$2 local src_count=$(find "$src_dir" -type f | wc -l) local dst_count=$(find "$dst_dir" -type f | wc -l) [ "$src_count" -ne "$dst_count" ] && return 1 local src_size=$(find "$src_dir" -type f -exec stat -c %s {} \; | awk '{sum+=$1} END {print sum}') local dst_size=$(find "$dst_dir" -type f -exec stat -c %s {} \; | awk '{sum+=$1} END {print sum}') [ "$src_size" -ne "$dst_size" ] && return 1 return 0 } # ── Run single test with connection count ────────────────────────────────── run_test() { local nconn=$1 local dst_dir="$DST_DIR/fastsync_${nconn}" local server_port=$((PORT + nconn)) rm -rf "$dst_dir" mkdir -p "$dst_dir" # Start server "$SERVER_BIN" -p "$server_port" -d "$dst_dir" > /dev/null 2>&1 & local srv_pid=$! sleep 0.2 # Run client local client_output client_output=$(timeout 300 "$CLIENT_BIN" -h "$HOST" -p "$server_port" -s "$SRC_DIR" -n "$nconn" 2>&1) || true # Wait for completion sleep 3 kill "$srv_pid" 2>/dev/null || true wait "$srv_pid" 2>/dev/null || true sync # Extract results local ms mbs ms=$(echo "$client_output" | grep "^BENCH:" | grep -oP 'ms=\K[0-9.]+' || echo "0") mbs=$(echo "$client_output" | grep "^BENCH:" | grep -oP 'throughput_mbs=\K[0-9.]+' || echo "0") local verified=1 if ! verify_sync "$SRC_DIR" "$dst_dir"; then verified=0 fi echo "${ms}|${mbs}|${verified}" # Cleanup rm -rf "$dst_dir" } # ── Bar chart helper ──────────────────────────────────────────────────────── draw_bar() { local val=$1 local max=$2 local width=30 [ "$max" -eq 0 ] && max=1 local filled filled=$(LC_ALL=C awk "BEGIN{printf \"%d\", ($val * $width / $max)}") [ "$filled" -gt "$width" ] && filled=$width local bar="" for ((i=0; i