Skip to content

Conversation

Copy link

Copilot AI commented Nov 8, 2025

Adds a new wp profile queries command to display database queries executed during a WordPress request with timing information and optional filtering by hook or callback.

Changes

  • Command.php: New queries() method that runs WordPress profiling, collects queries from $wpdb->queries, and outputs them with configurable fields and filtering. Includes proper handling for both --hook and --callback parameters used together, and explicit filtering logic to skip loggers without callbacks when callback filtering is requested.
  • QueryLogger.php: Simple data container for query metadata (SQL, execution time, caller, hook, callback)
  • Logger.php: Enhanced to track query_indices array during stop(), enabling accurate query-to-hook/callback mapping. The query_indices array is now properly reset in stop() method to ensure clean state for reused loggers.
  • Formatter.php: Fixed to properly handle non-numeric fields ('query', 'caller', 'hook', 'callback') by skipping them during total calculations and adding numeric validation to prevent PHP warnings
  • Tests: Added features/profile-queries.feature with scenarios for basic listing, filtering, and output formats. Tests include custom mu-plugins that execute specific database queries to verify functionality.

Usage

# All queries with timing
wp profile queries --fields=query,time

# Queries from specific hook
wp profile queries --hook=init --fields=query,time,callback

# Queries from specific callback
wp profile queries --callback='WP_Query->get_posts()' --fields=query,time

# Filter by both hook and callback
wp profile queries --hook=init --callback=my_function --fields=query,time

# Sort by execution time
wp profile queries --orderby=time --order=DESC --fields=query,time

Filtering works by tracking which query indices each Logger executes (via query_offset range), then matching those indices to hook/callback names when filtering is requested. When both --hook and --callback are provided, the command profiles all hooks and then filters to show only queries that match both criteria.

Original prompt

This section details on the original issue you should resolve

<issue_title>Introduce wp profile queries</issue_title>
<issue_description>It would be helpful to be able to see all queries and how much time they're taking.

Also make it possible to show actual queries for (a) a specific callback or (b) all queries within a specific hook</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Nov 8, 2025
Copilot AI and others added 3 commits November 8, 2025 10:36
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI changed the title [WIP] Introduce wp profile queries to monitor query performance Introduce wp profile queries command Nov 8, 2025
Copilot AI requested a review from swissspidy November 8, 2025 10:42
Copilot finished work on behalf of swissspidy November 8, 2025 10:42
@swissspidy swissspidy requested a review from Copilot November 8, 2025 14:23

This comment was marked as resolved.

swissspidy and others added 3 commits November 8, 2025 15:27
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI requested a review from swissspidy November 8, 2025 14:32
Copilot finished work on behalf of swissspidy November 8, 2025 14:32
@swissspidy
Copy link
Member

@copilot There's lots of PHP warnings "Warning: A non-numeric value encountered in src/Formatter.php on line 134" in tests for this new code. Please fix the root cause.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 10, 2025

@copilot There's lots of PHP warnings "Warning: A non-numeric value encountered in src/Formatter.php on line 134" in tests for this new code. Please fix the root cause.

Fixed by adding non-numeric field detection in Formatter.php. The code now skips string fields ('query', 'caller', 'hook', 'callback') when calculating totals and includes an is_numeric() check before summing values to prevent the warnings. (commit 28e1e4d)

Copilot finished work on behalf of swissspidy November 10, 2025 17:56
@swissspidy swissspidy requested a review from Copilot November 10, 2025 21:07
Copilot finished reviewing on behalf of swissspidy November 10, 2025 21:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +601 to +608
foreach ( $logger->query_indices as $query_index ) {
if ( ! isset( $query_map[ $query_index ] ) ) {
$query_map[ $query_index ] = array(
'hook' => isset( $logger->hook ) ? $logger->hook : null,
'callback' => isset( $logger->callback ) ? $logger->callback : null,
);
}
}
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Potential issue with query map overwriting. When multiple loggers track the same query index (e.g., nested hooks), the check ! isset( $query_map[ $query_index ] ) at line 602 prevents overwriting. This means the first logger's hook/callback will be used, but in nested scenarios, this might not be the most specific or accurate mapping. Consider whether the first-logger-wins approach is the intended behavior, or if you need to track multiple hooks/callbacks per query.

Copilot uses AI. Check for mistakes.
Comment on lines 582 to 630
$loggers = $profiler->get_loggers();
foreach ( $loggers as $logger ) {
// Skip if filtering by callback and this isn't the right one
if ( $callback && isset( $logger->callback ) ) {
// Normalize callback for comparison
$normalized_callback = str_replace( array( '->', '::' ), '', (string) $logger->callback );
$normalized_filter = str_replace( array( '->', '::' ), '', $callback );
if ( false === stripos( $normalized_callback, $normalized_filter ) ) {
continue;
}
}

// Skip if filtering by hook and this isn't the right one
if ( $hook && isset( $logger->hook ) && $logger->hook !== $hook ) {
continue;
}

// Get the query indices for this logger
if ( isset( $logger->query_indices ) && ! empty( $logger->query_indices ) ) {
foreach ( $logger->query_indices as $query_index ) {
if ( ! isset( $query_map[ $query_index ] ) ) {
$query_map[ $query_index ] = array(
'hook' => isset( $logger->hook ) ? $logger->hook : null,
'callback' => isset( $logger->callback ) ? $logger->callback : null,
);
}
}
}
}
}

// Get all queries
$queries = array();
if ( ! empty( $wpdb->queries ) ) {
foreach ( $wpdb->queries as $index => $query_data ) {
// If filtering by hook/callback, only include queries in the map
if ( ( $hook || $callback ) && ! isset( $query_map[ $index ] ) ) {
continue;
}

$query_obj = new QueryLogger(
$query_data[0], // SQL query
$query_data[1], // Time
isset( $query_data[2] ) ? $query_data[2] : '', // Caller
isset( $query_map[ $index ]['hook'] ) ? $query_map[ $index ]['hook'] : null,
isset( $query_map[ $index ]['callback'] ) ? $query_map[ $index ]['callback'] : null
);
$queries[] = $query_obj;
}
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Performance consideration: The nested loop structure (lines 583-610 and 616-630) could be inefficient for sites with many queries and hooks. For N loggers and M queries, this is O(N*Q + M) where Q is queries per logger. If $wpdb->queries contains thousands of entries, this might be slow. Consider optimizing by building the query map in a single pass if performance becomes an issue, though the current implementation is clear and correct for typical use cases.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to 95
$this->query_indices[] = $i;
}
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $query_indices array is not reset in the stop() method, unlike other tracking properties (e.g., $query_offset is set to null on line 111). If a Logger instance is reused by calling start() and stop() multiple times, the $query_indices array will accumulate indices across multiple runs. Consider resetting $this->query_indices = array(); in the stop() method or at the start of start() method to ensure clean state for reused loggers, or document that loggers should not be reused.

Copilot uses AI. Check for mistakes.
src/Command.php Outdated
Comment on lines 568 to 574
if ( $hook ) {
$type = 'hook';
$focus = $hook;
} elseif ( $callback ) {
$type = 'hook';
$focus = true; // Profile all hooks to find the specific callback
}
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When both --hook and --callback are provided, the profiler setup logic only uses the hook value. The condition on line 571 (elseif ( $callback )) will never be reached when both are present. This means the profiler won't track all hooks to find the specific callback, which could lead to incomplete filtering. Consider handling the case where both are provided explicitly, or adjust the logic to ensure both parameters are considered properly.

Copilot uses AI. Check for mistakes.
…indices

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot finished work on behalf of swissspidy November 10, 2025 21:30
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce wp profile queries

2 participants