Profiling PHP apps

2017/08/09

Part of my job at Nomad List, Remote OK et al. is to keep the sites fast and responsive, even from an influx of traffic to Pieter’s latest app. Launches are usually kept controlled by exporting the pages to static HTML files and serving using Nginx. However, in the best interest of server CPU load, processing time and futureproofing, I’ve recently begun profiling our PHP code and optimising out any issues.

This blog post is a guide/case study of how I do it and how it helps us.

1. Configuring Xdebug

Xdebug is a debugging and profiling tool for PHP. The website lists all the ways to install it. You’ll probably want to do it using PECL or Apt. Everything is well documented, so you should change the parameters to fit your setup but we have the following in /etc/php/7.1/fpm/php.ini:

In order, the configuration goes as follows: load the module, disable profiling by default, set the filename, enable triggering via GET/POST parameter, output in /tmp and only profile when given the key.

Restart php-fpm and you should be good to go.

2. Profiling a page

Now you should be able to go to visit any PHP page on your site, append ?XDEBUG_PROFILE=<secret key> and see the file in /tmp. The great thing about using this functionality is you have total control over which pages are profiled and it requires no in-app code.

It’ll take a bit longer than usual to load the page, but it’s storing all function calls and statistics so give it a minute.

3. Analysing the breakdown

From here, you can scp a copy of the file to yourself and use Kcachegrind to browse the file. This worked fine for me but I kept losing track of files and wondered if there were any web app solutions. I found webgrind, a PHP web app which loads Xdebug files and displays them in the browser, just like Kcachegrind. The advantage here is you don’t need to download each file individually and you can even profile and analyse pages on a portable device.

We host webgrind on a separate domain, protected by .htaccess. This prevents unauthorised users from hunting through your source code.

The interface is simple to use. I generally show 100% of the statistics in order to get a grasp of what is being called, and where from. The most important columns are Invocation Count and Total Self Cost.

Invocation Count is how many times that function was called. A simple tip for reducing this is to pull out any repetitive function calls from a for-loop into a single variable.

Total Self Cost is the total percentage or time that the function is responsible for. You can improve this by reducing code complexity, using built-in native functions or removing unused variables.

Reduce either of these and you’re winning.

Case Study

Pieter launched Mute this morning. The front page was static, generated by a cronjob running every minute or so. From our cronjob status page, I could see it was taking around 3 seconds to generate. I thought I’d take a look and see if anything could be improved.