Phase 2: Add comprehensive PHP analysis engine (lib/php-analyzer.sh)
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) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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 <username> <days>
|
||||||
|
# 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 <username> <days>
|
||||||
|
# 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 <username> <days> <threshold_seconds>
|
||||||
|
# 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 <username> <days>
|
||||||
|
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 <username>
|
||||||
|
# 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 <username> [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 <username> <days>
|
||||||
|
# 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 <username> <hours>
|
||||||
|
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 <username>
|
||||||
|
# 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 <username> <domain>
|
||||||
|
# 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 <username> <domain>
|
||||||
|
# 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
|
||||||
Reference in New Issue
Block a user