Debugging PHP Applications on Azure App Services Linux/Containers using XDEBUG

7 minute read | By Christopher Maldonado

When it comes to slow performance on custom PHP applications running on Azure App Services, there will come a time where XDEBUG will need to be enabled to further assist the troubleshooting effort of an issue. Here is how we could do that.

Enable XDEBUG (App Service on Linux)

When using our blessed PHP images, XDebug can be enabled using an Application Setting via the Azure Portal.

  1. Go to the Azure Portal https://portal.azure.com and select your App Service Linux PHP application.
  2. Select Configuration option for your application.
  3. Under the Application Settings, click the “+ New Application Setting” option.
  4. Adding the following:

     Name: PHP_ZENDEXTENSIONS
     Value: xdebug
    
  5. Save the changes.

Note: You will see that the container will be pulling from a different tag: <PHP-Version>-apache-xdebug_<release-version>

Install PHP XDEBUG Extension (Web App for Containers)

Use the following sets to install XDebug if you are using a custom container for your application.

Installing the extension

  1. Go to your KUDU console https://<sitename>.scm.azurewebites.net
  2. Select SSH.
  3. In your SSH session, run the command pear config-show
root@localhost:/home/site/wwwroot#> pear config-show
Configuration (channel pear.php.net):
=====================================
Auto-discover new Channels     auto_discover    <not set>
Default Channel                default_channel  pear.php.net
HTTP Proxy Server Address      http_proxy       <not set>
PEAR server [DEPRECATED]       master_server    pear.php.net
Default Channel Mirror         preferred_mirror pear.php.net
Remote Configuration File      remote_config    <not set>
PEAR executables directory     bin_dir          /usr/local/bin
PEAR documentation directory   doc_dir          /usr/local/lib/php/doc
PHP extension directory        ext_dir          /usr/local/lib/php/extensions/no-debug-non-zts-20180731
PEAR directory                 php_dir          /usr/local/lib/php
PEAR Installer cache directory cache_dir        /tmp/pear/cache
PEAR configuration file        cfg_dir          /usr/local/lib/php/cfg
  1. In the output above, the path for ext_dir is where your extension will be downloaded. Note the path for later use.
  2. To install an extension, perform the following.  I’ll be installing the PHP extension “xdebug” in this example.
root@localhost:/home/site/wwwroot#> pecl install xdebug
downloading xdebug-2.9.5.tgz ...
Starting to download xdebug-2.9.5.tgz (243,710 bytes)
..................................................done: 243,710 bytes
91 source files, building
running: phpize
Configuring for:
PHP Api Version:         20180731
Zend Module Api No:      20180731
Zend Extension Api No:   320180731
building in /tmp/pear/temp/pear-build-rootY28Lh2/xdebug-2.9.5
running: /tmp/pear/temp/xdebug/configure --with-php-config=/usr/local/bin/php-config
……
Build process completed successfully
Installing '/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so'
install ok: channel://pecl.php.net/xdebug-2.9.5
configuration option "php_ini" is not set to php.ini location
You should add "zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so" to php.ini
  1. You’ll notice the path to the newly installed extension is also returned after it’s installed and matches the ext_dir path returned from the Pear configurations.

  2. Now that the extension is installed, we’ll need to create an “ext” directory under “/home/site” and copy the extension to that directory so that the extension persists if the container is restarted.  Below are the commands to do this.

root@localhost:/home/site#> mkdir /home/site/ext
root@localhost:/home/site#> cp /usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so /home/site/ext

Note:  The text may continue and look *garbled.  Don’t worry, keep entering the command.

  1. The first command creates the “ext” directory and the second command copies the PHP extension file from the installation location to the new directory.

  2. Once this is done, we can move to updating our PHP settings.

Update application settings

Creating to INI file

While we are still in the SSH of our KUDU site let’s do the following.

  1. Go to your “/home/site” directory
root@localhost:/home/site#> cd /home/site
  1. Create a directory called “ini”
root@localhost:/home/site#> mkdir ini
  1. Change directories to “ini” directory
root@localhost:/home/site#> cd ini
  1. We’ll need to create an “ini” file to add our settings to. Since we are looking to activate xdebug and set some settings, I’m using “xdebug.ini” for this example.
root@localhost:/home/site/ini#> touch xdebug.ini
  1. Open the newly created file using VI or VIM.
root@localhost:/home/site/ini#> vim xdebug.ini

Press “i” on your keyboard to start editing and add the following:

For XDebug v2.x

# Include this line if your are using custom container
zend_extension=/home/site/ext/xdebug.so

xdebug.profiler_enable_trigger=1
xdebug.profiler_output_dir=/home/LogFiles

For XDebug v3.x

xdebug.output_dir=/home/LogFiles
xdebug.mode=profile
xdebug.start_with_request=trigger

Press “Esc”, then “:wq!” and enter to save.

Adding the Application Setting

We will now need to go to the Azure Portal and add an Application Setting to can the “ini” directory that we just created to enable XDEBUG.

  1. Go to the Azure Portal (https://portal.azure.com) and select your App Service Linux PHP application.
  2. Select Configuration for the app.
  3. Under the Application settings section, click the “+ New application setting”.
  4. For the App Setting name, enter PHP_INI_SCAN_DIR
  5. For the App Setting value, enter /usr/local/etc/php/conf.d:/home/site/ini

Testing and Capturing an XDEBUG profile

In the PHP settings we enabled xdebug.profile_enable_trigger=1 which would allow us to append ?XDEBUG_PROFILE=1 in the URL to trigger a profile capture. You could do this by navigating to your site like below:

http://<sitename>.azurewebsites.net/myphppage.php?XDEBUG_PROFILE=1

This will generate an XDEBUG profile in your /home/LogFiles folder. You should have files named like so: cachegrind.out.##

Analyzing the XDEBUG profile

In order to analyze your XDEBUG profile you will need to first install WinCacheGrind which will allow you to view these cachegrind.out files.

Once you have downloaded WinCacheGrind, open the WinCacheGrind.exe. You should have a window similar to below:

WinCacheGrind Application

Go to File > Open and open the cachegrind.out file for the XDEBUG profile you would like to look into.

Once you have opened your cachegrind.out file you will see a trace semi similar to this. Please keep in mind all PHP applications are different and will look different. This example is of a Laravel application so you will see lots of calls to the Illuminate library namespace.

WinCacheGrind XDEBUG Profile cachegrind.out.42

Here we can see that the commulative time is 1,536ms and the {main} function has a time of 1,536ms. Double click the parent call until we get to something within the application or something of concern.

With Laravel we want to get to the point where we have a call similar to runController.

WinCacheGrind XDEBUG Profile cachegrind.out.42 drill down

In this image we can see that we have gotten to our controller called App\Http\Controllers\SlowResponse and we are looking at the function index. Now for this example I made it easy to spot what the issue was and why it this call took a while. On the right, we can that php::sleep function was called and spent 1,500ms on itself. This is obviously something that would cause a response delay.

This next example we are going to take a look at something a little differently.

WinCacheGrind XDEBUG Profile cachegrind.out.39

In this example the cumulative time is around 705ms. Doesn’t seem like a lot but could be for some customers.

When we drill down to our application code we could see the following happening.

WinCacheGrind XDEBUG Profile cachegrind.out.39 drill down

Coming down to the controller itself, we could see that the function took about 527ms of the 705ms for the total request. Here we see that we are still calling into the controller App\Http\Controllers\SlowResponse, however we are now calling a different function called slowNested. In this function we can further go down into the goIntoSlowFunction function. Look at all those database insert calls. The majority of the time being used up here is on database executions. In this example, optimizing the database calls might help with performance in the application.

Conclusion

Although all applications are different, viewing the XDEBUG profiles are typically the same. When it comes to slow requests, we want to see where most of the time in the request is being taken place. This will better help us understand what to look for and how to go about looking at these traces.