LESSON 6 โฑ๏ธ 11 min read

Profiling & Debugging Performance

Performance Profiling Tools

ToolBest For
Query MonitorDatabase, hooks, PHP
New RelicAPM, full-stack
BlackfirePHP profiling
Chrome DevToolsFrontend
WebPageTestWaterfall analysis

Query Monitor

Essential WordPress profiling plugin. Shows:

  • Database queries with timing
  • HTTP API calls
  • Hooks and actions
  • PHP errors
  • Templates loaded

Using Query Monitor

// Mark slow queries
add_filter('qm/collect/hooks', function($hooks) {
    return $hooks;
});

// Custom timing panels
do_action('qm/start', 'my-process');
// ... expensive operation ...
do_action('qm/stop', 'my-process');

// Log messages
do_action('qm/debug', 'Debug message');
do_action('qm/warning', 'Warning message');
do_action('qm/error', 'Error message');

Chrome DevTools Performance

Recording Performance

  1. Open DevTools (F12)
  2. Go to Performance tab
  3. Click Record
  4. Reload page
  5. Stop recording

Key Metrics to Watch

MetricTargetImpact
First Paint< 1sPerceived speed
DOMContentLoaded< 2sInteractive
Long Tasks< 50msResponsiveness
Total Blocking Time< 200msINP

Network Tab Analysis

  • Check waterfall sequence
  • Identify large resources
  • Find slow requests
  • Verify caching headers

PHP Profiling

Debug Log

// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

// Custom timing
function log_time($label) {
    static $start;
    if (!$start) $start = microtime(true);
    $elapsed = round((microtime(true) - $start) * 1000, 2);
    error_log("[TIMING] {$label}: {$elapsed}ms");
}

// Usage
log_time('Start');
expensive_function();
log_time('After expensive_function');

Simple Profiler

class SimpleProfiler {
    private static $timers = [];
    
    public static function start($name) {
        self::$timers[$name] = microtime(true);
    }
    
    public static function stop($name) {
        if (isset(self::$timers[$name])) {
            $elapsed = microtime(true) - self::$timers[$name];
            error_log(sprintf('[PROFILE] %s: %.4f seconds', $name, $elapsed));
            return $elapsed;
        }
        return 0;
    }
    
    public static function memory() {
        $mem = memory_get_peak_usage(true);
        error_log(sprintf('[MEMORY] Peak: %.2f MB', $mem / 1024 / 1024));
    }
}

// Usage
SimpleProfiler::start('database-query');
$results = $wpdb->get_results($query);
SimpleProfiler::stop('database-query');
SimpleProfiler::memory();

Finding Slow Database Queries

// Log slow queries
add_filter('log_query_custom_data', function($data, $query, $time) {
    if ($time > 0.05) { // 50ms threshold
        error_log(sprintf(
            '[SLOW QUERY] %.4fs: %s',
            $time,
            substr($query, 0, 200)
        ));
    }
    return $data;
}, 10, 3);

// Enable query logging
define('SAVEQUERIES', true);

// Then review
global $wpdb;
foreach ($wpdb->queries as $query) {
    if ($query[1] > 0.05) {
        print_r($query);
    }
}

Hook Profiling

// Profile all hooks
add_action('all', function($hook) {
    static $hooks = [];
    static $start;
    
    if (!$start) $start = microtime(true);
    
    $time = microtime(true) - $start;
    $hooks[$hook] = ($hooks[$hook] ?? 0) + 1;
    
    if ($time > 5) { // After 5 seconds, dump
        arsort($hooks);
        error_log(print_r(array_slice($hooks, 0, 20), true));
    }
});

// Profile specific hooks
add_action('wp_head', function() {
    SimpleProfiler::start('wp_head');
}, 0);

add_action('wp_head', function() {
    SimpleProfiler::stop('wp_head');
}, PHP_INT_MAX);

WebPageTest Deep Dive

https://www.webpagetest.org/

Settings to use:
- Test Location: Nearest to your users
- Connection: 4G / Cable
- Number of Tests: 3
- Repeat View: Checked
- Capture Video: Checked

Reading the Waterfall

ColorMeaning
GreenServer response
PurpleDNS lookup
OrangeConnection
BlueDownloading
RedBlocked

Performance Budget

// Check against budget
const budget = {
    'First Contentful Paint': 1800,
    'Largest Contentful Paint': 2500,
    'Total Blocking Time': 200,
    'Cumulative Layout Shift': 0.1,
    'Total Transfer Size': 500000, // bytes
};

// In CI/CD pipeline
const results = JSON.parse(lighthouseOutput);
Object.keys(budget).forEach(metric => {
    if (results[metric] > budget[metric]) {
        console.error(`${metric} over budget!`);
        process.exit(1);
    }
});

Next Steps

In the final lesson, we'll cover hosting and server-level optimizations.

๐ŸŽฏ Lesson Complete! You can now profile and debug WordPress performance issues.