#!/bin/bash ################################################################################ # Website Slowness Diagnostics - Multi-Framework Analysis Tool # # Purpose: Comprehensive troubleshooting for website slowness across multiple # frameworks: WordPress, Drupal, Joomla, Magento, Laravel, custom PHP # # Design: Uses persistent /tmp working files to prevent memory exhaustion # No permanent artifacts (all cleaned up on exit) # # Author: Server Toolkit # License: MIT ################################################################################ set -o pipefail # Source required libraries TOOLKIT_DIR="/root/server-toolkit" source "$TOOLKIT_DIR/lib/common-functions.sh" || { echo "FATAL: Cannot source common-functions.sh"; exit 1; } source "$TOOLKIT_DIR/lib/system-detect.sh" || { echo "FATAL: Cannot source system-detect.sh"; exit 1; } source "$TOOLKIT_DIR/lib/domain-discovery.sh" || { echo "FATAL: Cannot source domain-discovery.sh"; exit 1; } source "$TOOLKIT_DIR/lib/php-detector.sh" || { echo "FATAL: Cannot source php-detector.sh"; exit 1; } source "$TOOLKIT_DIR/lib/mysql-analyzer.sh" || { echo "FATAL: Cannot source mysql-analyzer.sh"; exit 1; } source "$TOOLKIT_DIR/modules/website/lib/extended-analysis-functions.sh" || { echo "WARNING: Cannot source extended analysis functions"; } source "$TOOLKIT_DIR/modules/website/lib/remediation-engine.sh" || { echo "WARNING: Cannot source remediation engine"; } # Root check [ "$EUID" -eq 0 ] || { print_error "This script must be run as root"; exit 1; } # Global variables DOMAIN="" FRAMEWORK="" DOCROOT="" DB_HOST="localhost" DB_NAME="" DB_USER="" DB_PASS="" SITE_OWNER="" TEMP_DIR="" ################################################################################ # FRAMEWORK DETECTION FUNCTIONS ################################################################################ # Detect framework type based on docroot structure detect_framework() { local docroot="$1" # Check for WordPress if [ -f "$docroot/wp-config.php" ] && [ -f "$docroot/wp-load.php" ]; then echo "wordpress" return 0 fi # Check for Drupal if [ -f "$docroot/sites/default/settings.php" ] || [ -d "$docroot/sites/default" ]; then if grep -q "^\$databases\|^\$settings\[" "$docroot/sites/default/settings.php" 2>/dev/null; then echo "drupal" return 0 fi fi # Check for Joomla if [ -f "$docroot/configuration.php" ]; then if grep -q "class JConfig\|public \$" "$docroot/configuration.php" 2>/dev/null; then echo "joomla" return 0 fi fi # Check for Magento if [ -f "$docroot/app/etc/env.php" ] || [ -f "$docroot/app/etc/local.xml" ]; then if [ -d "$docroot/app/code" ]; then echo "magento" return 0 fi fi # Check for Laravel if [ -f "$docroot/artisan" ] || [ -d "$docroot/app/bootstrap" ]; then if [ -f "$docroot/composer.json" ] && grep -q '"laravel' "$docroot/composer.json" 2>/dev/null; then echo "laravel" return 0 fi fi # Check for Node.js if [ -f "$docroot/package.json" ] && [ -d "$docroot/node_modules" ]; then echo "nodejs" return 0 fi # Check for static HTML (no PHP files) if ! find "$docroot" -maxdepth 3 -name "*.php" -type f 2>/dev/null | grep -q .; then echo "static_html" return 0 fi # Default to custom PHP echo "custom_php" return 0 } # Get framework version get_framework_version() { local framework="$1" local docroot="$2" case "$framework" in wordpress) # Try wp-includes/version.php first if [ -f "$docroot/wp-includes/version.php" ]; then grep "\$wp_version = " "$docroot/wp-includes/version.php" | sed "s/.*'\([^']*\)'.*/\1/" | head -1 else # Fall back to wp-config.php grep "WP_VERSION\|wp_version" "$docroot/wp-config.php" 2>/dev/null | head -1 fi ;; drupal) # Check version.info or system module version if [ -f "$docroot/core/lib/Drupal.php" ]; then grep "const VERSION\|VERSION =" "$docroot/core/lib/Drupal.php" 2>/dev/null | sed 's/.*[=\s]\+["\x27]\([^"'\'']*\).*/\1/' | head -1 else # Drupal 7 grep "VERSION" "$docroot/modules/system/system.info" 2>/dev/null || echo "7.x" fi ;; joomla) # Check configuration.php or manifests if [ -f "$docroot/includes/defines.php" ]; then grep "JVERSION\|_JEXEC" "$docroot/includes/defines.php" 2>/dev/null | head -1 else grep "public \$version\|const VERSION" "$docroot/configuration.php" 2>/dev/null | sed 's/.*[=]\s*["\x27]\([^"'\'']*\).*/\1/' | head -1 fi ;; magento) if [ -f "$docroot/composer.json" ]; then grep '"version"' "$docroot/composer.json" 2>/dev/null | head -1 | sed 's/.*[":]\s*"\([^"]*\).*/\1/' else echo "unknown" fi ;; laravel) if [ -f "$docroot/composer.json" ]; then grep '"version"\|"laravel/framework"' "$docroot/composer.json" 2>/dev/null | head -1 else echo "unknown" fi ;; nodejs) if [ -f "$docroot/package.json" ]; then grep '"version"' "$docroot/package.json" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/' else echo "unknown" fi ;; *) echo "unknown" ;; esac } ################################################################################ # DOMAIN VALIDATION & LOOKUP ################################################################################ # Sanitize domain input (strip https://, www., trailing slashes, etc.) sanitize_domain() { local input="$1" # Remove https:// and http:// input="${input#https://}" input="${input#http://}" # Remove www. input="${input#www.}" # Remove trailing slashes and paths input="${input%%/*}" # Remove trailing colons (port numbers) input="${input%%:*}" # Convert to lowercase input=$(echo "$input" | tr '[:upper:]' '[:lower:]') # Trim whitespace input=$(echo "$input" | xargs) echo "$input" } # Validate and load domain information validate_and_load_domain() { local domain="$1" # Validate domain exists if ! domain_exists "$domain"; then print_error "Domain '$domain' not found on this server" return 1 fi # Get docroot DOCROOT=$(get_domain_docroot "$domain") if [ -z "$DOCROOT" ] || [ ! -d "$DOCROOT" ]; then print_error "Unable to determine document root for $domain" return 1 fi # Get owner SITE_OWNER=$(get_domain_owner "$domain") if [ -z "$SITE_OWNER" ]; then print_error "Unable to determine domain owner" return 1 fi return 0 } ################################################################################ # TEMPORARY FILE MANAGEMENT ################################################################################ # Initialize temporary session directory init_temp_session() { create_temp_session TEMP_DIR="$TEMP_SESSION_DIR" if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then print_error "Unable to create temporary session directory" return 1 fi print_info "Using temp directory: $TEMP_DIR" >&2 return 0 } # Save analysis data to temp file save_analysis_data() { local filename="$1" local content="$2" if [ -z "$TEMP_DIR" ]; then return 1 fi echo "$content" >> "$TEMP_DIR/$filename" 2>/dev/null || return 1 return 0 } # Read analysis data from temp file read_analysis_data() { local filename="$1" if [ -z "$TEMP_DIR" ] || [ ! -f "$TEMP_DIR/$filename" ]; then return 1 fi cat "$TEMP_DIR/$filename" 2>/dev/null return 0 } ################################################################################ # WORDPRESS-SPECIFIC ANALYSIS ################################################################################ analyze_wordpress() { local domain="$1" local docroot="$2" print_section "WordPress Analysis" print_info "Detecting WordPress configuration..." # Get WordPress version local wp_version=$(get_framework_version "wordpress" "$docroot") save_analysis_data "framework_info.tmp" "Framework: WordPress" save_analysis_data "framework_info.tmp" "Version: $wp_version" # Get database connection info if [ -f "$docroot/wp-config.php" ]; then DB_NAME=$(grep "define( 'DB_NAME'" "$docroot/wp-config.php" 2>/dev/null | awk -F"'" '{print $4}' | head -1) DB_USER=$(grep "define( 'DB_USER'" "$docroot/wp-config.php" 2>/dev/null | awk -F"'" '{print $4}' | head -1) DB_HOST=$(grep "define( 'DB_HOST'" "$docroot/wp-config.php" 2>/dev/null | awk -F"'" '{print $4}' | head -1) print_success "WordPress $wp_version detected" print_info "Database: $DB_NAME on $DB_HOST" fi # Check for common plugins causing slowness (via database) if [ -n "$DB_NAME" ] && [ -n "$SITE_OWNER" ]; then analyze_wp_database "$docroot" "$DB_NAME" fi return 0 } # Detect WordPress table prefix detect_wp_table_prefix() { local db_name="$1" if [ -z "$db_name" ]; then echo "wp_" return 0 fi # Try to find a WordPress table to determine prefix local prefix=$(mysql -Ns -e "SELECT SUBSTRING(table_name, 1, INSTR(table_name, 'options') - 1) FROM information_schema.TABLES WHERE TABLE_SCHEMA='${db_name}' AND table_name LIKE '%options' LIMIT 1;" 2>/dev/null) if [ -n "$prefix" ]; then echo "$prefix" else # Fallback to standard prefix echo "wp_" fi } # Analyze WordPress database for slowness issues analyze_wp_database() { local docroot="$1" local db_name="$2" print_info "Analyzing WordPress database tables..." # Detect actual table prefix used local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") if [ -z "$TABLE_PREFIX" ]; then TABLE_PREFIX="wp_" fi # Check autoloaded options local autoload_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}options WHERE autoload='yes';" 2>/dev/null || echo "0") if [ "$autoload_count" -gt 500 ]; then print_warning "High autoloaded options count: $autoload_count (recommend < 500)" save_analysis_data "database_analysis.tmp" "CRITICAL: Autoloaded options: $autoload_count" save_analysis_data "database_analysis.tmp" " Fix: wp option list --autoload=yes --format=count" else print_info "Autoloaded options: $autoload_count" save_analysis_data "database_analysis.tmp" "OK: Autoloaded options: $autoload_count" fi # Check for large postmeta table local postmeta_size=$(mysql -Ns -e "SELECT ROUND(((data_length+index_length)/1024/1024), 2) FROM information_schema.TABLES WHERE table_schema='${db_name}' AND table_name='${TABLE_PREFIX}postmeta';" 2>/dev/null || echo "0") if (( $(echo "$postmeta_size > 100" | bc -l) )); then print_warning "Large postmeta table: ${postmeta_size}MB (may have missing index)" save_analysis_data "database_analysis.tmp" "WARNING: Large postmeta table: ${postmeta_size}MB" fi # Check for WooCommerce session bloat if mysql -Ns -e "SHOW TABLES FROM ${db_name} LIKE '${TABLE_PREFIX}woocommerce_sessions';" 2>/dev/null | grep -q woocommerce_sessions; then local session_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}woocommerce_sessions WHERE session_expiry < UNIX_TIMESTAMP();" 2>/dev/null || echo "0") if [ "$session_count" -gt 1000 ]; then print_warning "WooCommerce: $session_count expired sessions in database" save_analysis_data "database_analysis.tmp" "WARNING: WooCommerce expired sessions: $session_count" fi fi # Check for Action Scheduler backlog if mysql -Ns -e "SHOW TABLES FROM ${db_name} LIKE '${TABLE_PREFIX}actionscheduler%';" 2>/dev/null | grep -q actionscheduler; then local pending_actions=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}actionscheduler_actions WHERE status='pending';" 2>/dev/null || echo "0") if [ "$pending_actions" -gt 100 ]; then print_warning "Action Scheduler: $pending_actions pending actions (may indicate slowness)" save_analysis_data "database_analysis.tmp" "WARNING: Action Scheduler backlog: $pending_actions" fi fi # Get top 5 largest tables print_info "Top 5 largest tables:" local largest_tables=$(mysql -Ns -e "SELECT table_name, ROUND(((data_length+index_length)/1024/1024), 2) as size_mb FROM information_schema.TABLES WHERE table_schema='${db_name}' ORDER BY size_mb DESC LIMIT 5;" 2>/dev/null) if [ -n "$largest_tables" ]; then echo "$largest_tables" | while read -r table_size; do print_info " $table_size" save_analysis_data "database_analysis.tmp" "Table size: $table_size" done fi return 0 } ################################################################################ # DRUPAL-SPECIFIC ANALYSIS ################################################################################ analyze_drupal() { local domain="$1" local docroot="$2" print_section "Drupal Analysis" print_info "Detecting Drupal configuration..." local drupal_version=$(get_framework_version "drupal" "$docroot") save_analysis_data "framework_info.tmp" "Framework: Drupal" save_analysis_data "framework_info.tmp" "Version: $drupal_version" print_success "Drupal $drupal_version detected" print_info "Analyzing Drupal-specific slowness patterns..." # Check for watchdog log bloat if [ -f "$docroot/sites/default/settings.php" ]; then # Extract database settings if grep -q "database.*=.*array\|^\\\$databases" "$docroot/sites/default/settings.php" 2>/dev/null; then save_analysis_data "framework_info.tmp" "Drupal sites/default configured" fi fi # Check cache directory bloat if [ -d "$docroot/sites/default/files" ]; then local cache_size=$(du -sh "$docroot/sites/default/files" 2>/dev/null | awk '{print $1}') if [ -n "$cache_size" ]; then print_info "Files directory size: $cache_size" save_analysis_data "database_analysis.tmp" "Drupal files dir: $cache_size" fi fi return 0 } ################################################################################ # JOOMLA-SPECIFIC ANALYSIS ################################################################################ analyze_joomla() { local domain="$1" local docroot="$2" print_section "Joomla Analysis" print_info "Detecting Joomla configuration..." local joomla_version=$(get_framework_version "joomla" "$docroot") save_analysis_data "framework_info.tmp" "Framework: Joomla" save_analysis_data "framework_info.tmp" "Version: $joomla_version" print_success "Joomla detected" print_info "Analyzing Joomla-specific slowness patterns..." return 0 } ################################################################################ # GENERIC PHP ANALYSIS ################################################################################ analyze_generic_php() { local domain="$1" local docroot="$2" print_section "Custom PHP Application Analysis" print_info "Analyzing generic PHP application slowness..." # Check for composer.json (framework hint) if [ -f "$docroot/composer.json" ]; then print_info "Found composer.json - likely modern framework" save_analysis_data "framework_info.tmp" "Framework: Modern PHP (composer-based)" else save_analysis_data "framework_info.tmp" "Framework: Custom PHP" fi return 0 } ################################################################################ # CROSS-FRAMEWORK CHECKS ################################################################################ # Analyze PHP handler and configuration analyze_php_handler() { local domain="$1" local owner="$2" print_section "PHP Handler & Configuration" # Detect PHP handler if is_using_php_fpm; then print_success "PHP-FPM detected" save_analysis_data "system_info.tmp" "PHP Handler: PHP-FPM" # Try to find FPM pool config local fpm_config=$(find_fpm_pool_config "$owner" "$domain" 2>/dev/null) if [ -n "$fpm_config" ] && [ -f "$fpm_config" ]; then print_info "FPM pool config found: $fpm_config" # Parse pool settings local max_children=$(grep "pm.max_children" "$fpm_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') local pm_mode=$(grep "^pm = " "$fpm_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') if [ -n "$max_children" ]; then print_info "FPM max_children: $max_children" save_analysis_data "system_info.tmp" "FPM max_children: $max_children" fi if [ -n "$pm_mode" ]; then print_info "FPM process manager mode: $pm_mode" save_analysis_data "system_info.tmp" "FPM pm mode: $pm_mode" fi fi else print_info "PHP Handler: mod_php or FastCGI" save_analysis_data "system_info.tmp" "PHP Handler: mod_php/FastCGI" fi return 0 } # Detect caching software analyze_caching() { local domain="$1" local docroot="$2" print_section "Caching Software Detection" # Check for Redis if command -v redis-cli &>/dev/null; then if redis-cli ping &>/dev/null 2>&1; then print_success "Redis detected and running" save_analysis_data "caching_info.tmp" "Redis: Installed and running" else print_warning "Redis installed but not running" save_analysis_data "caching_info.tmp" "Redis: Installed but not running" fi fi # Check for Memcached if command -v memcached &>/dev/null || pgrep -x memcached >/dev/null; then print_success "Memcached detected" save_analysis_data "caching_info.tmp" "Memcached: Installed" fi # Check for WordPress caching plugins if [ -f "$docroot/wp-config.php" ]; then if [ -d "$docroot/wp-content/plugins/w3-total-cache" ]; then print_success "W3 Total Cache plugin detected" save_analysis_data "caching_info.tmp" "W3TC: Active" fi if [ -d "$docroot/wp-content/plugins/wp-rocket" ]; then print_success "WP Rocket plugin detected" save_analysis_data "caching_info.tmp" "WP Rocket: Active" fi fi return 0 } # Measure TTFB (Time To First Byte) measure_ttfb() { local domain="$1" print_section "Time To First Byte (TTFB) Testing" print_info "Measuring TTFB for https://$domain..." # Try HTTPS first, then HTTP local ttfb_result=$(timeout 10 curl -s -w "Connect: %{time_connect}s, TTFB: %{time_starttransfer}s, Total: %{time_total}s\n" \ -o /dev/null -L --max-redirs 5 -k "https://$domain" 2>/dev/null) if [ -z "$ttfb_result" ]; then ttfb_result=$(timeout 10 curl -s -w "Connect: %{time_connect}s, TTFB: %{time_starttransfer}s, Total: %{time_total}s\n" \ -o /dev/null -L --max-redirs 5 "http://$domain" 2>/dev/null) fi if [ -n "$ttfb_result" ]; then print_success "$ttfb_result" save_analysis_data "ttfb_data.tmp" "$ttfb_result" else print_warning "Unable to measure TTFB (domain not responding or timed out)" save_analysis_data "ttfb_data.tmp" "Unable to measure" fi return 0 } # Analyze image formats in docroot analyze_images() { local docroot="$1" print_section "Image Format Analysis" print_info "Scanning for unoptimized images..." # Count image types local jpg_count=$(find "$docroot" -maxdepth 5 -iname "*.jpg" -o -iname "*.jpeg" 2>/dev/null | wc -l) local png_count=$(find "$docroot" -maxdepth 5 -iname "*.png" 2>/dev/null | wc -l) local gif_count=$(find "$docroot" -maxdepth 5 -iname "*.gif" 2>/dev/null | wc -l) local webp_count=$(find "$docroot" -maxdepth 5 -iname "*.webp" 2>/dev/null | wc -l) if [ "$jpg_count" -gt 0 ] || [ "$png_count" -gt 0 ] || [ "$gif_count" -gt 0 ]; then print_warning "Found JPEG/PNG/GIF images (WebP recommended for performance)" print_info "JPEG: $jpg_count, PNG: $png_count, GIF: $gif_count, WebP: $webp_count" save_analysis_data "image_analysis.tmp" "JPEG: $jpg_count, PNG: $png_count, GIF: $gif_count, WebP: $webp_count" if [ "$webp_count" -eq 0 ] && [ $((jpg_count + png_count + gif_count)) -gt 100 ]; then print_critical "No WebP images found - major performance opportunity" save_analysis_data "image_analysis.tmp" "CRITICAL: No WebP usage detected" fi else print_success "No unoptimized images found (or minimal)" save_analysis_data "image_analysis.tmp" "OK: No legacy image formats detected" fi return 0 } # Analyze .htaccess file analyze_htaccess() { local docroot="$1" print_section ".htaccess Configuration" if [ ! -f "$docroot/.htaccess" ]; then print_info "No .htaccess file found (using defaults)" save_analysis_data "config_analysis.tmp" ".htaccess: Not present" return 0 fi print_info "Analyzing .htaccess configuration..." save_analysis_data "config_analysis.tmp" ".htaccess: Present ($(wc -l < "$docroot/.htaccess") lines)" # Check for common slowness-causing directives if grep -q "RewriteEngine\|mod_rewrite" "$docroot/.htaccess" 2>/dev/null; then print_info " • Mod_rewrite rules detected" save_analysis_data "config_analysis.tmp" ".htaccess: Contains rewrite rules" # Count rewrite rules local rewrite_count=$(grep -c "^RewriteRule\|^RewriteCond" "$docroot/.htaccess" 2>/dev/null || echo "0") if [ "$rewrite_count" -gt 50 ]; then print_warning " High rewrite rule count: $rewrite_count (may impact performance)" save_analysis_data "config_analysis.tmp" ".htaccess: High rewrite rules ($rewrite_count)" else print_info " Rewrite rules: $rewrite_count" fi fi # Check for directory indexing if grep -q "DirectoryIndex" "$docroot/.htaccess" 2>/dev/null; then print_info " • Custom DirectoryIndex configured" save_analysis_data "config_analysis.tmp" ".htaccess: Custom DirectoryIndex" fi # Check for compression directives if grep -q "mod_deflate\|AddEncoding\|gzip" "$docroot/.htaccess" 2>/dev/null; then print_success " • Compression directives found (good for performance)" save_analysis_data "config_analysis.tmp" ".htaccess: Compression enabled" else print_warning " • No compression directives (gzip not enabled in .htaccess)" save_analysis_data "config_analysis.tmp" ".htaccess: No compression detected" fi # Check for caching directives if grep -q "ExpiresByType\|Cache-Control\|max-age" "$docroot/.htaccess" 2>/dev/null; then print_success " • Caching directives found" save_analysis_data "config_analysis.tmp" ".htaccess: Browser caching configured" fi return 0 } # Check swap usage for user's processes analyze_swap_usage() { local owner="$1" print_section "Swap Usage (User Processes)" # Find all processes for this user and check their swap usage local user_swap_total=0 local processes_with_swap=0 while IFS= read -r pid; do if [ -f "/proc/$pid/status" ]; then local swap=$(grep "^VmSwap:" "/proc/$pid/status" 2>/dev/null | awk '{print $2}') if [ -n "$swap" ] && [ "$swap" -gt 0 ]; then user_swap_total=$((user_swap_total + swap)) processes_with_swap=$((processes_with_swap + 1)) fi fi done < <(pgrep -u "$owner" 2>/dev/null) if [ "$user_swap_total" -eq 0 ]; then print_success "No swap usage detected for user (optimal)" save_analysis_data "resource_analysis.tmp" "Swap usage: 0 KB (optimal)" return 0 fi local swap_mb=$((user_swap_total / 1024)) print_warning "User processes using SWAP: ${swap_mb} MB in $processes_with_swap processes" save_analysis_data "resource_analysis.tmp" "CRITICAL: Swap usage: ${swap_mb} MB" if [ "$swap_mb" -gt 100 ]; then print_critical "High swap usage detected! This SEVERELY impacts performance." print_critical "Swap is 1000x slower than RAM - this is likely a major bottleneck." save_analysis_data "resource_analysis.tmp" "CRITICAL: High swap usage is major bottleneck!" fi return 0 } # Analyze I/O wait time for user processes analyze_io_performance() { local owner="$1" print_section "I/O Performance (User Processes)" # Get user PIDs local user_pids=$(pgrep -u "$owner" 2>/dev/null | tr '\n' ',' | sed 's/,$//') if [ -z "$user_pids" ]; then print_info "No active processes to analyze" save_analysis_data "resource_analysis.tmp" "I/O analysis: No active processes" return 0 fi # Try to get I/O stats from /proc/pid/io local io_read_bytes=0 local io_write_bytes=0 local io_syscalls=0 while IFS= read -r pid; do if [ -f "/proc/$pid/io" ]; then local read_bytes=$(grep "^read_bytes:" "/proc/$pid/io" 2>/dev/null | awk '{print $2}') local write_bytes=$(grep "^write_bytes:" "/proc/$pid/io" 2>/dev/null | awk '{print $2}') [ -n "$read_bytes" ] && io_read_bytes=$((io_read_bytes + read_bytes)) [ -n "$write_bytes" ] && io_write_bytes=$((io_write_bytes + write_bytes)) fi done < <(pgrep -u "$owner" 2>/dev/null) if [ "$io_read_bytes" -gt 0 ] || [ "$io_write_bytes" -gt 0 ]; then local read_mb=$((io_read_bytes / 1024 / 1024)) local write_mb=$((io_write_bytes / 1024 / 1024)) print_info "User I/O activity: Read ${read_mb}MB, Written ${write_mb}MB" save_analysis_data "resource_analysis.tmp" "I/O: Read ${read_mb}MB, Write ${write_mb}MB" if [ "$write_mb" -gt 1000 ]; then print_warning "High I/O write activity (${write_mb}MB) - may indicate excessive logging or file operations" save_analysis_data "resource_analysis.tmp" "WARNING: High I/O write activity" fi fi return 0 } # Analyze process saturation (Apache/PHP-FPM queue) analyze_process_saturation() { local owner="$1" print_section "Process Saturation Analysis" print_info "Checking Apache and PHP-FPM worker saturation..." # Check for Apache processes for this user local apache_processes=$(ps aux | grep -E "apache|httpd" | grep "$owner" | grep -v grep | wc -l) if [ "$apache_processes" -gt 0 ]; then print_info "Apache workers for user: $apache_processes" save_analysis_data "resource_analysis.tmp" "Apache workers: $apache_processes" if [ "$apache_processes" -gt 100 ]; then print_warning "High Apache worker count - may indicate requests queuing up" save_analysis_data "resource_analysis.tmp" "WARNING: High Apache worker count" fi fi # Check for PHP-FPM processes for this user local php_fpm_processes=$(ps aux | grep "php-fpm.*pool" | grep "$owner" | grep -v grep | wc -l) if [ "$php_fpm_processes" -gt 0 ]; then print_info "PHP-FPM child processes: $php_fpm_processes" save_analysis_data "resource_analysis.tmp" "PHP-FPM children: $php_fpm_processes" # Try to get FPM pool config to compare with max_children local fpm_config=$(find /etc -name "*${owner}*" -o -name "*fpm*pool*" 2>/dev/null | head -1) if [ -f "$fpm_config" ]; then local max_children=$(grep "pm.max_children" "$fpm_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ') if [ -n "$max_children" ]; then local percent=$((php_fpm_processes * 100 / max_children)) print_info "Pool saturation: $percent% ($php_fpm_processes / $max_children max)" save_analysis_data "resource_analysis.tmp" "FPM saturation: ${percent}%" if [ "$percent" -gt 80 ]; then print_critical "PHP-FPM pool near saturation (${percent}%) - requests may queue!" save_analysis_data "resource_analysis.tmp" "CRITICAL: FPM saturation > 80%" fi fi fi fi return 0 } # Analyze table fragmentation and missing indexes analyze_table_fragmentation() { local db_name="$1" print_section "Database Table Fragmentation & Index Analysis" if [ -z "$db_name" ]; then print_info "No database to analyze" return 0 fi print_info "Analyzing tables in database: $db_name" # Get tables with high fragmentation local fragmented_tables=$(mysql -Ns -e "SELECT table_name, ROUND((data_free/(data_length+index_length))*100, 2) as frag_percent FROM information_schema.TABLES WHERE table_schema='${db_name}' AND data_free > 0 ORDER BY frag_percent DESC LIMIT 5;" 2>/dev/null) if [ -n "$fragmented_tables" ]; then print_warning "Fragmented tables (DATA_FREE > 0):" echo "$fragmented_tables" | while read -r table_frag; do print_warning " $table_frag" save_analysis_data "database_analysis.tmp" "Fragmented table: $table_frag" done print_info " Recommendation: Run OPTIMIZE TABLE on fragmented tables" save_analysis_data "database_analysis.tmp" "Fix: OPTIMIZE TABLE for fragmented tables" else print_success "No significant table fragmentation detected" save_analysis_data "database_analysis.tmp" "Fragmentation: Minimal" fi # Check for tables without primary key (can cause slowness) local no_pk_tables=$(mysql -Ns -e "SELECT table_name FROM information_schema.TABLES WHERE table_schema='${db_name}' AND table_name NOT IN (SELECT table_name FROM information_schema.KEY_COLUMN_USAGE WHERE table_schema='${db_name}' AND constraint_name='PRIMARY');" 2>/dev/null) if [ -n "$no_pk_tables" ]; then print_warning "Tables without primary key:" echo "$no_pk_tables" | while read -r table; do [ -n "$table" ] && print_warning " $table" done save_analysis_data "database_analysis.tmp" "WARNING: Tables without PRIMARY KEY" fi return 0 } # Analyze WordPress post revisions bloat analyze_post_revisions() { local docroot="$1" local db_name="$2" if [ -z "$db_name" ]; then return 0 fi # Check if this is WordPress if [ ! -f "$docroot/wp-config.php" ]; then return 0 fi print_section "WordPress Post Revisions Analysis" # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Count post revisions local revision_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}posts WHERE post_type='revision';" 2>/dev/null || echo "0") local total_posts=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}posts WHERE post_type IN ('post', 'page');" 2>/dev/null || echo "0") if [ "$revision_count" -gt 0 ]; then print_info "Total post revisions in database: $revision_count" save_analysis_data "wordpress_analysis.tmp" "Post revisions: $revision_count" if [ "$total_posts" -gt 0 ]; then local avg_revisions=$((revision_count / total_posts)) print_info "Average revisions per post: $avg_revisions" save_analysis_data "wordpress_analysis.tmp" "Avg revisions per post: $avg_revisions" if [ "$avg_revisions" -gt 50 ]; then print_critical "High revision count (${avg_revisions} avg) - bloats wp_posts table" print_info " Recommendation: wp post delete --post_type=revision --force" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: High post revision count" fi fi else print_success "No post revisions found" save_analysis_data "wordpress_analysis.tmp" "Post revisions: 0" fi return 0 } # Analyze WordPress transients bloat analyze_transients_bloat() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "WordPress Transients Analysis" # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Count transient options local transient_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}options WHERE option_name LIKE '%transient%';" 2>/dev/null || echo "0") if [ "$transient_count" -gt 0 ]; then print_warning "Transient options in ${TABLE_PREFIX}options: $transient_count" save_analysis_data "wordpress_analysis.tmp" "Transients count: $transient_count" if [ "$transient_count" -gt 1000 ]; then print_critical "Very high transient count (${transient_count}) - bloats ${TABLE_PREFIX}options" print_info " Recommendation: wp transient delete --all" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Transient bloat > 1000" fi # Check for expired transients local expired_transients=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}options WHERE option_name LIKE '%transient_timeout%' AND option_value < UNIX_TIMESTAMP();" 2>/dev/null || echo "0") if [ "$expired_transients" -gt 0 ]; then print_warning "Expired transients still in database: $expired_transients" save_analysis_data "wordpress_analysis.tmp" "Expired transients: $expired_transients" fi else print_success "No transients or very few" save_analysis_data "wordpress_analysis.tmp" "Transients: Clean" fi return 0 } # Analyze InnoDB vs MyISAM storage engines analyze_storage_engines() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "Database Storage Engine Analysis" # Check which storage engines are used local engine_summary=$(mysql -Ns -e "SELECT ENGINE, COUNT(*) as table_count FROM information_schema.TABLES WHERE table_schema='${db_name}' GROUP BY ENGINE;" 2>/dev/null) if [ -n "$engine_summary" ]; then print_info "Storage engines in use:" echo "$engine_summary" | while read -r engine_count; do print_info " $engine_count" save_analysis_data "database_analysis.tmp" "Storage engine: $engine_count" done fi # Warn if using MyISAM (table locks = slowness) local myisam_count=$(mysql -Ns -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema='${db_name}' AND ENGINE='MyISAM';" 2>/dev/null || echo "0") if [ "$myisam_count" -gt 0 ]; then print_critical "Found $myisam_count MyISAM tables (use TABLE locks - slower!)" print_info " Recommendation: ALTER TABLE ... ENGINE=InnoDB (row-level locks = faster)" save_analysis_data "database_analysis.tmp" "CRITICAL: MyISAM tables detected ($myisam_count)" else print_success "All tables using InnoDB (row-level locks = optimal)" save_analysis_data "database_analysis.tmp" "Storage engines: All InnoDB (optimal)" fi return 0 } # Analyze active transactions and locks analyze_active_transactions() { local db_name="$1" print_section "Active Transactions & Lock Analysis" # Check for active processes in this database local active_queries=$(mysql -Ns -e "SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE DB='${db_name}' AND COMMAND != 'Sleep';" 2>/dev/null || echo "0") print_info "Active queries in database: $active_queries" save_analysis_data "database_analysis.tmp" "Active queries: $active_queries" # Check for long-running transactions (> 60 seconds) local long_queries=$(mysql -Ns -e "SELECT ID, USER, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE DB='${db_name}' AND TIME > 60 AND COMMAND != 'Sleep' LIMIT 5;" 2>/dev/null) if [ -n "$long_queries" ]; then print_critical "Found long-running queries (> 60 seconds):" echo "$long_queries" | while read -r query_info; do print_error " $query_info" save_analysis_data "database_analysis.tmp" "CRITICAL: Long query (60s+): $query_info" done print_info " Long queries = lock contention = other queries slow" else print_success "No queries running longer than 60 seconds" save_analysis_data "database_analysis.tmp" "Long queries: None detected" fi # Check for lock waits local locked_tables=$(mysql -Ns -e "SHOW OPEN TABLES FROM ${db_name} WHERE In_use > 0;" 2>/dev/null) if [ -n "$locked_tables" ]; then print_warning "Tables with active locks:" echo "$locked_tables" | while read -r table_lock; do print_warning " $table_lock" save_analysis_data "database_analysis.tmp" "Table lock: $table_lock" done fi return 0 } # Analyze WordPress spam and pending comments analyze_comments_bloat() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "WordPress Comments Analysis" # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Count spam comments local spam_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}comments WHERE comment_approved='spam';" 2>/dev/null || echo "0") if [ "$spam_count" -gt 0 ]; then print_warning "Spam comments in database: $spam_count" save_analysis_data "wordpress_analysis.tmp" "Spam comments: $spam_count" if [ "$spam_count" -gt 10000 ]; then print_critical "CRITICAL: $spam_count spam comments! Bloats ${TABLE_PREFIX}comments table" print_info " Fix: wp comment delete --approved=spam --force" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Spam comments > 10000" fi fi # Count pending/unapproved comments local pending_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}comments WHERE comment_approved='0';" 2>/dev/null || echo "0") if [ "$pending_count" -gt 0 ]; then print_warning "Pending moderation comments: $pending_count" save_analysis_data "wordpress_analysis.tmp" "Pending comments: $pending_count" if [ "$pending_count" -gt 1000 ]; then print_critical "Large pending comment queue (${pending_count}) may slow moderation" save_analysis_data "wordpress_analysis.tmp" "WARNING: Pending comments > 1000" fi fi # Total comments local total_comments=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}comments WHERE comment_approved='1';" 2>/dev/null || echo "0") if [ "$total_comments" -gt 100000 ]; then print_warning "Large comment history: $total_comments approved comments" save_analysis_data "wordpress_analysis.tmp" "Total comments: $total_comments" fi return 0 } # Analyze WordPress options bloat analyze_wordpress_options() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "WordPress Options Bloat Analysis" # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Count total options local total_options=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}options;" 2>/dev/null || echo "0") print_info "Total options in ${TABLE_PREFIX}options: $total_options" save_analysis_data "wordpress_analysis.tmp" "Total options: $total_options" if [ "$total_options" -gt 2000 ]; then print_critical "CRITICAL: Too many options (${total_options}) - should be 100-300" print_info " Large options bloat = slow autoload on every request" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Options count > 2000" elif [ "$total_options" -gt 500 ]; then print_warning "High options count (${total_options}) - may impact performance" save_analysis_data "wordpress_analysis.tmp" "WARNING: Options count > 500" fi # Find largest individual options print_info "Largest option entries:" local largest_options=$(mysql -Ns -e "SELECT option_name, LENGTH(option_value) as size FROM ${db_name}.${TABLE_PREFIX}options ORDER BY LENGTH(option_value) DESC LIMIT 5;" 2>/dev/null) if [ -n "$largest_options" ]; then echo "$largest_options" | while read -r option_size; do print_warning " $option_size" save_analysis_data "wordpress_analysis.tmp" "Large option: $option_size" done fi return 0 } # Analyze database table row counts analyze_table_row_counts() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "Database Table Row Counts (Largest Tables)" # Get tables with highest row count local row_counts=$(mysql -Ns -e "SELECT table_name, table_rows, ROUND(((data_length+index_length)/1024/1024), 2) as size_mb FROM information_schema.TABLES WHERE table_schema='${db_name}' ORDER BY table_rows DESC LIMIT 10;" 2>/dev/null) if [ -n "$row_counts" ]; then print_info "Top 10 tables by row count:" echo "$row_counts" | while read -r table_info; do print_info " $table_info" save_analysis_data "database_analysis.tmp" "Table rows: $table_info" # Extract row count from output local row_count=$(echo "$table_info" | awk '{print $2}') if [ "$row_count" -gt 1000000 ]; then print_warning " Large table (>1M rows) - may slow queries" fi done fi return 0 } # Identify plugin database tables and sizes analyze_plugin_tables() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "Plugin Database Tables (Largest by Size)" # Get all tables and try to identify plugin origin print_info "Tables by size (non-WordPress core):" local plugin_tables=$(mysql -Ns -e "SELECT table_name, ROUND(((data_length+index_length)/1024/1024), 2) as size_mb FROM information_schema.TABLES WHERE table_schema='${db_name}' AND table_name NOT IN ('wp_posts', 'wp_postmeta', 'wp_users', 'wp_options', 'wp_comments', 'wp_terms', 'wp_termmeta', 'wp_commentmeta', 'wp_links', 'wp_usermeta') ORDER BY (data_length+index_length) DESC LIMIT 15;" 2>/dev/null) if [ -n "$plugin_tables" ]; then echo "$plugin_tables" | while read -r table_info; do local table_name=$(echo "$table_info" | awk '{print $1}') local size=$(echo "$table_info" | awk '{print $2}') print_info " $table_info" save_analysis_data "database_analysis.tmp" "Plugin table: $table_info" # Identify plugin by table prefix if [[ "$table_name" =~ ^wp_wc ]]; then echo " → WooCommerce" elif [[ "$table_name" =~ ^wp_elementor ]]; then echo " → Elementor" elif [[ "$table_name" =~ ^wp_yoast ]]; then echo " → Yoast SEO" elif [[ "$table_name" =~ ^wp_wf ]]; then echo " → WordFence" elif [[ "$table_name" =~ ^wp_rank_math ]]; then echo " → Rank Math" fi done fi return 0 } # Analyze WooCommerce specific slowness analyze_woocommerce_slowness() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Check if WooCommerce tables exist if ! mysql -Ns -e "SHOW TABLES FROM ${db_name} LIKE '${TABLE_PREFIX}wc%';" 2>/dev/null | grep -q .; then return 0 fi print_section "WooCommerce Analysis" # Count orders local order_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}posts WHERE post_type='shop_order';" 2>/dev/null || echo "0") if [ "$order_count" -gt 0 ]; then print_info "Total orders: $order_count" save_analysis_data "wordpress_analysis.tmp" "WooCommerce orders: $order_count" if [ "$order_count" -gt 100000 ]; then print_critical "CRITICAL: Large order history (${order_count} orders) - consider archiving old orders" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: WC orders > 100000" fi fi # Check for expired WooCommerce sessions local expired_sessions=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}woocommerce_sessions WHERE session_expiry < UNIX_TIMESTAMP();" 2>/dev/null || echo "0") if [ "$expired_sessions" -gt 0 ]; then print_warning "Expired WooCommerce sessions: $expired_sessions" save_analysis_data "wordpress_analysis.tmp" "WC expired sessions: $expired_sessions" fi return 0 } # Analyze collation mismatches analyze_collation_mismatches() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "Database Collation Analysis" # Check database default collation local db_collation=$(mysql -Ns -e "SELECT COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='${db_name}';" 2>/dev/null) print_info "Database collation: $db_collation" save_analysis_data "database_analysis.tmp" "Database collation: $db_collation" # Find tables with mismatched collation local mismatch_count=$(mysql -Ns -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema='${db_name}' AND table_collation != '${db_collation}';" 2>/dev/null || echo "0") if [ "$mismatch_count" -gt 0 ]; then print_warning "Found $mismatch_count tables with different collation" save_analysis_data "database_analysis.tmp" "Collation mismatches: $mismatch_count" # Show mismatched tables local mismatched=$(mysql -Ns -e "SELECT table_name, table_collation FROM information_schema.TABLES WHERE table_schema='${db_name}' AND table_collation != '${db_collation}';" 2>/dev/null) echo "$mismatched" | while read -r table_coll; do print_warning " $table_coll" save_analysis_data "database_analysis.tmp" "Mismatched: $table_coll" done print_info " Mismatches cause charset conversion = slower queries" else print_success "All tables using same collation (optimal)" save_analysis_data "database_analysis.tmp" "Collation: All matched" fi return 0 } # Analyze WordPress plugin count analyze_plugin_count() { local docroot="$1" if [ ! -f "$docroot/wp-config.php" ]; then return 0 fi print_section "WordPress Plugin Analysis" # Count plugins (active and inactive) local plugin_count=$(find "$docroot/wp-content/plugins" -maxdepth 1 -type d 2>/dev/null | wc -l) plugin_count=$((plugin_count - 1)) # Subtract the plugins dir itself print_info "Total plugins installed: $plugin_count" save_analysis_data "wordpress_analysis.tmp" "Total plugins: $plugin_count" if [ "$plugin_count" -gt 100 ]; then print_critical "CRITICAL: Too many plugins (${plugin_count}) - slows down site" print_info " More plugins = more code loaded on every request" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Plugin count > 100" elif [ "$plugin_count" -gt 50 ]; then print_warning "High plugin count (${plugin_count}) - may impact performance" save_analysis_data "wordpress_analysis.tmp" "WARNING: Plugin count > 50" fi return 0 } # Analyze WordPress theme size analyze_theme_analysis() { local docroot="$1" if [ ! -d "$docroot/wp-content/themes" ]; then return 0 fi print_section "WordPress Theme Analysis" # Get active theme (try to determine from wp-options) local active_theme=$(ls -t "$docroot/wp-content/themes" 2>/dev/null | head -1) if [ -n "$active_theme" ]; then print_info "Theme directory: $active_theme" save_analysis_data "wordpress_analysis.tmp" "Active theme: $active_theme" # Get theme size local theme_size=$(du -sh "$docroot/wp-content/themes/$active_theme" 2>/dev/null | awk '{print $1}') if [ -n "$theme_size" ]; then print_info "Theme size: $theme_size" save_analysis_data "wordpress_analysis.tmp" "Theme size: $theme_size" # Count theme files local file_count=$(find "$docroot/wp-content/themes/$active_theme" -type f 2>/dev/null | wc -l) print_info "Theme files: $file_count" save_analysis_data "wordpress_analysis.tmp" "Theme files: $file_count" if [ "$file_count" -gt 500 ]; then print_warning "Large theme (${file_count} files) - more code = slower" fi fi fi return 0 } # Analyze scheduled posts pending analyze_scheduled_posts() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "WordPress Scheduled Posts Analysis" # Detect table prefix local TABLE_PREFIX=$(detect_wp_table_prefix "$db_name") [ -z "$TABLE_PREFIX" ] && TABLE_PREFIX="wp_" # Count scheduled posts local scheduled_count=$(mysql -Ns -e "SELECT COUNT(*) FROM ${db_name}.${TABLE_PREFIX}posts WHERE post_status='future' AND post_type='post';" 2>/dev/null || echo "0") if [ "$scheduled_count" -gt 0 ]; then print_info "Scheduled posts pending publication: $scheduled_count" save_analysis_data "wordpress_analysis.tmp" "Scheduled posts: $scheduled_count" if [ "$scheduled_count" -gt 100 ]; then print_warning "Large batch of scheduled posts (${scheduled_count}) - publishing spike may cause slowness" save_analysis_data "wordpress_analysis.tmp" "WARNING: Scheduled posts > 100" fi fi return 0 } # Analyze backup files in docroot analyze_backup_files() { local docroot="$1" print_section "Backup Files Analysis" # Find backup files (.sql, .tar, .zip, .gz, .tar.gz) local backup_count=$(find "$docroot" -maxdepth 3 \( -name "*.sql" -o -name "*.tar" -o -name "*.tar.gz" -o -name "*.zip" \) -type f 2>/dev/null | wc -l) if [ "$backup_count" -gt 0 ]; then print_warning "Found $backup_count backup files in docroot" save_analysis_data "wordpress_analysis.tmp" "Backup files: $backup_count" # Show backup files and sizes print_info "Backup files and sizes:" find "$docroot" -maxdepth 3 \( -name "*.sql" -o -name "*.tar" -o -name "*.tar.gz" -o -name "*.zip" \) -type f 2>/dev/null | while read -r backup_file; do local size=$(du -h "$backup_file" | awk '{print $1}') print_warning " $(basename "$backup_file"): $size" save_analysis_data "wordpress_analysis.tmp" "Backup file: $(basename "$backup_file") - $size" done print_critical "Backup files should NOT be in docroot - delete or move to secure location" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Backup files in docroot" else print_success "No backup files found in docroot" save_analysis_data "wordpress_analysis.tmp" "Backup files: None found" fi return 0 } # Analyze for duplicate or unused indexes analyze_duplicate_indexes() { local db_name="$1" if [ -z "$db_name" ]; then return 0 fi print_section "Duplicate & Unused Indexes Analysis" # Find duplicate indexes (same columns) local duplicate_indexes=$(mysql -Ns -e " SELECT a.TABLE_NAME, a.INDEX_NAME, b.INDEX_NAME as duplicate_index FROM information_schema.STATISTICS a JOIN information_schema.STATISTICS b ON a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME AND a.INDEX_NAME < b.INDEX_NAME WHERE a.TABLE_SCHEMA = '${db_name}' AND a.SEQ_IN_INDEX = 1 LIMIT 10;" 2>/dev/null) if [ -n "$duplicate_indexes" ]; then print_warning "Potential duplicate indexes found:" echo "$duplicate_indexes" | while read -r dup_index; do print_warning " $dup_index" save_analysis_data "database_analysis.tmp" "Duplicate index: $dup_index" done print_info " Duplicate indexes waste space and slow down INSERT/UPDATE" else print_success "No obvious duplicate indexes detected" save_analysis_data "database_analysis.tmp" "Duplicate indexes: None" fi return 0 } # Check for recent backup processes analyze_recent_backups() { local owner="$1" print_section "Recent Backup Activity" # Check for active backup processes local backup_process=$(ps aux | grep -E "mysqldump|backup|tar|rsync|zip" | grep "$owner" | grep -v grep) if [ -n "$backup_process" ]; then print_critical "Active backup process detected!" print_error " Backups running = CPU/I/O spike = site slowness" echo "$backup_process" | while read -r process; do print_error " $process" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Active backup: $process" done else print_success "No active backup processes" save_analysis_data "wordpress_analysis.tmp" "Active backups: None" fi return 0 } # Analyze search crawler activity analyze_crawler_activity() { local domain="$1" local docroot="$2" print_section "Search Engine Crawler Activity" # Check Apache access log for bot activity (last 1000 lines) local access_log=$(get_domain_access_log "$domain" 2>/dev/null) if [ -z "$access_log" ] || [ ! -f "$access_log" ]; then print_info "Access log not found or not accessible" return 0 fi # Count bot requests (Googlebot, Bingbot, Yandexbot, etc.) local bot_count=$(tail -5000 "$access_log" 2>/dev/null | grep -iE "bot|crawler|spider|slurp" | wc -l) if [ "$bot_count" -gt 0 ]; then local total_requests=$(tail -5000 "$access_log" 2>/dev/null | wc -l) local bot_percent=$((bot_count * 100 / total_requests)) print_info "Search crawler activity in recent requests:" print_info " Bot requests: $bot_count (${bot_percent}% of total)" save_analysis_data "wordpress_analysis.tmp" "Crawler activity: ${bot_percent}% of traffic" if [ "$bot_percent" -gt 50 ]; then print_critical "Excessive crawler activity (${bot_percent}% of traffic)" print_info " Check robots.txt - may be crawling too much" save_analysis_data "wordpress_analysis.tmp" "CRITICAL: Crawler activity > 50%" elif [ "$bot_percent" -gt 20 ]; then print_warning "High crawler activity (${bot_percent}%)" fi else print_info "No significant crawler activity detected" save_analysis_data "wordpress_analysis.tmp" "Crawler activity: Normal" fi return 0 } # Check for inode exhaustion analyze_inode_usage() { local docroot="$1" print_section "Inode Usage Analysis" # Get inode usage for the domain's partition local inode_info=$(df -i "$docroot" 2>/dev/null) if [ -z "$inode_info" ]; then print_info "Unable to determine inode usage" return 0 fi # Extract inode stats (last line of df output) local inode_line=$(echo "$inode_info" | tail -1) local total_inodes=$(echo "$inode_line" | awk '{print $2}') local used_inodes=$(echo "$inode_line" | awk '{print $3}') local free_inodes=$(echo "$inode_line" | awk '{print $4}') if [ -n "$total_inodes" ] && [ "$total_inodes" -gt 0 ]; then local inode_percent=$((used_inodes * 100 / total_inodes)) print_info "Inode usage: $used_inodes / $total_inodes (${inode_percent}%)" save_analysis_data "resource_analysis.tmp" "Inodes: ${inode_percent}% used" if [ "$inode_percent" -gt 90 ]; then print_critical "CRITICAL: Inode usage at ${inode_percent}% - can't create new files!" save_analysis_data "resource_analysis.tmp" "CRITICAL: Inode usage > 90%" elif [ "$inode_percent" -gt 75 ]; then print_warning "High inode usage (${inode_percent}%) - approaching limit" save_analysis_data "resource_analysis.tmp" "WARNING: Inode usage > 75%" else print_success "Inode usage normal (${inode_percent}%)" fi fi return 0 } # Check disk space usage analyze_disk_space() { local docroot="$1" print_section "Disk Space Analysis" # Get disk usage for the domain's partition local disk_info=$(df -h "$docroot" 2>/dev/null) if [ -z "$disk_info" ]; then print_info "Unable to determine disk usage" return 0 fi # Extract disk stats (last line of df output) local disk_line=$(echo "$disk_info" | tail -1) local total_space=$(echo "$disk_line" | awk '{print $2}') local used_space=$(echo "$disk_line" | awk '{print $3}') local free_space=$(echo "$disk_line" | awk '{print $4}') local disk_percent=$(echo "$disk_line" | awk '{print $5}') print_info "Disk usage: $used_space / $total_space ($disk_percent used)" print_info "Free space: $free_space" save_analysis_data "resource_analysis.tmp" "Disk usage: $disk_percent" # Extract percentage value local percent_val=$(echo "$disk_percent" | sed 's/%//') if [ "$percent_val" -gt 95 ]; then print_critical "CRITICAL: Disk almost full (${percent_val}%)! Site will fail soon" save_analysis_data "resource_analysis.tmp" "CRITICAL: Disk > 95% full" elif [ "$percent_val" -gt 90 ]; then print_critical "CRITICAL: Disk very full (${percent_val}%) - causes slowness and failures" save_analysis_data "resource_analysis.tmp" "CRITICAL: Disk > 90% full" elif [ "$percent_val" -gt 80 ]; then print_warning "High disk usage (${percent_val}%) - performance may degrade" save_analysis_data "resource_analysis.tmp" "WARNING: Disk > 80% full" fi return 0 } # Analyze URL canonicalization issues analyze_url_canonicalization() { local domain="$1" print_section "URL Canonicalization Analysis" print_info "Checking for redirect chains caused by URL variations..." # Check www vs non-www local www_url="https://www.${domain}" local non_www="https://${domain}" # Check www.domain.com redirect local www_redirects=$(timeout 5 curl -s -o /dev/null -w "%{num_redirects}" "$www_url" 2>/dev/null) local non_www_redirects=$(timeout 5 curl -s -o /dev/null -w "%{num_redirects}" "$non_www" 2>/dev/null) if [ -n "$www_redirects" ] && [ "$www_redirects" != "-1" ]; then print_info "www.$domain redirects: $www_redirects" save_analysis_data "redirect_analysis.tmp" "www redirects: $www_redirects" fi if [ -n "$non_www_redirects" ] && [ "$non_www_redirects" != "-1" ]; then print_info "$domain redirects: $non_www_redirects" save_analysis_data "redirect_analysis.tmp" "non-www redirects: $non_www_redirects" fi # Check for canonicalization issues if [ "$www_redirects" != "$non_www_redirects" ]; then print_warning "URL canonicalization mismatch detected" print_info " www.$domain → $www_redirects redirects" print_info " $domain → $non_www_redirects redirects" print_warning " Set canonical URL in WordPress settings or .htaccess" save_analysis_data "redirect_analysis.tmp" "WARNING: Canonicalization issue detected" fi # Check http vs https redirect local http_redirects=$(timeout 5 curl -s -o /dev/null -w "%{num_redirects}" "http://${domain}" 2>/dev/null) if [ -n "$http_redirects" ] && [ "$http_redirects" != "-1" ] && [ "$http_redirects" -gt 0 ]; then print_info "HTTP to HTTPS redirect: Yes ($http_redirects hop)" save_analysis_data "redirect_analysis.tmp" "HTTP→HTTPS: $http_redirects redirects" if [ "$http_redirects" -gt 1 ]; then print_warning "Multiple redirects from HTTP - check .htaccess redirect rules" fi fi return 0 } # Check PHP memory limit vs actual usage analyze_php_memory_limit() { local docroot="$1" local owner="$2" print_section "PHP Memory Configuration" # Try to get memory_limit from wp-config.php or PHP local memory_limit=$(grep -i "WP_MEMORY_LIMIT\|memory_limit" "$docroot/wp-config.php" 2>/dev/null | head -1 | grep -oE "[0-9]+M") if [ -z "$memory_limit" ]; then # Try to execute PHP to get limit (may fail if user lacks shell access) memory_limit=$(su - "$owner" -c "php -r 'echo ini_get(\"memory_limit\");'" 2>/dev/null || true) fi if [ -n "$memory_limit" ]; then print_info "PHP memory_limit: $memory_limit" save_analysis_data "resource_analysis.tmp" "PHP memory_limit: $memory_limit" fi # Get peak memory usage of user's processes local peak_memory=0 while IFS= read -r pid; do if [ -f "/proc/$pid/status" ]; then local vm_peak=$(grep "^VmPeak:" "/proc/$pid/status" 2>/dev/null | awk '{print $2}') if [ -n "$vm_peak" ]; then peak_memory=$((peak_memory + vm_peak)) fi fi done < <(pgrep -u "$owner" 2>/dev/null) if [ "$peak_memory" -gt 0 ]; then local peak_mb=$((peak_memory / 1024)) print_info "Peak memory used: ${peak_mb}MB" save_analysis_data "resource_analysis.tmp" "Peak memory: ${peak_mb}MB" # Compare to limit if we have it if [ -n "$memory_limit" ]; then local limit_val=$(echo "$memory_limit" | sed 's/M//') if [ "$peak_mb" -gt "$limit_val" ]; then print_critical "CRITICAL: Peak memory (${peak_mb}MB) EXCEEDS limit (${limit_val}MB)!" print_info " PHP scripts hitting memory limit = fatal errors" save_analysis_data "resource_analysis.tmp" "CRITICAL: Memory exceeds limit" elif [ "$peak_mb" -gt $((limit_val * 80 / 100)) ]; then print_warning "Memory usage near limit (${peak_mb}MB of ${limit_val}MB = 80%)" save_analysis_data "resource_analysis.tmp" "WARNING: Memory near limit" fi fi fi return 0 } # Check MySQL connection pool status analyze_mysql_connections() { local db_name="$1" print_section "MySQL Connection Pool Analysis" # Get max connections setting local max_connections=$(mysql -Ns -e "SHOW VARIABLES LIKE 'max_connections';" 2>/dev/null | awk '{print $2}') if [ -n "$max_connections" ]; then print_info "MySQL max_connections: $max_connections" save_analysis_data "database_analysis.tmp" "MySQL max_connections: $max_connections" fi # Get current connections local current_connections=$(mysql -Ns -e "SHOW STATUS LIKE 'Threads_connected';" 2>/dev/null | awk '{print $2}') if [ -n "$current_connections" ]; then print_info "Current connections: $current_connections" save_analysis_data "database_analysis.tmp" "Current connections: $current_connections" if [ -n "$max_connections" ]; then local conn_percent=$((current_connections * 100 / max_connections)) print_info "Connection pool usage: ${conn_percent}%" save_analysis_data "database_analysis.tmp" "Connection pool: ${conn_percent}%" if [ "$conn_percent" -gt 90 ]; then print_critical "CRITICAL: Connection pool at ${conn_percent}% - nearly exhausted!" print_warning " New connections will be rejected = site hangs" save_analysis_data "database_analysis.tmp" "CRITICAL: Connection pool > 90%" elif [ "$conn_percent" -gt 75 ]; then print_warning "High connection pool usage (${conn_percent}%) - approaching limit" save_analysis_data "database_analysis.tmp" "WARNING: Connection pool > 75%" fi fi fi # Get max used connections (peak) local max_used=$(mysql -Ns -e "SHOW STATUS LIKE 'Max_used_connections';" 2>/dev/null | awk '{print $2}') if [ -n "$max_used" ]; then print_info "Peak connections ever used: $max_used" save_analysis_data "database_analysis.tmp" "Peak connections: $max_used" fi return 0 } # Analyze file descriptor usage for user analyze_file_descriptors() { local owner="$1" print_section "File Descriptor Usage (User Limits)" # Get user's file descriptor limit (may fail if user lacks shell access) local fd_limit=$(su - "$owner" -c "ulimit -n" 2>/dev/null || echo "unknown") if [ "$fd_limit" != "unknown" ] && [[ "$fd_limit" =~ ^[0-9]+$ ]]; then print_info "File descriptor limit (ulimit -n): $fd_limit" save_analysis_data "resource_analysis.tmp" "FD limit: $fd_limit" if [ "$fd_limit" -lt 1024 ]; then print_warning "File descriptor limit is low ($fd_limit) - may cause issues if opening many files" save_analysis_data "resource_analysis.tmp" "WARNING: Low FD limit ($fd_limit)" fi elif [ "$fd_limit" != "unknown" ]; then print_warning "Unable to determine file descriptor limit (user may not have shell access)" save_analysis_data "resource_analysis.tmp" "FD limit: Unable to determine" return 0 fi # Check current open file descriptors for user's processes local current_fds=0 local proc_count=0 while IFS= read -r pid; do if [ -d "/proc/$pid/fd" ]; then local pfd=$(ls -1 "/proc/$pid/fd" 2>/dev/null | wc -l) current_fds=$((current_fds + pfd)) proc_count=$((proc_count + 1)) fi done < <(pgrep -u "$owner" 2>/dev/null) if [ "$proc_count" -gt 0 ]; then print_info "Current open file descriptors: $current_fds (across $proc_count processes)" save_analysis_data "resource_analysis.tmp" "Open FDs: $current_fds" if [ "$fd_limit" != "unknown" ] && [ "$current_fds" -gt 0 ]; then local percent=$((current_fds * 100 / fd_limit)) print_info "FD usage: ${percent}% of limit" save_analysis_data "resource_analysis.tmp" "FD usage: ${percent}%" if [ "$percent" -gt 80 ]; then print_critical "File descriptor usage at ${percent}% - approaching limit!" print_warning " Increasing limit: ulimit -n 4096 (for this user)" save_analysis_data "resource_analysis.tmp" "CRITICAL: FD usage > 80% of limit" fi fi fi return 0 } # Analyze per-domain/user resource usage analyze_domain_resources() { local domain="$1" local docroot="$2" local owner="$3" print_section "Per-Domain Resource Usage" print_info "Analyzing resource usage for user: $owner" # Get all processes for this user local user_processes=$(ps aux | grep "^$owner" | grep -v grep | wc -l) print_info "Active processes for user: $user_processes" save_analysis_data "resource_analysis.tmp" "Processes: $user_processes" # Get memory usage for this user local user_memory=$(ps aux | grep "^$owner" | grep -v grep | awk '{sum+=$6} END {printf "%.2f MB\n", sum/1024}') if [ -n "$user_memory" ]; then print_info "Memory used by user: $user_memory" save_analysis_data "resource_analysis.tmp" "Memory: $user_memory" fi # Get CPU usage for this user local user_cpu=$(ps aux | grep "^$owner" | grep -v grep | awk '{sum+=$3} END {printf "%.1f%%\n", sum}') if [ -n "$user_cpu" ]; then print_info "CPU used by user: $user_cpu" save_analysis_data "resource_analysis.tmp" "CPU: $user_cpu" fi # Get domain/directory size local domain_size=$(du -sh "$docroot" 2>/dev/null | awk '{print $1}') if [ -n "$domain_size" ]; then print_info "Domain directory size: $domain_size" save_analysis_data "resource_analysis.tmp" "Domain size: $domain_size" # Check if size might impact I/O local size_mb=$(du -sb "$docroot" 2>/dev/null | awk '{printf "%.0f\n", $1/1024/1024}') if [ "$size_mb" -gt 5000 ]; then print_warning " Large domain size (>5GB) may impact I/O performance" save_analysis_data "resource_analysis.tmp" "WARNING: Large domain size may affect I/O" fi fi # Check if CloudLinux and user has LVE limits if [ "$SYS_CLOUDLINUX" = "yes" ] && command -v lvectl &>/dev/null; then print_section "CloudLinux LVE Limits (User: $owner)" print_info "Checking LVE limits for this user..." # Get LVE stats for this user local lve_id=$(id -u "$owner" 2>/dev/null) if [ -n "$lve_id" ]; then local lve_stats=$(lvectl list --user-id=$lve_id 2>/dev/null | tail -1) if [ -n "$lve_stats" ]; then print_info "LVE Stats: $lve_stats" save_analysis_data "resource_analysis.tmp" "LVE Stats: $lve_stats" # Check for limit violations if lvectl list --by-fault 2>/dev/null | grep -q "$lve_id"; then print_critical "User has hit LVE limits (MAJOR bottleneck!)" save_analysis_data "resource_analysis.tmp" "CRITICAL: User has hit LVE limits" fi fi fi fi return 0 } # Analyze MySQL slow query log analyze_mysql_slow_log() { local db_name="$1" print_section "MySQL Slow Query Log Analysis" # Check if slow query log exists local slow_log="/var/log/mysql/slow.log" if [ ! -f "$slow_log" ]; then # Try to get slow log location from MySQL slow_log=$(mysql -Ns -e "SHOW VARIABLES LIKE 'slow_query_log_file';" 2>/dev/null | awk '{print $2}') fi if [ -z "$slow_log" ] || [ ! -f "$slow_log" ]; then print_info "Slow query log not found or disabled" print_info " (To enable: SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2;)" save_analysis_data "database_analysis.tmp" "Slow query log: DISABLED (not available for analysis)" return 0 fi print_success "Slow query log found: $slow_log" save_analysis_data "database_analysis.tmp" "Slow query log: ENABLED" # Check if database has slow queries local slow_query_count=$(grep -c "Query_time:" "$slow_log" 2>/dev/null || echo "0") if [ "$slow_query_count" -eq 0 ]; then print_info "No slow queries recorded" save_analysis_data "database_analysis.tmp" "Slow queries: 0" return 0 fi print_warning "Found $slow_query_count slow queries in log" save_analysis_data "database_analysis.tmp" "Slow queries: $slow_query_count" # Extract top 3 slowest queries print_info "Top slowest queries:" grep "Query_time:" "$slow_log" 2>/dev/null | \ sed 's/.*Query_time: \([^ ]*\).*/\1 /' | \ sort -rn | head -3 | while read -r query_time; do print_warning " Query took: ${query_time}s" save_analysis_data "database_analysis.tmp" "Slow query: ${query_time}s" done return 0 } # Analyze PHP error logs for the domain user analyze_php_errors() { local owner="$1" local docroot="$2" print_section "PHP Error Log Analysis" # Check for PHP error log in user's directory local php_error_log="$docroot/error_log" if [ ! -f "$php_error_log" ]; then php_error_log=$(find /home/"$owner" -maxdepth 3 -name "error_log" -type f 2>/dev/null | head -1) fi if [ -z "$php_error_log" ] || [ ! -f "$php_error_log" ]; then print_info "No PHP error log found" save_analysis_data "log_analysis.tmp" "PHP errors: No log found" return 0 fi print_info "PHP error log: $php_error_log" # Count recent errors (last 24 hours) local recent_errors=$(tail -10000 "$php_error_log" 2>/dev/null | grep -i "error\|fatal\|warning" | wc -l) if [ "$recent_errors" -gt 0 ]; then print_warning "Found $recent_errors errors/warnings in PHP log" save_analysis_data "log_analysis.tmp" "PHP errors: $recent_errors" # Extract critical errors that affect slowness print_info "Critical slowness-related errors:" tail -1000 "$php_error_log" 2>/dev/null | grep -i "memory exhausted\|out of memory\|fatal\|max_execution_time" | head -5 | while read -r error; do print_error " $error" save_analysis_data "log_analysis.tmp" "CRITICAL: $error" done else print_success "No recent errors in PHP log" save_analysis_data "log_analysis.tmp" "PHP errors: 0" fi return 0 } # Analyze Apache error log for the domain analyze_apache_errors() { local domain="$1" local owner="$2" print_section "Apache Error Log Analysis" # Get domain error log location local error_log=$(get_domain_error_log "$domain" 2>/dev/null) if [ -z "$error_log" ] || [ ! -f "$error_log" ]; then # Try common locations error_log=$(find /var/log -name "*${domain}*error*" -o -name "error_log" 2>/dev/null | grep "$owner" | head -1) fi if [ -z "$error_log" ] || [ ! -f "$error_log" ]; then print_info "No Apache error log found" save_analysis_data "log_analysis.tmp" "Apache errors: No domain-specific log" return 0 fi print_info "Apache error log: $error_log" # Count recent errors local recent_errors=$(tail -5000 "$error_log" 2>/dev/null | wc -l) if [ "$recent_errors" -gt 0 ]; then print_info "Found $recent_errors entries in Apache error log" save_analysis_data "log_analysis.tmp" "Apache log entries: $recent_errors" # Extract slowness-related errors print_info "Slowness-related Apache errors:" tail -500 "$error_log" 2>/dev/null | grep -i "timeout\|connection reset\|permission denied\|file not found" | head -5 | while read -r error; do print_warning " $error" save_analysis_data "log_analysis.tmp" "Apache: $error" done else print_info "No recent Apache errors" fi return 0 } # Analyze redirect chains analyze_redirects() { local domain="$1" print_section "Redirect Chain Analysis" print_info "Checking for redirect chains..." # Use curl to follow redirects and count them local redirect_count=$(timeout 10 curl -s -o /dev/null -w "%{num_redirects}" "https://$domain" 2>/dev/null) if [ -z "$redirect_count" ] || [ "$redirect_count" = "-1" ]; then redirect_count=$(timeout 10 curl -s -o /dev/null -w "%{num_redirects}" "http://$domain" 2>/dev/null) fi if [ -n "$redirect_count" ] && [ "$redirect_count" != "-1" ]; then if [ "$redirect_count" -eq 0 ]; then print_success "No redirects detected (optimal)" save_analysis_data "redirect_analysis.tmp" "No redirects" elif [ "$redirect_count" -lt 3 ]; then print_info "Detected $redirect_count redirect(s) (acceptable)" save_analysis_data "redirect_analysis.tmp" "Redirects: $redirect_count (acceptable)" else print_warning "Detected $redirect_count redirects (may slow down site)" save_analysis_data "redirect_analysis.tmp" "CRITICAL: $redirect_count redirects detected" fi else print_warning "Unable to measure redirects" save_analysis_data "redirect_analysis.tmp" "Unable to measure" fi return 0 } ################################################################################ # PERFORMANCE SCORING ################################################################################ # Calculate overall performance score based on issues found calculate_performance_score() { local temp_dir="$1" # Count CRITICAL and WARNING issues local critical_count=0 local warning_count=0 for file in "$temp_dir"/*.tmp; do [ -f "$file" ] || continue critical_count=$((critical_count + $(grep -c "CRITICAL" "$file" 2>/dev/null || echo 0))) warning_count=$((warning_count + $(grep -c "WARNING" "$file" 2>/dev/null || echo 0))) done # Calculate score (100 - issues) local score=$((100 - (critical_count * 10) - (warning_count * 2))) [ $score -lt 0 ] && score=0 [ $score -gt 100 ] && score=100 # Determine grade local grade if [ $score -ge 90 ]; then grade="A - EXCELLENT" elif [ $score -ge 80 ]; then grade="B - GOOD" elif [ $score -ge 70 ]; then grade="C - FAIR" elif [ $score -ge 60 ]; then grade="D - POOR" else grade="F - CRITICAL" fi echo "$score|$grade|$critical_count|$warning_count" } # Save report to file save_report_to_file() { local domain="$1" local temp_dir="$2" local report_file="/tmp/slowness-report-${domain}-$(date +%Y%m%d-%H%M%S).txt" { echo "================================================================================" echo " WEBSITE SLOWNESS DIAGNOSTIC REPORT" echo " Domain: $domain" echo " Generated: $(date)" echo "================================================================================" echo "" # Add performance score at top read_analysis_data "framework_info.tmp" echo "" # Rest of report echo "================================================================================" echo " Framework Information" echo "================================================================================" read_analysis_data "framework_info.tmp" echo "" echo "================================================================================" echo " System Information" echo "================================================================================" read_analysis_data "system_info.tmp" echo "" echo "================================================================================" echo " Database Analysis" echo "================================================================================" read_analysis_data "database_analysis.tmp" echo "" echo "================================================================================" echo " Configuration Analysis" echo "================================================================================" read_analysis_data "config_analysis.tmp" echo "" echo "================================================================================" echo " Resource Usage & Limits" echo "================================================================================" read_analysis_data "resource_analysis.tmp" echo "" echo "================================================================================" echo " WordPress-Specific Analysis" echo "================================================================================" read_analysis_data "wordpress_analysis.tmp" echo "" echo "================================================================================" echo " Log Analysis" echo "================================================================================" read_analysis_data "log_analysis.tmp" echo "" echo "================================================================================" echo " Caching Status" echo "================================================================================" read_analysis_data "caching_info.tmp" echo "" echo "================================================================================" echo " TTFB Measurements" echo "================================================================================" read_analysis_data "ttfb_data.tmp" echo "" echo "================================================================================" echo " Image Analysis" echo "================================================================================" read_analysis_data "image_analysis.tmp" echo "" echo "================================================================================" echo " Redirect Chain Analysis" echo "================================================================================" read_analysis_data "redirect_analysis.tmp" echo "" echo "================================================================================" echo " END OF REPORT" echo "================================================================================" } > "$report_file" 2>/dev/null if [ -f "$report_file" ]; then echo "$report_file" return 0 else return 1 fi } ################################################################################ # REPORT GENERATION ################################################################################ generate_report() { print_section "=== SLOWNESS DIAGNOSTIC REPORT ===" # Calculate and display performance score local score_data=$(calculate_performance_score "$TEMP_DIR") local score=$(echo "$score_data" | cut -d'|' -f1) local grade=$(echo "$score_data" | cut -d'|' -f2) local critical=$(echo "$score_data" | cut -d'|' -f3) local warning=$(echo "$score_data" | cut -d'|' -f4) echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ PERFORMANCE SCORE SUMMARY ║" echo "╠════════════════════════════════════════════════════════════════╣" printf "║ Overall Grade: %-51s║\n" "$grade" printf "║ Score: %d/100 | Issues: %d Critical, %d Warning(s)%15s║\n" "$score" "$critical" "$warning" "" echo "╚════════════════════════════════════════════════════════════════╝" echo "" # Read and display all analysis data print_banner "Framework Information" read_analysis_data "framework_info.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_banner "System Information" read_analysis_data "system_info.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_banner "Database Analysis" read_analysis_data "database_analysis.tmp" | while read -r line; do [ -n "$line" ] && print_warning "$line" done print_banner "Configuration Analysis" read_analysis_data "config_analysis.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_banner "Resource Usage & Limits" read_analysis_data "resource_analysis.tmp" | while read -r line; do if [[ "$line" =~ CRITICAL ]]; then print_error "$line" elif [[ "$line" =~ WARNING ]]; then print_warning "$line" else print_info "$line" fi done print_banner "WordPress-Specific Analysis" if [ -f "$TEMP_DIR/wordpress_analysis.tmp" ]; then read_analysis_data "wordpress_analysis.tmp" | while read -r line; do if [[ "$line" =~ CRITICAL ]]; then print_error "$line" else print_info "$line" fi done else print_info "Not a WordPress site" fi print_banner "Log Analysis" read_analysis_data "log_analysis.tmp" | while read -r line; do if [[ "$line" =~ CRITICAL ]]; then print_error "$line" else print_info "$line" fi done print_banner "Caching Status" read_analysis_data "caching_info.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_banner "TTFB Measurements" read_analysis_data "ttfb_data.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_banner "Image Analysis" read_analysis_data "image_analysis.tmp" | while read -r line; do [ -n "$line" ] && print_warning "$line" done print_banner "Redirect Chain Analysis" read_analysis_data "redirect_analysis.tmp" | while read -r line; do [ -n "$line" ] && print_info "$line" done print_section "=== END OF REPORT ===" return 0 } ################################################################################ # MAIN MENU ################################################################################ show_main_menu() { clear print_banner "Website Slowness Diagnostics" print_info "Multi-Framework Performance Analysis Tool\n" echo "1) Analyze specific domain" echo "2) Exit" echo "" read -p "Select option [1]: " menu_choice menu_choice=${menu_choice:-1} case "$menu_choice" in 1) read -p "Enter domain name: " DOMAIN if [ -n "$DOMAIN" ]; then # Sanitize domain input (remove https://, www., etc.) DOMAIN=$(sanitize_domain "$DOMAIN") if [ -n "$DOMAIN" ]; then run_diagnostics "$DOMAIN" else print_error "Invalid domain format" fi fi show_main_menu ;; 2) print_success "Exiting..." exit 0 ;; *) print_error "Invalid option" press_enter show_main_menu ;; esac } ################################################################################ # MAIN DIAGNOSTICS FLOW ################################################################################ run_diagnostics() { local domain="$1" # Initialize temp session if ! init_temp_session; then print_error "Failed to initialize temporary session" return 1 fi # Validate domain if ! validate_and_load_domain "$domain"; then print_error "Domain validation failed" press_enter return 1 fi print_banner "Starting diagnostics for $domain" print_info "Document root: $DOCROOT" print_info "Owner: $SITE_OWNER\n" # Detect framework FRAMEWORK=$(detect_framework "$DOCROOT") print_info "Detected framework: $FRAMEWORK" # Run framework-specific analysis case "$FRAMEWORK" in wordpress) analyze_wordpress "$domain" "$DOCROOT" ;; drupal) analyze_drupal "$domain" "$DOCROOT" ;; joomla) analyze_joomla "$domain" "$DOCROOT" ;; magento) print_section "Magento Analysis" print_info "Magento framework detected" save_analysis_data "framework_info.tmp" "Framework: Magento" ;; laravel) print_section "Laravel Analysis" print_info "Laravel framework detected" save_analysis_data "framework_info.tmp" "Framework: Laravel" ;; nodejs) print_section "Node.js Application Analysis" print_info "Node.js application detected" save_analysis_data "framework_info.tmp" "Framework: Node.js" ;; static_html) print_section "Static HTML Site" print_info "Static HTML site detected" save_analysis_data "framework_info.tmp" "Framework: Static HTML" ;; *) analyze_generic_php "$domain" "$DOCROOT" ;; esac # Run cross-framework checks print_banner "Running comprehensive slowness analysis..." # Configuration analysis analyze_htaccess "$DOCROOT" # Resource & performance analysis analyze_domain_resources "$domain" "$DOCROOT" "$SITE_OWNER" analyze_disk_space "$DOCROOT" analyze_inode_usage "$DOCROOT" analyze_swap_usage "$SITE_OWNER" analyze_io_performance "$SITE_OWNER" analyze_process_saturation "$SITE_OWNER" analyze_file_descriptors "$SITE_OWNER" analyze_php_memory_limit "$DOCROOT" "$SITE_OWNER" analyze_php_handler "$domain" "$SITE_OWNER" # Database analysis if [ -n "$DB_NAME" ]; then analyze_mysql_connections "$DB_NAME" analyze_mysql_slow_log "$DB_NAME" analyze_table_fragmentation "$DB_NAME" analyze_storage_engines "$DB_NAME" analyze_active_transactions "$DB_NAME" analyze_collation_mismatches "$DB_NAME" analyze_duplicate_indexes "$DB_NAME" analyze_table_row_counts "$DB_NAME" analyze_plugin_tables "$DB_NAME" analyze_post_revisions "$DOCROOT" "$DB_NAME" analyze_transients_bloat "$DB_NAME" analyze_comments_bloat "$DB_NAME" analyze_wordpress_options "$DB_NAME" analyze_scheduled_posts "$DB_NAME" analyze_woocommerce_slowness "$DB_NAME" fi # WordPress-specific analysis analyze_plugin_count "$DOCROOT" analyze_theme_analysis "$DOCROOT" analyze_backup_files "$DOCROOT" # Activity analysis analyze_recent_backups "$SITE_OWNER" analyze_crawler_activity "$domain" "$DOCROOT" # Log analysis analyze_php_errors "$SITE_OWNER" "$DOCROOT" analyze_apache_errors "$domain" "$SITE_OWNER" # Content & network analysis analyze_caching "$domain" "$DOCROOT" analyze_images "$DOCROOT" measure_ttfb "$domain" analyze_url_canonicalization "$domain" analyze_redirects "$domain" # Extended analysis: WordPress settings (8 checks) analyze_wp_debug "$DOCROOT" analyze_xmlrpc "$domain" analyze_heartbeat_api "$DOCROOT" analyze_autosave_frequency "$DOCROOT" analyze_rest_api_exposure "$domain" analyze_emoji_scripts "$domain" analyze_post_revision_distribution "$DB_NAME" analyze_pingbacks_trackbacks "$DOCROOT" # Extended analysis: Database tuning (8 checks) analyze_innodb_buffer_pool analyze_max_allowed_packet analyze_slow_query_threshold analyze_innodb_file_per_table analyze_query_cache analyze_temp_table_location analyze_connection_timeout analyze_innodb_flush_log analyze_missing_critical_indexes "$DB_NAME" analyze_database_memory_ratio "$DB_NAME" # Extended analysis: PHP performance (6 checks) analyze_opcache analyze_xdebug analyze_realpath_cache analyze_timezone_config analyze_display_errors analyze_disabled_functions # Extended analysis: Web server tuning (6 checks) analyze_http2 analyze_keepalive analyze_sendfile analyze_gzip_compression analyze_ssl_version analyze_apache_modules # Extended analysis: Cron & background tasks (4 checks) analyze_wordpress_cron "$DOCROOT" analyze_backup_schedule analyze_db_optimization_schedule analyze_slow_cron_jobs # Phase 4: Advanced Database Analysis (6 checks) print_section "PHASE 4: ADVANCED DATABASE CHECKS" analyze_table_engine_mismatch analyze_table_statistics_age analyze_index_cardinality analyze_query_cache_memory_waste analyze_replication_lag analyze_table_size_growth # Phase 4: System & Error Pattern Detection (6 checks) print_section "PHASE 4: SYSTEM & ERROR PATTERN CHECKS" analyze_timeout_errors analyze_memory_exhaustion_attempts analyze_disk_inode_usage analyze_zombie_processes analyze_swap_usage_phase4 analyze_load_average_trend # Phase 5: Content Optimization (10 checks) print_section "PHASE 5: CONTENT OPTIMIZATION CHECKS" analyze_unoptimized_images "$DOCROOT" analyze_webp_conversion "$DOCROOT" analyze_large_assets "$DOCROOT" analyze_render_blocking "$domain" analyze_font_loading "$domain" analyze_request_count "$domain" analyze_third_party_scripts "$domain" analyze_unused_assets "$domain" analyze_content_delivery "$domain" analyze_cache_headers "$domain" # Phase 5: Network & DNS Optimization (8 checks) print_section "PHASE 5: NETWORK & DNS OPTIMIZATION CHECKS" analyze_dns_resolution_time "$domain" analyze_dns_records "$domain" analyze_redirect_chains "$domain" analyze_ssl_certificate "$domain" analyze_connection_keepalive "$domain" analyze_https_redirect "$domain" analyze_network_waterfall "$domain" analyze_cdn_performance "$domain" # Generate report print_banner "Generating report..." generate_report # Generate intelligent remediation recommendations print_banner "Generating remediation recommendations..." analyze_findings_for_remediation "$TEMP_DIR" # Offer to save report to file echo "" read -p "Save report to file? (y/n): " save_choice if [[ "$save_choice" =~ ^[Yy]$ ]]; then local report_file=$(save_report_to_file "$domain" "$TEMP_DIR") if [ $? -eq 0 ]; then echo "" print_success "Report saved to: $report_file" echo "" else print_error "Failed to save report" fi fi # Print summary of what to do next print_remediation_summary "$TEMP_DIR" press_enter return 0 } ################################################################################ # ENTRY POINT ################################################################################ main() { require_root || exit 1 show_main_menu } # Run main function main "$@"