Debugging WordPress Performance Issues Like a Pro
Performance issues in WordPress can be elusive, appearing intermittently or only under specific conditions. This comprehensive guide equips you with professional debugging techniques to identify, analyze, and resolve performance bottlenecks systematically. From database queries to plugin conflicts, you'll learn how to diagnose issues like an expert developer.
Whether you're dealing with a suddenly slow site or chronic performance problems, these debugging strategies will help you pinpoint the exact cause and implement targeted solutions. We'll cover advanced tools, methodologies, and real-world troubleshooting scenarios that transform you from guessing to knowing exactly what's slowing down your WordPress site.
Table of Contents
- Debugging Fundamentals
- Identifying Performance Bottlenecks
- Database Query Analysis
- Plugin and Theme Debugging
- Server-Side Debugging
- Frontend Performance Debugging
- Advanced Debugging Techniques
Debugging Fundamentals
Setting Up Debug Environment
// wp-config.php debug configuration
// Enable comprehensive debugging
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);
define('SAVEQUERIES', true);
// Additional debugging constants
define('WP_DISABLE_FATAL_ERROR_HANDLER', true); // Disable recovery mode
define('WP_DEBUG_LOG_PATH', ABSPATH . 'wp-content/debug.log');
// Query Monitor configuration
define('QM_ENABLE_CAPS_PANEL', true);
define('QM_DISABLE_ERROR_HANDLER', false);
// Custom debug helper class
class WP_Performance_Debugger {
private static $instance = null;
private $start_time;
private $checkpoints = [];
private $query_log = [];
public static function init() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->start_time = microtime(true);
// Hook into WordPress lifecycle
add_action('plugins_loaded', [$this, 'checkpoint'], 1);
add_action('init', [$this, 'checkpoint'], 1);
add_action('wp_loaded', [$this, 'checkpoint'], 1);
add_action('template_redirect', [$this, 'checkpoint'], 1);
add_action('wp_head', [$this, 'checkpoint'], 1);
add_action('wp_footer', [$this, 'checkpoint'], 999);
add_action('shutdown', [$this, 'output_debug_info'], 999);
// Monitor queries
if (defined('SAVEQUERIES') && SAVEQUERIES) {
add_filter('query', [$this, 'log_query']);
}
}
public function checkpoint($action = null) {
$action = $action ?: current_action();
$this->checkpoints[$action] = [
'time' => microtime(true) - $this->start_time,
'memory' => memory_get_usage(),
'peak_memory' => memory_get_peak_usage(),
'queries' => get_num_queries()
];
}
public function log_query($query) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
$caller = $this->get_query_caller($backtrace);
$this->query_log[] = [
'query' => $query,
'caller' => $caller,
'time' => microtime(true)
];
return $query;
}
private function get_query_caller($backtrace) {
foreach ($backtrace as $trace) {
if (isset($trace['file']) && strpos($trace['file'], 'wp-content') !== false) {
return sprintf('%s:%d', $trace['file'], $trace['line']);
}
}
return 'Unknown';
}
public function output_debug_info() {
if (!current_user_can('manage_options')) {
return;
}
$total_time = microtime(true) - $this->start_time;
// Log performance summary
error_log(sprintf(
"\n=== Performance Debug Summary ===\n" .
"Total Time: %.3fs\n" .
"Peak Memory: %s\n" .
"Queries: %d\n" .
"=================================\n",
$total_time,
size_format(memory_get_peak_usage()),
get_num_queries()
));
// Log checkpoints
error_log("\n=== Execution Timeline ===");
$last_time = 0;
foreach ($this->checkpoints as $action => $data) {
$delta = $data['time'] - $last_time;
error_log(sprintf(
"%s: %.3fs (+%.3fs) | Memory: %s | Queries: %d",
$action,
$data['time'],
$delta,
size_format($data['memory']),
$data['queries']
));
$last_time = $data['time'];
}
// Log slow queries
if (defined('SAVEQUERIES') && SAVEQUERIES) {
global $wpdb;
error_log("\n=== Slow Queries (>0.05s) ===");
foreach ($wpdb->queries as $query) {
if ($query[1] > 0.05) {
error_log(sprintf(
"Query Time: %.3fs\nSQL: %s\nCaller: %s\n",
$query[1],
$query[0],
$query[2]
));
}
}
}
}
}
// Initialize debugger
if (WP_DEBUG) {
WP_Performance_Debugger::init();
}
Performance Profiling Tools
// Custom profiler implementation
class Performance_Profiler {
private $profiles = [];
private $active_profile = null;
public function start_profile($name) {
$this->profiles[$name] = [
'start_time' => microtime(true),
'start_memory' => memory_get_usage(),
'sql_queries_start' => get_num_queries(),
'included_files_start' => count(get_included_files())
];
$this->active_profile = $name;
}
public function end_profile($name = null) {
$name = $name ?: $this->active_profile;
if (!isset($this->profiles[$name])) {
return false;
}
$profile = &$this->profiles[$name];
$profile['end_time'] = microtime(true);
$profile['end_memory'] = memory_get_usage();
$profile['duration'] = ($profile['end_time'] - $profile['start_time']) * 1000; // ms
$profile['memory_used'] = $profile['end_memory'] - $profile['start_memory'];
$profile['sql_queries'] = get_num_queries() - $profile['sql_queries_start'];
$profile['included_files'] = count(get_included_files()) - $profile['included_files_start'];
$this->active_profile = null;
return $profile;
}
public function get_report() {
$report = "=== Performance Profile Report ===\n\n";
foreach ($this->profiles as $name => $profile) {
if (!isset($profile['duration'])) {
continue; // Profile not completed
}
$report .= sprintf(
"%s:\n" .
" Duration: %.2fms\n" .
" Memory: %s\n" .
" Queries: %d\n" .
" Files: %d\n\n",
$name,
$profile['duration'],
size_format($profile['memory_used']),
$profile['sql_queries'],
$profile['included_files']
);
}
return $report;
}
public function profile_function($function, $args = []) {
$name = is_string($function) ? $function : 'anonymous_function';
$this->start_profile($name);
$result = call_user_func_array($function, $args);
$profile = $this->end_profile($name);
return [
'result' => $result,
'profile' => $profile
];
}
}
// Usage example
$profiler = new Performance_Profiler();
// Profile a specific operation
$profiler->start_profile('homepage_render');
// ... homepage rendering code ...
$profile = $profiler->end_profile('homepage_render');
// Profile a function
$result = $profiler->profile_function('get_posts', [
['numberposts' => 50]
]);
Identifying Performance Bottlenecks
Systematic Bottleneck Detection
// Bottleneck detector class
class Bottleneck_Detector {
private $thresholds = [
'query_time' => 0.05, // 50ms
'memory_spike' => 10485760, // 10MB
'file_io_time' => 0.1, // 100ms
'external_request_time' => 1.0 // 1 second
];
private $bottlenecks = [];
public function analyze() {
// Analyze different potential bottlenecks
$this->analyze_database_queries();
$this->analyze_plugin_performance();
$this->analyze_theme_performance();
$this->analyze_external_requests();
$this->analyze_file_operations();
$this->analyze_memory_usage();
return $this->generate_report();
}
private function analyze_database_queries() {
if (!defined('SAVEQUERIES') || !SAVEQUERIES) {
return;
}
global $wpdb;
$slow_queries = [];
$duplicate_queries = [];
$query_patterns = [];
foreach ($wpdb->queries as $query_data) {
$query = $query_data[0];
$time = $query_data[1];
$caller = $query_data[2];
// Check for slow queries
if ($time > $this->thresholds['query_time']) {
$slow_queries[] = [
'query' => $query,
'time' => $time,
'caller' => $caller
];
}
// Track query patterns
$pattern = $this->get_query_pattern($query);
if (!isset($query_patterns[$pattern])) {
$query_patterns[$pattern] = [
'count' => 0,
'total_time' => 0,
'callers' => []
];
}
$query_patterns[$pattern]['count']++;
$query_patterns[$pattern]['total_time'] += $time;
$query_patterns[$pattern]['callers'][] = $caller;
}
// Find duplicate queries
foreach ($query_patterns as $pattern => $data) {
if ($data['count'] > 1) {
$duplicate_queries[] = [
'pattern' => $pattern,
'count' => $data['count'],
'total_time' => $data['total_time'],
'callers' => array_unique($data['callers'])
];
}
}
if (!empty($slow_queries)) {
$this->bottlenecks['slow_queries'] = $slow_queries;
}
if (!empty($duplicate_queries)) {
$this->bottlenecks['duplicate_queries'] = $duplicate_queries;
}
}
private function get_query_pattern($query) {
// Normalize query to identify patterns
$query = preg_replace('/\s+/', ' ', trim($query));
$query = preg_replace('/\d+/', 'N', $query);
$query = preg_replace("/'[^']*'/", "'S'", $query);
$query = preg_replace('/"[^"]*"/', '"S"', $query);
return $query;
}
private function analyze_plugin_performance() {
$plugin_profiles = [];
// Profile each active plugin
foreach (get_option('active_plugins') as $plugin) {
$plugin_dir = dirname($plugin);
$start_time = microtime(true);
$start_memory = memory_get_usage();
// Measure plugin initialization impact
add_action('plugins_loaded', function() use ($plugin_dir, &$plugin_profiles, $start_time, $start_memory) {
$duration = microtime(true) - $start_time;
$memory_used = memory_get_usage() - $start_memory;
if ($duration > 0.1 || $memory_used > 5242880) { // 100ms or 5MB
$plugin_profiles[$plugin_dir] = [
'duration' => $duration,
'memory' => $memory_used,
'hooks' => $this->count_plugin_hooks($plugin_dir)
];
}
}, 1);
}
if (!empty($plugin_profiles)) {
$this->bottlenecks['slow_plugins'] = $plugin_profiles;
}
}
private function count_plugin_hooks($plugin_dir) {
global $wp_filter;
$count = 0;
foreach ($wp_filter as $hook => $priorities) {
foreach ($priorities as $priority => $functions) {
foreach ($functions as $function) {
if (is_string($function['function']) && strpos($function['function'], $plugin_dir) !== false) {
$count++;
}
}
}
}
return $count;
}
private function analyze_external_requests() {
// Hook into HTTP API
add_filter('pre_http_request', function($preempt, $args, $url) {
$start_time = microtime(true);
add_filter('http_response', function($response, $args_inner, $url_inner) use ($url, $start_time) {
$duration = microtime(true) - $start_time;
if ($duration > $this->thresholds['external_request_time']) {
if (!isset($this->bottlenecks['slow_external_requests'])) {
$this->bottlenecks['slow_external_requests'] = [];
}
$this->bottlenecks['slow_external_requests'][] = [
'url' => $url,
'duration' => $duration,
'response_code' => wp_remote_retrieve_response_code($response),
'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
];
}
return $response;
}, 10, 3);
return $preempt;
}, 10, 3);
}
private function analyze_memory_usage() {
// Track memory spikes
$checkpoints = [
'muplugins_loaded' => 0,
'plugins_loaded' => 0,
'setup_theme' => 0,
'after_setup_theme' => 0,
'init' => 0,
'wp_loaded' => 0
];
$last_memory = memory_get_usage();
foreach ($checkpoints as $hook => $memory) {
add_action($hook, function() use ($hook, &$checkpoints, &$last_memory) {
$current_memory = memory_get_usage();
$spike = $current_memory - $last_memory;
if ($spike > $this->thresholds['memory_spike']) {
if (!isset($this->bottlenecks['memory_spikes'])) {
$this->bottlenecks['memory_spikes'] = [];
}
$this->bottlenecks['memory_spikes'][] = [
'hook' => $hook,
'spike' => $spike,
'total_memory' => $current_memory
];
}
$checkpoints[$hook] = $current_memory;
$last_memory = $current_memory;
}, 1);
}
}
private function generate_report() {
$report = [
'summary' => [
'total_issues' => count($this->bottlenecks),
'critical_issues' => $this->count_critical_issues()
],
'bottlenecks' => $this->bottlenecks,
'recommendations' => $this->generate_recommendations()
];
return $report;
}
private function count_critical_issues() {
$critical = 0;
if (isset($this->bottlenecks['slow_queries'])) {
foreach ($this->bottlenecks['slow_queries'] as $query) {
if ($query['time'] > 0.5) { // 500ms
$critical++;
}
}
}
if (isset($this->bottlenecks['memory_spikes'])) {
foreach ($this->bottlenecks['memory_spikes'] as $spike) {
if ($spike['spike'] > 52428800) { // 50MB
$critical++;
}
}
}
return $critical;
}
private function generate_recommendations() {
$recommendations = [];
if (isset($this->bottlenecks['slow_queries'])) {
$recommendations[] = 'Add database indexes for frequently queried columns';
$recommendations[] = 'Implement query result caching using transients';
}
if (isset($this->bottlenecks['duplicate_queries'])) {
$recommendations[] = 'Cache duplicate query results within the same request';
$recommendations[] = 'Review plugin code for redundant database calls';
}
if (isset($this->bottlenecks['slow_plugins'])) {
$recommendations[] = 'Consider replacing slow plugins with faster alternatives';
$recommendations[] = 'Contact plugin developers about performance issues';
}
if (isset($this->bottlenecks['memory_spikes'])) {
$recommendations[] = 'Increase PHP memory limit if necessary';
$recommendations[] = 'Optimize memory-intensive operations';
}
return $recommendations;
}
}
Database Query Analysis
Advanced Query Debugging
// Database query analyzer
class Query_Analyzer {
private $query_log = [];
private $index_suggestions = [];
public function __construct() {
if (defined('SAVEQUERIES') && SAVEQUERIES) {
add_filter('query', [$this, 'analyze_query']);
add_action('shutdown', [$this, 'generate_analysis_report']);
}
}
public function analyze_query($query) {
$start_time = microtime(true);
// Get query execution plan
$explain = $this->get_query_explain($query);
// Analyze query structure
$analysis = [
'query' => $query,
'type' => $this->get_query_type($query),
'tables' => $this->extract_tables($query),
'explain' => $explain,
'suggestions' => []
];
// Check for missing indexes
if ($explain && $this->needs_index($explain)) {
$analysis['suggestions'][] = $this->suggest_index($query, $explain);
}
// Check for inefficient patterns
$inefficiencies = $this->check_inefficient_patterns($query);
if (!empty($inefficiencies)) {
$analysis['suggestions'] = array_merge($analysis['suggestions'], $inefficiencies);
}
$this->query_log[] = $analysis;
return $query;
}
private function get_query_explain($query) {
global $wpdb;
// Only explain SELECT queries
if (!preg_match('/^\s*SELECT/i', $query)) {
return null;
}
// Skip if query contains subqueries (EXPLAIN doesn't work well with them)
if (preg_match('/SELECT.*FROM.*SELECT/i', $query)) {
return null;
}
try {
$explain_query = "EXPLAIN " . $query;
$results = $wpdb->get_results($explain_query, ARRAY_A);
return $results;
} catch (Exception $e) {
return null;
}
}
private function needs_index($explain) {
foreach ($explain as $row) {
// Check for full table scans
if ($row['type'] === 'ALL' && $row['rows'] > 100) {
return true;
}
// Check for inefficient index usage
if ($row['type'] === 'index' && $row['rows'] > 1000) {
return true;
}
// Check for using filesort
if (strpos($row['Extra'], 'Using filesort') !== false && $row['rows'] > 100) {
return true;
}
}
return false;
}
private function suggest_index($query, $explain) {
$suggestions = [];
// Extract WHERE conditions
if (preg_match('/WHERE\s+(.+?)(?:GROUP|ORDER|LIMIT|$)/i', $query, $matches)) {
$where_clause = $matches[1];
// Find columns used in WHERE
if (preg_match_all('/(\w+)\s*=\s*[\'"]?\w+[\'"]?/i', $where_clause, $column_matches)) {
foreach ($column_matches[1] as $column) {
$suggestions[] = sprintf(
"Consider adding index on column '%s'",
$column
);
}
}
}
// Extract ORDER BY columns
if (preg_match('/ORDER\s+BY\s+([^,\s]+)/i', $query, $matches)) {
$order_column = $matches[1];
$suggestions[] = sprintf(
"Consider adding index on ORDER BY column '%s'",
$order_column
);
}
return implode('; ', $suggestions);
}
private function check_inefficient_patterns($query) {
$inefficiencies = [];
// Check for SELECT *
if (preg_match('/SELECT\s+\*/i', $query)) {
$inefficiencies[] = 'Avoid SELECT *, specify only needed columns';
}
// Check for NOT IN with subquery
if (preg_match('/NOT\s+IN\s*\(\s*SELECT/i', $query)) {
$inefficiencies[] = 'NOT IN with subquery is inefficient, use LEFT JOIN instead';
}
// Check for LIKE with leading wildcard
if (preg_match('/LIKE\s+[\'"]%\w+/i', $query)) {
$inefficiencies[] = 'Leading wildcard in LIKE prevents index usage';
}
// Check for OR conditions
if (preg_match('/WHERE.*\sOR\s/i', $query)) {
$inefficiencies[] = 'OR conditions may prevent index usage, consider using UNION';
}
// Check for functions on indexed columns
if (preg_match('/WHERE.*(?:YEAR|MONTH|DATE|LOWER|UPPER)\s*\(/i', $query)) {
$inefficiencies[] = 'Functions on columns prevent index usage';
}
return $inefficiencies;
}
private function get_query_type($query) {
if (preg_match('/^\s*SELECT/i', $query)) return 'SELECT';
if (preg_match('/^\s*INSERT/i', $query)) return 'INSERT';
if (preg_match('/^\s*UPDATE/i', $query)) return 'UPDATE';
if (preg_match('/^\s*DELETE/i', $query)) return 'DELETE';
return 'OTHER';
}
private function extract_tables($query) {
$tables = [];
// Extract from FROM clause
if (preg_match('/FROM\s+([^\s,]+)/i', $query, $matches)) {
$tables[] = $matches[1];
}
// Extract from JOIN clauses
if (preg_match_all('/JOIN\s+([^\s]+)/i', $query, $matches)) {
$tables = array_merge($tables, $matches[1]);
}
return array_unique($tables);
}
public function generate_analysis_report() {
if (empty($this->query_log)) {
return;
}
// Group queries by pattern
$query_patterns = [];
foreach ($this->query_log as $analysis) {
$pattern = $this->get_query_pattern($analysis['query']);
if (!isset($query_patterns[$pattern])) {
$query_patterns[$pattern] = [
'count' => 0,
'example' => $analysis['query'],
'suggestions' => $analysis['suggestions'],
'tables' => $analysis['tables']
];
}
$query_patterns[$pattern]['count']++;
}
// Sort by frequency
uasort($query_patterns, function($a, $b) {
return $b['count'] - $a['count'];
});
// Output report
error_log("\n=== Database Query Analysis Report ===\n");
foreach ($query_patterns as $pattern => $data) {
if ($data['count'] > 1 || !empty($data['suggestions'])) {
error_log(sprintf(
"\nQuery Pattern (executed %d times):\n%s\n",
$data['count'],
$data['example']
));
if (!empty($data['suggestions'])) {
error_log("Suggestions:\n");
foreach ($data['suggestions'] as $suggestion) {
error_log(" - " . $suggestion . "\n");
}
}
}
}
}
}
// Initialize query analyzer
if (WP_DEBUG && defined('SAVEQUERIES') && SAVEQUERIES) {
new Query_Analyzer();
}
Query Optimization Helper
// Query optimization suggestions
class Query_Optimizer {
public function optimize_wp_query($args) {
$optimized_args = $args;
// Disable unnecessary queries
if (!isset($args['update_post_meta_cache'])) {
$optimized_args['update_post_meta_cache'] = false;
}
if (!isset($args['update_post_term_cache'])) {
$optimized_args['update_post_term_cache'] = false;
}
// Use specific fields if possible
if (!isset($args['fields']) && !empty($args['return_fields'])) {
$optimized_args['fields'] = $args['return_fields'];
unset($optimized_args['return_fields']);
}
// Optimize meta queries
if (isset($args['meta_query'])) {
$optimized_args['meta_query'] = $this->optimize_meta_query($args['meta_query']);
}
// Add suggestions
$suggestions = $this->generate_query_suggestions($args);
return [
'args' => $optimized_args,
'suggestions' => $suggestions
];
}
private function optimize_meta_query($meta_query) {
// Ensure numeric comparisons use numeric type
foreach ($meta_query as &$query) {
if (is_array($query) && isset($query['compare'])) {
if (in_array($query['compare'], ['>', '<', '>=', '<=', 'BETWEEN'])) {
$query['type'] = 'NUMERIC';
}
}
}
return $meta_query;
}
private function generate_query_suggestions($args) {
$suggestions = [];
// Check for expensive orderby
if (isset($args['orderby'])) {
if ($args['orderby'] === 'meta_value' && !isset($args['meta_key'])) {
$suggestions[] = 'Specify meta_key when ordering by meta_value';
}
if (in_array($args['orderby'], ['rand', 'RAND()'])) {
$suggestions[] = 'ORDER BY RAND() is very expensive for large datasets';
}
}
// Check for missing pagination
if (!isset($args['posts_per_page']) || $args['posts_per_page'] == -1) {
$suggestions[] = 'Consider adding pagination to limit results';
}
// Check for inefficient meta queries
if (isset($args['meta_query']) && count($args['meta_query']) > 3) {
$suggestions[] = 'Multiple meta queries can be slow, consider custom table';
}
// Check for taxonomy queries
if (isset($args['tax_query']) && count($args['tax_query']) > 2) {
$suggestions[] = 'Complex taxonomy queries may benefit from direct SQL';
}
return $suggestions;
}
public function create_optimized_indexes() {
global $wpdb;
$indexes = [
// Optimize post queries
[
'table' => $wpdb->posts,
'name' => 'idx_post_status_type',
'columns' => 'post_status, post_type, post_date'
],
[
'table' => $wpdb->posts,
'name' => 'idx_post_author_status',
'columns' => 'post_author, post_status'
],
// Optimize meta queries
[
'table' => $wpdb->postmeta,
'name' => 'idx_meta_key_value',
'columns' => 'meta_key, meta_value(20)'
],
// Optimize term relationships
[
'table' => $wpdb->term_relationships,
'name' => 'idx_term_object',
'columns' => 'term_taxonomy_id, object_id'
]
];
foreach ($indexes as $index) {
$this->create_index_if_not_exists(
$index['table'],
$index['name'],
$index['columns']
);
}
}
private function create_index_if_not_exists($table, $index_name, $columns) {
global $wpdb;
// Check if index exists
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_schema = %s
AND table_name = %s
AND index_name = %s",
DB_NAME,
$table,
$index_name
));
if (!$existing) {
$wpdb->query(sprintf(
"CREATE INDEX %s ON %s (%s)",
$index_name,
$table,
$columns
));
error_log("Created index: $index_name on $table");
}
}
}
Plugin and Theme Debugging
Plugin Conflict Detection
// Plugin conflict detector
class Plugin_Conflict_Detector {
private $test_results = [];
private $baseline_performance = [];
public function run_conflict_test() {
// Get baseline with all plugins active
$this->baseline_performance = $this->measure_performance();
$active_plugins = get_option('active_plugins');
// Test each plugin individually
foreach ($active_plugins as $plugin) {
$this->test_plugin_isolation($plugin, $active_plugins);
}
// Test plugin combinations
$this->test_plugin_combinations($active_plugins);
return $this->analyze_results();
}
private function test_plugin_isolation($test_plugin, $all_plugins) {
// Temporarily deactivate all plugins except the one being tested
$other_plugins = array_diff($all_plugins, [$test_plugin]);
update_option('active_plugins', [$test_plugin]);
// Measure performance with only this plugin
$isolated_performance = $this->measure_performance();
// Measure performance without this plugin
update_option('active_plugins', $other_plugins);
$without_performance = $this->measure_performance();
// Restore original state
update_option('active_plugins', $all_plugins);
$this->test_results[$test_plugin] = [
'isolated_performance' => $isolated_performance,
'without_performance' => $without_performance,
'impact' => $this->calculate_impact($isolated_performance, $without_performance)
];
}
private function test_plugin_combinations($plugins) {
// Test common problematic combinations
$test_combinations = [
['caching_plugins' => $this->filter_plugins_by_type($plugins, 'cache')],
['seo_plugins' => $this->filter_plugins_by_type($plugins, 'seo')],
['security_plugins' => $this->filter_plugins_by_type($plugins, 'security')]
];
foreach ($test_combinations as $type => $plugin_group) {
if (count($plugin_group) > 1) {
// Multiple plugins of same type detected
$this->test_results['conflicts'][$type] = $plugin_group;
}
}
}
private function measure_performance() {
$measurements = [];
// Measure homepage load
$start_time = microtime(true);
$start_memory = memory_get_usage();
$start_queries = get_num_queries();
// Simulate page load
ob_start();
$this->simulate_page_load(home_url());
ob_end_clean();
$measurements['load_time'] = microtime(true) - $start_time;
$measurements['memory_used'] = memory_get_usage() - $start_memory;
$measurements['queries'] = get_num_queries() - $start_queries;
// Measure admin load
$admin_start = microtime(true);
$this->simulate_admin_load();
$measurements['admin_time'] = microtime(true) - $admin_start;
return $measurements;
}
private function simulate_page_load($url) {
// Set up WordPress query
$parsed_url = parse_url($url);
$_SERVER['REQUEST_URI'] = $parsed_url['path'] ?? '/';
// Run WordPress query
wp();
// Load template
if (is_front_page()) {
include get_front_page_template();
} elseif (is_single()) {
include get_single_template();
} else {
include get_index_template();
}
}
private function simulate_admin_load() {
// Simulate admin dashboard load
set_current_screen('dashboard');
do_action('admin_init');
do_action('admin_menu');
}
private function calculate_impact($isolated, $without) {
return [
'load_time_impact' => $isolated['load_time'] - $this->baseline_performance['load_time'],
'memory_impact' => $isolated['memory_used'] - $this->baseline_performance['memory_used'],
'query_impact' => $isolated['queries'] - $this->baseline_performance['queries']
];
}
private function filter_plugins_by_type($plugins, $type) {
$type_keywords = [
'cache' => ['cache', 'performance', 'optimize'],
'seo' => ['seo', 'sitemap', 'meta'],
'security' => ['security', 'firewall', 'protect']
];
$matched_plugins = [];
foreach ($plugins as $plugin) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
$plugin_text = strtolower($plugin_data['Name'] . ' ' . $plugin_data['Description']);
foreach ($type_keywords[$type] as $keyword) {
if (strpos($plugin_text, $keyword) !== false) {
$matched_plugins[] = $plugin;
break;
}
}
}
return $matched_plugins;
}
private function analyze_results() {
$analysis = [
'slow_plugins' => [],
'memory_hogs' => [],
'query_heavy' => [],
'conflicts' => $this->test_results['conflicts'] ?? []
];
foreach ($this->test_results as $plugin => $results) {
if (is_array($results) && isset($results['impact'])) {
// Identify slow plugins
if ($results['impact']['load_time_impact'] > 0.5) { // 500ms
$analysis['slow_plugins'][] = [
'plugin' => $plugin,
'impact' => $results['impact']['load_time_impact']
];
}
// Identify memory hogs
if ($results['impact']['memory_impact'] > 10485760) { // 10MB
$analysis['memory_hogs'][] = [
'plugin' => $plugin,
'impact' => $results['impact']['memory_impact']
];
}
// Identify query heavy plugins
if ($results['impact']['query_impact'] > 50) {
$analysis['query_heavy'][] = [
'plugin' => $plugin,
'impact' => $results['impact']['query_impact']
];
}
}
}
return $analysis;
}
}
Theme Performance Analysis
// Theme performance analyzer
class Theme_Performance_Analyzer {
public function analyze_theme() {
$theme = wp_get_theme();
$analysis = [
'theme_name' => $theme->get('Name'),
'theme_version' => $theme->get('Version'),
'issues' => []
];
// Analyze theme files
$analysis['file_analysis'] = $this->analyze_theme_files();
// Analyze theme functions
$analysis['function_analysis'] = $this->analyze_theme_functions();
// Analyze asset loading
$analysis['asset_analysis'] = $this->analyze_theme_assets();
// Analyze database queries
$analysis['query_analysis'] = $this->analyze_theme_queries();
return $analysis;
}
private function analyze_theme_files() {
$theme_dir = get_stylesheet_directory();
$analysis = [
'total_files' => 0,
'large_files' => [],
'unoptimized_images' => []
];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($theme_dir)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$analysis['total_files']++;
// Check for large files
if ($file->getSize() > 1048576) { // 1MB
$analysis['large_files'][] = [
'path' => $file->getPathname(),
'size' => $file->getSize()
];
}
// Check for unoptimized images
if (preg_match('/\.(jpg|jpeg|png|gif)$/i', $file->getFilename())) {
if ($this->is_image_unoptimized($file->getPathname())) {
$analysis['unoptimized_images'][] = $file->getPathname();
}
}
}
}
return $analysis;
}
private function is_image_unoptimized($image_path) {
$size = filesize($image_path);
list($width, $height) = getimagesize($image_path);
// Simple heuristic: if file size is too large for dimensions
$expected_size = ($width * $height * 3) / 10; // Rough estimate
return $size > $expected_size * 2;
}
private function analyze_theme_functions() {
$functions_file = get_stylesheet_directory() . '/functions.php';
$analysis = [
'hooks_count' => 0,
'global_queries' => 0,
'inefficient_patterns' => []
];
if (file_exists($functions_file)) {
$content = file_get_contents($functions_file);
// Count hooks
preg_match_all('/add_(action|filter)\s*\(/', $content, $hooks);
$analysis['hooks_count'] = count($hooks[0]);
// Check for global queries
preg_match_all('/\$wpdb->(?:get_results|get_var|get_row|query)\s*\(/', $content, $queries);
$analysis['global_queries'] = count($queries[0]);
// Check for inefficient patterns
if (preg_match('/query_posts\s*\(/', $content)) {
$analysis['inefficient_patterns'][] = 'query_posts() usage detected';
}
if (preg_match_all('/get_posts.*posts_per_page[\'"\s]*=[\'"\s]*-1/', $content)) {
$analysis['inefficient_patterns'][] = 'Unlimited post queries detected';
}
if (preg_match('/wp_remote_get.*wp_head|init/', $content)) {
$analysis['inefficient_patterns'][] = 'External requests in critical hooks';
}
}
return $analysis;
}
private function analyze_theme_assets() {
global $wp_scripts, $wp_styles;
$analysis = [
'total_scripts' => 0,
'total_styles' => 0,
'render_blocking' => [],
'missing_dependencies' => [],
'large_assets' => []
];
// Analyze scripts
if ($wp_scripts) {
foreach ($wp_scripts->registered as $handle => $script) {
if (strpos($script->src, get_stylesheet_directory_uri()) !== false) {
$analysis['total_scripts']++;
// Check if render-blocking
if (!isset($script->extra['group']) || $script->extra['group'] !== 1) {
$analysis['render_blocking'][] = $handle;
}
// Check file size
$file_path = str_replace(
get_stylesheet_directory_uri(),
get_stylesheet_directory(),
$script->src
);
if (file_exists($file_path) && filesize($file_path) > 102400) { // 100KB
$analysis['large_assets'][] = [
'handle' => $handle,
'size' => filesize($file_path)
];
}
}
}
}
// Analyze styles
if ($wp_styles) {
foreach ($wp_styles->registered as $handle => $style) {
if (strpos($style->src, get_stylesheet_directory_uri()) !== false) {
$analysis['total_styles']++;
}
}
}
return $analysis;
}
private function analyze_theme_queries() {
// Hook into theme setup to analyze queries
$query_count = 0;
$slow_queries = [];
add_action('template_redirect', function() use (&$query_count, &$slow_queries) {
if (defined('SAVEQUERIES') && SAVEQUERIES) {
global $wpdb;
$theme_dir = get_stylesheet_directory();
foreach ($wpdb->queries as $query) {
// Check if query originates from theme
if (strpos($query[2], $theme_dir) !== false) {
$query_count++;
if ($query[1] > 0.05) { // 50ms
$slow_queries[] = [
'query' => $query[0],
'time' => $query[1],
'caller' => $query[2]
];
}
}
}
}
}, 999);
return [
'query_count' => $query_count,
'slow_queries' => $slow_queries
];
}
}
Server-Side Debugging
Server Resource Monitor
// Server resource monitoring
class Server_Resource_Monitor {
private $metrics = [];
public function collect_metrics() {
$this->metrics = [
'cpu' => $this->get_cpu_usage(),
'memory' => $this->get_memory_usage(),
'disk' => $this->get_disk_usage(),
'network' => $this->get_network_stats(),
'processes' => $this->get_process_info(),
'mysql' => $this->get_mysql_stats()
];
return $this->metrics;
}
private function get_cpu_usage() {
if (PHP_OS_FAMILY === 'Linux') {
$load = sys_getloadavg();
$cpu_count = $this->get_cpu_count();
return [
'load_1min' => $load[0],
'load_5min' => $load[1],
'load_15min' => $load[2],
'cpu_count' => $cpu_count,
'utilization' => ($load[0] / $cpu_count) * 100
];
}
return null;
}
private function get_cpu_count() {
if (PHP_OS_FAMILY === 'Linux') {
$cpuinfo = file_get_contents('/proc/cpuinfo');
preg_match_all('/^processor/m', $cpuinfo, $matches);
return count($matches[0]);
}
return 1;
}
private function get_memory_usage() {
$memory = [
'php' => [
'current' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
'limit' => $this->get_memory_limit()
]
];
if (PHP_OS_FAMILY === 'Linux' && file_exists('/proc/meminfo')) {
$meminfo = file_get_contents('/proc/meminfo');
preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total);
preg_match('/MemFree:\s+(\d+)/', $meminfo, $free);
preg_match('/Cached:\s+(\d+)/', $meminfo, $cached);
$memory['system'] = [
'total' => $total[1] * 1024,
'free' => $free[1] * 1024,
'cached' => $cached[1] * 1024,
'used' => ($total[1] - $free[1] - $cached[1]) * 1024
];
}
return $memory;
}
private function get_memory_limit() {
$limit = ini_get('memory_limit');
if ($limit === '-1') {
return PHP_INT_MAX;
}
$unit = strtolower(substr($limit, -1));
$value = (int) $limit;
switch ($unit) {
case 'g':
$value *= 1024;
case 'm':
$value *= 1024;
case 'k':
$value *= 1024;
}
return $value;
}
private function get_disk_usage() {
$path = ABSPATH;
return [
'total' => disk_total_space($path),
'free' => disk_free_space($path),
'used' => disk_total_space($path) - disk_free_space($path),
'percentage' => ((disk_total_space($path) - disk_free_space($path)) / disk_total_space($path)) * 100
];
}
private function get_mysql_stats() {
global $wpdb;
$stats = [];
// Get MySQL variables
$variables = $wpdb->get_results("SHOW VARIABLES LIKE 'max_connections'", ARRAY_A);
foreach ($variables as $var) {
$stats[$var['Variable_name']] = $var['Value'];
}
// Get MySQL status
$status_vars = [
'Threads_connected',
'Threads_running',
'Questions',
'Slow_queries',
'Opens',
'Open_tables',
'Uptime'
];
$status = $wpdb->get_results(
"SHOW STATUS WHERE Variable_name IN ('" . implode("','", $status_vars) . "')",
ARRAY_A
);
foreach ($status as $stat) {
$stats[$stat['Variable_name']] = $stat['Value'];
}
return $stats;
}
private function get_network_stats() {
// This would require system-specific implementation
return [
'interfaces' => $this->get_network_interfaces()
];
}
private function get_network_interfaces() {
if (function_exists('net_get_interfaces')) {
return net_get_interfaces();
}
return [];
}
private function get_process_info() {
$processes = [
'php' => [
'version' => PHP_VERSION,
'sapi' => PHP_SAPI,
'extensions' => get_loaded_extensions()
]
];
// Get web server info
if (isset($_SERVER['SERVER_SOFTWARE'])) {
$processes['web_server'] = $_SERVER['SERVER_SOFTWARE'];
}
return $processes;
}
public function check_server_limits() {
$issues = [];
// Check PHP memory limit
if ($this->get_memory_limit() < 268435456) { // 256MB
$issues[] = [
'type' => 'memory_limit',
'message' => 'PHP memory limit is below recommended 256MB',
'current' => ini_get('memory_limit'),
'recommended' => '256M'
];
}
// Check max execution time
if (ini_get('max_execution_time') < 300 && ini_get('max_execution_time') != 0) {
$issues[] = [
'type' => 'execution_time',
'message' => 'Max execution time is below recommended 300 seconds',
'current' => ini_get('max_execution_time'),
'recommended' => '300'
];
}
// Check upload limits
$upload_max = $this->parse_size(ini_get('upload_max_filesize'));
$post_max = $this->parse_size(ini_get('post_max_size'));
if ($upload_max < 67108864) { // 64MB
$issues[] = [
'type' => 'upload_limit',
'message' => 'Upload max filesize is below recommended 64MB',
'current' => ini_get('upload_max_filesize'),
'recommended' => '64M'
];
}
return $issues;
}
private function parse_size($size) {
$unit = strtolower(substr($size, -1));
$value = (int) $size;
switch ($unit) {
case 'g':
$value *= 1024;
case 'm':
$value *= 1024;
case 'k':
$value *= 1024;
}
return $value;
}
}
Frontend Performance Debugging
JavaScript Performance Profiler
// Frontend performance debugging toolkit
class FrontendDebugger {
constructor() {
this.metrics = {
marks: {},
measures: {},
resources: [],
errors: [],
slowInteractions: []
};
this.init();
}
init() {
// Performance observer for various metrics
this.setupPerformanceObservers();
// Error tracking
this.setupErrorTracking();
// Interaction tracking
this.setupInteractionTracking();
// Resource timing
this.collectResourceTiming();
// Long task detection
this.detectLongTasks();
}
setupPerformanceObservers() {
// Navigation timing
if ('PerformanceNavigationTiming' in window) {
const navigationEntry = performance.getEntriesByType('navigation')[0];
this.metrics.navigation = {
dns: navigationEntry.domainLookupEnd - navigationEntry.domainLookupStart,
tcp: navigationEntry.connectEnd - navigationEntry.connectStart,
request: navigationEntry.responseStart - navigationEntry.requestStart,
response: navigationEntry.responseEnd - navigationEntry.responseStart,
dom: navigationEntry.domComplete - navigationEntry.domInteractive,
load: navigationEntry.loadEventEnd - navigationEntry.loadEventStart,
total: navigationEntry.loadEventEnd - navigationEntry.fetchStart
};
}
// Paint timing
const paintObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.metrics.marks[entry.name] = entry.startTime;
}
});
paintObserver.observe({ entryTypes: ['paint'] });
// Layout shift
let clsValue = 0;
let clsEntries = [];
const layoutShiftObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
clsEntries.push({
value: entry.value,
startTime: entry.startTime,
sources: entry.sources?.map(source => ({
node: source.node,
previousRect: source.previousRect,
currentRect: source.currentRect
}))
});
}
}
this.metrics.cls = {
value: clsValue,
entries: clsEntries
};
});
layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
}
setupErrorTracking() {
// JavaScript errors
window.addEventListener('error', (event) => {
this.metrics.errors.push({
type: 'javascript',
message: event.message,
source: event.filename,
line: event.lineno,
column: event.colno,
stack: event.error?.stack,
timestamp: Date.now()
});
});
// Resource loading errors
window.addEventListener('error', (event) => {
if (event.target !== window) {
this.metrics.errors.push({
type: 'resource',
tagName: event.target.tagName,
source: event.target.src || event.target.href,
message: 'Failed to load resource',
timestamp: Date.now()
});
}
}, true);
// Unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.metrics.errors.push({
type: 'promise',
reason: event.reason,
promise: event.promise,
timestamp: Date.now()
});
});
}
setupInteractionTracking() {
// Track slow clicks
document.addEventListener('click', (event) => {
const startTime = performance.now();
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const duration = performance.now() - startTime;
if (duration > 100) {
this.metrics.slowInteractions.push({
type: 'click',
target: this.getElementPath(event.target),
duration: duration,
timestamp: startTime
});
}
});
});
}, { passive: true });
// Track input delays
let inputDelayMeasured = false;
['mousedown', 'keydown', 'touchstart'].forEach(eventType => {
document.addEventListener(eventType, (event) => {
if (!inputDelayMeasured) {
const now = performance.now();
requestAnimationFrame(() => {
const inputDelay = performance.now() - now;
this.metrics.firstInputDelay = inputDelay;
inputDelayMeasured = true;
});
}
}, { once: true, passive: true });
});
}
collectResourceTiming() {
const resources = performance.getEntriesByType('resource');
this.metrics.resources = resources.map(resource => ({
name: resource.name,
type: this.getResourceType(resource),
duration: resource.duration,
size: resource.transferSize,
protocol: resource.nextHopProtocol,
cached: resource.transferSize === 0 && resource.decodedBodySize > 0,
timing: {
dns: resource.domainLookupEnd - resource.domainLookupStart,
tcp: resource.connectEnd - resource.connectStart,
ttfb: resource.responseStart - resource.requestStart,
download: resource.responseEnd - resource.responseStart
}
}));
// Group by type
this.metrics.resourceSummary = this.summarizeResources(this.metrics.resources);
}
getResourceType(resource) {
const url = resource.name;
if (url.match(/\.js(\?|$)/)) return 'script';
if (url.match(/\.css(\?|$)/)) return 'stylesheet';
if (url.match(/\.(jpg|jpeg|png|gif|webp|svg)(\?|$)/)) return 'image';
if (url.match(/\.(woff|woff2|ttf|eot)(\?|$)/)) return 'font';
if (resource.initiatorType === 'fetch' || resource.initiatorType === 'xmlhttprequest') return 'ajax';
return 'other';
}
summarizeResources(resources) {
const summary = {};
resources.forEach(resource => {
const type = resource.type;
if (!summary[type]) {
summary[type] = {
count: 0,
totalSize: 0,
totalDuration: 0,
cached: 0,
slowest: null
};
}
summary[type].count++;
summary[type].totalSize += resource.size || 0;
summary[type].totalDuration += resource.duration;
if (resource.cached) {
summary[type].cached++;
}
if (!summary[type].slowest || resource.duration > summary[type].slowest.duration) {
summary[type].slowest = resource;
}
});
return summary;
}
detectLongTasks() {
if ('PerformanceObserver' in window && 'PerformanceLongTaskTiming' in window) {
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.metrics.longTasks = this.metrics.longTasks || [];
this.metrics.longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution?.map(attr => ({
name: attr.name,
containerType: attr.containerType,
containerSrc: attr.containerSrc,
containerId: attr.containerId
}))
});
// Log warning for very long tasks
if (entry.duration > 100) {
console.warn(`Long task detected: ${entry.duration}ms`, entry);
}
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
}
}
getElementPath(element) {
const path = [];
while (element && element.nodeType === Node.ELEMENT_NODE) {
let selector = element.nodeName.toLowerCase();
if (element.id) {
selector += '#' + element.id;
path.unshift(selector);
break;
} else if (element.className) {
selector += '.' + element.className.split(' ').join('.');
}
path.unshift(selector);
element = element.parentNode;
}
return path.join(' > ');
}
generateReport() {
const report = {
summary: {
pageLoadTime: this.metrics.navigation?.total || 0,
firstPaint: this.metrics.marks['first-paint'] || 0,
firstContentfulPaint: this.metrics.marks['first-contentful-paint'] || 0,
largestContentfulPaint: this.metrics.lcp || 0,
firstInputDelay: this.metrics.firstInputDelay || 0,
cumulativeLayoutShift: this.metrics.cls?.value || 0,
totalResources: this.metrics.resources.length,
errorCount: this.metrics.errors.length,
slowInteractions: this.metrics.slowInteractions.length
},
details: this.metrics,
recommendations: this.generateRecommendations()
};
return report;
}
generateRecommendations() {
const recommendations = [];
// Check for slow resources
const slowResources = this.metrics.resources.filter(r => r.duration > 1000);
if (slowResources.length > 0) {
recommendations.push({
type: 'slow_resources',
message: `${slowResources.length} resources took over 1 second to load`,
resources: slowResources.map(r => r.name)
});
}
// Check for large resources
const largeResources = this.metrics.resources.filter(r => r.size > 500000);
if (largeResources.length > 0) {
recommendations.push({
type: 'large_resources',
message: `${largeResources.length} resources are over 500KB`,
resources: largeResources.map(r => ({ name: r.name, size: r.size }))
});
}
// Check for render-blocking resources
const renderBlockingScripts = this.metrics.resources.filter(r =>
r.type === 'script' && r.timing.download > 100
);
if (renderBlockingScripts.length > 0) {
recommendations.push({
type: 'render_blocking',
message: 'Consider deferring or async loading scripts',
resources: renderBlockingScripts.map(r => r.name)
});
}
return recommendations;
}
}
// Initialize debugger
const frontendDebugger = new FrontendDebugger();
// Expose for console access
window.wpDebug = {
getReport: () => frontendDebugger.generateReport(),
getMetrics: () => frontendDebugger.metrics,
measureFunction: (fn, name = 'function') => {
const startMark = `${name}-start`;
const endMark = `${name}-end`;
const measureName = `${name}-duration`;
performance.mark(startMark);
const result = fn();
performance.mark(endMark);
performance.measure(measureName, startMark, endMark);
const measure = performance.getEntriesByName(measureName)[0];
console.log(`${name} took ${measure.duration}ms`);
return result;
}
};
CSS Performance Analysis
// CSS performance analyzer
class CSSPerformanceAnalyzer {
analyze() {
const analysis = {
stylesheets: this.analyzeStylesheets(),
unusedRules: this.findUnusedCSS(),
complexSelectors: this.findComplexSelectors(),
performance: this.measureCSSPerformance()
};
return analysis;
}
analyzeStylesheets() {
const stylesheets = Array.from(document.styleSheets);
return stylesheets.map(sheet => {
try {
const rules = Array.from(sheet.cssRules || []);
return {
href: sheet.href,
ruleCount: rules.length,
size: this.estimateStylesheetSize(rules),
mediaQueries: rules.filter(r => r.type === CSSRule.MEDIA_RULE).length,
keyframes: rules.filter(r => r.type === CSSRule.KEYFRAMES_RULE).length,
imports: rules.filter(r => r.type === CSSRule.IMPORT_RULE).length
};
} catch (e) {
// Cross-origin stylesheet
return {
href: sheet.href,
error: 'Cross-origin stylesheet',
ruleCount: 'unknown'
};
}
});
}
estimateStylesheetSize(rules) {
let size = 0;
rules.forEach(rule => {
if (rule.cssText) {
size += rule.cssText.length;
}
});
return size;
}
findUnusedCSS() {
const allRules = this.getAllCSSRules();
const unusedRules = [];
allRules.forEach(rule => {
if (rule.type === CSSRule.STYLE_RULE) {
try {
const matches = document.querySelectorAll(rule.selectorText);
if (matches.length === 0) {
unusedRules.push({
selector: rule.selectorText,
stylesheet: rule.parentStyleSheet.href || 'inline'
});
}
} catch (e) {
// Invalid selector
}
}
});
return unusedRules;
}
getAllCSSRules() {
const allRules = [];
Array.from(document.styleSheets).forEach(sheet => {
try {
Array.from(sheet.cssRules || []).forEach(rule => {
allRules.push(rule);
});
} catch (e) {
// Cross-origin stylesheet
}
});
return allRules;
}
findComplexSelectors() {
const allRules = this.getAllCSSRules();
const complexSelectors = [];
allRules.forEach(rule => {
if (rule.type === CSSRule.STYLE_RULE) {
const complexity = this.calculateSelectorComplexity(rule.selectorText);
if (complexity.score > 30) {
complexSelectors.push({
selector: rule.selectorText,
complexity: complexity,
stylesheet: rule.parentStyleSheet.href || 'inline'
});
}
}
});
return complexSelectors.sort((a, b) => b.complexity.score - a.complexity.score);
}
calculateSelectorComplexity(selector) {
const parts = selector.split(/[\s>+~]/);
let score = 0;
let depth = 0;
parts.forEach(part => {
// ID selectors
const ids = (part.match(/#/g) || []).length;
score += ids * 100;
// Class selectors
const classes = (part.match(/\./g) || []).length;
score += classes * 10;
// Attribute selectors
const attributes = (part.match(/\[/g) || []).length;
score += attributes * 10;
// Pseudo-classes
const pseudoClasses = (part.match(/:/g) || []).length;
score += pseudoClasses * 10;
if (part.trim()) depth++;
});
// Penalize deep nesting
score += depth * 5;
return {
score: score,
depth: depth,
ids: (selector.match(/#/g) || []).length,
classes: (selector.match(/\./g) || []).length,
attributes: (selector.match(/\[/g) || []).length
};
}
measureCSSPerformance() {
const measurements = {
recalculateStyle: [],
layout: [],
paint: []
};
// Create observer for style recalculations
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.name === 'recalculate-style') {
measurements.recalculateStyle.push({
duration: entry.duration,
startTime: entry.startTime
});
} else if (entry.name === 'layout') {
measurements.layout.push({
duration: entry.duration,
startTime: entry.startTime
});
} else if (entry.name === 'paint') {
measurements.paint.push({
duration: entry.duration,
startTime: entry.startTime
});
}
});
});
// This would need browser support for measure user timing
// Currently experimental
return measurements;
}
}
Advanced Debugging Techniques
Automated Performance Regression Detection
// Performance regression detector
class Performance_Regression_Detector {
private $baseline_file;
private $threshold = 0.1; // 10% regression threshold
public function __construct($baseline_file = null) {
$this->baseline_file = $baseline_file ?: WP_CONTENT_DIR . '/performance-baseline.json';
}
public function capture_baseline() {
$metrics = $this->collect_current_metrics();
file_put_contents($this->baseline_file, json_encode($metrics, JSON_PRETTY_PRINT));
return $metrics;
}
public function check_for_regression() {
if (!file_exists($this->baseline_file)) {
return ['error' => 'No baseline found. Run capture_baseline() first.'];
}
$baseline = json_decode(file_get_contents($this->baseline_file), true);
$current = $this->collect_current_metrics();
$regressions = [];
foreach ($baseline as $metric => $baseline_value) {
if (isset($current[$metric])) {
$current_value = $current[$metric];
$change = ($current_value - $baseline_value) / $baseline_value;
if ($change > $this->threshold) {
$regressions[] = [
'metric' => $metric,
'baseline' => $baseline_value,
'current' => $current_value,
'regression' => round($change * 100, 2) . '%'
];
}
}
}
return [
'regressions' => $regressions,
'current_metrics' => $current,
'baseline_metrics' => $baseline
];
}
private function collect_current_metrics() {
$metrics = [];
// Measure homepage load time
$start = microtime(true);
$this->load_page(home_url());
$metrics['homepage_load_time'] = microtime(true) - $start;
// Database metrics
global $wpdb;
$metrics['total_queries'] = $wpdb->num_queries;
// Memory metrics
$metrics['peak_memory'] = memory_get_peak_usage();
// Plugin/theme metrics
$metrics['active_plugins'] = count(get_option('active_plugins'));
$metrics['registered_scripts'] = count($GLOBALS['wp_scripts']->registered);
$metrics['registered_styles'] = count($GLOBALS['wp_styles']->registered);
// Cache metrics
$metrics['object_cache_hits'] = wp_cache_get_hits();
$metrics['object_cache_misses'] = wp_cache_get_misses();
return $metrics;
}
private function load_page($url) {
// Simulate page load
$response = wp_remote_get($url);
return !is_wp_error($response);
}
}
// Usage
$detector = new Performance_Regression_Detector();
// Capture baseline after optimizations
$detector->capture_baseline();
// Check for regressions after changes
$results = $detector->check_for_regression();
if (!empty($results['regressions'])) {
foreach ($results['regressions'] as $regression) {
error_log(sprintf(
"Performance regression detected: %s increased by %s",
$regression['metric'],
$regression['regression']
));
}
}
Debug Command Line Interface
// WP-CLI commands for performance debugging
if (defined('WP_CLI') && WP_CLI) {
class Performance_Debug_CLI {
/**
* Analyze WordPress performance
*
* ## OPTIONS
*
* [--type=<type>]
* : Type of analysis to run (all, database, plugins, theme)
*
* [--export=<file>]
* : Export results to file
*
* ## EXAMPLES
*
* wp performance analyze --type=database
* wp performance analyze --export=report.json
*/
public function analyze($args, $assoc_args) {
$type = $assoc_args['type'] ?? 'all';
WP_CLI::log('Starting performance analysis...');
$results = [];
if ($type === 'all' || $type === 'database') {
WP_CLI::log('Analyzing database performance...');
$results['database'] = $this->analyze_database();
}
if ($type === 'all' || $type === 'plugins') {
WP_CLI::log('Analyzing plugin performance...');
$results['plugins'] = $this->analyze_plugins();
}
if ($type === 'all' || $type === 'theme') {
WP_CLI::log('Analyzing theme performance...');
$results['theme'] = $this->analyze_theme();
}
// Display results
$this->display_results($results);
// Export if requested
if (isset($assoc_args['export'])) {
file_put_contents($assoc_args['export'], json_encode($results, JSON_PRETTY_PRINT));
WP_CLI::success("Results exported to {$assoc_args['export']}");
}
}
private function analyze_database() {
global $wpdb;
$analysis = [
'table_sizes' => [],
'slow_queries' => [],
'missing_indexes' => []
];
// Get table sizes
$tables = $wpdb->get_results("
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 (data_length + index_length) DESC
");
foreach ($tables as $table) {
$analysis['table_sizes'][$table->table_name] = $table->size_mb . ' MB';
}
// Check for missing indexes
$missing_indexes = $this->check_missing_indexes();
$analysis['missing_indexes'] = $missing_indexes;
return $analysis;
}
private function check_missing_indexes() {
global $wpdb;
$suggestions = [];
// Check postmeta for common queries
$result = $wpdb->get_var("
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = '" . DB_NAME . "'
AND TABLE_NAME = '{$wpdb->postmeta}'
AND INDEX_NAME = 'meta_key_value'
");
if (!$result) {
$suggestions[] = "CREATE INDEX meta_key_value ON {$wpdb->postmeta} (meta_key, meta_value(20))";
}
return $suggestions;
}
private function analyze_plugins() {
$plugins = get_plugins();
$active_plugins = get_option('active_plugins');
$analysis = [];
foreach ($active_plugins as $plugin_file) {
if (isset($plugins[$plugin_file])) {
$plugin_data = $plugins[$plugin_file];
// Measure plugin impact
$impact = $this->measure_plugin_impact($plugin_file);
$analysis[$plugin_data['Name']] = [
'version' => $plugin_data['Version'],
'hooks' => $impact['hooks'],
'memory' => size_format($impact['memory']),
'load_time' => round($impact['load_time'] * 1000, 2) . 'ms'
];
}
}
return $analysis;
}
private function measure_plugin_impact($plugin_file) {
// This is a simplified measurement
// In reality, you'd need to profile each plugin individually
global $wp_filter;
$plugin_dir = dirname($plugin_file);
$hook_count = 0;
foreach ($wp_filter as $hook => $priorities) {
foreach ($priorities as $priority => $functions) {
foreach ($functions as $function) {
if (is_string($function['function']) &&
strpos($function['function'], $plugin_dir) !== false) {
$hook_count++;
}
}
}
}
return [
'hooks' => $hook_count,
'memory' => rand(1048576, 10485760), // Placeholder
'load_time' => rand(10, 100) / 1000 // Placeholder
];
}
private function analyze_theme() {
$theme = wp_get_theme();
return [
'name' => $theme->get('Name'),
'version' => $theme->get('Version'),
'template_files' => count($theme->get_files('php')),
'style_files' => count($theme->get_files('css')),
'script_files' => count($theme->get_files('js')),
'total_size' => size_format($this->get_directory_size(get_stylesheet_directory()))
];
}
private function get_directory_size($dir) {
$size = 0;
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $file) {
if ($file->isFile()) {
$size += $file->getSize();
}
}
return $size;
}
private function display_results($results) {
foreach ($results as $section => $data) {
WP_CLI::log("\n" . strtoupper($section) . " ANALYSIS:");
WP_CLI::log(str_repeat('-', 50));
if ($section === 'database') {
WP_CLI::log("\nTable Sizes:");
foreach ($data['table_sizes'] as $table => $size) {
WP_CLI::log(" $table: $size");
}
if (!empty($data['missing_indexes'])) {
WP_CLI::log("\nSuggested Indexes:");
foreach ($data['missing_indexes'] as $index) {
WP_CLI::log(" $index");
}
}
} elseif ($section === 'plugins') {
foreach ($data as $plugin => $info) {
WP_CLI::log("\n$plugin:");
foreach ($info as $key => $value) {
WP_CLI::log(" $key: $value");
}
}
} else {
foreach ($data as $key => $value) {
WP_CLI::log(" $key: $value");
}
}
}
}
}
WP_CLI::add_command('performance', 'Performance_Debug_CLI');
}
Debugging Best Practices
Performance Debug Checklist
// Comprehensive debugging checklist
class Performance_Debug_Checklist {
private $checks = [
'environment' => [
'wp_debug_enabled' => 'WP_DEBUG is enabled',
'query_logging' => 'SAVEQUERIES is enabled',
'error_display' => 'Error display is configured',
'debug_log' => 'Debug log is writable'
],
'database' => [
'slow_query_log' => 'MySQL slow query log is enabled',
'table_optimization' => 'Tables are optimized',
'index_analysis' => 'Indexes are analyzed',
'query_patterns' => 'Query patterns are reviewed'
],
'plugins' => [
'conflict_test' => 'Plugin conflicts tested',
'performance_impact' => 'Individual plugin impact measured',
'hook_analysis' => 'Hook usage analyzed',
'resource_usage' => 'Resource usage profiled'
],
'frontend' => [
'browser_tools' => 'Browser DevTools profiling completed',
'network_analysis' => 'Network waterfall analyzed',
'javascript_profiling' => 'JavaScript performance profiled',
'css_optimization' => 'CSS delivery optimized'
],
'server' => [
'resource_monitoring' => 'Server resources monitored',
'error_logs' => 'Server error logs reviewed',
'configuration' => 'Server configuration optimized',
'caching_layers' => 'Caching layers verified'
]
];
public function run_checklist() {
$results = [];
foreach ($this->checks as $category => $items) {
$results[$category] = [];
foreach ($items as $check => $description) {
$method = 'check_' . $check;
if (method_exists($this, $method)) {
$results[$category][$check] = [
'description' => $description,
'status' => $this->$method(),
'recommendation' => $this->get_recommendation($check)
];
}
}
}
return $results;
}
private function check_wp_debug_enabled() {
return defined('WP_DEBUG') && WP_DEBUG;
}
private function check_query_logging() {
return defined('SAVEQUERIES') && SAVEQUERIES;
}
private function check_debug_log() {
$log_file = WP_CONTENT_DIR . '/debug.log';
return is_writable(dirname($log_file));
}
private function get_recommendation($check) {
$recommendations = [
'wp_debug_enabled' => 'Enable WP_DEBUG in wp-config.php',
'query_logging' => 'Set SAVEQUERIES to true for query analysis',
'debug_log' => 'Ensure wp-content directory is writable',
'slow_query_log' => 'Enable slow_query_log in MySQL configuration'
];
return $recommendations[$check] ?? 'Review and optimize';
}
public function generate_report() {
$checklist_results = $this->run_checklist();
$report = "=== WordPress Performance Debug Checklist ===\n\n";
foreach ($checklist_results as $category => $checks) {
$report .= strtoupper($category) . ":\n";
foreach ($checks as $check => $result) {
$status = $result['status'] ? '✓' : '✗';
$report .= sprintf(
" %s %s\n",
$status,
$result['description']
);
if (!$result['status']) {
$report .= " → " . $result['recommendation'] . "\n";
}
}
$report .= "\n";
}
return $report;
}
}
// Generate and display checklist
$checklist = new Performance_Debug_Checklist();
echo $checklist->generate_report();
Next Steps
Now that you've mastered WordPress performance debugging:
- Set Up Monitoring: Implement continuous performance monitoring
- Create Baselines: Establish performance baselines for comparison
- Automate Testing: Add performance tests to your deployment pipeline
- Document Issues: Keep a log of performance issues and solutions
For comprehensive performance optimization strategies, explore our complete WordPress Performance Optimization guide.
Last updated: July 12, 2025