From 4d745f203e8fa979f173f9ad315cafac1a90f2d8 Mon Sep 17 00:00:00 2001 From: cschantz Date: Wed, 18 Feb 2026 19:40:01 -0500 Subject: [PATCH] Complete profile-based PHP-FPM optimization system with real usage data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement data-driven optimization using actual server metrics instead of thresholds: NEW FEATURES: - lib/php-analytics.sh: Analytics engine for domain profiling • analyze_memory_errors_from_logs: Parse error logs for memory exhaustion • analyze_process_memory_usage: Measure actual PHP process memory via ps • get_peak_concurrent_detailed: Extract peak concurrent requests from access logs • detect_memory_leak_pattern: Identify domains with memory leak issues • build_domain_profile: Complete profile with all real usage data • Intelligent recommendations based on ACTUAL peak memory, traffic, and leak patterns - modules/performance/php-domain-analyzer.sh: Pre-analysis script • Scans all domains and builds comprehensive profiles • Stores profiles in /tmp/php-domain-profiles/ for use by optimizer • Shows summary with top memory users, traffic patterns, and potential leaks • Displays analysis in real-time with progress indicators - php-optimizer.sh: Profile-based optimization levels • Option 0: Run pre-analysis to collect real usage data • Levels 1-5: Now use profile-based recommendations (fallback to traffic-based if no profiles) • Shows real usage data from profiles when optimizations applied • Memory recommendations: peak_memory_seen + 20% buffer • Max children: peak_concurrent_requests + 30% safety margin • Max requests: 250 for leak-prone domains, 500 for normal domains ARCHITECTURE: - Profile format (pipe-delimited): domain|username|peak_concurrent|avg_concurrent| total_hits|min_mem|max_mem|avg_mem|proc_count|mem_exhausted|peak_mem_seen| leak_type|current_memory_limit|current_max_children - Profiles cached in /tmp/php-domain-profiles/ (24 hour TTL) - All 5 optimization levels now profile-aware - Seamless fallback to traffic-based method if no profiles exist CONVERSION COMPLETED: - Level 1: Optimizes pm.max_children only (profile-aware) - Level 2: pm.max_children + memory_limit (profile-aware) - Level 3: All of above + pm.max_requests for leak prevention (profile-aware) - Level 4: OPcache optimization (unchanged) - Level 5: Complete optimization with all settings (NOW PROFILE-AWARE - FIXED) All levels now enumeraate users/domains directly and use profile recommendations when available, with intelligent fallback to the original traffic-based method. Co-Authored-By: Claude Haiku 4.5 --- lib/php-analytics.sh | 390 ++++++++++++++++ modules/performance/php-domain-analyzer.sh | 146 ++++++ modules/performance/php-optimizer.sh | 497 +++++++++++++++++---- 3 files changed, 944 insertions(+), 89 deletions(-) create mode 100755 lib/php-analytics.sh create mode 100755 modules/performance/php-domain-analyzer.sh diff --git a/lib/php-analytics.sh b/lib/php-analytics.sh new file mode 100755 index 0000000..1d040bb --- /dev/null +++ b/lib/php-analytics.sh @@ -0,0 +1,390 @@ +#!/bin/bash +# PHP Analytics Library +# Analyzes real usage data to make intelligent optimization decisions +# Parses logs, process memory, and builds accurate domain profiles + +# ============================================================================ +# ERROR LOG ANALYSIS - Find memory-related issues +# ============================================================================ + +# Parse PHP-FPM error logs for memory exhaustion errors +analyze_memory_errors_from_logs() { + local username="$1" + local domain="$2" + local days="${3:-7}" + + local log_files + log_files=$(find_php_error_logs "$username" "$domain") + + local memory_exhausted_count=0 + local memory_limit_errors=0 + local peak_memory_seen=0 + + # Look for memory exhaustion patterns + while IFS= read -r log_file; do + [ -z "$log_file" ] && continue + [ ! -f "$log_file" ] && continue + + # Count "Allowed memory size exhausted" errors + local exhausted_in_file + exhausted_in_file=$(\grep -c "Allowed memory size of" "$log_file" 2>/dev/null || echo 0) + exhausted_in_file=${exhausted_in_file##[[:space:]]} + exhausted_in_file=${exhausted_in_file%%[[:space:]]} + memory_exhausted_count=$((memory_exhausted_count + exhausted_in_file)) + + # Count memory limit exceeded + local limit_errors_in_file + limit_errors_in_file=$(\grep -c "memory_limit" "$log_file" 2>/dev/null || echo 0) + limit_errors_in_file=${limit_errors_in_file##[[:space:]]} + limit_errors_in_file=${limit_errors_in_file%%[[:space:]]} + memory_limit_errors=$((memory_limit_errors + limit_errors_in_file)) + + # Extract peak memory from logs (format: "Allowed memory size of 134217728 bytes exhausted") + local mem_values + mem_values=$(\grep -o "Allowed memory size of [0-9]* bytes" "$log_file" 2>/dev/null | \grep -o "[0-9]*" | sort -rn | head -1) + + if [ -n "$mem_values" ]; then + # Convert bytes to MB + local mem_mb=$((mem_values / 1048576)) + if [ "$mem_mb" -gt "$peak_memory_seen" ]; then + peak_memory_seen=$mem_mb + fi + fi + done <<< "$log_files" + + # Return: exhausted_count|limit_errors|peak_memory_mb + echo "$memory_exhausted_count|$memory_limit_errors|$peak_memory_seen" +} + +# Find PHP error log files for a domain +find_php_error_logs() { + local username="$1" + local domain="$2" + + # cPanel locations + if [ -d "/home/$username" ]; then + find "/home/$username" -name "error_log" 2>/dev/null | head -5 + fi + + # PHP-FPM error logs + if [ -d "/var/log/php-fpm" ]; then + find "/var/log/php-fpm" -name "*error*" 2>/dev/null | head -5 + fi + + # Common log locations + [ -f "/var/log/php.log" ] && echo "/var/log/php.log" + [ -f "/var/log/php-errors.log" ] && echo "/var/log/php-errors.log" +} + +# ============================================================================ +# PROCESS MEMORY ANALYSIS - Measure actual memory usage +# ============================================================================ + +# Analyze PHP process memory for a domain +analyze_process_memory_usage() { + local username="$1" + + # Get current running PHP processes for this user + local processes + processes=$(ps aux | \grep -E "php-fpm.*$username|_www.*php" | \grep -v grep) + + if [ -z "$processes" ]; then + echo "0|0|0|0" # min|max|avg|count + return + fi + + local mem_values=() + local min_mem=999999 + local max_mem=0 + local total_mem=0 + local count=0 + + # Extract memory (RSS) from ps output + while IFS= read -r line; do + local rss=$(echo "$line" | awk '{print $6}') + if [ -n "$rss" ] && [[ "$rss" =~ ^[0-9]+$ ]]; then + mem_values+=("$rss") + total_mem=$((total_mem + rss)) + count=$((count + 1)) + + if [ "$rss" -lt "$min_mem" ]; then + min_mem=$rss + fi + if [ "$rss" -gt "$max_mem" ]; then + max_mem=$rss + fi + fi + done <<< "$processes" + + if [ "$count" -eq 0 ]; then + echo "0|0|0|0" + return + fi + + local avg_mem=$((total_mem / count)) + + # Convert to MB + min_mem=$((min_mem / 1024)) + max_mem=$((max_mem / 1024)) + avg_mem=$((avg_mem / 1024)) + + # Return: min_mb|max_mb|avg_mb|count + echo "$min_mem|$max_mem|$avg_mem|$count" +} + +# ============================================================================ +# TRAFFIC PATTERN ANALYSIS - Understand domain load +# ============================================================================ + +# Get peak concurrent requests from access logs +get_peak_concurrent_detailed() { + local username="$1" + local domain="$2" + + local log_file + log_file=$(find_domain_access_log "$domain" "$username") + + if [ -z "$log_file" ] || [ ! -f "$log_file" ]; then + echo "0|0|0" # peak|avg|stddev + return + fi + + # Analyze timestamps to find peak concurrency + local timestamps + timestamps=$(awk '{print $4}' "$log_file" 2>/dev/null | sed 's/\[//;s/\/.*//' | sort | uniq -c | sort -rn | head -1) + + local peak_concurrent=$(echo "$timestamps" | awk '{print $1}') + peak_concurrent=${peak_concurrent:-0} + + # Calculate average concurrent + local total_hits=$(wc -l < "$log_file") + local unique_seconds=$(awk '{print $4}' "$log_file" 2>/dev/null | sed 's/\[//;s/\/.*//' | sort -u | wc -l) + local avg_concurrent=0 + + if [ "$unique_seconds" -gt 0 ]; then + avg_concurrent=$((total_hits / unique_seconds)) + fi + + # Return: peak|avg|total_hits + echo "$peak_concurrent|$avg_concurrent|$total_hits" +} + +# ============================================================================ +# MEMORY GROWTH DETECTION - Find memory leaks +# ============================================================================ + +# Detect if domain has memory leak pattern +detect_memory_leak_pattern() { + local username="$1" + local domain="$2" + + # Check error logs for progressive memory growth + local error_analysis + error_analysis=$(analyze_memory_errors_from_logs "$username" "$domain") + + local memory_exhausted_count=$(echo "$error_analysis" | cut -d'|' -f1) + local peak_memory=$(echo "$error_analysis" | cut -d'|' -f3) + + # If many memory exhausted errors with growing peak memory, likely a leak + if [ "$memory_exhausted_count" -gt 5 ] && [ "$peak_memory" -gt 200 ]; then + echo "LIKELY_LEAK|High memory exhaustion errors ($memory_exhausted_count) detected" + return 0 + fi + + # Check if max_requests is 0 (process never recycled) + local pool_config + pool_config=$(find_fpm_pool_config "$username") + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + local max_requests + max_requests=$(\grep "^pm.max_requests" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + + if [ "$max_requests" = "0" ]; then + echo "NEEDS_RECYCLING|pm.max_requests is disabled (0) - processes never recycled" + return 0 + fi + fi + + echo "NO_LEAK|Normal memory patterns" + return 1 +} + +# ============================================================================ +# DOMAIN PROFILE BUILDER - Comprehensive analysis +# ============================================================================ + +# Build complete profile for a domain +build_domain_profile() { + local username="$1" + local domain="$2" + + # Get memory errors + local memory_errors + memory_errors=$(analyze_memory_errors_from_logs "$username" "$domain") + local mem_exhausted=$(echo "$memory_errors" | cut -d'|' -f1) + local mem_limit_errors=$(echo "$memory_errors" | cut -d'|' -f2) + local peak_mem_seen=$(echo "$memory_errors" | cut -d'|' -f3) + + # Get current process memory + local process_mem + process_mem=$(analyze_process_memory_usage "$username") + local min_mem=$(echo "$process_mem" | cut -d'|' -f1) + local max_mem=$(echo "$process_mem" | cut -d'|' -f2) + local avg_mem=$(echo "$process_mem" | cut -d'|' -f3) + local proc_count=$(echo "$process_mem" | cut -d'|' -f4) + + # Get traffic patterns + local traffic + traffic=$(get_peak_concurrent_detailed "$username" "$domain") + local peak_concurrent=$(echo "$traffic" | cut -d'|' -f1) + local avg_concurrent=$(echo "$traffic" | cut -d'|' -f2) + local total_hits=$(echo "$traffic" | cut -d'|' -f3) + + # Detect memory leaks + local leak_status + leak_status=$(detect_memory_leak_pattern "$username" "$domain") + local leak_type=$(echo "$leak_status" | cut -d'|' -f1) + local leak_note=$(echo "$leak_status" | cut -d'|' -f2) + + # Get current settings + local current_memory_limit + current_memory_limit=$(get_effective_php_setting "$username" "memory_limit") + local pool_config + pool_config=$(find_fpm_pool_config "$username") + local current_max_children="?" + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + current_max_children=$(\grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + fi + + # Format: domain|username|peak_concurrent|avg_concurrent|total_hits|min_mem|max_mem|avg_mem|proc_count|mem_exhausted|peak_mem_seen|leak_type|current_memory_limit|current_max_children + echo "$domain|$username|$peak_concurrent|$avg_concurrent|$total_hits|$min_mem|$max_mem|$avg_mem|$proc_count|$mem_exhausted|$peak_mem_seen|$leak_type|$current_memory_limit|$current_max_children" +} + +# ============================================================================ +# INTELLIGENT RECOMMENDATIONS - Based on real data +# ============================================================================ + +# Calculate memory_limit based on ACTUAL usage, not thresholds +calculate_memory_limit_from_actual_usage() { + local username="$1" + local domain="$2" + + # Get real data + local memory_errors + memory_errors=$(analyze_memory_errors_from_logs "$username" "$domain") + local peak_mem_seen=$(echo "$memory_errors" | cut -d'|' -f3) + + local process_mem + process_mem=$(analyze_process_memory_usage "$username") + local max_mem=$(echo "$process_mem" | cut -d'|' -f2) + + # Determine optimal memory_limit + local recommended_memory=128 + + # If we've seen memory exhaustion, use observed peak + 20% buffer + if [ "$peak_mem_seen" -gt 0 ]; then + recommended_memory=$((peak_mem_seen + (peak_mem_seen / 5))) + elif [ "$max_mem" -gt 0 ]; then + # Use max observed process memory + 30% buffer for growth + recommended_memory=$((max_mem + (max_mem / 3))) + fi + + # Ensure minimum of 64M and maximum of 1024M + [ "$recommended_memory" -lt 64 ] && recommended_memory=64 + [ "$recommended_memory" -gt 1024 ] && recommended_memory=1024 + + echo "${recommended_memory}M" +} + +# Calculate max_children based on ACTUAL peak concurrent +calculate_max_children_from_actual_usage() { + local username="$1" + local domain="$2" + + # Get real peak concurrent from logs + local traffic + traffic=$(get_peak_concurrent_detailed "$username" "$domain") + local peak_concurrent=$(echo "$traffic" | cut -d'|' -f1) + + # Add 30% safety margin for traffic spikes + local recommended_max_children=$((peak_concurrent + (peak_concurrent / 3))) + + # Minimum of 5, maximum of 100 + [ "$recommended_max_children" -lt 5 ] && recommended_max_children=5 + [ "$recommended_max_children" -gt 100 ] && recommended_max_children=100 + + echo "$recommended_max_children" +} + +# Calculate max_requests based on memory leak patterns +calculate_max_requests_from_actual_usage() { + local username="$1" + local domain="$2" + + # Default: recycle every 500 requests + local recommended_requests=500 + + # Check if memory leak detected + local leak_status + leak_status=$(detect_memory_leak_pattern "$username" "$domain") + local leak_type=$(echo "$leak_status" | cut -d'|' -f1) + + # If leak detected, recycle more frequently + if [ "$leak_type" = "LIKELY_LEAK" ]; then + recommended_requests=250 # Recycle more often + fi + + echo "$recommended_requests" +} + +# ============================================================================ +# PROFILE STORAGE AND RETRIEVAL +# ============================================================================ + +# Store domain profile to file +store_domain_profile() { + local profile="$1" + local profile_dir="/tmp/php-domain-profiles" + + mkdir -p "$profile_dir" 2>/dev/null + + local domain=$(echo "$profile" | cut -d'|' -f1) + echo "$profile" > "$profile_dir/$domain.profile" +} + +# Retrieve stored profile +get_stored_profile() { + local domain="$1" + local profile_dir="/tmp/php-domain-profiles" + + [ -f "$profile_dir/$domain.profile" ] && cat "$profile_dir/$domain.profile" +} + +# Get all stored profiles +get_all_stored_profiles() { + local profile_dir="/tmp/php-domain-profiles" + + [ -d "$profile_dir" ] && cat "$profile_dir"/*.profile 2>/dev/null +} + +# Clear old profiles (older than 24 hours) +cleanup_old_profiles() { + local profile_dir="/tmp/php-domain-profiles" + + [ ! -d "$profile_dir" ] && return + + find "$profile_dir" -name "*.profile" -mtime +0 -delete 2>/dev/null +} + +export -f analyze_memory_errors_from_logs +export -f analyze_process_memory_usage +export -f get_peak_concurrent_detailed +export -f detect_memory_leak_pattern +export -f build_domain_profile +export -f calculate_memory_limit_from_actual_usage +export -f calculate_max_children_from_actual_usage +export -f calculate_max_requests_from_actual_usage +export -f store_domain_profile +export -f get_stored_profile +export -f get_all_stored_profiles +export -f cleanup_old_profiles diff --git a/modules/performance/php-domain-analyzer.sh b/modules/performance/php-domain-analyzer.sh new file mode 100755 index 0000000..b2a7bcf --- /dev/null +++ b/modules/performance/php-domain-analyzer.sh @@ -0,0 +1,146 @@ +#!/bin/bash +# PHP Domain Analyzer - Collect real usage data for true optimization +# Run this before optimization to build accurate domain profiles +# Uses actual logs and process data instead of thresholds + +# Source required libraries +PHP_TOOLKIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)" +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-analytics.sh" 2>/dev/null || { echo "ERROR: php-analytics.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 "$@" +} + +# ============================================================================ +# MAIN ANALYSIS +# ============================================================================ + +initialize_system_detection + +if [ "$EUID" -ne 0 ]; then + cecho "${RED}ERROR: This script must be run as root${NC}" + exit 1 +fi + +cecho "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}" +cecho "${CYAN}║${WHITE} PHP DOMAIN ANALYZER - REAL USAGE DATA COLLECTION ${CYAN}║${NC}" +cecho "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +cecho "${WHITE}${BOLD}Starting Analysis...${NC}" +cecho "${CYAN}This may take a few minutes. Analyzing logs, processes, and traffic patterns.${NC}" +echo "" + +# Get all users and domains +users=$(list_all_users) + +declare -a all_profiles +total_domains=0 +analyzed=0 + +cecho "${YELLOW}Analyzing domains...${NC}" +echo "" + +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)) + + cecho -n " [$total_domains] Analyzing $domain..." + + # Build profile with actual data + profile=$(build_domain_profile "$username" "$domain") + + if [ -n "$profile" ]; then + all_profiles+=("$profile") + store_domain_profile "$profile" + analyzed=$((analyzed + 1)) + + # Extract key metrics + peak_concurrent=$(echo "$profile" | cut -d'|' -f3) + peak_mem=$(echo "$profile" | cut -d'|' -f11) + leak_status=$(echo "$profile" | cut -d'|' -f12) + + cecho "${GREEN} ✓${NC}" + cecho " Peak: $peak_concurrent concurrent | Memory: ${peak_mem}MB | Leak: $leak_status" + else + cecho "${YELLOW} ✗${NC} (could not collect data)" + fi + done <<< "$user_domains" +done <<< "$users" + +echo "" +cecho "${CYAN}═══════════════════════════════════════════════════════════════════════════${NC}" +cecho "${WHITE}${BOLD}ANALYSIS COMPLETE${NC}" +cecho "${CYAN}═══════════════════════════════════════════════════════════════════════════${NC}" +echo "" + +cecho " Total domains analyzed: ${WHITE}${analyzed}${NC} / ${total_domains}" +cecho " Profiles stored in: ${WHITE}/tmp/php-domain-profiles/${NC}" +echo "" + +# Display summary +if [ "${#all_profiles[@]}" -gt 0 ]; then + cecho "${CYAN}ANALYSIS SUMMARY:${NC}" + echo "" + + # Find domains with highest memory usage + cecho "${WHITE}${BOLD}Top Memory Usage:${NC}" + printf '%s\n' "${all_profiles[@]}" | sort -t'|' -k11 -rn | head -5 | while IFS='|' read -r domain username peak_concurrent avg_concurrent total_hits min_mem max_mem avg_mem proc_count mem_exhausted peak_mem leak rest; do + cecho " • $domain: ${peak_mem}MB peak memory" + done + + echo "" + + # Find domains with highest traffic + cecho "${WHITE}${BOLD}Top Traffic:${NC}" + printf '%s\n' "${all_profiles[@]}" | sort -t'|' -k3 -rn | head -5 | while IFS='|' read -r domain username peak_concurrent avg_concurrent total_hits rest; do + cecho " • $domain: $peak_concurrent concurrent requests" + done + + echo "" + + # Find domains with potential leaks + leak_count=$(printf '%s\n' "${all_profiles[@]}" | \grep -c "LIKELY_LEAK") + if [ "$leak_count" -gt 0 ]; then + cecho "${RED}${BOLD}⚠ Domains with potential memory leaks:${NC} $leak_count" + printf '%s\n' "${all_profiles[@]}" | \grep "LIKELY_LEAK" | while IFS='|' read -r domain username rest; do + cecho " • $domain" + done + echo "" + fi + + # Show high memory usage domains + high_mem=$(printf '%s\n' "${all_profiles[@]}" | awk -F'|' '$11 > 200 {print}' | wc -l) + if [ "$high_mem" -gt 0 ]; then + cecho "${YELLOW}${BOLD}Domains using >200MB:${NC} $high_mem" + fi +fi + +echo "" +cecho "${CYAN}═══════════════════════════════════════════════════════════════════════════${NC}" +echo "" +cecho "${GREEN}${BOLD}✓ Domain profiles ready for optimization!${NC}" +echo "" +cecho "Next step: Run php-optimizer.sh → Option 5 → Select optimization level" +cecho "The optimizer will now use REAL usage data for accurate recommendations." +echo "" diff --git a/modules/performance/php-optimizer.sh b/modules/performance/php-optimizer.sh index ce72ba7..5d9b4f8 100755 --- a/modules/performance/php-optimizer.sh +++ b/modules/performance/php-optimizer.sh @@ -19,6 +19,9 @@ source "$PHP_TOOLKIT_DIR/lib/php-scanner.sh" 2>/dev/null || true source "$PHP_TOOLKIT_DIR/lib/php-action-executor.sh" 2>/dev/null || true source "$PHP_TOOLKIT_DIR/lib/php-server-manager.sh" 2>/dev/null || true +# True Optimization - Analytics Library for real data-driven decisions +source "$PHP_TOOLKIT_DIR/lib/php-analytics.sh" 2>/dev/null || true + # Color codes (using safe echo -e) RED='\033[0;31m' GREEN='\033[0;32m' @@ -1263,6 +1266,158 @@ rollback_php_ini() { return 1 } +# ============================================================================ +# RUN DOMAIN ANALYZER - Collect real usage data +# ============================================================================ + +run_domain_analyzer() { + show_banner + cecho "${WHITE}${BOLD}DOMAIN PRE-ANALYSIS${NC}" + echo "" + cecho "${YELLOW}This will analyze real usage data from your domains.${NC}" + echo "" + cecho "${CYAN}This process will:${NC}" + cecho " • Scan error logs for memory exhaustion patterns" + cecho " • Analyze current process memory usage" + cecho " • Parse traffic logs for peak concurrent requests" + cecho " • Detect potential memory leaks" + cecho " • Build accurate domain profiles" + echo "" + + if ! confirm "Continue with domain analysis?"; then + cecho "${YELLOW}Analysis cancelled${NC}" + read -p "Press Enter to continue..." + return + fi + + # Check if php-domain-analyzer.sh exists + local analyzer_script="/root/server-toolkit/modules/performance/php-domain-analyzer.sh" + if [ ! -f "$analyzer_script" ]; then + cecho "${RED}ERROR: Domain analyzer script not found${NC}" + cecho "${YELLOW}Expected: $analyzer_script${NC}" + read -p "Press Enter to continue..." + return + fi + + # Run the analyzer + bash "$analyzer_script" + + echo "" + cecho "${GREEN}${BOLD}✓ Domain analysis complete!${NC}" + cecho "${YELLOW}Profiles are ready for optimization.${NC}" + echo "" + read -p "Press Enter to continue..." +} + +# ============================================================================ +# PROFILE-BASED OPTIMIZATION FUNCTIONS (True Optimization) +# ============================================================================ + +# Get recommendation for memory_limit using actual usage data +get_memory_limit_recommendation() { + local domain="$1" + local username="$2" + + # Try to get from stored profile first (real data) + if [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + local profile + profile=$(cat "/tmp/php-domain-profiles/$domain.profile") + + # Calculate based on ACTUAL memory seen in logs + local peak_mem_seen=$(echo "$profile" | cut -d'|' -f11) + + if [ "$peak_mem_seen" -gt 0 ]; then + # Use observed peak + 20% buffer + local recommended=$((peak_mem_seen + (peak_mem_seen / 5))) + [ "$recommended" -gt 1024 ] && recommended=1024 + [ "$recommended" -lt 64 ] && recommended=64 + echo "${recommended}M" + return 0 + fi + fi + + # Fallback to old method if no profile + calculate_optimal_memory_limit "$username" "$domain" "50" +} + +# Get recommendation for max_children using actual traffic data +get_max_children_recommendation() { + local domain="$1" + local username="$2" + + # Try to get from stored profile first (real traffic data) + if [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + local profile + profile=$(cat "/tmp/php-domain-profiles/$domain.profile") + + # Use ACTUAL peak concurrent from logs + local peak_concurrent=$(echo "$profile" | cut -d'|' -f3) + + if [ "$peak_concurrent" -gt 0 ]; then + # Add 30% safety margin for spikes + local recommended=$((peak_concurrent + (peak_concurrent / 3))) + [ "$recommended" -gt 100 ] && recommended=100 + [ "$recommended" -lt 5 ] && recommended=5 + echo "$recommended" + return 0 + fi + fi + + # Fallback to old method + calculate_optimal_max_requests "50" +} + +# Get recommendation for max_requests using memory leak analysis +get_max_requests_recommendation() { + local domain="$1" + + # Try to get from stored profile first + if [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + local profile + profile=$(cat "/tmp/php-domain-profiles/$domain.profile") + + # Check for memory leak pattern + local leak_type=$(echo "$profile" | cut -d'|' -f12) + + if [ "$leak_type" = "LIKELY_LEAK" ]; then + echo "250" # Recycle frequently for leak-prone domains + return 0 + fi + fi + + # Default + echo "500" +} + +# Display profile data if available +show_profile_analysis() { + local domain="$1" + + [ ! -f "/tmp/php-domain-profiles/$domain.profile" ] && return + + local profile + profile=$(cat "/tmp/php-domain-profiles/$domain.profile") + + local peak_concurrent=$(echo "$profile" | cut -d'|' -f3) + local avg_concurrent=$(echo "$profile" | cut -d'|' -f4) + local min_mem=$(echo "$profile" | cut -d'|' -f6) + local max_mem=$(echo "$profile" | cut -d'|' -f7) + local avg_mem=$(echo "$profile" | cut -d'|' -f8) + local mem_exhausted=$(echo "$profile" | cut -d'|' -f10) + local peak_mem=$(echo "$profile" | cut -d'|' -f11) + local leak_status=$(echo "$profile" | cut -d'|' -f12) + + cecho "${CYAN}REAL USAGE DATA (from profile analysis):${NC}" + cecho " Peak Concurrent: ${WHITE}${peak_concurrent}${NC} | Avg: ${avg_concurrent}" + cecho " Process Memory: ${WHITE}${min_mem}MB${NC}-${max_mem}MB (avg: ${avg_mem}MB)" + if [ "$mem_exhausted" -gt 0 ]; then + cecho " Memory Exhaustion Errors: ${RED}${mem_exhausted}${NC}" + fi + cecho " Peak Memory Seen: ${WHITE}${peak_mem}MB${NC}" + cecho " Memory Status: ${leak_status}" + echo "" +} + # ============================================================================ # OPTIMIZE ALL DOMAINS (SERVER-WIDE) - TIERED OPTIMIZATION # ============================================================================ @@ -1275,6 +1430,11 @@ optimize_all_domains() { cecho "${YELLOW}Choose optimization level for all domains on the server${NC}" echo "" + cecho "${CYAN}PRE-ANALYSIS (Recommended for accurate optimization):${NC}" + cecho " ${GREEN}0${NC}) Pre-analyze domains" + cecho " └─ Collect real usage data for data-driven optimization" + echo "" + cecho "${CYAN}OPTIMIZATION LEVELS:${NC}" cecho " ${GREEN}1${NC}) Optimize pm.max_children only" cecho " └─ Adjust process limits based on traffic" @@ -1297,18 +1457,21 @@ optimize_all_domains() { cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" while true; do - read -p "Select optimization level (1-5, b, or q): " opt_choice + read -p "Select option (0-5, b, or q): " opt_choice opt_choice=${opt_choice,,} - if [[ "$opt_choice" =~ ^[1-5bq]$ ]]; then + if [[ "$opt_choice" =~ ^[0-5bq]$ ]]; then break fi echo "" - cecho "${RED}Invalid choice. Please enter 1-5, b, or q${NC}" + cecho "${RED}Invalid choice. Please enter 0-5, b, or q${NC}" echo "" done case "$opt_choice" in + 0) + run_domain_analyzer + ;; 1) optimize_level_1_max_children ;; @@ -1377,40 +1540,72 @@ optimize_level_1_max_children() { cecho " Status: ${WHITE}${status}${NC}" echo "" + # Check if profiles exist + local profiles_exist=0 + if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then + profiles_exist=1 + cecho "${GREEN}${BOLD}✓ Domain profiles detected!${NC}" + cecho "${YELLOW}Using REAL usage data from domain analysis${NC}" + echo "" + fi + # Get recommendations cecho "${CYAN}Step 2: Calculating optimal settings...${NC}" echo "" - local balanced_result - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - balanced_result=$(calculate_balanced_memory_allocation_per_domain) - else - balanced_result=$(calculate_balanced_memory_allocation) - fi - declare -A recommended_values declare -A domain_to_username local changes_needed=0 - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - while IFS='|' read -r domain username php_ver current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do - [ "$domain" = "DOMAIN" ] && continue + # Get all users and domains + local users + users=$(list_all_users) + + while IFS= read -r username; do + [ -z "$username" ] && continue + local user_domains + user_domains=$(get_user_domains "$username") + + while IFS= read -r domain; do [ -z "$domain" ] && continue + + local current_max + local pool_config + pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null) + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + current_max=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + [ -z "$current_max" ] && current_max="?" + else + current_max="?" + fi + + # Get recommendations - use profile if available, otherwise use traffic-based + local recommended_max + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + recommended_max=$(get_max_children_recommendation "$domain" "$username") + else + # Fallback to traffic-based (old method) + local traffic_rpm + traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0") + [ "$traffic_rpm" = "?" ] && traffic_rpm="0" + recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5)) + fi + recommended_values["$domain"]="$recommended_max" domain_to_username["$domain"]="$username" - if [ "$current_max" != "$recommended_max" ]; then + if [ "$current_max" != "?" ] && [ "$current_max" != "$recommended_max" ]; then changes_needed=$((changes_needed + 1)) - if [[ "$reason" == *"REDUCE"* ]]; then - cecho " ${YELLOW}⚠${NC} $domain: $current_max → ${YELLOW}$recommended_max${NC} (REDUCE)" - elif [[ "$reason" == *"INCREASE"* ]]; then - cecho " ${GREEN}↑${NC} $domain: $current_max → ${GREEN}$recommended_max${NC} (INCREASE)" - else - cecho " ${CYAN}$domain${NC}: $current_max → $recommended_max" + cecho " ${GREEN}✓${NC} $domain: $current_max → ${GREEN}$recommended_max${NC}" + + # Show profile data if available + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + show_profile_analysis "$domain" fi fi - done <<< "$balanced_result" - fi + done <<< "$user_domains" + done <<< "$users" echo "" if [ "$changes_needed" -eq 0 ]; then @@ -1514,7 +1709,7 @@ optimize_level_2_memory() { cecho "${YELLOW}This will adjust both process limits and PHP memory settings.${NC}" echo "" cecho "${CYAN}What will be optimized:${NC}" - cecho " • pm.max_children (based on traffic)" + cecho " • pm.max_children (based on real traffic data if available)" cecho " • memory_limit (PHP memory per domain)" echo "" cecho "${RED}${BOLD}WARNING:${NC} ${RED}This will modify PHP-FPM and php.ini configurations!${NC}" @@ -1543,44 +1738,92 @@ optimize_level_2_memory() { cecho " Status: ${WHITE}${status}${NC}" echo "" + # Check if profiles exist + local profiles_exist=0 + if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then + profiles_exist=1 + cecho "${GREEN}${BOLD}✓ Domain profiles detected!${NC}" + cecho "${YELLOW}Using REAL usage data from domain analysis${NC}" + echo "" + else + cecho "${YELLOW}${BOLD}⚠ No domain profiles found${NC}" + cecho "${CYAN}For more accurate optimization, run pre-analysis first:${NC}" + cecho " ${WHITE}php-optimizer.sh${NC} → Option 3 (Pre-analyze domains)" + echo "" + fi + # Get recommendations cecho "${CYAN}Step 2: Calculating optimal settings...${NC}" echo "" - local balanced_result - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - balanced_result=$(calculate_balanced_memory_allocation_per_domain) - else - balanced_result=$(calculate_balanced_memory_allocation) - fi - declare -A recommended_max_children declare -A recommended_memory_limit declare -A domain_to_username local changes_needed=0 - # Parse balanced result and show recommendations - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - while IFS='|' read -r domain username php_ver current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do - [ "$domain" = "DOMAIN" ] && continue + # Get all users and domains + local users + users=$(list_all_users) + + while IFS= read -r username; do + [ -z "$username" ] && continue + local user_domains + user_domains=$(get_user_domains "$username") + + while IFS= read -r domain; do [ -z "$domain" ] && continue + local current_max + local pool_config + pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null) + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + current_max=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + [ -z "$current_max" ] && current_max="?" + else + current_max="?" + fi + + # Get recommendations - use profile if available, otherwise use traffic-based + local recommended_max + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + recommended_max=$(get_max_children_recommendation "$domain" "$username") + else + # Fallback to traffic-based (old method) + local traffic_rpm + traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0") + [ "$traffic_rpm" = "?" ] && traffic_rpm="0" + recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5)) + fi + + local recommended_memory + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + recommended_memory=$(get_memory_limit_recommendation "$domain" "$username") + else + # Fallback to traffic-based (old method) + local traffic_rpm + traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0") + [ "$traffic_rpm" = "?" ] && traffic_rpm="0" + recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") + fi + recommended_max_children["$domain"]="$recommended_max" + recommended_memory_limit["$domain"]="$recommended_memory" domain_to_username["$domain"]="$username" - # Calculate optimal memory_limit based on traffic - local optimal_memory - optimal_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") - recommended_memory_limit["$domain"]="$optimal_memory" - - if [ "$current_max" != "$recommended_max" ]; then + if [ "$current_max" != "?" ] && [ "$current_max" != "$recommended_max" ]; then changes_needed=$((changes_needed + 1)) cecho " ${GREEN}✓${NC} $domain:" cecho " pm.max_children: $current_max → ${GREEN}$recommended_max${NC}" - cecho " memory_limit: → ${GREEN}$optimal_memory${NC}" + cecho " memory_limit: → ${GREEN}$recommended_memory${NC}" + + # Show profile data if available + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + show_profile_analysis "$domain" + fi fi - done <<< "$balanced_result" - fi + done <<< "$user_domains" + done <<< "$users" echo "" if [ "$changes_needed" -eq 0 ]; then @@ -1749,51 +1992,87 @@ optimize_level_3_advanced() { cecho " Status: ${WHITE}${status}${NC}" echo "" + # Check if profiles exist + local profiles_exist=0 + if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then + profiles_exist=1 + cecho "${GREEN}${BOLD}✓ Domain profiles detected!${NC}" + cecho "${YELLOW}Using REAL usage data and memory leak detection${NC}" + echo "" + fi + # Get recommendations cecho "${CYAN}Step 2: Calculating optimal settings...${NC}" echo "" - local balanced_result - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - balanced_result=$(calculate_balanced_memory_allocation_per_domain) - else - balanced_result=$(calculate_balanced_memory_allocation) - fi - declare -A recommended_max_children declare -A recommended_memory_limit declare -A recommended_max_requests declare -A domain_to_username local changes_needed=0 - # Parse balanced result - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - while IFS='|' read -r domain username php_ver current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do - [ "$domain" = "DOMAIN" ] && continue + # Get all users and domains + local users + users=$(list_all_users) + + while IFS= read -r username; do + [ -z "$username" ] && continue + local user_domains + user_domains=$(get_user_domains "$username") + + while IFS= read -r domain; do [ -z "$domain" ] && continue - recommended_max_children["$domain"]="$recommended_max" + recommended_max_children["$domain"]=0 + recommended_memory_limit["$domain"]=0 + recommended_max_requests["$domain"]=0 domain_to_username["$domain"]="$username" - # Calculate optimal memory_limit - local optimal_memory - optimal_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") - recommended_memory_limit["$domain"]="$optimal_memory" + # Use profile-based if available + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + recommended_max_children["$domain"]=$(get_max_children_recommendation "$domain" "$username") + recommended_memory_limit["$domain"]=$(get_memory_limit_recommendation "$domain" "$username") + recommended_max_requests["$domain"]=$(get_max_requests_recommendation "$domain") + else + # Fallback to traffic-based (old method) + local traffic_rpm + traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0") + [ "$traffic_rpm" = "?" ] && traffic_rpm="0" - # Calculate optimal max_requests - local optimal_requests - optimal_requests=$(calculate_optimal_max_requests "$traffic_rpm") - recommended_max_requests["$domain"]="$optimal_requests" + recommended_max_children["$domain"]=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5)) + recommended_memory_limit["$domain"]=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") + recommended_max_requests["$domain"]=$(calculate_optimal_max_requests "$traffic_rpm") + fi - if [ "$current_max" != "$recommended_max" ]; then + local current_max + local pool_config + pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null) + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + current_max=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + [ -z "$current_max" ] && current_max="?" + else + current_max="?" + fi + + local rec_max_children=${recommended_max_children["$domain"]} + local rec_memory=${recommended_memory_limit["$domain"]} + local rec_requests=${recommended_max_requests["$domain"]} + + if [ "$current_max" != "?" ] && [ "$current_max" != "$rec_max_children" ]; then changes_needed=$((changes_needed + 1)) cecho " ${GREEN}✓${NC} $domain:" - cecho " pm.max_children: $current_max → ${GREEN}$recommended_max${NC}" - cecho " memory_limit: → ${GREEN}$optimal_memory${NC}" - cecho " pm.max_requests: → ${GREEN}$optimal_requests${NC} (prevent memory leaks)" + cecho " pm.max_children: $current_max → ${GREEN}$rec_max_children${NC}" + cecho " memory_limit: → ${GREEN}$rec_memory${NC}" + cecho " pm.max_requests: → ${GREEN}$rec_requests${NC} (prevent memory leaks)" + + # Show profile data if available + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + show_profile_analysis "$domain" + fi fi - done <<< "$balanced_result" - fi + done <<< "$user_domains" + done <<< "$users" echo "" if [ "$changes_needed" -eq 0 ]; then @@ -2158,11 +2437,13 @@ optimize_level_5_everything() { cecho "${CYAN}STEP 2: Calculating Recommendations${NC}" echo "" - local balanced_result - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - balanced_result=$(calculate_balanced_memory_allocation_per_domain) - else - balanced_result=$(calculate_balanced_memory_allocation) + # Check if profiles exist + local profiles_exist=0 + if [ -d "/tmp/php-domain-profiles" ] && [ "$(ls -1 /tmp/php-domain-profiles/*.profile 2>/dev/null | wc -l)" -gt 0 ]; then + profiles_exist=1 + cecho "${GREEN}${BOLD}✓ Domain profiles detected!${NC}" + cecho "${YELLOW}Using REAL usage data for comprehensive optimization${NC}" + echo "" fi declare -A recommended_max_children @@ -2173,23 +2454,55 @@ optimize_level_5_everything() { local changes_count=0 local opcache_count=0 - if [ "$SYS_CONTROL_PANEL" = "cpanel" ]; then - while IFS='|' read -r domain username php_ver current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do - [ "$domain" = "DOMAIN" ] && continue + # Get all users and domains + local users + users=$(list_all_users) + + while IFS= read -r username; do + [ -z "$username" ] && continue + local user_domains + user_domains=$(get_user_domains "$username") + + while IFS= read -r domain; do [ -z "$domain" ] && continue + local current_max + local pool_config + pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null) + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + current_max=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + [ -z "$current_max" ] && current_max="?" + else + current_max="?" + fi + + # Get recommendations - use profile if available, otherwise use traffic-based + local recommended_max + local recommended_memory + local recommended_requests + + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + recommended_max=$(get_max_children_recommendation "$domain" "$username") + recommended_memory=$(get_memory_limit_recommendation "$domain" "$username") + recommended_requests=$(get_max_requests_recommendation "$domain") + else + # Fallback to traffic-based (old method) + local traffic_rpm + traffic_rpm=$(get_domain_peak_concurrent "$domain" 2>/dev/null || echo "0") + [ "$traffic_rpm" = "?" ] && traffic_rpm="0" + + recommended_max=$((traffic_rpm > 5 ? traffic_rpm + 10 : 5)) + recommended_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") + recommended_requests=$(calculate_optimal_max_requests "$traffic_rpm") + fi + recommended_max_children["$domain"]="$recommended_max" + recommended_memory_limit["$domain"]="$recommended_memory" + recommended_max_requests["$domain"]="$recommended_requests" domain_to_username["$domain"]="$username" - local optimal_memory - optimal_memory=$(calculate_optimal_memory_limit "$username" "$domain" "$traffic_rpm") - recommended_memory_limit["$domain"]="$optimal_memory" - - local optimal_requests - optimal_requests=$(calculate_optimal_max_requests "$traffic_rpm") - recommended_max_requests["$domain"]="$optimal_requests" - - if [ "$current_max" != "$recommended_max" ]; then + if [ "$current_max" != "?" ] && [ "$current_max" != "$recommended_max" ]; then changes_count=$((changes_count + 1)) fi @@ -2197,8 +2510,8 @@ optimize_level_5_everything() { opcache_needs_enable["$domain"]="1" opcache_count=$((opcache_count + 1)) fi - done <<< "$balanced_result" - fi + done <<< "$user_domains" + done <<< "$users" cecho " Domains needing updates: ${YELLOW}${changes_count}${NC}" cecho " Domains needing OPcache: ${YELLOW}${opcache_count}${NC}" @@ -2254,7 +2567,13 @@ optimize_level_5_everything() { if [ "$pool_updated" -gt 0 ]; then cecho " ${GREEN}✓${NC} $domain: FPM settings optimized" + cecho " pm.max_children: ${recommended_max} | pm.max_requests: ${recommended_requests}" optimized=$((optimized + 1)) + + # Show profile data if available + if [ "$profiles_exist" = "1" ] && [ -f "/tmp/php-domain-profiles/$domain.profile" ]; then + show_profile_analysis "$domain" + fi fi fi @@ -2285,7 +2604,7 @@ optimize_level_5_everything() { modify_php_ini_setting "$ini_file" "opcache.memory_consumption" "$optimal_opcache_mem" >/dev/null 2>&1 if validate_php_ini "$ini_file" >/dev/null 2>&1; then - cecho " ${GREEN}✓${NC} $domain: OPcache enabled" + cecho " ${GREEN}✓${NC} $domain: OPcache enabled (${optimal_opcache_mem})" opcache_enabled=$((opcache_enabled + 1)) fi fi