From 7c550ebeb062db10238a500d3b134dc2fa79e28f Mon Sep 17 00:00:00 2001 From: cschantz Date: Tue, 2 Dec 2025 20:28:27 -0500 Subject: [PATCH] Phase 2: Add comprehensive PHP analysis engine (lib/php-analyzer.sh) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ANALYSIS CAPABILITIES (12 functions): - Error log analysis (memory exhausted, max_children, timeouts, slow requests) - Resource usage calculations (memory per process, optimal max_children) - Traffic analysis (peak concurrent requests, avg requests/minute) - OPcache effectiveness analysis (hit rate, memory usage, recommendations) - Configuration issue detection (security, performance, capacity issues) - Complete domain analysis reporting ERROR LOG ANALYSIS: - analyze_memory_exhausted_errors: Track "Allowed memory size exhausted" - analyze_max_children_errors: Detect "server reached pm.max_children" (CRITICAL!) - analyze_slow_requests: Parse slow request logs, track slowest scripts - analyze_execution_timeout_errors: Find "Maximum execution time exceeded" RESOURCE CALCULATIONS: - calculate_memory_per_process: Average KB per PHP-FPM process - calculate_optimal_max_children: Intelligent calculation based on: * Available system memory (total - reserved) * Average memory per process * 20% safety buffer * Minimum sanity checks TRAFFIC ANALYSIS: - calculate_peak_concurrent_requests: Peak concurrent from access logs - calculate_avg_requests_per_minute: Average load over time period OPCACHE ANALYSIS: - analyze_opcache_effectiveness: Status, hit rate, memory usage, recommendations * Detects if disabled (40-70% perf loss!) * Calculates hit rate (should be >90%) * Checks wasted memory and cache capacity ISSUE DETECTION (7 critical checks): - detect_php_config_issues: Comprehensive configuration validation 1. post_max_size < upload_max_filesize (CRITICAL - uploads fail) 2. display_errors = On (HIGH - security risk) 3. memory_limit too low (MEDIUM - performance issue) 4. pm.max_children errors (CRITICAL - capacity issue) 5. Memory exhausted errors (HIGH - need more RAM or optimization) 6. OPcache disabled or low hit rate (HIGH/MEDIUM - performance) 7. pm.max_requests = 0 (MEDIUM - memory leaks accumulate) 8. pm = static on low traffic (LOW - wastes memory) COMPREHENSIVE REPORTING: - analyze_domain_php: Complete analysis report including: * PHP version detection * Configuration hierarchy (4 priority levels) * Effective settings (memory, execution, uploads) * PHP-FPM pool configuration * Resource usage (processes, memory) * OPcache status and hit rates * Traffic analysis (24h) * Error analysis (7 days) * Issues detected with severity levels * Optimization recommendations with reasoning HELPER FUNCTIONS: - convert_to_bytes: Parse human-readable sizes (128M → bytes) INTEGRATION: - Uses lib/php-detector.sh for all detection - Uses lib/system-detect.sh for system info - All functions exported for use by main optimizer NEXT PHASE: modules/performance/php-optimizer.sh (interactive menu + apply changes) --- lib/php-analyzer.sh | 728 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 728 insertions(+) create mode 100644 lib/php-analyzer.sh diff --git a/lib/php-analyzer.sh b/lib/php-analyzer.sh new file mode 100644 index 0000000..df9f171 --- /dev/null +++ b/lib/php-analyzer.sh @@ -0,0 +1,728 @@ +#!/bin/bash +# PHP Analysis Engine - Analyzes PHP configurations and identifies issues +# Part of Server Toolkit - Phase 2: Analysis +# Dependencies: lib/php-detector.sh, lib/system-detect.sh + +# Source required libraries +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; exit 1; } +source "$SCRIPT_DIR/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; exit 1; } + +# ============================================================================ +# ERROR LOG ANALYSIS +# ============================================================================ + +# Analyze PHP error logs for memory exhausted errors +# Usage: analyze_memory_exhausted_errors +# Returns: count|file pairs +analyze_memory_exhausted_errors() { + local username="$1" + local days="${2:-7}" # Default last 7 days + + local error_logs + error_logs=$(find_php_error_logs "$username") + + if [ -z "$error_logs" ]; then + echo "0|No logs found" + return + fi + + local total_count=0 + local results="" + + while IFS= read -r log_file; do + [ ! -f "$log_file" ] && continue + + # Find errors in last N days + local count + count=$(find "$log_file" -mtime -"$days" -exec grep -c "Allowed memory size.*exhausted" {} \; 2>/dev/null || echo "0") + + if [ "$count" -gt 0 ]; then + total_count=$((total_count + count)) + results+="$count|$log_file"$'\n' + fi + done <<< "$error_logs" + + echo -e "$total_count|TOTAL\n$results" +} + +# Analyze PHP-FPM error logs for max_children errors +# Usage: analyze_max_children_errors +# Returns: count|timestamp|pool format +analyze_max_children_errors() { + local username="$1" + local days="${2:-7}" + + local fpm_logs + fpm_logs=$(find_fpm_error_logs "$username") + + if [ -z "$fpm_logs" ]; then + echo "0|No FPM logs found" + return + fi + + local total_count=0 + local results="" + + while IFS= read -r log_file; do + [ ! -f "$log_file" ] && continue + + # Find max_children errors with timestamps + local errors + errors=$(find "$log_file" -mtime -"$days" -exec grep -E "server reached (pm\.)?max_children" {} \; 2>/dev/null) + + if [ -n "$errors" ]; then + local count + count=$(echo "$errors" | wc -l) + total_count=$((total_count + count)) + + # Extract most recent occurrence + local last_occurrence + last_occurrence=$(echo "$errors" | tail -1 | grep -oE '\[[0-9]{2}-[A-Za-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}\]' | tr -d '[]') + + results+="$count|$last_occurrence|$(basename "$log_file")"$'\n' + fi + done <<< "$fpm_logs" + + echo -e "$total_count|TOTAL\n$results" +} + +# Analyze slow request logs +# Usage: analyze_slow_requests +# Returns: count|script|duration format +analyze_slow_requests() { + local username="$1" + local days="${2:-7}" + local threshold="${3:-5}" # Default 5 seconds + + local slow_logs + slow_logs=$(find_fpm_slow_logs "$username") + + if [ -z "$slow_logs" ]; then + echo "0|No slow logs found" + return + fi + + local total_count=0 + local results="" + declare -A slow_scripts + + while IFS= read -r log_file; do + [ ! -f "$log_file" ] && continue + + # Parse slow log format + # [pool www] pid 12345 + # script_filename = /path/to/script.php + # [duration] 7.123456 + + local entries + entries=$(find "$log_file" -mtime -"$days" -exec grep -A2 "^\[" {} \; 2>/dev/null) + + if [ -n "$entries" ]; then + local script="" + local duration="" + + while IFS= read -r line; do + if [[ "$line" =~ script_filename.*=\ (.+)$ ]]; then + script="${BASH_REMATCH[1]}" + elif [[ "$line" =~ ^\[.*\]\ ([0-9]+\.[0-9]+)$ ]]; then + duration="${BASH_REMATCH[1]}" + + # Check if exceeds threshold + if [ -n "$script" ] && [ -n "$duration" ]; then + local duration_int=${duration%.*} + if [ "$duration_int" -ge "$threshold" ]; then + total_count=$((total_count + 1)) + + # Track slowest occurrence per script + if [ -z "${slow_scripts[$script]}" ] || (( $(echo "${slow_scripts[$script]} < $duration" | bc -l) )); then + slow_scripts[$script]="$duration" + fi + fi + fi + + script="" + duration="" + fi + done <<< "$entries" + fi + done <<< "$slow_logs" + + # Format results + for script in "${!slow_scripts[@]}"; do + results+="1|$script|${slow_scripts[$script]}s"$'\n' + done + + echo -e "$total_count|TOTAL\n$results" +} + +# Analyze execution timeout errors +# Usage: analyze_execution_timeout_errors +analyze_execution_timeout_errors() { + local username="$1" + local days="${2:-7}" + + local error_logs + error_logs=$(find_php_error_logs "$username") + + if [ -z "$error_logs" ]; then + echo "0|No logs found" + return + fi + + local total_count=0 + local results="" + + while IFS= read -r log_file; do + [ ! -f "$log_file" ] && continue + + local count + count=$(find "$log_file" -mtime -"$days" -exec grep -c "Maximum execution time.*exceeded" {} \; 2>/dev/null || echo "0") + + if [ "$count" -gt 0 ]; then + total_count=$((total_count + count)) + results+="$count|$log_file"$'\n' + fi + done <<< "$error_logs" + + echo -e "$total_count|TOTAL\n$results" +} + +# ============================================================================ +# RESOURCE USAGE CALCULATIONS +# ============================================================================ + +# Calculate average memory per PHP-FPM process +# Usage: calculate_memory_per_process +# Returns: average_kb|total_processes|total_memory_mb +calculate_memory_per_process() { + local username="$1" + + local memory_stats + memory_stats=$(get_fpm_memory_usage "$username") + + if [ -z "$memory_stats" ] || [[ "$memory_stats" == "0|0" ]]; then + echo "0|0|0" + return + fi + + local avg_kb total_mb + avg_kb=$(echo "$memory_stats" | cut -d'|' -f1) + total_mb=$(echo "$memory_stats" | cut -d'|' -f2) + + local process_count + process_count=$(get_fpm_process_count "$username") + + echo "$avg_kb|$process_count|$total_mb" +} + +# Calculate optimal max_children based on available memory +# Usage: calculate_optimal_max_children [reserved_mb] +# Returns: recommended_max_children|reason +calculate_optimal_max_children() { + local username="$1" + local reserved_mb="${2:-1024}" # Reserve 1GB for system by default + + # Get current memory usage + local memory_stats + memory_stats=$(calculate_memory_per_process "$username") + + local avg_kb process_count + avg_kb=$(echo "$memory_stats" | cut -d'|' -f1) + process_count=$(echo "$memory_stats" | cut -d'|' -f2) + + if [ "$avg_kb" -eq 0 ]; then + echo "0|No active processes to measure" + return + fi + + # Get total system memory + local total_mem_mb + total_mem_mb=$(free -m | awk '/^Mem:/ {print $2}') + + # Calculate available memory for PHP-FPM + local available_mb=$((total_mem_mb - reserved_mb)) + + # Convert average KB to MB + local avg_mb=$((avg_kb / 1024)) + + # Calculate max children (with 20% safety buffer) + local theoretical_max=$((available_mb / avg_mb)) + local recommended=$((theoretical_max * 80 / 100)) + + # Sanity checks + if [ "$recommended" -lt 5 ]; then + recommended=5 + echo "$recommended|Minimum safe value (memory very limited)" + elif [ "$recommended" -lt "$process_count" ]; then + echo "$recommended|WARNING: Less than current process count ($process_count)" + else + echo "$recommended|Based on ${avg_mb}MB avg per process, ${available_mb}MB available" + fi +} + +# Calculate peak concurrent requests from access logs +# Usage: calculate_peak_concurrent_requests +# Returns: peak_concurrent|timestamp +calculate_peak_concurrent_requests() { + local username="$1" + local days="${2:-1}" # Default last 24 hours + + # Find access logs + local access_logs + access_logs=$(find /home/"$username"/*/logs -name "access_log*" -o -name "access.log*" 2>/dev/null) + + if [ -z "$access_logs" ]; then + echo "0|No access logs found" + return + fi + + # Analyze logs in 1-second windows to find peak concurrency + # This is a simplified estimation based on request timestamps + local peak=0 + local peak_time="" + + while IFS= read -r log_file; do + [ ! -f "$log_file" ] && continue + + # Extract timestamps and count requests per second + local log_data + if [[ "$log_file" == *.gz ]]; then + log_data=$(zcat "$log_file" 2>/dev/null || continue) + else + log_data=$(cat "$log_file" 2>/dev/null || continue) + fi + + # Apache/Nginx common log format timestamp extraction + local per_second + per_second=$(echo "$log_data" | \ + grep -oE '\[[0-9]{2}/[A-Za-z]{3}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2}' | \ + sort | uniq -c | sort -rn | head -1) + + if [ -n "$per_second" ]; then + local count timestamp + count=$(echo "$per_second" | awk '{print $1}') + timestamp=$(echo "$per_second" | awk '{print $2}' | tr -d '[') + + if [ "$count" -gt "$peak" ]; then + peak=$count + peak_time=$timestamp + fi + fi + done <<< "$access_logs" + + echo "$peak|$peak_time" +} + +# Calculate requests per minute average +# Usage: calculate_avg_requests_per_minute +calculate_avg_requests_per_minute() { + local username="$1" + local hours="${2:-24}" + + local access_logs + access_logs=$(find /home/"$username"/*/logs -name "access_log" -o -name "access.log" 2>/dev/null | head -1) + + if [ -z "$access_logs" ]; then + echo "0|No access logs" + return + fi + + # Count total requests in last N hours + local total_requests + total_requests=$(find "$access_logs" -mmin -$((hours * 60)) -exec wc -l {} \; 2>/dev/null | awk '{sum+=$1} END {print sum}') + + if [ -z "$total_requests" ] || [ "$total_requests" -eq 0 ]; then + echo "0|No recent requests" + return + fi + + # Calculate average per minute + local total_minutes=$((hours * 60)) + local avg_per_min=$((total_requests / total_minutes)) + + echo "$avg_per_min|Last $hours hours" +} + +# ============================================================================ +# OPCACHE ANALYSIS +# ============================================================================ + +# Analyze OPcache effectiveness +# Usage: analyze_opcache_effectiveness +# Returns: status|hit_rate|memory_used_mb|cached_scripts|recommendation +analyze_opcache_effectiveness() { + local username="$1" + + # Check if OPcache is enabled + local enabled + enabled=$(check_opcache_enabled "$username") + + if [ "$enabled" != "1" ]; then + echo "DISABLED|0|0|0|Enable OPcache for 40-70% performance boost" + return + fi + + # Get OPcache statistics + local stats + stats=$(get_opcache_stats "$username") + + if [ -z "$stats" ]; then + echo "ENABLED|0|0|0|Unable to retrieve statistics" + return + fi + + # Parse statistics + local memory_used hits misses cached_scripts max_cached wasted + memory_used=$(echo "$stats" | grep "memory_usage_mb" | cut -d'=' -f2) + hits=$(echo "$stats" | grep "^hits=" | cut -d'=' -f2) + misses=$(echo "$stats" | grep "^misses=" | cut -d'=' -f2) + cached_scripts=$(echo "$stats" | grep "num_cached_scripts=" | cut -d'=' -f2) + max_cached=$(echo "$stats" | grep "max_cached_keys=" | cut -d'=' -f2) + wasted=$(echo "$stats" | grep "wasted_memory_mb=" | cut -d'=' -f2) + + # Calculate hit rate + local hit_rate + hit_rate=$(calculate_opcache_hit_rate "$username") + + # Generate recommendation + local recommendation="" + + if (( $(echo "$hit_rate < 90" | bc -l) )); then + recommendation="Hit rate below 90% - Increase opcache.memory_consumption" + elif (( $(echo "$wasted > 5" | bc -l) )); then + recommendation="High wasted memory (${wasted}MB) - Consider increasing opcache.max_accelerated_files" + elif (( $(echo "$cached_scripts > $max_cached * 0.8" | bc -l) )); then + recommendation="Cached scripts at 80% capacity - Increase opcache.max_accelerated_files" + else + recommendation="OPcache performing optimally" + fi + + echo "ENABLED|$hit_rate|$memory_used|$cached_scripts|$recommendation" +} + +# ============================================================================ +# CONFIGURATION ISSUE DETECTION +# ============================================================================ + +# Detect common PHP configuration issues +# Usage: detect_php_config_issues +# Returns: multiline issue_type|severity|message|recommendation +detect_php_config_issues() { + local username="$1" + local domain="$2" + + local issues="" + + # Get all effective settings + local settings + settings=$(get_all_php_settings "$username") + + # Extract key settings + local memory_limit upload_max post_max max_execution display_errors + memory_limit=$(echo "$settings" | grep "^memory_limit=" | cut -d'=' -f2) + upload_max=$(echo "$settings" | grep "^upload_max_filesize=" | cut -d'=' -f2) + post_max=$(echo "$settings" | grep "^post_max_size=" | cut -d'=' -f2) + max_execution=$(echo "$settings" | grep "^max_execution_time=" | cut -d'=' -f2) + display_errors=$(echo "$settings" | grep "^display_errors=" | cut -d'=' -f2) + + # Convert to bytes for comparison + local upload_bytes post_bytes + upload_bytes=$(convert_to_bytes "$upload_max") + post_bytes=$(convert_to_bytes "$post_max") + + # ISSUE 1: post_max_size < upload_max_filesize + if [ "$post_bytes" -lt "$upload_bytes" ]; then + issues+="CONFIG_MISMATCH|CRITICAL|post_max_size ($post_max) < upload_max_filesize ($upload_max)|Set post_max_size >= upload_max_filesize"$'\n' + fi + + # ISSUE 2: display_errors = On in production + if [[ "$display_errors" =~ ^(On|1)$ ]]; then + issues+="SECURITY|HIGH|display_errors is enabled|Set display_errors = Off in production (security risk)"$'\n' + fi + + # ISSUE 3: memory_limit too low + local memory_bytes + memory_bytes=$(convert_to_bytes "$memory_limit") + if [ "$memory_bytes" -lt $((128 * 1024 * 1024)) ]; then + issues+="PERFORMANCE|MEDIUM|memory_limit is very low ($memory_limit)|Consider increasing to at least 128M"$'\n' + fi + + # ISSUE 4: Check for max_children errors + local max_children_errors + max_children_errors=$(analyze_max_children_errors "$username" 7) + local error_count + error_count=$(echo "$max_children_errors" | grep "TOTAL" | cut -d'|' -f1) + + if [ "$error_count" -gt 0 ]; then + issues+="CAPACITY|CRITICAL|pm.max_children limit reached $error_count times in last 7 days|Increase pm.max_children setting"$'\n' + fi + + # ISSUE 5: Check for memory exhausted errors + local memory_errors + memory_errors=$(analyze_memory_exhausted_errors "$username" 7) + error_count=$(echo "$memory_errors" | grep "TOTAL" | cut -d'|' -f1) + + if [ "$error_count" -gt 0 ]; then + issues+="MEMORY|HIGH|Memory exhausted errors occurred $error_count times in last 7 days|Increase memory_limit or optimize code"$'\n' + fi + + # ISSUE 6: OPcache disabled or ineffective + local opcache_status + opcache_status=$(analyze_opcache_effectiveness "$username") + local status hit_rate + status=$(echo "$opcache_status" | cut -d'|' -f1) + hit_rate=$(echo "$opcache_status" | cut -d'|' -f2) + + if [ "$status" = "DISABLED" ]; then + issues+="PERFORMANCE|HIGH|OPcache is disabled|Enable OPcache for 40-70% performance improvement"$'\n' + elif (( $(echo "$hit_rate < 90" | bc -l) )); then + issues+="PERFORMANCE|MEDIUM|OPcache hit rate is low (${hit_rate}%)|Increase opcache.memory_consumption"$'\n' + fi + + # ISSUE 7: Check FPM pool configuration + local pool_config + pool_config=$(find_fpm_pool_config "$username") + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + local pool_settings + pool_settings=$(parse_fpm_pool_config "$pool_config") + + local pm pm_max_requests + pm=$(echo "$pool_settings" | grep "^pm=" | cut -d'=' -f2) + pm_max_requests=$(echo "$pool_settings" | grep "^pm.max_requests=" | cut -d'=' -f2) + + # ISSUE 7a: pm.max_requests = 0 (no process recycling) + if [ "$pm_max_requests" = "0" ]; then + issues+="MEMORY_LEAK|MEDIUM|pm.max_requests is disabled (0)|Set to 500-1000 to prevent memory leak accumulation"$'\n' + fi + + # ISSUE 7b: pm = static on low-traffic site + if [ "$pm" = "static" ]; then + local avg_rpm + avg_rpm=$(calculate_avg_requests_per_minute "$username" 24 | cut -d'|' -f1) + + if [ "$avg_rpm" -lt 10 ]; then + issues+="RESOURCE_WASTE|LOW|pm=static on low-traffic site ($avg_rpm req/min)|Consider pm=dynamic or pm=ondemand to save memory"$'\n' + fi + fi + fi + + # Return all issues + if [ -z "$issues" ]; then + echo "NONE|INFO|No critical issues detected|Configuration appears healthy" + else + echo -e "$issues" + fi +} + +# ============================================================================ +# COMPREHENSIVE DOMAIN ANALYSIS +# ============================================================================ + +# Perform complete analysis for a domain +# Usage: analyze_domain_php +# Returns: JSON-like formatted comprehensive report +analyze_domain_php() { + local username="$1" + local domain="$2" + + echo "=== PHP Analysis Report for $domain ===" + echo "" + + # 1. PHP Version + echo "PHP VERSION:" + local php_version + php_version=$(detect_php_version_for_domain "$domain") + echo " Version: $php_version" + echo "" + + # 2. Configuration Files + echo "CONFIGURATION HIERARCHY:" + local configs + configs=$(find_all_php_configs "$username" "$domain") + local priority=1 + while IFS= read -r config; do + [ -z "$config" ] && continue + echo " Priority $priority: $config" + priority=$((priority + 1)) + done <<< "$configs" + echo "" + + # 3. Key Settings + echo "EFFECTIVE SETTINGS:" + local memory_limit upload_max post_max max_exec + memory_limit=$(get_effective_php_setting "$username" "memory_limit") + upload_max=$(get_effective_php_setting "$username" "upload_max_filesize") + post_max=$(get_effective_php_setting "$username" "post_max_size") + max_exec=$(get_effective_php_setting "$username" "max_execution_time") + + echo " memory_limit: $memory_limit" + echo " upload_max_filesize: $upload_max" + echo " post_max_size: $post_max" + echo " max_execution_time: $max_exec" + echo "" + + # 4. PHP-FPM Pool + echo "PHP-FPM POOL:" + local pool_config + pool_config=$(find_fpm_pool_config "$username") + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + local pool_settings + pool_settings=$(parse_fpm_pool_config "$pool_config") + + echo " Config: $pool_config" + while IFS= read -r setting; do + [ -z "$setting" ] && continue + echo " $setting" + done <<< "$pool_settings" + else + echo " Status: No FPM pool config found (using mod_php?)" + fi + echo "" + + # 5. Process & Memory Stats + echo "RESOURCE USAGE:" + 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'|' -f2) + + echo " Current Processes: $process_count" + echo " Avg Memory/Process: $((avg_kb / 1024))MB" + echo " Total Memory: ${total_mb}MB" + echo "" + + # 6. OPcache Status + echo "OPCACHE STATUS:" + local opcache_status + opcache_status=$(analyze_opcache_effectiveness "$username") + local status hit_rate memory_used cached_scripts recommendation + status=$(echo "$opcache_status" | cut -d'|' -f1) + hit_rate=$(echo "$opcache_status" | cut -d'|' -f2) + memory_used=$(echo "$opcache_status" | cut -d'|' -f3) + cached_scripts=$(echo "$opcache_status" | cut -d'|' -f4) + recommendation=$(echo "$opcache_status" | cut -d'|' -f5) + + echo " Status: $status" + if [ "$status" = "ENABLED" ]; then + echo " Hit Rate: ${hit_rate}%" + echo " Memory Used: ${memory_used}MB" + echo " Cached Scripts: $cached_scripts" + fi + echo " Recommendation: $recommendation" + echo "" + + # 7. Traffic Analysis + echo "TRAFFIC ANALYSIS (Last 24h):" + local avg_rpm peak_concurrent + avg_rpm=$(calculate_avg_requests_per_minute "$username" 24) + peak_concurrent=$(calculate_peak_concurrent_requests "$username" 1) + + echo " Avg Requests/Min: $(echo "$avg_rpm" | cut -d'|' -f1)" + echo " Peak Concurrent: $(echo "$peak_concurrent" | cut -d'|' -f1)" + echo "" + + # 8. Error Analysis + echo "ERROR ANALYSIS (Last 7 days):" + local memory_errors max_children_errors timeout_errors slow_requests + + memory_errors=$(analyze_memory_exhausted_errors "$username" 7 | grep "TOTAL" | cut -d'|' -f1) + max_children_errors=$(analyze_max_children_errors "$username" 7 | grep "TOTAL" | cut -d'|' -f1) + timeout_errors=$(analyze_execution_timeout_errors "$username" 7 | grep "TOTAL" | cut -d'|' -f1) + slow_requests=$(analyze_slow_requests "$username" 7 5 | grep "TOTAL" | cut -d'|' -f1) + + echo " Memory Exhausted: $memory_errors" + echo " Max Children Reached: $max_children_errors" + echo " Execution Timeouts: $timeout_errors" + echo " Slow Requests (>5s): $slow_requests" + echo "" + + # 9. Issues & Recommendations + echo "ISSUES DETECTED:" + local issues + issues=$(detect_php_config_issues "$username" "$domain") + + while IFS='|' read -r issue_type severity message recommendation; do + [ -z "$issue_type" ] && continue + echo " [$severity] $issue_type: $message" + echo " → $recommendation" + done <<< "$issues" + echo "" + + # 10. Optimization Recommendations + echo "OPTIMIZATION RECOMMENDATIONS:" + + # Calculate optimal max_children + local optimal_max_children + optimal_max_children=$(calculate_optimal_max_children "$username" 1024) + local recommended reason + recommended=$(echo "$optimal_max_children" | cut -d'|' -f1) + reason=$(echo "$optimal_max_children" | cut -d'|' -f2) + + 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 ' ') + + if [ "$recommended" -ne "$current_max_children" ]; then + echo " 1. Adjust pm.max_children from $current_max_children to $recommended" + echo " Reason: $reason" + fi + fi + + echo "" + echo "=== End of Report ===" +} + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + +# Convert human-readable size to bytes +# Usage: convert_to_bytes "128M" +convert_to_bytes() { + local size="$1" + + # Remove whitespace + size=$(echo "$size" | tr -d ' ') + + # Extract number and unit + local number="${size//[^0-9]/}" + local unit="${size//[0-9]/}" + + # Default to bytes if no unit + [ -z "$unit" ] && echo "$number" && return + + # Convert based on unit + case "${unit^^}" in + K|KB) + echo $((number * 1024)) + ;; + M|MB) + echo $((number * 1024 * 1024)) + ;; + G|GB) + echo $((number * 1024 * 1024 * 1024)) + ;; + *) + echo "$number" + ;; + esac +} + +# Export all functions +export -f analyze_memory_exhausted_errors +export -f analyze_max_children_errors +export -f analyze_slow_requests +export -f analyze_execution_timeout_errors +export -f calculate_memory_per_process +export -f calculate_optimal_max_children +export -f calculate_peak_concurrent_requests +export -f calculate_avg_requests_per_minute +export -f analyze_opcache_effectiveness +export -f detect_php_config_issues +export -f analyze_domain_php +export -f convert_to_bytes