Server Setup / Troubleshooting

How to log and fix slow PHP requests

by , , revisited on


We have by far the largest RPM repository with dynamic stable NGINX modules and VMODs for Varnish 4.1 and 6.0 LTS. If you want to install NGINX, Varnish, and lots of useful modules for them, this is your one-stop repository to get all performance-related software.
You have to maintain an active subscription in order to be able to use the repository!

When troubleshooting a slow PHP website, logging slow requests is very important.

The most common way to run a PHP website is via PHP-FPM, and, surprise, it has some directives for the job of logging slow requests.

How to enable PHP-FPM slow log

PHP-FPM slow log can be configured on a per pool basis, which means you can configure for a specific website when you need to troubleshoot performance issues.
Simply edit the PHP-FPM pool configuration, e.g. /etc/php-fpm.d/example.com.conf and add directives specifying the desired location of the slow log file and the time it takes for a script to run for it to be logged:

slowlog = /srv/www/example.com/logs/slow.log
request_slowlog_timeout = 3s

The slowlog directive is the path to a file where the slow requests will be logged. Of course, the path to the slow log file should be somewhere that the PHP-FPM pool user can create/read/write files.

The request_slowlog_timeout is the time after which PHP-FPM will dump a backtrace for slow requests. 3 seconds is a good starting point where a script can be deemed as slow.
Although I’d really think of any script that takes more than one second to be slow, we should account for under-powered servers.

That’s pretty much all it takes for configuring it. Simply run systemctl reload php-fpm to apply the configuration, and slow requests will be logged going forward.

Working with the PHP-FPM slow log files

Each slow request, exceeding in time than the configured threshold will be logged as an entry containing the date, pool ID, the script_filename, and the backtrace of the recent function calls.
Example:

[05-Jun-2020 12:16:43]  [pool example.com] pid 19995
script_filename = /srv/www/example.com/httpdocs/wp-admin/index.php
[0x00007f53de01d7d0] curl_exec() /srv/www/example.com/httpdocs/wp-includes/Requests/Transport/cURL.php:162
[0x00007f53de01d710] request() /srv/www/example.com/httpdocs/wp-includes/class-requests.php:379
[0x00007f53de01d610] request() /srv/www/example.com/httpdocs/wp-includes/class-http.php:394
[0x00007f53de01d480] request() /srv/www/example.com/httpdocs/wp-includes/class-http.php:611
[0x00007f53de01d3e0] post() /srv/www/example.com/httpdocs/wp-includes/http.php:181
[0x00007f53de01d350] wp_remote_post() /srv/www/example.com/httpdocs/wp-admin/includes/dashboard.php:1687
[0x00007f53de01d270] wp_check_browser_version() /srv/www/example.com/httpdocs/wp-admin/includes/dashboard.php:27
[0x00007f53de01d120] wp_dashboard_setup() /srv/www/example.com/httpdocs/wp-admin/index.php:15

The slow log entries, unfortunately, only include script_filename and not the URI that is being called.
So you can’t look at the slow log alone to get complete information about the slow request, because in most front-controller pattern frameworks (which WordPress is),
there will be mostly a single, or in some cases a couple of scripts handling all the requests (e.g. /index.php for front page and /wp-admin/index.php for the admin pages).

So you will have to correlate the time of the slow log request with the time in access.log in your web server.

To find the related access log requests, the easiest is having an aggregated log facility, e.g. PaperTrail.
In case of the aggregated log, you would simply scroll up through the log records above the slow PHP-FPM record to locate the one from the web server.

But you can simply use SSH for locating log entries. Simply browse the access log:

less access.log

Then type / followed by the time specifications of a minute when the PHP-FPM slow request took place, e.g. 12:16:, then hit Enter on your keyboard.

The text browser search for the specified text and you can easily locate the related access log entry.

For our sample request, the related entry in access.log is the following:

<IP> - - [05/Jun/2020:12:16:45 +0000] "GET /wp-admin/ HTTP/1.1" 200 46224 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" "<IP>, 127.0.0.1" "www.example.com" sn="www.example.com" rt=0.973 ua="unix:/var/run/php-fpm/php-fpm-example.com.sock" us="200" ut="0.973" ul="224549" cs=-

So simply browsing /wp-admin/ is slow. Why? Let’s look back at our slow PHP-FPM backtrace and see how to read it.

The function calls are listed in the order of recency: the function that ran earlier will be at the bottom; the function to run last will be at the top.
So we see that when the script exceeded the 3 seconds threshold, it was running curl_exec function, which indicates the most common reason of a slow website in modern content management systems – curl requests to remote websites.

The solution there is typically halting the often unnecessary requests from being made, or adjusting the URL of the requested remote services, if they are crucial to website’s function.

So how to identify which code is responsible for such requests, why and to which URLs? Simply read the slow entry from the bottom to the top.
The functions were in invoked in this order

  • wp_dashboard_setup()
  • wp_check_browser_version()
  • wp_remote_post()

You can browse through each function call via less as well.
While you can do it for each entry (in order to understand why the code ended up using curl_exec), you can use some intuition there. Run command:

less +1687 /srv/www/example.com/httpdocs/wp-admin/includes/dashboard.php

This will take you to where wp_remote_post was called. Navigating the code few lines above, you can locate the actual URL being invoked:

$url     = 'http://api.wordpress.org/core/browse-happy/1.1/';

A Google search away, is the answer to your query:

WordPress checks whether your browser is up-to-date, and displays a notice if it is not (i.e., “You are using an insecure browser!” or “Your browser is out of date!”).

Specific to this case, it is a WordPress behavior which calls its own API to check if your browser is up-to-date.
Thanks WordPress, if you do this, at least you should have used a faster web server for your API.

So you can disable this with a simple paste-in to your functions.php:

add_filter('pre_http_request', function($ret, array $request, string $url) {
    if (\preg_match('!^https?://api\.wordpress\.org/core/browse-happy/!i', $url)) {
        return new \WP_Error('http_request_failed', \sprintf('Request to %s is not allowed.', $url));
    }

    return $ret;
}, 10, 3);

Other common Causes of Slow Requests

Aside from slow curl function calls to remote services (a.k.a slow websites that makes your website slow), there are few other common cases why PHP is slow to run, and thus logged to slow PHP-FPM log

Slow MySQL queries

That will usually manifest in a form of logged mysql_query function calls. The issue is typically with a missing database index, which will speed up queries’ execution

Long running scripts running in web context

This is caused by long-running scripts launched via browser, where there shouldn’t be.
An example is a data import/export, that has to be executed via cron/message queues, etc.

Typical mistake is running website cron tasks via curl invocation in crontab. This is wrong in 99% cases.

E.g. curl http://example.com/cron.php simply makes no sense if cron.php is located on the same server as where you run curl. Simply rewrite cron to launch php interpreter directly, with argument set to the location of the cron file:

* * * * * /usr/bin/php /srv/www/example.com/cron.php

Tips

When you’re done fixing slow PHP requests, do not forget to disable PHP-FPM slow log on a production website by commenting directives that enabled it.

; slowlog = /srv/www/example.com/logs/slow.log
; request_slowlog_timeout = 3s

This ensures that PHP-FPM doesn’t incur additional overhead to keep the backtrace.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: