66acf190e1
- 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>
2392 lines
89 KiB
Bash
Executable File
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 "$@"
|