Files
Linux-Server-Management-Too…/modules/performance/php-optimizer.sh
T
cschantz 257e846685 Add server-wide memory capacity check (Option 9) - Critical OOM prevention
NEW FEATURES:
- Menu Option 9: Check Server Memory Capacity (OOM Risk)
- Calculates total memory if ALL PHP-FPM pools hit max_children
- Identifies servers at risk of Out-Of-Memory (OOM) kills
- Provides balanced memory allocation recommendations

TWO NEW ANALYZER FUNCTIONS:

1. calculate_server_memory_capacity()
   - Iterates through all users/PHP-FPM pools
   - Calculates: max_children × avg_memory_per_process
   - Sums total across all pools
   - Compares to total RAM
   - Returns: total_required|total_ram|percentage|status

   Status Levels:
   - HEALTHY:  <60% RAM (safe)
   - CAUTION:  60-75% RAM (watch)
   - WARNING:  75-90% RAM (risky)
   - CRITICAL: >90% RAM (OOM likely!)

2. calculate_balanced_memory_allocation()
   - Analyzes traffic for each user (requests/minute)
   - Calculates proportional memory allocation
   - Reserves 20% of RAM for system (min 2GB)
   - Distributes remaining RAM based on traffic
   - Returns recommendations: REDUCE / INCREASE / OPTIMAL

   Example output:
   USER     CURRENT_MAX  AVG_MB  TRAFFIC_RPM  RECOMMENDED_MAX  REASON
   user1    50          45MB     120          75              INCREASE (traffic demands)
   user2    100         60MB     10           15              REDUCE (prevent OOM)

MENU OPTION 9 FEATURES:
- Shows total RAM vs required memory
- Displays percentage and color-coded status
- Optional per-user breakdown table
- Optional balanced recommendations
- Interactive: ask user what details to show

USE CASE:
Server has 16GB RAM. 10 users each with max_children=50, avg 50MB/process.
Total required: 10 × 50 × 50MB = 25GB
Percentage: 156% of RAM → CRITICAL!
Result: Server WILL run out of memory and kill processes!

This feature addresses user's request:
"calculating max children and memory allocation and then combining all the
 accounts to see if the memory will hit over the memory cap if at capacity"

CRITICAL for preventing OOM kills on shared hosting servers!
2025-12-02 20:39:20 -05:00

943 lines
34 KiB
Bash
Executable File

#!/bin/bash
# PHP & Server Performance Optimizer
# Interactive tool for analyzing and optimizing PHP-FPM configurations
# Part of Server Toolkit - Phase 3: Main Interactive Script
# Source required libraries
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)"
source "$SCRIPT_DIR/lib/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; exit 1; }
source "$SCRIPT_DIR/lib/php-analyzer.sh" 2>/dev/null || { echo "ERROR: php-analyzer.sh not found"; exit 1; }
source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; exit 1; }
source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || { echo "ERROR: user-manager.sh not found"; exit 1; }
# Color codes (using safe echo -e)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
BOLD='\033[1m'
NC='\033[0m' # No Color
# Safe color echo function
cecho() {
echo -e "$@"
}
# ============================================================================
# BANNER & DISPLAY FUNCTIONS
# ============================================================================
show_banner() {
clear
cecho "${CYAN}╔══════════════════════════════════════════════════════════════════════╗${NC}"
cecho "${CYAN}${WHITE} PHP & SERVER PERFORMANCE OPTIMIZER ${CYAN}${NC}"
cecho "${CYAN}╚══════════════════════════════════════════════════════════════════════╝${NC}"
echo ""
}
show_main_menu() {
cecho "${WHITE}${BOLD}MAIN MENU${NC}"
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
cecho " ${GREEN}1${NC}) Analyze Single Domain"
cecho " ${GREEN}2${NC}) Analyze All Domains (Server-Wide)"
cecho " ${GREEN}3${NC}) Quick Health Check (All Domains)"
cecho " ${GREEN}4${NC}) Optimize Domain PHP Settings"
cecho " ${GREEN}5${NC}) Optimize Server-Wide PHP Settings"
cecho " ${GREEN}6${NC}) View OPcache Statistics"
cecho " ${GREEN}7${NC}) View PHP-FPM Process Stats"
cecho " ${GREEN}8${NC}) Check for Configuration Issues"
cecho " ${GREEN}9${NC}) Check Server Memory Capacity (OOM Risk)"
echo ""
cecho " ${YELLOW}b${NC}) Backup Current Configurations"
cecho " ${YELLOW}r${NC}) Restore from Backup"
echo ""
cecho " ${RED}q${NC}) Quit"
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
}
# ============================================================================
# DOMAIN SELECTION
# ============================================================================
select_domain() {
local action="${1:-analyze}"
cecho "${WHITE}${BOLD}SELECT DOMAIN${NC}"
echo ""
# Get all users with domains
local users
users=$(list_all_users)
if [ -z "$users" ]; then
cecho "${RED}ERROR: No users found on system${NC}"
read -p "Press Enter to continue..."
return 1
fi
# Build domain list
declare -a domains
declare -A domain_to_user
while IFS= read -r username; do
local user_domains
user_domains=$(get_user_domains "$username")
while IFS= read -r domain; do
[ -z "$domain" ] && continue
domains+=("$domain")
domain_to_user["$domain"]="$username"
done <<< "$user_domains"
done <<< "$users"
if [ ${#domains[@]} -eq 0 ]; then
cecho "${RED}ERROR: No domains found on system${NC}"
read -p "Press Enter to continue..."
return 1
fi
# Display numbered list
cecho "${CYAN}Available domains:${NC}"
echo ""
local index=1
for domain in "${domains[@]}"; do
local username="${domain_to_user[$domain]}"
local php_version
php_version=$(detect_php_version_for_domain "$domain" 2>/dev/null || echo "unknown")
printf " ${GREEN}%-3d${NC}) %-40s ${CYAN}[${username}]${NC} ${YELLOW}(${php_version})${NC}\n" "$index" "$domain"
index=$((index + 1))
done
echo ""
read -p "Select domain number (or 'q' to cancel): " selection
if [[ "$selection" == "q" ]]; then
return 1
fi
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#domains[@]} ]; then
cecho "${RED}Invalid selection${NC}"
sleep 2
return 1
fi
# Return selected domain and username
local selected_domain="${domains[$((selection - 1))]}"
local selected_user="${domain_to_user[$selected_domain]}"
echo "$selected_domain|$selected_user"
return 0
}
# ============================================================================
# OPTION 1: ANALYZE SINGLE DOMAIN
# ============================================================================
analyze_single_domain() {
show_banner
local selection
selection=$(select_domain "analyze")
if [ $? -ne 0 ] || [ -z "$selection" ]; then
return
fi
local domain username
domain=$(echo "$selection" | cut -d'|' -f1)
username=$(echo "$selection" | cut -d'|' -f2)
show_banner
cecho "${WHITE}${BOLD}Analyzing: ${GREEN}$domain${WHITE} (user: ${CYAN}$username${WHITE})${NC}"
echo ""
# Run comprehensive analysis
analyze_domain_php "$username" "$domain"
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 2: ANALYZE ALL DOMAINS
# ============================================================================
analyze_all_domains() {
show_banner
cecho "${WHITE}${BOLD}SERVER-WIDE ANALYSIS${NC}"
echo ""
cecho "${YELLOW}This will analyze PHP configuration for ALL domains...${NC}"
echo ""
read -p "Continue? (y/n): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
return
fi
show_banner
cecho "${WHITE}${BOLD}Analyzing all domains...${NC}"
echo ""
# Get all users
local users
users=$(list_all_users)
local total_domains=0
local domains_with_issues=0
while IFS= read -r username; do
local user_domains
user_domains=$(get_user_domains "$username")
while IFS= read -r domain; do
[ -z "$domain" ] && continue
total_domains=$((total_domains + 1))
cecho "${CYAN}[$total_domains] Analyzing: ${WHITE}$domain${NC}"
# Detect issues
local issues
issues=$(detect_php_config_issues "$username" "$domain")
# Count critical/high severity issues
local critical_count
critical_count=$(echo "$issues" | grep -c "CRITICAL\|HIGH" || echo "0")
if [ "$critical_count" -gt 0 ]; then
domains_with_issues=$((domains_with_issues + 1))
cecho " ${RED}$critical_count critical/high severity issues detected${NC}"
# Show issues
while IFS='|' read -r issue_type severity message recommendation; do
[ -z "$issue_type" ] && continue
if [[ "$severity" == "CRITICAL" ]] || [[ "$severity" == "HIGH" ]]; then
cecho " ${RED}[$severity]${NC} $issue_type: $message"
fi
done <<< "$issues"
else
cecho " ${GREEN}✓ No critical issues${NC}"
fi
echo ""
done <<< "$user_domains"
done <<< "$users"
# Summary
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
cecho "${WHITE}${BOLD}SUMMARY${NC}"
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
cecho " Total domains analyzed: ${WHITE}$total_domains${NC}"
cecho " Domains with issues: ${RED}$domains_with_issues${NC}"
cecho " Domains healthy: ${GREEN}$((total_domains - domains_with_issues))${NC}"
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 3: QUICK HEALTH CHECK
# ============================================================================
quick_health_check() {
show_banner
cecho "${WHITE}${BOLD}QUICK HEALTH CHECK${NC}"
echo ""
# Get all users
local users
users=$(list_all_users)
declare -A issue_counts
issue_counts["CRITICAL"]=0
issue_counts["HIGH"]=0
issue_counts["MEDIUM"]=0
issue_counts["LOW"]=0
local total_domains=0
while IFS= read -r username; do
local user_domains
user_domains=$(get_user_domains "$username")
while IFS= read -r domain; do
[ -z "$domain" ] && continue
total_domains=$((total_domains + 1))
# Detect issues
local issues
issues=$(detect_php_config_issues "$username" "$domain")
# Count by severity
while IFS='|' read -r issue_type severity message recommendation; do
[ -z "$severity" ] && continue
issue_counts["$severity"]=$((issue_counts["$severity"] + 1))
done <<< "$issues"
done <<< "$user_domains"
done <<< "$users"
# Display results
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
cecho "${WHITE}${BOLD}HEALTH CHECK RESULTS${NC}"
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
cecho " Total Domains: ${WHITE}$total_domains${NC}"
echo ""
cecho " ${RED}CRITICAL${NC} issues: ${issue_counts["CRITICAL"]}"
cecho " ${YELLOW}HIGH${NC} issues: ${issue_counts["HIGH"]}"
cecho " ${BLUE}MEDIUM${NC} issues: ${issue_counts["MEDIUM"]}"
cecho " ${GREEN}LOW${NC} issues: ${issue_counts["LOW"]}"
echo ""
# Overall health score
local health_score
health_score=$((100 - (issue_counts["CRITICAL"] * 20) - (issue_counts["HIGH"] * 10) - (issue_counts["MEDIUM"] * 5) - (issue_counts["LOW"] * 2)))
[ "$health_score" -lt 0 ] && health_score=0
if [ "$health_score" -ge 90 ]; then
cecho " Overall Health: ${GREEN}${BOLD}$health_score/100 - EXCELLENT${NC}"
elif [ "$health_score" -ge 70 ]; then
cecho " Overall Health: ${YELLOW}${BOLD}$health_score/100 - GOOD${NC}"
elif [ "$health_score" -ge 50 ]; then
cecho " Overall Health: ${YELLOW}${BOLD}$health_score/100 - FAIR${NC}"
else
cecho " Overall Health: ${RED}${BOLD}$health_score/100 - POOR${NC}"
fi
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 4: OPTIMIZE DOMAIN
# ============================================================================
optimize_domain() {
show_banner
local selection
selection=$(select_domain "optimize")
if [ $? -ne 0 ] || [ -z "$selection" ]; then
return
fi
local domain username
domain=$(echo "$selection" | cut -d'|' -f1)
username=$(echo "$selection" | cut -d'|' -f2)
show_banner
cecho "${WHITE}${BOLD}OPTIMIZE: ${GREEN}$domain${NC}"
echo ""
# Detect issues
cecho "${YELLOW}Detecting issues...${NC}"
local issues
issues=$(detect_php_config_issues "$username" "$domain")
# Display issues
local has_issues=false
while IFS='|' read -r issue_type severity message recommendation; do
[ -z "$issue_type" ] && continue
if [ "$issue_type" != "NONE" ]; then
has_issues=true
case "$severity" in
CRITICAL)
cecho "${RED}[CRITICAL]${NC} $message"
;;
HIGH)
cecho "${YELLOW}[HIGH]${NC} $message"
;;
MEDIUM)
cecho "${BLUE}[MEDIUM]${NC} $message"
;;
LOW)
cecho "${GREEN}[LOW]${NC} $message"
;;
esac
cecho " ${CYAN}${NC} $recommendation"
echo ""
fi
done <<< "$issues"
if [ "$has_issues" = false ]; then
cecho "${GREEN}${BOLD}✓ No issues detected - configuration is optimal!${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
# Get optimization recommendations
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
cecho "${WHITE}${BOLD}RECOMMENDED OPTIMIZATIONS${NC}"
echo ""
# Calculate optimal max_children
local optimal_result
optimal_result=$(calculate_optimal_max_children "$username" 1024)
local recommended_max_children reason
recommended_max_children=$(echo "$optimal_result" | cut -d'|' -f1)
reason=$(echo "$optimal_result" | cut -d'|' -f2)
# Get current max_children
local pool_config
pool_config=$(find_fpm_pool_config "$username")
if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then
local current_max_children
current_max_children=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ')
if [ -n "$current_max_children" ] && [ "$recommended_max_children" -ne "$current_max_children" ]; then
cecho "${GREEN}1.${NC} Adjust ${BOLD}pm.max_children${NC} from ${RED}$current_max_children${NC} to ${GREEN}$recommended_max_children${NC}"
cecho " Reason: $reason"
echo ""
fi
fi
# Check OPcache
local opcache_status
opcache_status=$(analyze_opcache_effectiveness "$username")
local status hit_rate opcache_rec
status=$(echo "$opcache_status" | cut -d'|' -f1)
hit_rate=$(echo "$opcache_status" | cut -d'|' -f2)
opcache_rec=$(echo "$opcache_status" | cut -d'|' -f5)
if [ "$status" = "DISABLED" ]; then
cecho "${GREEN}2.${NC} ${BOLD}Enable OPcache${NC} for 40-70% performance boost"
echo ""
elif (( $(echo "$hit_rate < 90" | bc -l 2>/dev/null || echo "0") )); then
cecho "${GREEN}2.${NC} ${BOLD}Increase opcache.memory_consumption${NC} (current hit rate: ${hit_rate}%)"
echo ""
fi
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
cecho "${YELLOW}${BOLD}WARNING:${NC} ${YELLOW}Automatic optimization not yet implemented${NC}"
cecho "${YELLOW}Please apply the above recommendations manually${NC}"
echo ""
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 6: VIEW OPCACHE STATISTICS
# ============================================================================
view_opcache_stats() {
show_banner
local selection
selection=$(select_domain "opcache")
if [ $? -ne 0 ] || [ -z "$selection" ]; then
return
fi
local domain username
domain=$(echo "$selection" | cut -d'|' -f1)
username=$(echo "$selection" | cut -d'|' -f2)
show_banner
cecho "${WHITE}${BOLD}OPCACHE STATISTICS: ${GREEN}$domain${NC}"
echo ""
# Get OPcache stats
local stats
stats=$(get_opcache_stats "$username")
if [ -z "$stats" ]; then
cecho "${RED}Unable to retrieve OPcache statistics${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
# Parse and display
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
cecho "${WHITE}${BOLD}OPCACHE STATUS${NC}"
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
local enabled
enabled=$(check_opcache_enabled "$username")
if [ "$enabled" = "1" ]; then
cecho " Status: ${GREEN}ENABLED${NC}"
else
cecho " Status: ${RED}DISABLED${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
# Display stats
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
case "$key" in
memory_usage_mb)
cecho " Memory Used: ${WHITE}${value}MB${NC}"
;;
hits)
cecho " Cache Hits: ${GREEN}$value${NC}"
;;
misses)
cecho " Cache Misses: ${YELLOW}$value${NC}"
;;
num_cached_scripts)
cecho " Cached Scripts: ${WHITE}$value${NC}"
;;
max_cached_keys)
cecho " Max Cached Keys: ${WHITE}$value${NC}"
;;
wasted_memory_mb)
cecho " Wasted Memory: ${RED}${value}MB${NC}"
;;
esac
done <<< "$stats"
# Calculate and display hit rate
local hit_rate
hit_rate=$(calculate_opcache_hit_rate "$username")
echo ""
cecho " Hit Rate: ${GREEN}${BOLD}${hit_rate}%${NC}"
# Recommendation
echo ""
if (( $(echo "$hit_rate < 90" | bc -l 2>/dev/null || echo "0") )); then
cecho " ${YELLOW}⚠ Hit rate below 90% - Consider increasing opcache.memory_consumption${NC}"
else
cecho " ${GREEN}✓ Hit rate is excellent${NC}"
fi
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 7: VIEW PHP-FPM PROCESS STATS
# ============================================================================
view_fpm_stats() {
show_banner
local selection
selection=$(select_domain "fpm")
if [ $? -ne 0 ] || [ -z "$selection" ]; then
return
fi
local domain username
domain=$(echo "$selection" | cut -d'|' -f1)
username=$(echo "$selection" | cut -d'|' -f2)
show_banner
cecho "${WHITE}${BOLD}PHP-FPM PROCESS STATISTICS: ${GREEN}$domain${NC}"
echo ""
# Get process stats
local memory_stats
memory_stats=$(calculate_memory_per_process "$username")
local avg_kb process_count total_mb
avg_kb=$(echo "$memory_stats" | cut -d'|' -f1)
process_count=$(echo "$memory_stats" | cut -d'|' -f2)
total_mb=$(echo "$memory_stats" | cut -d'|' -f3)
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
cecho "${WHITE}${BOLD}CURRENT RESOURCE USAGE${NC}"
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
if [ "$process_count" -eq 0 ]; then
cecho "${YELLOW}No active PHP-FPM processes found${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
cecho " Active Processes: ${WHITE}$process_count${NC}"
cecho " Avg Memory/Process: ${WHITE}$((avg_kb / 1024))MB${NC} (${avg_kb}KB)"
cecho " Total Memory: ${WHITE}${total_mb}MB${NC}"
echo ""
# Get pool config
local pool_config
pool_config=$(find_fpm_pool_config "$username")
if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
cecho "${WHITE}${BOLD}POOL CONFIGURATION${NC}"
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
local pool_settings
pool_settings=$(parse_fpm_pool_config "$pool_config")
# Display key settings
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
cecho " $key: ${WHITE}$value${NC}"
done <<< "$pool_settings"
fi
# Calculate optimal max_children
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
cecho "${WHITE}${BOLD}RECOMMENDATION${NC}"
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
local optimal_result
optimal_result=$(calculate_optimal_max_children "$username" 1024)
local recommended reason
recommended=$(echo "$optimal_result" | cut -d'|' -f1)
reason=$(echo "$optimal_result" | cut -d'|' -f2)
cecho " Optimal pm.max_children: ${GREEN}${BOLD}$recommended${NC}"
cecho " Reason: $reason"
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 8: CHECK FOR ISSUES
# ============================================================================
check_config_issues() {
show_banner
local selection
selection=$(select_domain "check")
if [ $? -ne 0 ] || [ -z "$selection" ]; then
return
fi
local domain username
domain=$(echo "$selection" | cut -d'|' -f1)
username=$(echo "$selection" | cut -d'|' -f2)
show_banner
cecho "${WHITE}${BOLD}CONFIGURATION ISSUES: ${GREEN}$domain${NC}"
echo ""
# Detect issues
local issues
issues=$(detect_php_config_issues "$username" "$domain")
# Display by severity
local has_critical=false
local has_high=false
local has_medium=false
local has_low=false
# Check for each severity level
echo "$issues" | grep -q "CRITICAL" && has_critical=true
echo "$issues" | grep -q "HIGH" && has_high=true
echo "$issues" | grep -q "MEDIUM" && has_medium=true
echo "$issues" | grep -q "LOW" && has_low=true
# Display CRITICAL
if [ "$has_critical" = true ]; then
cecho "${RED}${BOLD}CRITICAL ISSUES:${NC}"
echo ""
while IFS='|' read -r issue_type severity message recommendation; do
[ "$severity" != "CRITICAL" ] && continue
cecho " ${RED}${NC} $message"
cecho " ${CYAN}${NC} $recommendation"
echo ""
done <<< "$issues"
fi
# Display HIGH
if [ "$has_high" = true ]; then
cecho "${YELLOW}${BOLD}HIGH PRIORITY ISSUES:${NC}"
echo ""
while IFS='|' read -r issue_type severity message recommendation; do
[ "$severity" != "HIGH" ] && continue
cecho " ${YELLOW}${NC} $message"
cecho " ${CYAN}${NC} $recommendation"
echo ""
done <<< "$issues"
fi
# Display MEDIUM
if [ "$has_medium" = true ]; then
cecho "${BLUE}${BOLD}MEDIUM PRIORITY ISSUES:${NC}"
echo ""
while IFS='|' read -r issue_type severity message recommendation; do
[ "$severity" != "MEDIUM" ] && continue
cecho " ${BLUE}${NC} $message"
cecho " ${CYAN}${NC} $recommendation"
echo ""
done <<< "$issues"
fi
# Display LOW
if [ "$has_low" = true ]; then
cecho "${GREEN}${BOLD}LOW PRIORITY ISSUES:${NC}"
echo ""
while IFS='|' read -r issue_type severity message recommendation; do
[ "$severity" != "LOW" ] && continue
cecho " ${GREEN}${NC} $message"
cecho " ${CYAN}${NC} $recommendation"
echo ""
done <<< "$issues"
fi
# No issues
if [ "$has_critical" = false ] && [ "$has_high" = false ] && [ "$has_medium" = false ] && [ "$has_low" = false ]; then
cecho "${GREEN}${BOLD}✓ No configuration issues detected!${NC}"
cecho "${GREEN}Configuration appears to be optimal.${NC}"
echo ""
fi
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION 9: CHECK SERVER MEMORY CAPACITY
# ============================================================================
check_server_memory_capacity() {
show_banner
cecho "${WHITE}${BOLD}SERVER MEMORY CAPACITY CHECK${NC}"
echo ""
cecho "${YELLOW}This checks if all PHP-FPM pools hitting max_children would cause OOM...${NC}"
echo ""
# Run capacity analysis
cecho "${CYAN}Analyzing PHP-FPM memory requirements...${NC}"
echo ""
local result
result=$(calculate_server_memory_capacity 2>&1)
# Parse result (main output is last line)
local main_result
main_result=$(echo "$result" | tail -1)
local total_required total_ram percentage status details
total_required=$(echo "$main_result" | cut -d'|' -f1)
total_ram=$(echo "$main_result" | cut -d'|' -f2)
percentage=$(echo "$main_result" | cut -d'|' -f3)
status=$(echo "$main_result" | cut -d'|' -f4)
# Display summary
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
cecho "${WHITE}${BOLD}MEMORY CAPACITY ANALYSIS${NC}"
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
cecho " Total Server RAM: ${WHITE}${total_ram}MB${NC}"
cecho " Required if ALL pools at max_children: ${WHITE}${total_required}MB${NC}"
cecho " Percentage of RAM: ${WHITE}${percentage}%${NC}"
echo ""
# Display status with color
case "$status" in
CRITICAL)
cecho " Status: ${RED}${BOLD}CRITICAL - HIGH OOM RISK!${NC}"
cecho ""
cecho " ${RED}WARNING: If all PHP-FPM pools hit their max_children limit,${NC}"
cecho " ${RED}the server will likely run out of memory and kill processes!${NC}"
;;
WARNING)
cecho " Status: ${YELLOW}${BOLD}WARNING - MODERATE OOM RISK${NC}"
cecho ""
cecho " ${YELLOW}CAUTION: Memory usage is high. Some pools may need reduction.${NC}"
;;
CAUTION)
cecho " Status: ${YELLOW}${BOLD}CAUTION - WATCH MEMORY USAGE${NC}"
cecho ""
cecho " ${YELLOW}Memory usage is elevated but manageable.${NC}"
;;
HEALTHY)
cecho " Status: ${GREEN}${BOLD}HEALTHY - LOW OOM RISK${NC}"
cecho ""
cecho " ${GREEN}Memory allocation appears safe.${NC}"
;;
esac
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
# Ask if user wants detailed breakdown
read -p "Show detailed per-user breakdown? (y/n): " show_details
if [[ "$show_details" =~ ^[Yy]$ ]]; then
echo ""
cecho "${WHITE}${BOLD}PER-USER BREAKDOWN${NC}"
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
# Get details from stderr of previous call
local details_output
details_output=$(echo "$result" | head -n -1)
printf "%-20s %12s %12s %12s\n" "USER" "MAX_CHILDREN" "AVG/PROCESS" "MAX_MEMORY"
printf "%-20s %12s %12s %12s\n" "--------------------" "------------" "------------" "------------"
while IFS='|' read -r username max_children avg_mb pool_max_mb; do
[ -z "$username" ] && continue
printf "%-20s %12s %12s %12s\n" "$username" "$max_children" "$avg_mb" "$pool_max_mb"
done <<< "$details_output"
echo ""
fi
# Ask if user wants balanced recommendations
echo ""
read -p "Calculate balanced memory allocation recommendations? (y/n): " show_recommendations
if [[ "$show_recommendations" =~ ^[Yy]$ ]]; then
echo ""
cecho "${WHITE}${BOLD}BALANCED MEMORY ALLOCATION RECOMMENDATIONS${NC}"
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
cecho "${YELLOW}Calculating optimal max_children based on traffic...${NC}"
echo ""
local recommendations
recommendations=$(calculate_balanced_memory_allocation 2>/dev/null)
# Display header
local header
header=$(echo "$recommendations" | head -1)
echo "$header" | awk -F'|' '{printf "%-15s %8s %10s %12s %12s %15s %20s\n", $1, $2, $3, $4, $5, $6, $7}'
echo "--------------------------------------------------------------------------------------------------------"
# Display recommendations
echo "$recommendations" | tail -n +2 | while IFS='|' read -r user current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do
[ -z "$user" ] && continue
# Color code reason
if [[ "$reason" == *"REDUCE"* ]]; then
color="${RED}"
elif [[ "$reason" == *"INCREASE"* ]]; then
color="${GREEN}"
else
color="${WHITE}"
fi
printf "%-15s %8s %10s %12s ${color}%12s${NC} %15s %20s\n" "$user" "$current_max" "$avg_mb" "$traffic_rpm" "$recommended_max" "$allocated_mb" "$reason"
done
echo ""
cecho "${YELLOW}NOTE: These are recommendations based on proportional traffic.${NC}"
cecho "${YELLOW}Actual needs may vary. Always test changes carefully.${NC}"
echo ""
fi
echo ""
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
read -p "Press Enter to continue..."
}
# ============================================================================
# MAIN LOOP
# ============================================================================
main() {
# Detect system
detect_system
# Check if running as root
if [ "$EUID" -ne 0 ]; then
cecho "${RED}ERROR: This script must be run as root${NC}"
exit 1
fi
# Check for PHP-FPM
if ! is_using_php_fpm; then
cecho "${YELLOW}WARNING: PHP-FPM not detected. Some features may not work.${NC}"
read -p "Press Enter to continue anyway..."
fi
# Main loop
while true; do
show_banner
show_main_menu
read -p "Select option: " choice
case "$choice" in
1)
analyze_single_domain
;;
2)
analyze_all_domains
;;
3)
quick_health_check
;;
4)
optimize_domain
;;
5)
cecho "${YELLOW}Server-wide optimization not yet implemented${NC}"
sleep 2
;;
6)
view_opcache_stats
;;
7)
view_fpm_stats
;;
8)
check_config_issues
;;
9)
check_server_memory_capacity
;;
b)
cecho "${YELLOW}Backup feature not yet implemented${NC}"
sleep 2
;;
r)
cecho "${YELLOW}Restore feature not yet implemented${NC}"
sleep 2
;;
q|Q)
cecho "${GREEN}Exiting PHP Optimizer...${NC}"
exit 0
;;
*)
cecho "${RED}Invalid option${NC}"
sleep 1
;;
esac
done
}
# Run main
main "$@"