Files
Linux-Server-Management-Too…/modules/website/website-slowness-diagnostics.sh
T
cschantz 66acf190e1 Integrate performance scoring and report file saving features
- Add calculate_performance_score() function that counts CRITICAL/WARNING issues
- Calculate A-F grade based on severity: A (90+), B (80-89), C (70-79), D (60-69), F (<60)
- Score formula: 100 - (critical_count * 10) - (warning_count * 2), bounded 0-100
- Integrate performance score display at top of diagnostic report with box formatting
- Add save_report_to_file() function to save full report to /tmp with timestamp
- Add interactive prompt after report generation to save to file (y/n)
- Display file path where report was saved for easy reference
- Improve score parsing using cut instead of read for more reliable variable assignment

The diagnostic report now displays overall site health grade and score summary at the
beginning, making it easy to quickly assess site performance. Users can optionally save
the full report to file for archival, sharing, or future reference.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-26 20:19:26 -05:00

2392 lines
89 KiB
Bash
Executable File

#!/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; }
# 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"
# Generate report
print_banner "Generating report..."
generate_report
# 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
press_enter
return 0
}
################################################################################
# ENTRY POINT
################################################################################
main() {
require_root || exit 1
show_main_menu
}
# Run main function
main "$@"