294 lines
No EOL
14 KiB
Bash
Executable file
294 lines
No EOL
14 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
# Server Capacity Metrics Capture
|
|
# Run this WHILE your load test is running
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
PROCESS_NAME="${1:-broadcast_server}"
|
|
DURATION="${2:-60}"
|
|
INTERVAL="${3:-1}"
|
|
LOGFILE="server_metrics_$(date +%Y%m%d_%H%M%S).txt"
|
|
CSVFILE="server_metrics_$(date +%Y%m%d_%H%M%S).csv"
|
|
|
|
# Find the process
|
|
SERVER_PID=$(pgrep -f "$PROCESS_NAME" | head -1)
|
|
|
|
if [ -z "$SERVER_PID" ]; then
|
|
echo "❌ Process '$PROCESS_NAME' not found"
|
|
echo "Usage: $0 [process_name] [duration_seconds] [interval_seconds]"
|
|
exit 1
|
|
fi
|
|
|
|
echo "═══════════════════════════════════════════════════════════════════"
|
|
echo "📊 Server Capacity Metrics Capture"
|
|
echo "═══════════════════════════════════════════════════════════════════"
|
|
echo "Process: $PROCESS_NAME (PID: $SERVER_PID)"
|
|
echo "Duration: ${DURATION}s"
|
|
echo "Interval: ${INTERVAL}s"
|
|
echo "Log file: $LOGFILE"
|
|
echo "CSV file: $CSVFILE"
|
|
echo "═══════════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
|
|
# Get baseline memory (before load)
|
|
BASELINE_RSS=$(ps -o rss= -p $SERVER_PID 2>/dev/null | tr -d ' ')
|
|
BASELINE_RSS_MB=$(echo "scale=2; ${BASELINE_RSS:-0} / 1024" | bc)
|
|
|
|
echo "Baseline RSS: ${BASELINE_RSS_MB} MB"
|
|
echo ""
|
|
|
|
# Write headers
|
|
{
|
|
echo "═══════════════════════════════════════════════════════════════════"
|
|
echo "SERVER CAPACITY METRICS - $(date)"
|
|
echo "Process: $PROCESS_NAME (PID: $SERVER_PID)"
|
|
echo "Baseline RSS: ${BASELINE_RSS_MB} MB"
|
|
echo "═══════════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
} > "$LOGFILE"
|
|
|
|
echo "timestamp,elapsed_sec,cpu_pct,rss_mb,vsz_mb,threads,established,time_wait,fds,total_tcp" > "$CSVFILE"
|
|
|
|
# Tracking variables for peak/avg calculation
|
|
TOTAL_CPU=0
|
|
TOTAL_RSS=0
|
|
TOTAL_CONNS=0
|
|
PEAK_CPU=0
|
|
PEAK_RSS=0
|
|
PEAK_CONNS=0
|
|
SAMPLE_COUNT=0
|
|
START_TIME=$(date +%s)
|
|
|
|
echo "Capturing metrics for ${DURATION}s... (Ctrl+C to stop early)"
|
|
echo ""
|
|
|
|
capture_sample() {
|
|
local ELAPSED=$1
|
|
|
|
# Get CPU and memory from ps (more reliable than top for scripting)
|
|
local PS_OUTPUT=$(ps -o %cpu=,rss=,vsz= -p $SERVER_PID 2>/dev/null)
|
|
local CPU=$(echo "$PS_OUTPUT" | awk '{print $1}' | tr -d ' ')
|
|
local RSS=$(echo "$PS_OUTPUT" | awk '{print $2}' | tr -d ' ')
|
|
local VSZ=$(echo "$PS_OUTPUT" | awk '{print $3}' | tr -d ' ')
|
|
|
|
# Convert to MB
|
|
local RSS_MB=$(echo "scale=2; ${RSS:-0} / 1024" | bc)
|
|
local VSZ_MB=$(echo "scale=2; ${VSZ:-0} / 1024" | bc)
|
|
|
|
# Thread count
|
|
local THREADS=$(ps -M $SERVER_PID 2>/dev/null | tail -n +2 | wc -l | tr -d ' ')
|
|
|
|
# Network stats
|
|
local ESTABLISHED=$(netstat -an 2>/dev/null | grep -E "\.8080\s" | grep -c ESTABLISHED)
|
|
local TIME_WAIT=$(netstat -an 2>/dev/null | grep -E "\.8080\s" | grep -c TIME_WAIT)
|
|
local TOTAL_TCP=$(netstat -an 2>/dev/null | grep -c tcp)
|
|
|
|
# File descriptors
|
|
local FDS=$(lsof -p $SERVER_PID 2>/dev/null | wc -l | tr -d ' ')
|
|
|
|
# Update totals for averages
|
|
TOTAL_CPU=$(echo "$TOTAL_CPU + ${CPU:-0}" | bc)
|
|
TOTAL_RSS=$(echo "$TOTAL_RSS + ${RSS_MB:-0}" | bc)
|
|
TOTAL_CONNS=$((TOTAL_CONNS + ESTABLISHED))
|
|
SAMPLE_COUNT=$((SAMPLE_COUNT + 1))
|
|
|
|
# Update peaks
|
|
if (( $(echo "${CPU:-0} > $PEAK_CPU" | bc -l) )); then
|
|
PEAK_CPU=${CPU:-0}
|
|
fi
|
|
if (( $(echo "${RSS_MB:-0} > $PEAK_RSS" | bc -l) )); then
|
|
PEAK_RSS=${RSS_MB:-0}
|
|
fi
|
|
if [ "$ESTABLISHED" -gt "$PEAK_CONNS" ]; then
|
|
PEAK_CONNS=$ESTABLISHED
|
|
fi
|
|
|
|
# Timestamp
|
|
local TS=$(date +%H:%M:%S)
|
|
|
|
# Print to console
|
|
printf "\r[%s] CPU: %5.1f%% | RSS: %7.2f MB | Conns: %5d | FDs: %5d | Threads: %3d" \
|
|
"$TS" "${CPU:-0}" "${RSS_MB:-0}" "$ESTABLISHED" "$FDS" "$THREADS"
|
|
|
|
# Write to log
|
|
{
|
|
echo "[$TS] Elapsed: ${ELAPSED}s"
|
|
echo " CPU: ${CPU:-0}%"
|
|
echo " RSS: ${RSS_MB:-0} MB"
|
|
echo " VSZ: ${VSZ_MB:-0} MB"
|
|
echo " Threads: $THREADS"
|
|
echo " Established: $ESTABLISHED"
|
|
echo " TIME_WAIT: $TIME_WAIT"
|
|
echo " FDs: $FDS"
|
|
echo " Total TCP: $TOTAL_TCP"
|
|
echo ""
|
|
} >> "$LOGFILE"
|
|
|
|
# Write to CSV
|
|
echo "$TS,$ELAPSED,${CPU:-0},${RSS_MB:-0},${VSZ_MB:-0},$THREADS,$ESTABLISHED,$TIME_WAIT,$FDS,$TOTAL_TCP" >> "$CSVFILE"
|
|
}
|
|
|
|
# Main capture loop
|
|
ELAPSED=0
|
|
while [ $ELAPSED -lt $DURATION ]; do
|
|
# Check if process still exists
|
|
if ! kill -0 $SERVER_PID 2>/dev/null; then
|
|
echo ""
|
|
echo "⚠️ Process $SERVER_PID terminated"
|
|
break
|
|
fi
|
|
|
|
capture_sample $ELAPSED
|
|
sleep $INTERVAL
|
|
ELAPSED=$((ELAPSED + INTERVAL))
|
|
done
|
|
|
|
echo ""
|
|
echo ""
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
# Calculate summary statistics
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
if [ $SAMPLE_COUNT -gt 0 ]; then
|
|
AVG_CPU=$(echo "scale=2; $TOTAL_CPU / $SAMPLE_COUNT" | bc)
|
|
AVG_RSS=$(echo "scale=2; $TOTAL_RSS / $SAMPLE_COUNT" | bc)
|
|
AVG_CONNS=$(echo "scale=0; $TOTAL_CONNS / $SAMPLE_COUNT" | bc)
|
|
|
|
# Calculate per-user metrics (using peak connections as user count)
|
|
if [ $PEAK_CONNS -gt 0 ]; then
|
|
CPU_PER_USER=$(echo "scale=4; $PEAK_CPU / $PEAK_CONNS" | bc)
|
|
RSS_PER_USER_KB=$(echo "scale=2; ($PEAK_RSS - $BASELINE_RSS_MB) * 1024 / $PEAK_CONNS" | bc)
|
|
RSS_PER_USER_MB=$(echo "scale=4; ($PEAK_RSS - $BASELINE_RSS_MB) / $PEAK_CONNS" | bc)
|
|
else
|
|
CPU_PER_USER="N/A"
|
|
RSS_PER_USER_KB="N/A"
|
|
RSS_PER_USER_MB="N/A"
|
|
fi
|
|
|
|
# Get system info
|
|
TOTAL_RAM_MB=$(sysctl -n hw.memsize 2>/dev/null | awk '{print $1/1024/1024}')
|
|
CPU_CORES=$(sysctl -n hw.ncpu 2>/dev/null)
|
|
FD_LIMIT=$(ulimit -n)
|
|
SOMAXCONN=$(sysctl -n kern.ipc.somaxconn 2>/dev/null)
|
|
|
|
# Calculate projected capacity
|
|
if [ "$RSS_PER_USER_KB" != "N/A" ] && [ $(echo "$RSS_PER_USER_KB > 0" | bc) -eq 1 ]; then
|
|
# RAM-based max (using 80% of total RAM)
|
|
AVAILABLE_RAM=$(echo "scale=0; $TOTAL_RAM_MB * 0.8" | bc)
|
|
MAX_USERS_RAM=$(echo "scale=0; ($AVAILABLE_RAM - $BASELINE_RSS_MB) * 1024 / $RSS_PER_USER_KB" | bc)
|
|
else
|
|
MAX_USERS_RAM="N/A"
|
|
fi
|
|
|
|
if [ "$CPU_PER_USER" != "N/A" ] && [ $(echo "$CPU_PER_USER > 0" | bc) -eq 1 ]; then
|
|
# CPU-based max (using 80% of all cores)
|
|
AVAILABLE_CPU=$(echo "scale=0; $CPU_CORES * 80" | bc)
|
|
MAX_USERS_CPU=$(echo "scale=0; $AVAILABLE_CPU / $CPU_PER_USER" | bc)
|
|
else
|
|
MAX_USERS_CPU="N/A"
|
|
fi
|
|
|
|
# FD-based max
|
|
MAX_USERS_FD=$((FD_LIMIT - 100))
|
|
|
|
# Overall max is minimum of all limits
|
|
# (simplified - just report each separately)
|
|
|
|
# Generate report
|
|
REPORT=$(cat << EOF
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
📊 SERVER CAPACITY ANALYSIS REPORT
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
SYSTEM INFORMATION
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
Total RAM: ${TOTAL_RAM_MB} MB
|
|
CPU Cores: ${CPU_CORES}
|
|
FD Limit: ${FD_LIMIT}
|
|
somaxconn: ${SOMAXCONN}
|
|
|
|
BASELINE METRICS (before load)
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
Baseline RSS: ${BASELINE_RSS_MB} MB
|
|
|
|
OBSERVED METRICS (during ${SAMPLE_COUNT} samples over ${DURATION}s)
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
AVERAGE PEAK
|
|
CPU: ${AVG_CPU}% ${PEAK_CPU}%
|
|
RSS: ${AVG_RSS} MB ${PEAK_RSS} MB
|
|
Connections: ${AVG_CONNS} ${PEAK_CONNS}
|
|
|
|
PER-USER RESOURCE CONSUMPTION (at peak ${PEAK_CONNS} users)
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
CPU per user: ${CPU_PER_USER}%
|
|
RAM per user: ${RSS_PER_USER_KB} KB (${RSS_PER_USER_MB} MB)
|
|
|
|
PROJECTED CAPACITY LIMITS
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
Max users (RAM @ 80%): ${MAX_USERS_RAM}
|
|
Max users (CPU @ 80%): ${MAX_USERS_CPU}
|
|
Max users (FD limit): ${MAX_USERS_FD}
|
|
Max users (somaxconn): ${SOMAXCONN}
|
|
|
|
RECOMMENDED SERVER SPECS BY USER COUNT
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
┌────────────┬────────────┬────────────┬────────────────────────────────────┐
|
|
│ Users │ RAM Needed │ CPU Cores │ Notes │
|
|
├────────────┼────────────┼────────────┼────────────────────────────────────┤
|
|
EOF
|
|
)
|
|
|
|
# Calculate spec table dynamically
|
|
for USERS in 1000 2500 5000 10000 25000 50000 100000; do
|
|
if [ "$RSS_PER_USER_KB" != "N/A" ] && [ "$CPU_PER_USER" != "N/A" ]; then
|
|
RAM_NEEDED=$(echo "scale=0; $BASELINE_RSS_MB + ($USERS * $RSS_PER_USER_KB / 1024)" | bc)
|
|
CPU_NEEDED=$(echo "scale=1; $USERS * $CPU_PER_USER / 100" | bc)
|
|
CPU_CORES_NEEDED=$(echo "scale=0; ($CPU_NEEDED + 0.9) / 1" | bc) # Round up
|
|
|
|
if [ $CPU_CORES_NEEDED -lt 1 ]; then
|
|
CPU_CORES_NEEDED=1
|
|
fi
|
|
|
|
# Add notes based on scale
|
|
if [ $USERS -le $PEAK_CONNS ]; then
|
|
NOTES="✅ Tested stable"
|
|
elif [ $USERS -le 5000 ]; then
|
|
NOTES="Likely achievable"
|
|
elif [ $USERS -le 20000 ]; then
|
|
NOTES="Needs optimization"
|
|
else
|
|
NOTES="Consider clustering"
|
|
fi
|
|
|
|
REPORT="${REPORT}
|
|
│ $(printf '%10d' $USERS) │ $(printf '%7d MB' $RAM_NEEDED) │ $(printf '%10.0f' $CPU_CORES_NEEDED) │ $(printf '%-34s' "$NOTES") │"
|
|
fi
|
|
done
|
|
|
|
REPORT="${REPORT}
|
|
└────────────┴────────────┴────────────┴────────────────────────────────────┘
|
|
|
|
FILES GENERATED
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
Detailed log: $LOGFILE
|
|
CSV data: $CSVFILE
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
EOF
|
|
|
|
# Print and save report
|
|
echo "$REPORT"
|
|
echo "$REPORT" >> "$LOGFILE"
|
|
|
|
# Also save a summary file
|
|
SUMMARY_FILE="capacity_summary_$(date +%Y%m%d_%H%M%S).txt"
|
|
echo "$REPORT" > "$SUMMARY_FILE"
|
|
echo ""
|
|
echo "📁 Summary saved to: $SUMMARY_FILE"
|
|
|
|
else
|
|
echo "❌ No samples captured"
|
|
fi |