Files
Linux-Server-Management-Too…/modules/performance/php-optimizer.sh
T
cschantz 8dc6d3a2e8 Eliminate all bc command dependencies - replace with awk for portability
PROBLEM:
- bc command not installed on all systems (requires bc package)
- 30 instances across toolkit causing potential failures
- bc is external dependency for floating-point arithmetic

SOLUTION:
- Replaced all bc usage with awk (universally available)
- Pattern: echo "X * Y" | bc → awk "BEGIN {printf \"%.2f\", X * Y}"
- Pattern: (( $(echo "X > Y" | bc -l) )) → awk comparison + bash test

FILES MODIFIED (8 files, 30 bc instances eliminated):
1. lib/threat-intelligence.sh (1 fix)
   - Line 310: Load average to integer conversion

2. lib/reference-db.sh (2 fixes)
   - Line 554: CPU load percentage calculation
   - Line 570: TCP retransmission comparison

3. lib/php-analyzer.sh (5 fixes)
   - Line 138: Script duration comparison
   - Lines 391-395: OPcache hit rate + wasted memory + cached scripts
   - Line 479: OPcache hit rate threshold

4. modules/performance/hardware-health-check.sh (1 fix)
   - Line 264: CPU frequency conversion (KHz to GHz)

5. modules/performance/network-bandwidth-analyzer.sh (3 fixes)
   - Line 168: Daily bandwidth threshold (50 GiB)
   - Line 238: Bytes to MB conversion
   - Lines 388-390: TCP retransmission percentage

6. modules/performance/php-optimizer.sh (2 fixes)
   - Lines 457, 653: OPcache hit rate comparisons

7. modules/diagnostics/system-health-check.sh (10 fixes)
   - Lines 345-350: Load per core + threshold calculations
   - Lines 354-358: Load trend detection (3 comparisons)
   - Lines 367-406: Load critical/warning/elevated checks
   - Lines 828-829: TCP retransmission analysis
   - Line 901: Clock offset detection
   - Line 1692: Network stats TCP retrans percent

8. tools/toolkit-qa-check.sh (QA improvements)
   - Added --exclude="toolkit-qa-check.sh" to prevent self-scanning
   - Eliminates false positives from QA script itself

TECHNICAL DETAILS:
- All awk commands use BEGIN block for pure calculation
- printf formatting preserves decimal precision (%.2f, %.1f, %.0f)
- Error handling with 2>/dev/null || echo fallbacks
- Ternary operators for comparisons: (condition ? 1 : 0)

TESTING:
✓ QA scan shows 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW issues
✓ All 30 bc instances eliminated
✓ No external dependencies beyond standard bash + awk
✓ Toolkit now portable to minimal Linux installations

IMPACT:
+ Eliminates bc package dependency
+ 100% portable (awk included in all Unix/Linux systems)
+ Same accuracy for floating-point calculations
+ Faster execution (awk is typically faster than bc)
+ Better error handling with fallback values
2025-12-03 20:49:46 -05:00

1217 lines
44 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
PHP_TOOLKIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)"
source "$PHP_TOOLKIT_DIR/lib/common-functions.sh" || { echo "ERROR: common-functions.sh not found"; exit 1; }
source "$PHP_TOOLKIT_DIR/lib/system-detect.sh" || { echo "ERROR: system-detect.sh not found"; exit 1; }
source "$PHP_TOOLKIT_DIR/lib/user-manager.sh" || { echo "ERROR: user-manager.sh not found"; exit 1; }
source "$PHP_TOOLKIT_DIR/lib/php-detector.sh" || { echo "ERROR: php-detector.sh not found"; exit 1; }
source "$PHP_TOOLKIT_DIR/lib/php-analyzer.sh" || { echo "ERROR: php-analyzer.sh not found"; exit 1; }
source "$PHP_TOOLKIT_DIR/lib/php-config-manager.sh" || { echo "ERROR: php-config-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 issues by severity
local critical_count high_count medium_count low_count
critical_count=$(echo "$issues" | grep -c "^[^|]*|CRITICAL|" || true)
high_count=$(echo "$issues" | grep -c "^[^|]*|HIGH|" || true)
medium_count=$(echo "$issues" | grep -c "^[^|]*|MEDIUM|" || true)
low_count=$(echo "$issues" | grep -c "^[^|]*|LOW|" || true)
# Default to 0 if empty
critical_count=${critical_count:-0}
high_count=${high_count:-0}
medium_count=${medium_count:-0}
low_count=${low_count:-0}
local total_issues=$((critical_count + high_count + medium_count + low_count))
if [ "$total_issues" -gt 0 ]; then
domains_with_issues=$((domains_with_issues + 1))
# Build issue summary
local issue_summary=""
[ "$critical_count" -gt 0 ] && issue_summary+="${RED}$critical_count CRITICAL${NC}, "
[ "$high_count" -gt 0 ] && issue_summary+="${YELLOW}$high_count HIGH${NC}, "
[ "$medium_count" -gt 0 ] && issue_summary+="$medium_count MEDIUM, "
[ "$low_count" -gt 0 ] && issue_summary+="$low_count LOW, "
issue_summary=${issue_summary%, } # Remove trailing comma
cecho " ${RED}${NC} Issues found: $issue_summary"
# Show critical/high 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"
# Show what passed
local checks_passed=""
echo "$issues" | grep -q "max_children" || checks_passed+="max_children OK, "
echo "$issues" | grep -q "memory_exhausted" || checks_passed+="memory OK, "
echo "$issues" | grep -q "execution timeout" || checks_passed+="timeouts OK, "
if [ -n "$checks_passed" ]; then
checks_passed=${checks_passed%, }
cecho " ${GREEN}${NC} Checks passed: $checks_passed"
fi
else
cecho " ${GREEN}✓ All checks passed${NC} (max_children, memory, timeouts, config)"
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 ""
else
local hit_rate_low=$(awk "BEGIN {print ($hit_rate < 90 ? 1 : 0)}" 2>/dev/null || echo 0)
if [ "$hit_rate_low" -eq 1 ]; then
cecho "${GREEN}2.${NC} ${BOLD}Increase opcache.memory_consumption${NC} (current hit rate: ${hit_rate}%)"
echo ""
fi
fi
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
echo ""
# Ask if user wants to apply changes
read -p "Apply these recommendations? (y/n): " apply_choice
if [[ ! "$apply_choice" =~ ^[Yy]$ ]]; then
cecho "${YELLOW}Optimization cancelled - no changes made${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
# Create backup first
cecho "${CYAN}Creating backup before making changes...${NC}"
local backup_dir
backup_dir=$(backup_user_php_configs "$username" "$domain" 2>&1)
if [ $? -ne 0 ]; then
cecho "${RED}${BOLD}✗ Backup failed - aborting changes${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
cecho "${GREEN}✓ Backup created: $(basename "$backup_dir")${NC}"
echo ""
# Apply changes
cecho "${CYAN}Applying optimizations...${NC}"
echo ""
local changes_made=0
local changes_failed=0
# Find pool config
local pool_config
pool_config=$(find_fpm_pool_config "$username")
if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then
# Apply max_children change if recommended
if [ -n "$recommended_max_children" ] && [ "$recommended_max_children" -ne "$current_max_children" ]; then
if modify_fpm_pool_setting "$pool_config" "pm.max_children" "$recommended_max_children" >/dev/null 2>&1; then
cecho " ${GREEN}${NC} Set pm.max_children = $recommended_max_children"
changes_made=$((changes_made + 1))
else
cecho " ${RED}${NC} Failed to set pm.max_children"
changes_failed=$((changes_failed + 1))
fi
fi
fi
echo ""
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
if [ "$changes_made" -gt 0 ]; then
cecho "${GREEN}${BOLD}✓ Applied $changes_made optimization(s)${NC}"
if [ "$changes_failed" -gt 0 ]; then
cecho "${YELLOW}⚠ Failed to apply $changes_failed optimization(s)${NC}"
fi
echo ""
cecho "${YELLOW}Changes have been applied. Restart PHP-FPM for changes to take effect.${NC}"
echo ""
read -p "Restart PHP-FPM now? (y/n): " restart_choice
if [[ "$restart_choice" =~ ^[Yy]$ ]]; then
# Detect PHP version
local php_version
php_version=$(detect_php_version_for_domain "$domain")
cecho "${CYAN}Restarting PHP-FPM ($php_version)...${NC}"
if reload_php_fpm "$php_version" >/dev/null 2>&1; then
cecho "${GREEN}✓ PHP-FPM reloaded successfully${NC}"
# Verify it's running
sleep 1
if verify_php_fpm_running "$php_version" >/dev/null 2>&1; then
cecho "${GREEN}✓ PHP-FPM is running${NC}"
else
cecho "${RED}✗ WARNING: PHP-FPM may not be running!${NC}"
cecho "${YELLOW}Run: systemctl status ea-php${php_version#ea-php}-php-fpm${NC}"
fi
else
cecho "${RED}✗ Failed to restart PHP-FPM${NC}"
cecho "${YELLOW}You may need to restart manually:${NC}"
cecho "${YELLOW}systemctl restart ea-php${php_version#ea-php}-php-fpm${NC}"
fi
else
cecho "${YELLOW}Skipping restart - remember to restart PHP-FPM manually!${NC}"
fi
echo ""
cecho "${CYAN}Backup location: ${WHITE}$(basename "$backup_dir")${NC}"
cecho "${CYAN}To rollback: Use Option 'r' (Restore from Backup)${NC}"
else
cecho "${YELLOW}No changes were applied${NC}"
fi
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 ""
local hit_rate_low=$(awk "BEGIN {print ($hit_rate < 90 ? 1 : 0)}" 2>/dev/null || echo 0)
if [ "$hit_rate_low" -eq 1 ]; 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>/dev/null)
# Parse result - first line is summary, remaining lines are details
local main_result
main_result=$(echo "$result" | head -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)
# Get details (all lines after first)
details=$(echo "$result" | tail -n +2)
# 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 ""
printf "%-30s %-20s %12s %12s %12s\n" "DOMAIN" "USER" "MAX_CHILDREN" "AVG/PROCESS" "MAX_MEMORY"
printf "%-30s %-20s %12s %12s %12s\n" "------------------------------" "--------------------" "------------" "------------" "------------"
while IFS='|' read -r domain username max_children avg_mb pool_max_mb; do
[ -z "$domain" ] && continue
printf "%-30s %-20s %12s %12s %12s\n" "$domain" "$username" "$max_children" "$avg_mb" "$pool_max_mb"
done <<< "$details"
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..."
}
# ============================================================================
# OPTION B: BACKUP CONFIGURATIONS
# ============================================================================
backup_configurations() {
show_banner
cecho "${WHITE}${BOLD}BACKUP PHP CONFIGURATIONS${NC}"
echo ""
# Select domain
local selection
selection=$(select_domain "backup")
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}BACKUP: ${GREEN}$domain${NC}"
echo ""
# Initialize backup system
initialize_backup_system
# Create backup
cecho "${YELLOW}Creating backup of PHP configurations...${NC}"
echo ""
local backup_dir
backup_dir=$(backup_user_php_configs "$username" "$domain" 2>&1)
if [ $? -eq 0 ]; then
cecho "${GREEN}${BOLD}✓ Backup created successfully!${NC}"
echo ""
cecho " Backup location: ${WHITE}$backup_dir${NC}"
echo ""
# Show what was backed up
if [ -f "$backup_dir/metadata.txt" ]; then
cecho "${CYAN}Files backed up:${NC}"
grep "^ /" "$backup_dir/metadata.txt" | while IFS= read -r line; do
local file=$(echo "$line" | awk '{print $1}')
cecho " ${GREEN}${NC} $file"
done
fi
else
cecho "${RED}${BOLD}✗ Backup failed${NC}"
echo ""
echo "$backup_dir"
fi
echo ""
read -p "Press Enter to continue..."
}
# ============================================================================
# OPTION R: RESTORE FROM BACKUP
# ============================================================================
restore_configurations() {
show_banner
cecho "${WHITE}${BOLD}RESTORE FROM BACKUP${NC}"
echo ""
# List available backups
cecho "${CYAN}Available backups:${NC}"
echo ""
local backups
backups=$(list_backups)
if [ $? -ne 0 ]; then
cecho "${YELLOW}No backups found${NC}"
echo ""
read -p "Press Enter to continue..."
return
fi
# Display backups
local backup_array=()
local index=1
echo "$backups" | tail -n +2 | while IFS='|' read -r backup_name created username domain file_count; do
printf "${GREEN}%3d${NC}) %-20s %s ${CYAN}[%s]${NC} ${YELLOW}(%s)${NC} %s files\n" \
"$index" "$backup_name" "$created" "$username" "$domain" "$file_count"
backup_array+=("$backup_name")
index=$((index + 1))
done
# Store backup names in array for selection
mapfile -t backup_array < <(echo "$backups" | tail -n +2 | cut -d'|' -f1)
echo ""
read -p "Select backup number to restore (or 'q' to cancel): " selection
if [[ "$selection" == "q" ]]; then
return
fi
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#backup_array[@]} ]; then
cecho "${RED}Invalid selection${NC}"
sleep 2
return
fi
local selected_backup="${backup_array[$((selection - 1))]}"
# Confirm restoration
echo ""
cecho "${YELLOW}${BOLD}WARNING: This will overwrite current configurations!${NC}"
echo ""
read -p "Are you sure you want to restore from $selected_backup? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
cecho "${YELLOW}Restore cancelled${NC}"
sleep 2
return
fi
# Perform restore
show_banner
cecho "${WHITE}${BOLD}RESTORING FROM BACKUP${NC}"
echo ""
if restore_from_backup "$selected_backup"; then
echo ""
cecho "${GREEN}${BOLD}✓ Restore completed successfully!${NC}"
echo ""
cecho "${YELLOW}Don't forget to restart PHP-FPM for changes to take effect!${NC}"
else
echo ""
cecho "${RED}${BOLD}✗ Restore failed${NC}"
fi
echo ""
read -p "Press Enter to continue..."
}
# ============================================================================
# MAIN LOOP
# ============================================================================
main() {
# Detect system
initialize_system_detection
# 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)
backup_configurations
;;
r)
restore_configurations
;;
q|Q)
cecho "${GREEN}Exiting PHP Optimizer...${NC}"
exit 0
;;
*)
cecho "${RED}Invalid option${NC}"
sleep 1
;;
esac
done
}
# Run main
main "$@"