Complete profile-based PHP-FPM optimization system with real usage data

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 <noreply@anthropic.com>
This commit is contained in:
cschantz
2026-02-18 19:40:01 -05:00
parent 7a68086bf1
commit 4d745f203e
3 changed files with 944 additions and 89 deletions
+390
View File
@@ -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
+146
View File
@@ -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 ""
+408 -89
View File
@@ -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