#!/bin/bash # PHP-FPM Batch Analyzer - One-Shot Diagnostic Script # Analyzes all domains on server, shows current vs recommended max_children # Shows memory impact and optimization opportunities # Drop in, run once, then delete set -e PHP_TOOLKIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)" # Source required libraries source "$PHP_TOOLKIT_DIR/lib/common-functions.sh" 2>/dev/null || { echo "ERROR: common-functions.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/user-manager.sh" 2>/dev/null || { echo "ERROR: user-manager.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/php-analyzer.sh" 2>/dev/null || { echo "ERROR: php-analyzer.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/php-calculator-improved.sh" 2>/dev/null || { echo "ERROR: php-calculator-improved.sh not found"; exit 1; } source "$PHP_TOOLKIT_DIR/lib/php-scanner.sh" 2>/dev/null || { echo "ERROR: php-scanner.sh not found"; exit 1; } # Color codes RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' WHITE='\033[1;37m' BOLD='\033[1m' NC='\033[0m' cecho() { echo -e "$@" } # ============================================================================ # INITIALIZATION # ============================================================================ initialize_system_detection if [ "$EUID" -ne 0 ]; then cecho "${RED}ERROR: This script must be run as root${NC}" exit 1 fi # ============================================================================ # MAIN ANALYSIS # ============================================================================ cecho "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}" cecho "${CYAN}║${WHITE} PHP-FPM BATCH ANALYZER - DIAGNOSTIC REPORT ${CYAN}║${NC}" cecho "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}" echo "" # Get server info cecho "${WHITE}${BOLD}SERVER INFORMATION${NC}" cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" TOTAL_RAM_MB=$(free -m | awk '/^Mem:/ {print $2}') CPU_CORES=$(nproc) CONTROL_PANEL="$SYS_CONTROL_PANEL" cecho " Total RAM: ${WHITE}${TOTAL_RAM_MB}MB${NC}" cecho " CPU Cores: ${WHITE}${CPU_CORES}${NC}" cecho " Control Panel: ${WHITE}${CONTROL_PANEL}${NC}" cecho " Scan Date: ${WHITE}$(date)${NC}" echo "" # ============================================================================ # STEP 1: CALCULATE SERVER CAPACITY # ============================================================================ cecho "${WHITE}${BOLD}STEP 1: SERVER CAPACITY ANALYSIS${NC}" cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" server_capacity_result=$(calculate_server_capacity "$TOTAL_RAM_MB") server_capacity=$(echo "$server_capacity_result" | cut -d'|' -f1) available_memory=$(echo "$server_capacity_result" | cut -d'|' -f2) memory_per_process=$(echo "$server_capacity_result" | cut -d'|' -f3) cecho " Available RAM for PHP-FPM: ${WHITE}${available_memory}MB${NC}" cecho " Memory per process: ${WHITE}${memory_per_process}MB${NC}" cecho " Server capacity: ${WHITE}${server_capacity}${NC} total max_children" echo "" # ============================================================================ # STEP 2: DOMAIN ENUMERATION & TRAFFIC ANALYSIS # ============================================================================ cecho "${WHITE}${BOLD}STEP 2: DOMAIN ENUMERATION & TRAFFIC ANALYSIS${NC}" cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" # Get all users and domains users=$(list_all_users) # CRITICAL FIX: Build list of ALL domains on server FIRST # This is needed for accurate traffic percentage calculation declare -a all_server_domains local all_domains_string="" while IFS= read -r username; do [ -z "$username" ] && continue user_domains=$(get_user_domains "$username") while IFS= read -r domain; do [ -z "$domain" ] && continue all_domains_string="$all_domains_string$domain"$'\n' done <<< "$user_domains" done <<< "$users" # Initialize tracking arrays declare -a domain_list declare -a domain_owner declare -a current_max_children declare -a recommended_max_children declare -a memory_impact declare -a needs_optimization declare -a peak_concurrent declare -a pm_mode declare -a pm_max_requests declare -a pm_min_spare declare -a pm_max_spare declare -a pm_idle_timeout declare -a traffic_percentage_arr declare -a limiting_factor_arr TOTAL_DOMAINS=0 TOTAL_CURRENT_MEMORY=0 TOTAL_RECOMMENDED_MEMORY=0 TOTAL_CURRENT_MEMORY_WITH_MAX=0 while IFS= read -r username; do [ -z "$username" ] && continue user_domains=$(get_user_domains "$username") while IFS= read -r domain; do [ -z "$domain" ] && continue TOTAL_DOMAINS=$((TOTAL_DOMAINS + 1)) domain_list[$TOTAL_DOMAINS]="$domain" domain_owner[$TOTAL_DOMAINS]="$username" # Find pool config pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null) if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then current_max_children[$TOTAL_DOMAINS]="ERROR" recommended_max_children[$TOTAL_DOMAINS]="ERROR" memory_impact[$TOTAL_DOMAINS]="?" continue fi # Get current max_children current=$(grep "^pm.max_children" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') current=${current:-40} current_max_children[$TOTAL_DOMAINS]="$current" # Get all pool settings pm_mode_val=$(grep "^pm = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') pm_mode[$TOTAL_DOMAINS]="${pm_mode_val:-static}" pm_max_req=$(grep "^pm.max_requests = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') pm_max_requests[$TOTAL_DOMAINS]="${pm_max_req:-0}" pm_min=$(grep "^pm.min_spare_servers = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') pm_min_spare[$TOTAL_DOMAINS]="${pm_min:-2}" pm_max=$(grep "^pm.max_spare_servers = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') pm_max_spare[$TOTAL_DOMAINS]="${pm_max:-8}" pm_idle=$(grep "^pm.process_idle_timeout = " "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') pm_idle_timeout[$TOTAL_DOMAINS]="${pm_idle:-10}" # Calculate recommended using THREE-CONSTRAINT INTELLIGENT ALGORITHM # Get traffic percentage for this domain # CRITICAL FIX: Pass ALL server domains, not just user's domains! traffic_percentage=$(get_domain_traffic_percentage "$username" "$domain" "$all_domains_string" 2>/dev/null | cut -d'|' -f1) traffic_percentage=${traffic_percentage:-50} # Use intelligent three-constraint model: MIN(memory, traffic, fair_share) recommended_result=$(calculate_optimal_php_settings_intelligent "$username" "$TOTAL_RAM_MB" "$server_capacity" "$traffic_percentage" 2>/dev/null || echo "20|dynamic|1|5|ERROR|Failed") recommended=$(echo "$recommended_result" | cut -d'|' -f1) recommended=${recommended:-20} limiting_factor=$(echo "$recommended_result" | cut -d'|' -f5) recommended_max_children[$TOTAL_DOMAINS]="$recommended" traffic_percentage_arr[$TOTAL_DOMAINS]="$traffic_percentage" limiting_factor_arr[$TOTAL_DOMAINS]="$limiting_factor" # Calculate memory impact using ACTUAL memory per process (not hardcoded 20MB) current_memory=$((current * memory_per_process)) recommended_memory=$((recommended * memory_per_process)) impact=$((current_memory - recommended_memory)) memory_impact[$TOTAL_DOMAINS]="$impact" # Get peak concurrent requests for this domain peak=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "?") peak_concurrent[$TOTAL_DOMAINS]="$peak" # Track totals TOTAL_CURRENT_MEMORY=$((TOTAL_CURRENT_MEMORY + current_memory)) TOTAL_RECOMMENDED_MEMORY=$((TOTAL_RECOMMENDED_MEMORY + recommended_memory)) TOTAL_CURRENT_MEMORY_WITH_MAX=$((TOTAL_CURRENT_MEMORY_WITH_MAX + current_memory)) # Determine if optimization needed # Flag as YES if: different from current (increase or decrease) # AND has meaningful traffic (>= 5 concurrent) OR memory efficiency gain (> 20% reduction) memory_reduction=0 if [ "$recommended" -lt "$current" ]; then memory_reduction=$(( (current - recommended) * 100 / current )) fi if [ "$recommended" -ne "$current" ]; then # Check if change is meaningful: # 1. Has significant traffic (>= 5 concurrent requests) # 2. OR significant memory reduction (>= 20%) has_traffic=0 [ "$peak" != "?" ] && [ "$peak" -ge 5 ] && has_traffic=1 if [ "$has_traffic" = "1" ] || [ "$memory_reduction" -ge 20 ]; then needs_optimization[$TOTAL_DOMAINS]="YES" else needs_optimization[$TOTAL_DOMAINS]="NO" fi else needs_optimization[$TOTAL_DOMAINS]="NO" fi done <<< "$user_domains" done <<< "$users" # ============================================================================ # DISPLAY RESULTS (Prioritized by Traffic) # ============================================================================ # Build sortable list with priority (traffic-based) sorted_indices=() domain_sort_data=() for idx in $(seq 1 $TOTAL_DOMAINS); do domain="${domain_list[$idx]}" peak="${peak_concurrent[$idx]}" optimize="${needs_optimization[$idx]}" if [ "$peak" == "?" ]; then peak=0 fi # Create sort key: prioritize domains needing optimization with high traffic if [ "$optimize" == "YES" ]; then # High priority: domains needing optimization with traffic >= 5 if [[ "$peak" =~ ^[0-9]+$ ]] && [ "$peak" -ge 5 ]; then sort_priority="0" # Highest priority else sort_priority="1" # Medium priority (needs optimization, low traffic) fi else sort_priority="2" # Low priority (already optimized) fi domain_sort_data+=("$sort_priority|$peak|$idx") done # Sort by priority then by peak concurrent requests (descending) mapfile -t sorted_data < <(printf '%s\n' "${domain_sort_data[@]}" | sort -t'|' -k1,1 -k2,2nr) # Extract sorted indices for sort_entry in "${sorted_data[@]}"; do idx=$(echo "$sort_entry" | cut -d'|' -f3) sorted_indices+=("$idx") done # Sort and display domains (prioritized) OPTIMIZATION_COUNT=0 for idx in "${sorted_indices[@]}"; do domain="${domain_list[$idx]}" owner="${domain_owner[$idx]}" current="${current_max_children[$idx]}" recommended="${recommended_max_children[$idx]}" impact="${memory_impact[$idx]}" optimize="${needs_optimization[$idx]}" peak="${peak_concurrent[$idx]}" if [ "$current" == "ERROR" ]; then continue fi # Determine traffic indicator traffic_indicator="" if [[ "$peak" =~ ^[0-9]+$ ]]; then if [ "$peak" -ge 20 ]; then traffic_indicator="${RED}⚠ CRITICAL TRAFFIC (${peak})${NC}" elif [ "$peak" -ge 10 ]; then traffic_indicator="${YELLOW}⚠ HIGH TRAFFIC (${peak})${NC}" elif [ "$peak" -ge 5 ]; then traffic_indicator="${CYAN}→ MEDIUM TRAFFIC (${peak})${NC}" else traffic_indicator="${WHITE}○ LOW TRAFFIC (${peak})${NC}" fi else traffic_indicator="${WHITE}○ TRAFFIC UNKNOWN${NC}" fi # Format output with all pool settings if [ "$optimize" == "YES" ]; then cecho "${YELLOW}[$idx]${NC} $domain" cecho " Owner: $owner" cecho " Traffic: $traffic_indicator (${traffic_percentage_arr[$idx]}% of server)" cecho " Limiting Factor: ${limiting_factor_arr[$idx]}" cecho "" cecho " ${BOLD}Current Pool Settings:${NC}" cecho " pm.max_children: ${RED}$current${NC} → Recommended: ${GREEN}$recommended${NC}" cecho " pm: ${WHITE}${pm_mode[$idx]}${NC}" cecho " pm.min_spare_servers: ${WHITE}${pm_min_spare[$idx]}${NC}" cecho " pm.max_spare_servers: ${WHITE}${pm_max_spare[$idx]}${NC}" cecho " pm.max_requests: ${WHITE}${pm_max_requests[$idx]}${NC}" cecho " pm.process_idle_timeout: ${WHITE}${pm_idle_timeout[$idx]}${NC}" cecho "" cecho " Memory impact: ${GREEN}+${impact}MB${NC} if optimized" cecho " Status: ${YELLOW}NEEDS OPTIMIZATION${NC}" OPTIMIZATION_COUNT=$((OPTIMIZATION_COUNT + 1)) else cecho "${GREEN}[$idx]${NC} $domain" cecho " Owner: $owner" cecho " Traffic: $traffic_indicator" cecho "" cecho " ${BOLD}Pool Settings:${NC}" cecho " pm.max_children: $current" cecho " pm: ${WHITE}${pm_mode[$idx]}${NC}" cecho " pm.min_spare_servers: ${WHITE}${pm_min_spare[$idx]}${NC}" cecho " pm.max_spare_servers: ${WHITE}${pm_max_spare[$idx]}${NC}" cecho " pm.max_requests: ${WHITE}${pm_max_requests[$idx]}${NC}" cecho " pm.process_idle_timeout: ${WHITE}${pm_idle_timeout[$idx]}${NC}" cecho "" cecho " Status: ${GREEN}OK${NC}" fi echo "" done # ============================================================================ # SERVER-WIDE SUMMARY # ============================================================================ echo "" cecho "${WHITE}${BOLD}SERVER-WIDE SUMMARY${NC}" cecho "${CYAN}═════════════════════════════════════════════════════════════════════${NC}" echo "" # Calculate percentages CURRENT_PERCENT=$((TOTAL_CURRENT_MEMORY * 100 / TOTAL_RAM_MB)) RECOMMENDED_PERCENT=$((TOTAL_RECOMMENDED_MEMORY * 100 / TOTAL_RAM_MB)) POTENTIAL_SAVINGS=$((TOTAL_CURRENT_MEMORY - TOTAL_RECOMMENDED_MEMORY)) POTENTIAL_SAVINGS_PERCENT=$((POTENTIAL_SAVINGS * 100 / TOTAL_CURRENT_MEMORY)) cecho " Total domains analyzed: ${WHITE}$TOTAL_DOMAINS${NC}" cecho " Domains needing optimization: ${YELLOW}$OPTIMIZATION_COUNT${NC}" cecho " Domains already optimized: ${GREEN}$((TOTAL_DOMAINS - OPTIMIZATION_COUNT))${NC}" echo "" cecho " ${BOLD}Current Memory Allocation:${NC}" cecho " Total: ${WHITE}${TOTAL_CURRENT_MEMORY}MB${NC} (${RED}${CURRENT_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB RAM)" echo "" cecho " ${BOLD}Recommended Memory Allocation:${NC}" cecho " Total: ${WHITE}${TOTAL_RECOMMENDED_MEMORY}MB${NC} (${GREEN}${RECOMMENDED_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB RAM)" echo "" cecho " ${BOLD}Optimization Potential:${NC}" cecho " Memory that could be freed: ${GREEN}${POTENTIAL_SAVINGS}MB${NC} (${POTENTIAL_SAVINGS_PERCENT}% reduction)" echo "" # Combined Memory Capacity Check cecho "${WHITE}${BOLD}COMBINED MEMORY CAPACITY (If ALL pools hit max_children):${NC}" ALL_MAX_PERCENT=$((TOTAL_CURRENT_MEMORY_WITH_MAX * 100 / TOTAL_RAM_MB)) if [ "$ALL_MAX_PERCENT" -gt 100 ]; then cecho " Total if all at max: ${RED}${TOTAL_CURRENT_MEMORY_WITH_MAX}MB${NC} (${RED}${ALL_MAX_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB)" cecho " Status: ${RED}${BOLD}CRITICAL - SERVER WILL RUN OUT OF MEMORY!${NC}" cecho " ${RED}⚠ The server would exceed available RAM by $((TOTAL_CURRENT_MEMORY_WITH_MAX - TOTAL_RAM_MB))MB${NC}" elif [ "$ALL_MAX_PERCENT" -gt 90 ]; then cecho " Total if all at max: ${YELLOW}${TOTAL_CURRENT_MEMORY_WITH_MAX}MB${NC} (${YELLOW}${ALL_MAX_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB)" cecho " Status: ${YELLOW}${BOLD}WARNING - High memory pressure${NC}" cecho " ${YELLOW}⚠ Only $((TOTAL_RAM_MB - TOTAL_CURRENT_MEMORY_WITH_MAX))MB headroom remaining${NC}" elif [ "$ALL_MAX_PERCENT" -gt 75 ]; then cecho " Total if all at max: ${CYAN}${TOTAL_CURRENT_MEMORY_WITH_MAX}MB${NC} (${CYAN}${ALL_MAX_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB)" cecho " Status: ${CYAN}CAUTION - Monitor memory usage${NC}" else cecho " Total if all at max: ${GREEN}${TOTAL_CURRENT_MEMORY_WITH_MAX}MB${NC} (${GREEN}${ALL_MAX_PERCENT}%${NC} of ${TOTAL_RAM_MB}MB)" cecho " Status: ${GREEN}${BOLD}HEALTHY${NC} - Sufficient memory headroom" fi echo "" if [ "$OPTIMIZATION_COUNT" -gt 0 ]; then cecho " ${BOLD}Recommendation:${NC}" cecho " ${YELLOW}⚠ $OPTIMIZATION_COUNT domain(s) could be optimized${NC}" cecho " Run: ${WHITE}php-optimizer.sh${NC} → ${CYAN}Option 5${NC} (Optimize Server-Wide)" else cecho " ${BOLD}Status:${NC}" cecho " ${GREEN}✓ All domains are already optimized${NC}" fi echo "" cecho "${CYAN}═════════════════════════════════════════════════════════════════════${NC}" echo "" # ============================================================================ # SAFETY WARNINGS # ============================================================================ # Check memory headroom AVAILABLE_AFTER_RECOMMENDED=$((TOTAL_RAM_MB - TOTAL_RECOMMENDED_MEMORY)) if [ "$AVAILABLE_AFTER_RECOMMENDED" -lt 2048 ]; then cecho "${RED}${BOLD}⚠ WARNING: Limited memory headroom${NC}" cecho " After applying recommended settings, only ${AVAILABLE_AFTER_RECOMMENDED}MB would be available" echo "" fi # Check if already optimized if [ "$OPTIMIZATION_COUNT" -eq 0 ]; then cecho "${GREEN}${BOLD}✓ All domains are already optimized${NC}" echo "" fi # ============================================================================ # CLEANUP # ============================================================================ cecho "${WHITE}${BOLD}Report complete${NC}" cecho " Generated: $(date '+%Y-%m-%d %H:%M:%S')" echo "" # Ask if user wants to save report echo "" cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" read -p "Save detailed report to file? (y/n): " save_report echo "" REPORT_FILE="/tmp/php-fpm-analysis-$(date +%Y%m%d-%H%M%S).txt" if [[ "$save_report" =~ ^[yY]$ ]] && [ -w /tmp ]; then { echo "╔════════════════════════════════════════════════════════════════════════╗" echo "║ PHP-FPM BATCH ANALYSIS REPORT ║" echo "╚════════════════════════════════════════════════════════════════════════╝" echo "" echo "REPORT GENERATED: $(date '+%Y-%m-%d %H:%M:%S')" echo "" echo "═══════════════════════════════════════════════════════════════════════════" echo "SERVER INFORMATION" echo "═══════════════════════════════════════════════════════════════════════════" echo "Total RAM: ${TOTAL_RAM_MB}MB" echo "CPU Cores: ${CPU_CORES}" echo "Control Panel: ${CONTROL_PANEL}" echo "" echo "═══════════════════════════════════════════════════════════════════════════" echo "DOMAIN-BY-DOMAIN ANALYSIS" echo "═══════════════════════════════════════════════════════════════════════════" echo "" # Output domain details for idx in $(seq 1 $TOTAL_DOMAINS); do domain="${domain_list[$idx]}" owner="${domain_owner[$idx]}" current="${current_max_children[$idx]}" recommended="${recommended_max_children[$idx]}" impact="${memory_impact[$idx]}" peak="${peak_concurrent[$idx]}" if [ "$current" == "ERROR" ]; then continue fi echo "[$idx] $domain" echo " Owner: $owner" echo " Peak concurrent requests: $peak" echo " Current max_children: $current" echo " Recommended max_children: $recommended" echo " Memory impact: ${impact}MB" echo "" done echo "═══════════════════════════════════════════════════════════════════════════" echo "SUMMARY" echo "═══════════════════════════════════════════════════════════════════════════" echo "" echo "Total domains analyzed: $TOTAL_DOMAINS" echo "Domains needing optimization: $OPTIMIZATION_COUNT" echo "Domains already optimized: $((TOTAL_DOMAINS - OPTIMIZATION_COUNT))" echo "" echo "Current memory allocation: ${TOTAL_CURRENT_MEMORY}MB (${CURRENT_PERCENT}% of RAM)" echo "Recommended memory allocation: ${TOTAL_RECOMMENDED_MEMORY}MB (${RECOMMENDED_PERCENT}% of RAM)" echo "Potential savings: ${POTENTIAL_SAVINGS}MB (${POTENTIAL_SAVINGS_PERCENT}% reduction)" echo "" echo "Memory available after optimization: $((TOTAL_RAM_MB - TOTAL_RECOMMENDED_MEMORY))MB" echo "" echo "═══════════════════════════════════════════════════════════════════════════" echo "RECOMMENDATIONS" echo "═══════════════════════════════════════════════════════════════════════════" echo "" if [ "$OPTIMIZATION_COUNT" -gt 0 ]; then echo "Action: Optimize $OPTIMIZATION_COUNT domain(s)" echo "Run: php-optimizer.sh → Option 5 (Optimize Server-Wide PHP Settings)" echo "Expected outcome: Free up ${POTENTIAL_SAVINGS}MB of RAM" else echo "Status: All domains are already optimally configured" echo "No optimization needed at this time" fi if [ "$(echo "$AVAILABLE_AFTER_RECOMMENDED < 2048" | bc)" -eq 1 ] 2>/dev/null; then echo "" echo "WARNING: Memory headroom after optimization is limited (${AVAILABLE_AFTER_RECOMMENDED}MB)" echo "Consider reducing some domain limits further or upgrading server RAM" fi } > "$REPORT_FILE" cecho "${GREEN}✓${NC} Detailed report saved to: ${CYAN}$REPORT_FILE${NC}" echo "" fi echo ""