Query Monitor: Diagnosing Slow Queries and Hooks in WordPress

Query Monitor is the most powerful free debugging plugin for WordPress — it shows every database query with its execution time, the PHP call stack that triggered it, HTTP API calls, hooks, and block render times. Knowing how to read its output is essential for diagnosing slow pages.

Problem: A WordPress page is slow but it is unclear whether the bottleneck is database queries, PHP execution, hook callbacks, or outbound HTTP requests — standard profilers show total execution time but not the WordPress-specific breakdown.

Solution: Install Query Monitor — it adds a toolbar panel showing every database query with duration and call stack, all hooks with their callbacks and execution time, HTTP API calls, template file paths, and PHP errors. Filter by slow queries or specific hooks to pinpoint exactly where time is spent.

The tips below cover the most useful Query Monitor panels, how to add custom data to its output, and how to programmatically log slow queries to a file for production environments.

// 1. Identify the slowest queries — open QM → Queries → Duration (sort descending)
//    Look for:
//    - Queries over 0.05s on a local dev machine
//    - Duplicate queries (same SQL run 10+ times → missing cache)
//    - Full table scans (no 'Using index' in QM call stack)

// 2. Add custom logging to Query Monitor output
add_action( 'qm/collect', function( QM_Collector $collector ) {
    if ( $collector->id !== 'timings' ) return;
    // Use QM timers in your own code:
    do_action( 'qm/start', 'my_expensive_operation' );
    // ... your code ...
    do_action( 'qm/stop', 'my_expensive_operation' );
} );

// Simpler: use the QM logger directly
do_action( 'qm/debug', 'Cache hit for key: ' . $cache_key );
do_action( 'qm/warning', 'Slow external API call detected', [ 'duration' => $ms ] );
do_action( 'qm/error', 'Failed to connect to external service' );

// 3. Log slow DB queries to a file without Query Monitor (production-safe)
add_filter( 'query', function( string $sql ): string {
    $start = microtime( true );
    // Query runs after this filter returns
    add_filter( 'query', function( $q ) use ( $sql, $start ) {
        $duration = microtime( true ) - $start;
        if ( $duration > 0.1 ) { // 100ms threshold
            error_log( sprintf( '[SLOW QUERY %.3fs] %s', $duration, $sql ) );
        }
        return $q;
    }, PHP_INT_MAX );
    return $sql;
} );

NOTE: Use Query Monitor's HTTP API panel to catch slow wp_remote_get() calls, its Hooks panel to trace execution order, and its Block Editor panel to find blocks with expensive render_callback functions. Install it only on staging/dev — or use the QM_DISABLED constant to toggle it via environment variable.

Leave Comment

Your email address will not be published. Required fields are marked *