Files
1jehuang--jcode/scripts/stress_test_40.sh
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

413 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Stress test: spawn 40 jcode TUI client instances rapidly
# Measures startup time, memory usage, CPU, fd count, socket health
#
set -euo pipefail
NUM_INSTANCES=${1:-40}
JCODE_BIN="${JCODE_BIN:-$(which jcode)}"
LOG_DIR="/tmp/jcode-stress-test-$(date +%s)"
mkdir -p "$LOG_DIR"
MAIN_SOCK="/run/user/$(id -u)/jcode.sock"
DEBUG_SOCK="/run/user/$(id -u)/jcode-debug.sock"
echo "========================================="
echo " jcode Stress Test: $NUM_INSTANCES instances"
echo "========================================="
echo "Binary: $JCODE_BIN"
echo "Log dir: $LOG_DIR"
echo "Main socket: $MAIN_SOCK"
echo ""
# --- Helper functions ---
get_server_pid() {
# Server listens on the main socket
lsof -U 2>/dev/null | grep "$(basename $MAIN_SOCK)" | awk '{print $2}' | sort -u | head -1
}
snapshot() {
local label="$1"
local ts=$(date +%s%N)
echo "--- Snapshot: $label ---" >> "$LOG_DIR/snapshots.log"
echo "timestamp_ns: $ts" >> "$LOG_DIR/snapshots.log"
# Memory
free -m >> "$LOG_DIR/snapshots.log" 2>/dev/null
echo "" >> "$LOG_DIR/snapshots.log"
# jcode process count and total RSS
local jcode_procs=$(pgrep -c jcode 2>/dev/null || echo 0)
local total_rss=0
local total_vms=0
for pid in $(pgrep jcode 2>/dev/null); do
local rss=$(awk '/^VmRSS:/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
local vms=$(awk '/^VmSize:/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
total_rss=$((total_rss + rss))
total_vms=$((total_vms + vms))
done
echo "jcode_processes: $jcode_procs" >> "$LOG_DIR/snapshots.log"
echo "total_rss_kb: $total_rss" >> "$LOG_DIR/snapshots.log"
echo "total_vms_kb: $total_vms" >> "$LOG_DIR/snapshots.log"
# Open file descriptors for server
local server_pid=$(get_server_pid)
if [ -n "$server_pid" ]; then
local fd_count=$(ls /proc/$server_pid/fd 2>/dev/null | wc -l)
local thread_count=$(ls /proc/$server_pid/task 2>/dev/null | wc -l)
echo "server_pid: $server_pid" >> "$LOG_DIR/snapshots.log"
echo "server_fd_count: $fd_count" >> "$LOG_DIR/snapshots.log"
echo "server_threads: $thread_count" >> "$LOG_DIR/snapshots.log"
# Server RSS specifically
local server_rss=$(awk '/^VmRSS:/{print $2}' /proc/$server_pid/status 2>/dev/null || echo 0)
echo "server_rss_kb: $server_rss" >> "$LOG_DIR/snapshots.log"
fi
# CPU load
cat /proc/loadavg >> "$LOG_DIR/snapshots.log"
echo "" >> "$LOG_DIR/snapshots.log"
echo "===" >> "$LOG_DIR/snapshots.log"
# Print summary line to stdout
echo "[$label] procs=$jcode_procs rss=${total_rss}KB($(( total_rss / 1024 ))MB) server_rss=$(awk '/^VmRSS:/{print $2}' /proc/${server_pid:-0}/status 2>/dev/null || echo '?')KB fds=$(ls /proc/${server_pid:-0}/fd 2>/dev/null | wc -l) threads=$(ls /proc/${server_pid:-0}/task 2>/dev/null | wc -l)"
}
check_socket_health() {
local label="$1"
# Try a quick connect-and-disconnect on the main socket
local start_ns=$(date +%s%N)
if python3 -c "
import socket, json, sys, time
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.settimeout(5)
sock.connect('$MAIN_SOCK')
# just connect and close - test socket responsiveness
sock.close()
sys.exit(0)
except Exception as e:
print(f'Socket error: {e}', file=sys.stderr)
sys.exit(1)
" 2>>"$LOG_DIR/socket_errors.log"; then
local end_ns=$(date +%s%N)
local dur_ms=$(( (end_ns - start_ns) / 1000000 ))
echo "[$label] Socket connect: ${dur_ms}ms" | tee -a "$LOG_DIR/socket_latency.log"
else
echo "[$label] Socket connect: FAILED" | tee -a "$LOG_DIR/socket_latency.log"
fi
}
debug_cmd() {
local cmd="$1"
local timeout="${2:-5}"
python3 -c "
import socket, json, sys
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.settimeout($timeout)
sock.connect('$DEBUG_SOCK')
req = {'type': 'debug_command', 'id': 1, 'command': '$cmd'}
sock.send((json.dumps(req) + '\n').encode())
data = sock.recv(65536).decode()
print(data)
sock.close()
except Exception as e:
print(json.dumps({'error': str(e)}))
" 2>/dev/null
}
# --- Pre-flight ---
echo "=== Pre-flight checks ==="
# Check if server is running
if ! [ -S "$MAIN_SOCK" ]; then
echo "ERROR: No jcode server running at $MAIN_SOCK"
echo "Start one with: jcode serve &"
exit 1
fi
# Test socket
check_socket_health "pre-flight"
# Baseline snapshot
snapshot "baseline"
echo ""
# --- Record system baseline ---
BASELINE_RSS=$(pgrep jcode 2>/dev/null | while read pid; do awk '/^VmRSS:/{print $2}' /proc/$pid/status 2>/dev/null; done | paste -sd+ | bc 2>/dev/null || echo 0)
BASELINE_PROCS=$(pgrep -c jcode 2>/dev/null || echo 0)
BASELINE_SERVER_PID=$(get_server_pid)
BASELINE_FDS=$(ls /proc/${BASELINE_SERVER_PID:-0}/fd 2>/dev/null | wc -l)
echo "=== Baseline ==="
echo " Processes: $BASELINE_PROCS"
echo " Total RSS: ${BASELINE_RSS}KB ($(( BASELINE_RSS / 1024 ))MB)"
echo " Server FDs: $BASELINE_FDS"
echo ""
# --- Start background monitoring ---
echo "=== Starting background monitor ==="
(
while true; do
ts=$(date +%s)
jcode_procs=$(pgrep -c jcode 2>/dev/null || echo 0)
total_rss=0
for pid in $(pgrep jcode 2>/dev/null); do
rss=$(awk '/^VmRSS:/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
total_rss=$((total_rss + rss))
done
server_pid=$(get_server_pid)
server_rss=$(awk '/^VmRSS:/{print $2}' /proc/${server_pid:-0}/status 2>/dev/null || echo 0)
server_fds=$(ls /proc/${server_pid:-0}/fd 2>/dev/null | wc -l)
server_threads=$(ls /proc/${server_pid:-0}/task 2>/dev/null | wc -l)
cpu_load=$(awk '{print $1}' /proc/loadavg)
echo "$ts,$jcode_procs,$total_rss,$server_rss,$server_fds,$server_threads,$cpu_load"
sleep 1
done
) > "$LOG_DIR/timeseries.csv" &
MONITOR_PID=$!
echo "Monitor PID: $MONITOR_PID"
echo ""
# --- Spawn instances ---
echo "=== Spawning $NUM_INSTANCES jcode instances ==="
PIDS=()
SPAWN_TIMES=()
# Use script to give each instance a pty (jcode requires tty)
for i in $(seq 1 $NUM_INSTANCES); do
local_start=$(date +%s%N)
# Each instance gets its own pseudo-terminal via script(1)
# We connect to the existing server, which creates sessions
script -q -c "$JCODE_BIN --no-update --no-selfdev" /dev/null \
> "$LOG_DIR/instance_${i}_stdout.log" \
2> "$LOG_DIR/instance_${i}_stderr.log" &
pid=$!
PIDS+=($pid)
local_end=$(date +%s%N)
spawn_ms=$(( (local_end - local_start) / 1000000 ))
SPAWN_TIMES+=($spawn_ms)
# Log it
echo " [$i/$NUM_INSTANCES] PID=$pid spawn=${spawn_ms}ms" | tee -a "$LOG_DIR/spawn.log"
# Snapshot every 10 instances
if (( i % 10 == 0 )); then
sleep 1 # Let things settle
snapshot "after_${i}_spawns"
check_socket_health "after_${i}_spawns"
fi
# Small delay to avoid overwhelming everything at once
sleep 0.2
done
echo ""
echo "=== All $NUM_INSTANCES instances spawned ==="
echo ""
# Let them run for a bit
echo "=== Letting instances stabilize for 10 seconds ==="
sleep 10
snapshot "post_spawn_settled"
check_socket_health "post_spawn_settled"
echo ""
# --- Debug socket probe under load ---
echo "=== Debug socket responsiveness under load ==="
for probe in 1 2 3; do
start_ns=$(date +%s%N)
result=$(debug_cmd "state" 10)
end_ns=$(date +%s%N)
dur_ms=$(( (end_ns - start_ns) / 1000000 ))
echo " Probe $probe: ${dur_ms}ms" | tee -a "$LOG_DIR/debug_probe.log"
sleep 0.5
done
echo ""
# --- Session listing under load ---
echo "=== Session list under load ==="
start_ns=$(date +%s%N)
sessions_result=$(debug_cmd "sessions" 15)
end_ns=$(date +%s%N)
dur_ms=$(( (end_ns - start_ns) / 1000000 ))
session_count=$(echo "$sessions_result" | python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
output = data.get('output', '')
sessions = json.loads(output) if output else []
print(len(sessions))
except:
print('?')
" 2>/dev/null)
echo " Sessions: $session_count, query time: ${dur_ms}ms"
echo ""
# --- Kill all spawned instances ---
echo "=== Killing all spawned instances ==="
for pid in "${PIDS[@]}"; do
kill "$pid" 2>/dev/null || true
done
# Wait for them to die
sleep 3
# Force kill stragglers
for pid in "${PIDS[@]}"; do
kill -9 "$pid" 2>/dev/null || true
done
sleep 2
snapshot "post_kill"
echo ""
# --- Post-kill: check for leaked resources ---
echo "=== Post-kill resource check ==="
POST_PROCS=$(pgrep -c jcode 2>/dev/null || echo 0)
POST_RSS=0
for pid in $(pgrep jcode 2>/dev/null); do
rss=$(awk '/^VmRSS:/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
POST_RSS=$((POST_RSS + rss))
done
POST_SERVER_PID=$(get_server_pid)
POST_FDS=$(ls /proc/${POST_SERVER_PID:-0}/fd 2>/dev/null | wc -l)
POST_SERVER_RSS=$(awk '/^VmRSS:/{print $2}' /proc/${POST_SERVER_PID:-0}/status 2>/dev/null || echo 0)
echo " Processes: $BASELINE_PROCS -> $POST_PROCS (delta: $((POST_PROCS - BASELINE_PROCS)))"
echo " Total RSS: ${BASELINE_RSS}KB -> ${POST_RSS}KB (delta: $((POST_RSS - BASELINE_RSS))KB)"
echo " Server FDs: $BASELINE_FDS -> $POST_FDS (delta: $((POST_FDS - BASELINE_FDS)))"
echo " Server RSS: ${POST_SERVER_RSS}KB"
echo ""
# Check socket health after cleanup
echo "=== Post-cleanup socket health ==="
check_socket_health "post_cleanup_1"
sleep 1
check_socket_health "post_cleanup_2"
echo ""
# --- Wait a bit more and check for memory leak ---
echo "=== Waiting 15s for GC/cleanup ==="
sleep 15
snapshot "post_gc"
FINAL_SERVER_PID=$(get_server_pid)
FINAL_FDS=$(ls /proc/${FINAL_SERVER_PID:-0}/fd 2>/dev/null | wc -l)
FINAL_SERVER_RSS=$(awk '/^VmRSS:/{print $2}' /proc/${FINAL_SERVER_PID:-0}/status 2>/dev/null || echo 0)
check_socket_health "final"
echo ""
# --- Stop background monitor ---
kill $MONITOR_PID 2>/dev/null || true
# --- Summary report ---
echo "========================================="
echo " STRESS TEST SUMMARY"
echo "========================================="
echo ""
echo "Configuration:"
echo " Instances spawned: $NUM_INSTANCES"
echo " Binary: $JCODE_BIN"
echo ""
# Spawn time stats
if [ ${#SPAWN_TIMES[@]} -gt 0 ]; then
total=0
min=${SPAWN_TIMES[0]}
max=${SPAWN_TIMES[0]}
for t in "${SPAWN_TIMES[@]}"; do
total=$((total + t))
if (( t < min )); then min=$t; fi
if (( t > max )); then max=$t; fi
done
avg=$((total / ${#SPAWN_TIMES[@]}))
echo "Spawn Times (fork+exec overhead):"
echo " Min: ${min}ms"
echo " Max: ${max}ms"
echo " Avg: ${avg}ms"
echo " Total: ${total}ms"
echo ""
fi
echo "Memory Impact:"
echo " Baseline total RSS: ${BASELINE_RSS}KB ($(( BASELINE_RSS / 1024 ))MB)"
echo " Peak server RSS: (see timeseries)"
echo " Final server RSS: ${FINAL_SERVER_RSS}KB ($(( FINAL_SERVER_RSS / 1024 ))MB)"
echo " Server RSS delta from baseline: $((FINAL_SERVER_RSS - $(awk '/^VmRSS:/{print $2}' /proc/${BASELINE_SERVER_PID:-0}/status 2>/dev/null || echo $FINAL_SERVER_RSS)))KB"
echo ""
echo "File Descriptors (leak check):"
echo " Baseline: $BASELINE_FDS"
echo " After kill: $POST_FDS (delta: $((POST_FDS - BASELINE_FDS)))"
echo " After GC: $FINAL_FDS (delta: $((FINAL_FDS - BASELINE_FDS)))"
if (( FINAL_FDS > BASELINE_FDS + 5 )); then
echo " ⚠️ POSSIBLE FD LEAK: $((FINAL_FDS - BASELINE_FDS)) fds not cleaned up"
else
echo " ✅ FDs cleaned up properly"
fi
echo ""
echo "Socket Latency:"
if [ -f "$LOG_DIR/socket_latency.log" ]; then
cat "$LOG_DIR/socket_latency.log"
fi
echo ""
echo "Debug Socket Latency:"
if [ -f "$LOG_DIR/debug_probe.log" ]; then
cat "$LOG_DIR/debug_probe.log"
fi
echo ""
# Check for errors
echo "Errors:"
error_count=0
for f in "$LOG_DIR"/instance_*_stderr.log; do
if [ -s "$f" ]; then
instance=$(basename "$f" | sed 's/instance_\([0-9]*\)_.*/\1/')
errors=$(grep -i "error\|panic\|crash\|failed\|refused" "$f" 2>/dev/null | head -3)
if [ -n "$errors" ]; then
echo " Instance $instance:"
echo "$errors" | sed 's/^/ /'
error_count=$((error_count + 1))
fi
fi
done
if (( error_count == 0 )); then
echo " ✅ No errors detected"
else
echo ""
echo " ⚠️ $error_count instances had errors"
fi
echo ""
# Generate timeseries summary
echo "Timeseries data: $LOG_DIR/timeseries.csv"
echo " Format: timestamp,procs,total_rss_kb,server_rss_kb,server_fds,server_threads,cpu_load"
if [ -f "$LOG_DIR/timeseries.csv" ]; then
echo " Rows: $(wc -l < "$LOG_DIR/timeseries.csv")"
echo ""
echo " Peak values from timeseries:"
awk -F, '{
if ($2+0 > max_procs) max_procs=$2+0;
if ($3+0 > max_rss) max_rss=$3+0;
if ($4+0 > max_srv_rss) max_srv_rss=$4+0;
if ($5+0 > max_fds) max_fds=$5+0;
if ($6+0 > max_threads) max_threads=$6+0;
} END {
printf " Max processes: %d\n", max_procs;
printf " Max total RSS: %d KB (%d MB)\n", max_rss, max_rss/1024;
printf " Max server RSS: %d KB (%d MB)\n", max_srv_rss, max_srv_rss/1024;
printf " Max server FDs: %d\n", max_fds;
printf " Max server threads: %d\n", max_threads;
}' "$LOG_DIR/timeseries.csv"
fi
echo ""
echo "Full logs: $LOG_DIR/"
echo "========================================="