Query Monitor https://querymonitor.com The developer tools panel for WordPress Mon, 18 Nov 2024 13:03:34 GMT https://validator.w3.org/feed/docs/rss2.html https://github.com/jpmonette/feed Copyright (c) 2009-2024, John Blackbourn <![CDATA[Add-on plugins]]> https://querymonitor.com/help/add-on-plugins.html https://querymonitor.com/help/add-on-plugins.html Sat, 27 Jul 2024 22:20:46 GMT Query Monitor add-on plugins

Third-party plugins that extend Query Monitor

Debug Bar add-ons

Query Monitor also supports all Debug Bar add-on plugins. You can deactivate the core Debug Bar plugin, and Query Monitor will take over the display of any add-ons you have installed. There are add-on plugins available for:

]]>
<![CDATA[Assertions]]> https://querymonitor.com/wordpress-debugging/assertions.html https://querymonitor.com/wordpress-debugging/assertions.html Tue, 02 Apr 2024 19:56:56 GMT Performing assertions in Query Monitor

New

This feature is new in Query Monitor 3.15

Query Monitor allows developers to perform assertions which will log an error in the Logs panel in Query Monitor when they fail. This is a convenience wrapper around the logging feature which allows you to get alerted to problems without performing conditional logic.

Here's what assertions look like in the Logs panel:

Query Monitor's Logging Panel

Let's take a look at how to use this feature and what it's useful for.

Basic usage

php
do_action( 'qm/assert', $value === 5 );
do_action( 'qm/assert', $value === 5, 'Value is 5' );
do_action( 'qm/assert', $value === 5, 'Value is 5', $value );
do_action( 'qm/assert', $value === 5 );
do_action( 'qm/assert', $value === 5, 'Value is 5' );
do_action( 'qm/assert', $value === 5, 'Value is 5', $value );

The qm/assert action accepts an assertion value as its first parameter which you'll usually provide in the form of an expression. This should be a boolean true or false value, although technically anything truthy or falsey is accepted.

If the assertion fails then Query Monitor will show an error in the Logs panel, which in turn causes a red warning to appear in the admin toolbar so you get notified about the failure. If the assertion passes then a debug level message will be shown in the Logs panel, which helps you confirm that your assertion is being executed.

The second parameter is an optional short description of the assertion. If provided, this will be shown along with the assertion failure or pass message.

The third parameter is an optional value of any type that will get output below the error message if the assertion fails. This is useful for debugging an unexpected value.

WARNING

Be careful not to log very large values such as an array of post objects or the raw response from an HTTP request. If you really need to debug the value of something large, use a tool such as step debugging in Xdebug or debugging in Ray.

More examples

You can use this assertion feature to ensure your code is behaving as expected, for example to assert how many database queries are being performed or not performed:

php
foreach ( $posts as $post ) {
	$before = $wpdb->num_queries;
	$this->process_post( $post );
	$after = $wpdb->num_queries;

	// Assert that no database queries are performed as we process each post:
	do_action( 'qm/assert', $after === $before );
}
foreach ( $posts as $post ) {
	$before = $wpdb->num_queries;
	$this->process_post( $post );
	$after = $wpdb->num_queries;

	// Assert that no database queries are performed as we process each post:
	do_action( 'qm/assert', $after === $before );
}

Preconditions can be used to assert a certain state before performing logic based on expectations:

php
do_action( 'qm/assert', is_array( $data ), 'Data is an array', $data );
do_action( 'qm/assert', array_key_exists( 'foo', $data ), 'Data contains foo', $data );
do_action( 'qm/assert', is_array( $data ), 'Data is an array', $data );
do_action( 'qm/assert', array_key_exists( 'foo', $data ), 'Data contains foo', $data );

Postconditions can be used to assert that a particular outcome occurred:

php
do_action( 'qm/assert', did_action( 'my-action' ) );
do_action( 'qm/assert', did_action( 'my-action' ) );

The static assertion method on the QM class can be used instead of calling do_action():

php
QM::assert( $value === 5 );
QM::assert( $value === 5, 'Value is 5' );
QM::assert( $value === 5, 'Value is 5', $value );
QM::assert( $value === 5 );
QM::assert( $value === 5, 'Value is 5' );
QM::assert( $value === 5, 'Value is 5', $value );

Differences from assert()

This feature differs from the native assert() function in PHP because they serve different purposes.

  • The assert() function in PHP will terminate execution of the script if the assertion fails, this is not true for an assertion in Query Monitor. Think of this like a soft assertion that raises an error instead. Code should behave as expected regardless of whether the assertion passes.
  • Query Monitor logs passed assertions too. This is useful for verifying that your assertion is being executed.
  • Assertions in Query Monitor will always be performed and logged as necessary. The assert() function in PHP will only perform the assertion if assertions are enabled in the php.ini configuration.
  • Assertions in Query Monitor can be passed an optional value to output for debugging purposes, which is not possible with assert().

Notes on usage

Assertions are primarily a development tool to identify bugs or sub-optimal behaviour in your code. This is distinct from error handling or data validation, which assertions are not intended for.

Just as with the assert() function in PHP, your code must handle the situation where your assertion fails because in a production environment the code will continue to execute past the assertion.

Profiling and logging

Read more about the profiling and logging functionality in Query Monitor.

]]>
<![CDATA[How to use Query Monitor]]> https://querymonitor.com/wordpress-debugging/how-to-use.html https://querymonitor.com/wordpress-debugging/how-to-use.html Tue, 02 Apr 2024 18:34:41 GMT How to use Query Monitor

Installation

Install and activate Query Monitor as you would any other WordPress plugin. You can download it here on WordPress.org.

Usage

While you're logged in as an Administrator you'll see a new menu in the admin toolbar:

Admin Toolbar Menu

The numbers at the top show, in order:

  1. Page generation time in seconds
  2. Peak memory usage
  3. Total time taken by SQL queries in seconds
  4. Total number of SQL queries

All of the information shown by Query Monitor is for the current page load. Historical information is not available (although this feature is planned for a future version).

Click the top of the menu to open the Overview panel, or click any menu item to open its corresponding panel.

Initially you'll probably be most interested in the Queries panel, which shows all of the database queries that were performed during the page load and allows you to filter and sort them and determine which component was responsible for each query.

Debugging a Slow Site

So your site is slow and you've decided to install Query Monitor in order to identify the cause. There are a few panels that you should look at first.

1. Queries by Component

The Queries → Queries by Component panel shows you aggregate information about the database queries that were executed by each plugin and theme during the page load. This is a good way to identify poorly performing themes or plugins.

This panel is sorted by the total time taken for all the queries executed by each component. A plugin that performs a high number of queries, or performs queries that are slow, will contribute to the time that your site takes to load.

Aggregate Database Queries by Component

2. HTTP API Calls

The HTTP API Calls panel shows you information about the server-side HTTP requests that were performed during the page load. These are usually "invisible" causes of a slow site, and can occur sporadically.

If a plugin or theme regularly triggers an HTTP API call during the loading of a page, this will increase the time your site takes to load.

HTTP API Requests

3. Object Cache

The Overview panel shows you information about several aspects of your site, one of which is the object cache. If you don't have a persistent object caching plugin active then Query Monitor will show you a message.

A persistent object cache plugin greatly improves performance by caching the result of operations such as database queries, HTTP API calls, and other slow operations. By default WordPress uses a non-persistent object cache, which means repeated operations within the same page load get cached but repeated operations across page loads don't.

If you see the message "External object cache not in use" then you should install an object caching plugin for a driver such as Redis or Memcached which will allow the object cache to persist across page loads and greatly improve the performance of your site. Speak to your hosting provider if you need help with this.

4. PHP Errors

The PHP Errors panel will appear only if some code that executed during the loading of the page caused a PHP error, such as a notice or a warning. If you see a red or orange highlight in Query Monitor's admin toolbar menu, this means an error occurred and you should investigate it.

Code that triggers a Warning means the code is not operating as expected and may be causing broken behaviour on your site. You should investigate warnings straight away. In addition, warnings can increase the time your site takes to load because each error gets logged by your server.

Code that triggers a Notice is less critical but should still be investigated as it can be indicative of poorly written code. Notices may or may not be logged by your server depending on its configuration.

5. Scripts and Styles

If your site loads many JavaScript or CSS files it won't necessarily slow down the page generation time on your server but it will slow down the page load time for your visitors.

The Scripts and Styles panels show you which files have been enqueued via the WordPress dependency system. If these panels show a large number of files you should consider installing a plugin which minifies and combines them.

Everything Else

Despite its name, Query Monitor includes a large amount of functionality unrelated to database queries. For example in the Request panel you can see information about rewrite rules and query variables; in the Template panel you can see information about the theme template hierarchy and template parts; and in the Environment panel you can see configuration settings and information about your server.

Click through each of the panels and hopefully you'll find something interesting!

]]>
<![CDATA[User Capabilities]]> https://querymonitor.com/wordpress-debugging/user-capabilities.html https://querymonitor.com/wordpress-debugging/user-capabilities.html Wed, 21 Feb 2024 23:03:35 GMT Debugging user capability checks with Query Monitor

Query Monitor can log all of the user capability checks that are performed during a page load of WordPress. It will show you the result of the capability check, information about the user, and where the check was called from. This can be very helpful when testing functionality on your site for users who are not Administrators.

Step 1: Enable the panel

The user capabilities panel is not enabled by default because it can cause performance issues on sites that perform a large number of user capability checks. To enable this panel, add the following code to your wp-config.php file:

php
define( 'QM_ENABLE_CAPS_PANEL', true );
define( 'QM_ENABLE_CAPS_PANEL', true );

Step 2: Authenticate

From the Settings panel in Query Monitor (click the cog next to the Close icon), click the "Set authentication cookie" button. This will allow you to view Query Monitor output while you're logged in as a different user.

Step 3: Log in as another user

Log in to your site using a user account with a lower level role, for example an Editor or Author. To save yourself time you can use the User Switching plugin to instantly swap between user accounts in WordPress at the click of a button.

Step 4: View the Capability Checks panel

You're now able to test the functionality on your site as a lower level user and the "Capability Checks" panel in Query Monitor will show you all the capability checks performed on the page, along with the result, the caller, and the component. No more guesswork!

Screenshot of the "Capability Checks" panel in Query Monitor

]]>
<![CDATA[Blocks]]> https://querymonitor.com/wordpress-debugging/blocks.html https://querymonitor.com/wordpress-debugging/blocks.html Wed, 10 Jan 2024 13:49:03 GMT Debugging blocks with Query Monitor

The Blocks panel in Query Monitor lists all of the blocks on the current page, along with their attributes and other debugging information. Nested blocks (for example for groups or columns) are fully supported.

Screenshot of the Blocks panel in Query Monitor

If you're developing dynamic blocks, the block render callback and render timing information will help you keep an eye on performance that can otherwise be difficult to measure.

]]>
<![CDATA[Translation files]]> https://querymonitor.com/wordpress-debugging/javascript-translation-files.html https://querymonitor.com/wordpress-debugging/javascript-translation-files.html Wed, 10 Jan 2024 13:49:03 GMT Debugging translation files with Query Monitor

WordPress 5.0 introduced the ability to use internationalisation functions in JavaScript and provide Jed translations for messages that use them.

Query Monitor fully supports debugging the loading of these translation files in the Languages panel, so you know which files WordPress is attempting to load:

Screenshot of the Languages panel in Query Monitor showing the Jed translation files that WordPress attempts to load

]]>
<![CDATA[Profiling and logging]]> https://querymonitor.com/wordpress-debugging/profiling-and-logging.html https://querymonitor.com/wordpress-debugging/profiling-and-logging.html Wed, 10 Jan 2024 13:49:03 GMT Profiling and logging with Query Monitor

Query Monitor allows developers to profile the running time and memory usage of a piece of code on your WordPress site and to log debugging messages to the Query Monitor interface.

Let's take a look at profiling and logging in detail.

Profiling

Basic profiling can be performed and displayed in the Timings panel in Query Monitor using actions in your code:

php
// Start the 'foo' timer:
do_action( 'qm/start', 'foo' );

// Run some code
my_potentially_slow_function();

// Stop the 'foo' timer:
do_action( 'qm/stop', 'foo' );
// Start the 'foo' timer:
do_action( 'qm/start', 'foo' );

// Run some code
my_potentially_slow_function();

// Stop the 'foo' timer:
do_action( 'qm/stop', 'foo' );

The time taken and approximate memory usage used between the qm/start and qm/stop actions for the given function name will be recorded and shown in the Timings panel. Timers can be nested, although be aware that this reduces the accuracy of the memory usage calculations.

Timers can also make use of laps with the qm/lap action:

php
// Start the 'bar' timer:
do_action( 'qm/start', 'bar' );

// Iterate over some data:
foreach ( range( 1, 10 ) as $i ) {
    my_potentially_slow_function( $i );
    do_action( 'qm/lap', 'bar' );
}

// Stop the 'bar' timer:
do_action( 'qm/stop', 'bar' );
// Start the 'bar' timer:
do_action( 'qm/start', 'bar' );

// Iterate over some data:
foreach ( range( 1, 10 ) as $i ) {
    my_potentially_slow_function( $i );
    do_action( 'qm/lap', 'bar' );
}

// Stop the 'bar' timer:
do_action( 'qm/stop', 'bar' );

Here's what the Timing panel looks like:

Query Monitor's Timing Panel

Note that the times and memory usage displayed in the Timings panel should be treated as approximations, because they are recorded at the PHP level and can be skewed by your environment and by other code. If you require highly accurate timings, you'll need to use a low level profiling tool such as XHProf.

Logging

Messages and variables can be logged in Query Monitor similarly to how you can call console.log in JavaScript to log data from WordPress to the console. This can be used as a replacement for var_dump().

php
do_action( 'qm/debug', 'This happened!' );
do_action( 'qm/debug', 'This happened!' );

You can use any of the following actions which correspond to PSR-3 and syslog log levels:

  • qm/debug
  • qm/info
  • qm/notice
  • qm/warning
  • qm/error
  • qm/critical
  • qm/alert
  • qm/emergency

A log level of warning or higher will trigger a notification in Query Monitor's admin toolbar.

Here's what the Logs panel looks like:

Query Monitor's Logging Panel

Contextual interpolation can be used via the curly brace syntax:

php
do_action( 'qm/warning', 'Unexpected value of {foo} encountered', [
    'foo' => $foo,
] );
do_action( 'qm/warning', 'Unexpected value of {foo} encountered', [
    'foo' => $foo,
] );

A WP_Error, Exception, or Throwable object can be passed directly into the logger:

php
if ( is_wp_error( $response ) ) {
    do_action( 'qm/error', $response );
}
if ( is_wp_error( $response ) ) {
    do_action( 'qm/error', $response );
}
php
try {
    // your code
} catch ( Exception $e ) {
    do_action( 'qm/error', $e );
}
try {
    // your code
} catch ( Exception $e ) {
    do_action( 'qm/error', $e );
}

Variables of any type can be logged and they'll be formatted appropriately:

WARNING

Be careful not to log very large values such as an array of post objects or the raw response from an HTTP request. If you really need to debug the value of something large, use a tool such as step debugging in Xdebug or debugging in Ray.

php
$var = [ 1, 2, 3 ];
do_action( 'qm/debug', $var );
$var = [ 1, 2, 3 ];
do_action( 'qm/debug', $var );

Finally, the static logging methods on the QM class can be used instead of calling do_action():

php
QM::error( 'Everything is broken' );
QM::error( 'Everything is broken' );

The QM class is PSR-3 compatible, although it doesn't actually implement Psr\Log\LoggerInterface.

Assertions

New

New in Query Monitor 3.15

Query Monitor allows developers to perform assertions which will log an error in the Logs panel in Query Monitor when they fail. Read more about using assertions in Query Monitor.

]]>
<![CDATA[wp_die()]]> https://querymonitor.com/wordpress-debugging/wp-die.html https://querymonitor.com/wordpress-debugging/wp-die.html Wed, 10 Jan 2024 13:49:03 GMT wp_die() debugging with Query Monitor

The wp_die() output in WordPress is a thing of beauty... if you’re into minimalism.

Screenshot of the useless output of a call to wp_die()

Query Monitor adds some debugging information to the output of wp_die(), including the component responsible and the call stack, to help you identify the source of the message:

Screenshot of a slightly more useful output of a call to wp_die() with Query Monitor enabled

]]>
<![CDATA[Clickable stack traces]]> https://querymonitor.com/help/clickable-stack-traces-and-function-names.html https://querymonitor.com/help/clickable-stack-traces-and-function-names.html Wed, 10 Jan 2024 11:18:57 GMT Clickable stack traces and function names in Query Monitor

Many panels in Query Monitor display function names or stack traces. Wouldn't it be great if you could click the function name and the file opens up in your text editor or IDE at the correct position? With the clickable file links feature you can, and you'll wonder how you lived without it:

Screenshot of clickable function names in Query Monitor

You just need to open up the Settings panel in Query Monitor (click the cog next to the Close icon) and choose your editor in the "Editor" section. That's it!

Screenshot of the Editor setting in Query Monitor

If you use an editor other than VS Code or PhpStorm then you may first need to configure it so it opens when a certain URL scheme is encountered:

Remote File Path Mapping

If you're debugging a remote site or using Docker or a virtual machine, you'll need to map the path on the server to its path on your local machine so your editor doesn't try to load a non-existent file. You can do this using a filter on the qm/output/file_path_map hook which accepts an array of remote paths and the local path they map to.

For example, if you use the Docker-based development environment that's built in to WordPress core, your path mapping needs to look like this:

php
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/var/www/'] = '/path/to/wordpress/';
	return $map;
} );
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/var/www/'] = '/path/to/wordpress/';
	return $map;
} );

If you use VVV your path mapping needs to look like this:

php
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/srv/'] = '/path/to/vvv/';
	return $map;
} );
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/srv/'] = '/path/to/vvv/';
	return $map;
} );

If you use Chassis or another Vagrant-based VM, your path mapping needs to look like this:

php
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/vagrant/'] = '/path/to/local/project/';
	return $map;
} );
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/vagrant/'] = '/path/to/local/project/';
	return $map;
} );

If you've changed the paths configuration in Chassis, you may need to map the /chassis directory instead:

php
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/chassis/'] = '/path/to/local/project/';
	return $map;
} );
add_filter( 'qm/output/file_path_map', function( $map ) {
	$map['/chassis/'] = '/path/to/local/project/';
	return $map;
} );

That's it!

I hope you enjoy clicking on your newly-clickable file links.

]]>
<![CDATA[Configuration constants]]> https://querymonitor.com/help/configuration-constants.html https://querymonitor.com/help/configuration-constants.html Wed, 10 Jan 2024 11:18:57 GMT Configuration constants

The following PHP constants can be defined in your wp-config.php file in order to control the behaviour of Query Monitor:

QM_DB_EXPENSIVE

If an individual database query takes longer than this time to execute, it's considered "slow" and triggers a warning.

Default 0.05

QM_DISABLED

Disable Query Monitor entirely.

Default false

QM_DISABLE_ERROR_HANDLER

Disable the handling of PHP errors.

Default false

QM_ENABLE_CAPS_PANEL

Enable the Capability Checks panel.

Default false

QM_HIDE_CORE_ACTIONS

Hide WordPress core on the Hooks & Actions panel.

Default false

QM_HIDE_SELF

Hide Query Monitor itself from various panels. Set to false if you want to see how Query Monitor hooks into WordPress.

Default true

QM_NO_JQUERY

Don't specify jQuery as a dependency of Query Monitor. If jQuery isn't enqueued then Query Monitor will still operate, but with some reduced functionality.

Default false

QM_SHOW_ALL_HOOKS

In the Hooks & Actions panel, show every hook that has an action or filter attached (instead of every action hook that fired during the request).

Default false

Allow the wp-content/db.php file symlink to be put into place during activation. Set to false to prevent the symlink creation.

Default true

]]>
<![CDATA[Silencing errors]]> https://querymonitor.com/help/silencing-errors.html https://querymonitor.com/help/silencing-errors.html Wed, 10 Jan 2024 11:18:57 GMT Silencing errors from certain plugins and themes in Query Monitor

When a PHP warning or notice occurs during the page load, Query Monitor displays a coloured notification in the admin toolbar that links to the PHP Errors panel. This is great for debugging but can be an annoyance if a third party plugin or theme continually triggers errors that aren't your responsibility to fix.

Screenshot of a PHP error in Query Monitor

Query Monitor allows you to silence errors from specified plugins or themes. Errors will still be shown in the PHP Errors panel but they won't trigger a coloured notification in the admin toolbar.

Here's how you hide PHP notices from a plugin named "foo":

php
add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
	$levels['plugin']['foo'] = ( E_ALL & ~E_NOTICE );
	return $levels;
} );
add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
	$levels['plugin']['foo'] = ( E_ALL & ~E_NOTICE );
	return $levels;
} );

This code hooks into the qm/collect/php_error_levels filter and specifies the error levels which should get reported by Query Monitor for the specified plugin. The error levels are specified using the same bitmask syntax used for PHP's error_reporting() function, and in this example is telling Query Monitor to report all errors except notices.

The name to use for the plugin array's index is what Query Monitor shows as the name for the "Component", for example "Plugin: foo" ends up as "foo".

You could also tell Query Monitor to only report warnings from your child theme, and completely silence errors from its parent theme (probably not a good idea):

php
add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
	$levels['theme']['stylesheet'] = ( E_WARNING & E_USER_WARNING );
	$levels['theme']['template']   = ( 0 );
	return $levels;
} );
add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
	$levels['theme']['stylesheet'] = ( E_WARNING & E_USER_WARNING );
	$levels['theme']['template']   = ( 0 );
	return $levels;
} );

Any plugin or theme which doesn't have an error level specified via this filter is assumed to have the default level of E_ALL, which shows all errors.

To silence deprecated errors from WordPress core:

php
add_filter( 'qm/collect/php_error_levels', function( $levels ) {
	$levels['core']['core'] = ( E_ALL & ~E_DEPRECATED );
	return $levels;
} );
add_filter( 'qm/collect/php_error_levels', function( $levels ) {
	$levels['core']['core'] = ( E_ALL & ~E_DEPRECATED );
	return $levels;
} );

Finally, if you have special PHP error handling in place on your site and you don't want Query Monitor to handle errors at all, you can disable the error handling functionality completely:

php
define( 'QM_DISABLE_ERROR_HANDLER', true );
define( 'QM_DISABLE_ERROR_HANDLER', true );
]]>
<![CDATA[Related hooks]]> https://querymonitor.com/wordpress-debugging/related-hooks.html https://querymonitor.com/wordpress-debugging/related-hooks.html Wed, 10 Jan 2024 11:18:57 GMT Related hooks with filters or actions attached

Many of the panels in Query Monitor include a sub-menu which lists related hooks where filters or actions are attached to them. This can greatly reduce the amount of time you spend trying to find out what's making changes to certain behaviour on your site.

I've found this to be particularly helpful on the Request panel, which tracks hooks related to rewrite rules, query parsing, request handling, query vars, and more.

Screenshot of the "Hooks in Use" sub-menu of the Request panel in Query Monitor

It's also very useful for figuring out what's making changes to user role and capability handling on your site:

Screenshot of the "Hooks in Use" sub-menu of the Capability Checks panel in Query Monitor

Some panels track certain option names too, which means all the filters related to that option get automatically tracked. For example, you'll be able to see if something is hooked onto the pre_option_stylesheet filter on the Template panel, or the site_option_WPLANG filter on the Languages panel.

Not all of the panels are tracking related hooks yet. I'll continue expanding and improving this feature in future releases of QM, including adding a way to expose all of the hooks that each panel is tracking, regardless of whether filters or actions are attached to them.

]]>
<![CDATA[REST API requests]]> https://querymonitor.com/wordpress-debugging/rest-api-requests.html https://querymonitor.com/wordpress-debugging/rest-api-requests.html Wed, 10 Jan 2024 11:18:57 GMT Debugging WordPress REST API requests with Query Monitor

Query Monitor includes a feature which allows you to see comprehensive performance information about a REST API request on your site.

Authentication

Just like requests to the front end or the admin area of your site, in order to see debugging information for the REST API you need to perform a request which is authenticated as a user who has permission to view Query Monitor’s output, for example an Administrator.

  • This usually means including a valid _wpnonce parameter in the URL, the value of which you can get by visiting wp-admin/admin-ajax.php?action=rest-nonce
  • Alternatively you can pass an Application Password if you’re using WordPress 5.6 or later

Overview and PHP error information

The following additional HTTP headers will be included in the response:

  • x-qm-overview-time_taken – Response generation time in seconds
  • x-qm-overview-time_usage – Response generation time as a percentage of PHP’s max execution time limit
  • x-qm-overview-memory – Memory usage in kB
  • x-qm-overview-memory_usage – Memory usage as a percentage of PHP’s memory limit
  • x-qm-php_errors-error-count – Number of PHP errors that occurred (0 or more)
  • x-qm-php_errors-error-{n} – Details about each individual PHP error

Full performance and debugging information

When a REST API request is performed which requests an enveloped response via the ?_envelope parameter, an additional qm property will be present in the JSON response with information about:

  • qm.db_queries.dbs – All database queries
  • qm.db_queries.dupes – Duplicate database queries
  • qm.db_queries.errors – Database queries with errors
  • qm.cache – Object cache stats for hits and misses
  • qm.http – HTTP API requests and response details
  • qm.logger – Logged messages and variables
  • qm.transients – Updated transients

The information is somewhat trimmed down from the information that you would see in the main Query Monitor panel for a regular HTML request, but it contains the key information that you need to investigate performance issues.

The qm.db_queries property contains overview information as well as full details for each individual SQL query, including timing, stack traces, and returned rows, and the qm.http property includes details about the each requested URL, response code, timing, and stack traces.

Example data

Given a GET request to a default endpoint such as example.com/wp-json/wp/v2/posts/?_envelope&_wpnonce=<nonce>, you would not typically expect to see server-side HTTP API requests or transients being updated on every request, but QM will now expose this information so you can investigate!

Here’s an example of the qm property in a response:

json
{
  "db_queries": {
    "dbs": {
      "$wpdb": {
        "total": 15,
        "time": 0.0108,
        "queries": [
          {
            "sql": "SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'",
            "time": 0.0011,
            "stack": [
              "wp_load_alloptions()",
              "is_blog_installed()",
              "wp_not_installed()"
            ],
            "result": 317
          },
          {
            "sql": "SELECT * FROM wp_users WHERE ID = '1' LIMIT 1",
            "time": 0.0003,
            "stack": [
              "WP_User::get_data_by()",
              "WP_User->__construct()",
              "wp_set_current_user()",
              "_wp_get_current_user()",
              "wp_get_current_user()",
              "get_current_user_id()",
              "get_user_option()",
              "Classic_Editor::get_settings()",
              "Classic_Editor::init_actions()",
              "do_action('plugins_loaded')"
            ],
            "result": 1
          },
          {
            "sql": "SELECT wp_posts.ID FROM wp_posts WHERE 1=1  AND wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) ORDER BY wp_posts.post_date DESC LIMIT 0, 5",
            "time": 0.0003,
            "stack": [
              "WP_Query->get_posts()",
              "WP_Query->query()",
              "get_posts()",
              "DoubleUnderscore\\entrypoint()",
              "do_action('init')"
            ],
            "result": 5
          }
        ]
      }
    },
    "errors": {
      "total": 1,
      "errors": [
        {
          "caller": "do_action('init')",
          "caller_name": "do_action('init')",
          "sql": "SELECT * FROM table_that_does_not_exist",
          "ltime": 0,
          "result": {
            "errors": {
              "1146": [
                "Table 'wp.table_that_does_not_exist' doesn't exist"
              ]
            }
          }
        }
      ]
    },
    "dupes": {
      "total": 1,
      "queries": {
        "SELECT wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) ORDER BY wp_posts.post_date DESC LIMIT 0, 5": [
          3,
          14,
          35
        ]
      }
    }
  },
  "cache": {
    "hit_percentage": 67.8,
    "hits": 931,
    "misses": 442
  },
  "http": {
    "total": 1,
    "time": 0.6586,
    "requests": [
      {
        "url": "https://example.org",
        "method": "GET",
        "response": {
          "code": 200,
          "message": "OK"
        },
        "time": 0.6586,
        "stack": [
          "WP_Http->request()",
          "WP_Http->get()",
          "wp_remote_get()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ]
  },
  "logger": {
    "warning": [
      {
        "message": "Preloading was not found, generating fresh",
        "stack": [
          "DoubleUnderscore\\dispatcher()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ],
    "debug": [
      {
        "message": "Language: en_US",
        "stack": [
          "DoubleUnderscore\\do_logs()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ]
  }
}
{
  "db_queries": {
    "dbs": {
      "$wpdb": {
        "total": 15,
        "time": 0.0108,
        "queries": [
          {
            "sql": "SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'",
            "time": 0.0011,
            "stack": [
              "wp_load_alloptions()",
              "is_blog_installed()",
              "wp_not_installed()"
            ],
            "result": 317
          },
          {
            "sql": "SELECT * FROM wp_users WHERE ID = '1' LIMIT 1",
            "time": 0.0003,
            "stack": [
              "WP_User::get_data_by()",
              "WP_User->__construct()",
              "wp_set_current_user()",
              "_wp_get_current_user()",
              "wp_get_current_user()",
              "get_current_user_id()",
              "get_user_option()",
              "Classic_Editor::get_settings()",
              "Classic_Editor::init_actions()",
              "do_action('plugins_loaded')"
            ],
            "result": 1
          },
          {
            "sql": "SELECT wp_posts.ID FROM wp_posts WHERE 1=1  AND wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) ORDER BY wp_posts.post_date DESC LIMIT 0, 5",
            "time": 0.0003,
            "stack": [
              "WP_Query->get_posts()",
              "WP_Query->query()",
              "get_posts()",
              "DoubleUnderscore\\entrypoint()",
              "do_action('init')"
            ],
            "result": 5
          }
        ]
      }
    },
    "errors": {
      "total": 1,
      "errors": [
        {
          "caller": "do_action('init')",
          "caller_name": "do_action('init')",
          "sql": "SELECT * FROM table_that_does_not_exist",
          "ltime": 0,
          "result": {
            "errors": {
              "1146": [
                "Table 'wp.table_that_does_not_exist' doesn't exist"
              ]
            }
          }
        }
      ]
    },
    "dupes": {
      "total": 1,
      "queries": {
        "SELECT wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) ORDER BY wp_posts.post_date DESC LIMIT 0, 5": [
          3,
          14,
          35
        ]
      }
    }
  },
  "cache": {
    "hit_percentage": 67.8,
    "hits": 931,
    "misses": 442
  },
  "http": {
    "total": 1,
    "time": 0.6586,
    "requests": [
      {
        "url": "https://example.org",
        "method": "GET",
        "response": {
          "code": 200,
          "message": "OK"
        },
        "time": 0.6586,
        "stack": [
          "WP_Http->request()",
          "WP_Http->get()",
          "wp_remote_get()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ]
  },
  "logger": {
    "warning": [
      {
        "message": "Preloading was not found, generating fresh",
        "stack": [
          "DoubleUnderscore\\dispatcher()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ],
    "debug": [
      {
        "message": "Language: en_US",
        "stack": [
          "DoubleUnderscore\\do_logs()",
          "DoubleUnderscore\\entrypoint()",
          "do_action('init')"
        ]
      }
    ]
  }
}
]]>
<![CDATA[Template part loading]]> https://querymonitor.com/wordpress-debugging/template-part-loading.html https://querymonitor.com/wordpress-debugging/template-part-loading.html Wed, 10 Jan 2024 11:18:57 GMT Debugging WordPress template part loading with Query Monitor

Template parts are a fundamental part of building WordPress themes, but sometimes it can be difficult to find out why a given template part is or isn't loading.

Query Monitor makes debugging template part loading easier by exposing the list of template parts that either were or were not loaded. Here's a screenshot of it in action:

Screenshot of the Template Parts section of the Template panel in Query Monitor

What this allows you to do is to see the value of the $slug and $name parameters that were passed to get_template_part() when an unsuccessful request for a template part was made. Query Monitor will show you the file name and line number where the call was made, so you can find it easily and investigate if necessary, or if you've got clickable stack traces enabled from the settings screen you can just click it to be taken straight there.

I hope this feature is useful to you! I've certainly been finding it useful myself.

]]>
<![CDATA[Cache hit rate]]> https://querymonitor.com/help/cache-hit-rate.html https://querymonitor.com/help/cache-hit-rate.html Sun, 12 Nov 2023 19:52:26 GMT Cache hit rate

Does your object cache hit rate always show as 100%? If so, this is possibly due to a bug in the Memcached object cache controller.

The bug was fixed here: https://github.com/Ipstenu/memcached-redux/pull/2 . You may need to update your object cache drop-in.

If you're not using the Memcached object cache controller and you're always seeing either 0% or 100% cache hit rate, please let me know!

]]>
<![CDATA[db.php symlink]]> https://querymonitor.com/help/db-php-symlink.html https://querymonitor.com/help/db-php-symlink.html Sun, 12 Nov 2023 19:52:26 GMT db.php symlink

In addition to the main plugin files, Query Monitor includes a file named db.php which gets symlinked into your wp-content directory when the plugin is activated. This special file is a WordPress dropin plugin and it allows Query Monitor to provide extended functionality such as the result count, full stack trace, and error detection for all database queries.

Occasionally the PHP process won't be able to put this symlink in place. Some common causes are:

  • The file permissions of the wp-content directory means it isn't writable by PHP
  • Another wp-content/db.php file is already in place
  • Files for the site were copied from elsewhere (eg. during a migration from another hosting provider) and the existing symlink no longer points to a valid location

Query Monitor will still work fine in this situation but you won't see extended information that makes Query Monitor much more useful.

In this situation you can create the symlink manually using one of the methods below.

Relax the file permissions

Relax the file permissions on the wp-content directory so it's writable by the PHP process, then de-activate and re-activate Query Monitor and it'll attempt to create the symlink again.

Use WP-CLI

Query Monitor includes a WP-CLI command for putting the symlink into place:

wp qm enable
wp qm enable

Use the command line

If you don't have access to WP-CLI you can run a command to create the symlink manually:

macOS / Linux:

ln -s /path/to/wordpress/wp-content/plugins/query-monitor/wp-content/db.php /path/to/wordpress/wp-content/db.php
ln -s /path/to/wordpress/wp-content/plugins/query-monitor/wp-content/db.php /path/to/wordpress/wp-content/db.php

Windows (requires administrator privileges):

mklink C:\path\to\wordpress\wp-content\db.php C:\path\to\wordpress\wp-content\plugins\query-monitor\wp-content\db.php
mklink C:\path\to\wordpress\wp-content\db.php C:\path\to\wordpress\wp-content\plugins\query-monitor\wp-content\db.php

Via your hosting control panel

If you're unable to do any of the above you should be able to use your web hosting control panel (such as Plesk or cPanel) to create the symlink. Contact your web host if you're unsure.

When an existing db.php file is already in place

The db.php file will sometimes conflict with another plugin that also uses a db.php file. Such plugins include:

  • W3 Total Cache
  • LudicrousDB
  • HyperDB

There is nothing that can be done about this. This a WordPress core limitation due to the fact that the dropin plugin file must be called db.php and placed in the wp-content directory, and only one can exist there.

]]>