Icono del sitio Shakaran

Depurar y perfilar funciones PHP gráficamente con XDebug, KCacheGrind y Webgrind en Ubuntu 11.10


El arte de depurar y perfilar con PHP

En desarrollo web y por lo general, en cualquier aplicación que requiera tener una demanda escalable, son muy importantes los algoritmos empleados, las llamadas a funciones nativas y las estructuras de datos utilizadas.

En aplicaciones PHP con miles de líneas de código se necesita un diseño eficiente para poder manejar una buena ejecución del código PHP.

Principalmente se busca reducir el consumo de memoria y CPU al máximo posible, sin que la aplicación pierda funcionalidad, ni características. Para lograr este objetivo y que la aplicación sea escalable, se busca utilizar buenas herramientas de depuración y profiling (perfilado de rendimiento). Con ello se consigue que el programador o ingeniero disponga de una buena perspectiva de lo que esta haciendo a bajo nivel su aplicación y decidir en consecuencia que técnica aplicar.

El perfilado o medición del rendimiento (performance profiling) permite ejecutar código en un entorno controlado y devolver un listado con estadísticas de tiempo gastado en cada funcion, longitud de consultas a la base de datos o la cantidad de memoria que ha sido usada. De esta forma, se puede desechar codígo lento o superfluo de una manera muy rápida o incluso realizar pequeños trucos en partes críticas para ganar más rendimiento.

Herramientas

En PHP existen varias herramientas de depuración(debugging) y localización de cuellos de botella(bottleneck) para perfilado de rendimiento.

Las herramientas más utilizadas son:

Instalación y configuración de Xdebug

Puesto que Xdebug ofrece las mejores características y facilidades, es la opción preferida, ademas la instalación y configuración en Ubuntu 11.10 es bastante sencilla.

Se pueden bajar los f uentes y compilarlos, pero resulta más cómodo bajar del repositorio PEAR la aplicación e instalarla.

Para instalar el paquete PEAR, debemos tener previamente PEAR instalado:

$ sudo apt-get install php-pear php5-dev

Despues, instalamos Xdebug a través de PECL

$ sudo pecl install xdebug

Una vez finalizada la instalación, podemos encontrar la biblioteca compartida compilada en /usr/lib/php5/20090626+lfs/xdebug.so

Pasamos a configurar el php.ini para que cargue Xdebug. Por defecto Ubuntu 11.10 tiene varios php.ini, como son /etc/php5/cli/php.ini/etc/php5/apache2/php.ini. Este último es que se utiliza para la configuración global.

Pasamos a modo root:

$ sudo su -

Añadimos Xdebug al php.ini cargándolo como extensión zend y con el path completo, en nuestro caso el path es /usr/lib/php5/20090626+lfs/xdebug.so

# echo 'zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so"' >> /etc/php5/apache2/php.ini

Después lo cargamos como extensión PHP:

# echo "extension=xdebug.so" >> /etc/php5/apache2/php.ini

Para comprobar que ha sido cargado, podemos mirar los módulos cargados de PHP:

# php -m | grep Xdebug
Xdebug

O bien en la información general:

# php -i | grep Xdebug
with Xdebug v2.1.2, Copyright (c) 2002-2011, by Derick Rethans

Si filtramos en minúsculas para la información general, podemos ver todas las opción de configuración:

# php -i | grep xdebug
xdebug
xdebug support => enabled
xdebug.auto_trace => Off => Off
xdebug.collect_assignments => Off => Off
xdebug.collect_includes => On => On
xdebug.collect_params => 0 => 0
xdebug.collect_return => Off => Off
xdebug.collect_vars => Off => Off
xdebug.default_enable => On => On
xdebug.dump.COOKIE => no value => no value
xdebug.dump.ENV => no value => no value
xdebug.dump.FILES => no value => no value
xdebug.dump.GET => no value => no value
xdebug.dump.POST => no value => no value
xdebug.dump.REQUEST => no value => no value
xdebug.dump.SERVER => no value => no value
xdebug.dump.SESSION => no value => no value
xdebug.dump_globals => On => On
xdebug.dump_once => On => On
xdebug.dump_undefined => Off => Off
xdebug.extended_info => On => On
xdebug.file_link_format => no value => no value
xdebug.idekey => root => no value
xdebug.manual_url => http://www.php.net => http://www.php.net
xdebug.max_nesting_level => 100 => 100
xdebug.overload_var_dump => On => On
xdebug.profiler_aggregate => Off => Off
xdebug.profiler_append => Off => Off
xdebug.profiler_enable => On => On
xdebug.profiler_enable_trigger => On => On
xdebug.profiler_output_dir => /var/www/webgrind/tmp => /var/www/webgrind/tmp
xdebug.profiler_output_name => cachegrind.out.%t.%p => cachegrind.out.%t.%p
xdebug.remote_autostart => Off => Off
xdebug.remote_connect_back => Off => Off
xdebug.remote_cookie_expire_time => 3600 => 3600
xdebug.remote_enable => On => On
xdebug.remote_handler => dbgp => dbgp
xdebug.remote_host => 127.0.0.1 => 127.0.0.1
xdebug.remote_log => no value => no value
xdebug.remote_mode => req => req
xdebug.remote_port => 9000 => 9000
xdebug.scream => Off => Off
xdebug.show_exception_trace => Off => Off
xdebug.show_local_vars => Off => Off
xdebug.show_mem_delta => Off => Off
xdebug.trace_format => 0 => 0
xdebug.trace_options => 0 => 0
xdebug.trace_output_dir => /tmp => /tmp
xdebug.trace_output_name => trace.%c => trace.%c
xdebug.var_display_max_children => 128 => 128
xdebug.var_display_max_data => 512 => 512
xdebug.var_display_max_depth => 3 => 3

Activando configuraciones en Xdebug

Para generar información de depuración, debemos activar como mínimo las siguientes configuraciones de la familia xdebug.profiler_*:

La configuración mínima recomendada que resultaría al final del php.ini quedaría como:

zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so
extension=xdebug.so

[xdebug]
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir = "/var/www/webgrind/tmp"
xdebug.profiler_output_name = cachegrind.out.%t.%p

Si queremos activar ademas la depuración remota en un puerto y máquina (o bien en la misma), usaremos la familia de configuraciones xdebug.remote_*. Las mínimas recomendadas son:

Puedes ver más información en la introducción al depurado remoto de Xdebug.

Por lo que el resultado final sería:

zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so
extension=xdebug.so

[xdebug]
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir = "/var/www/webgrind/tmp"
xdebug.profiler_output_name = cachegrind.out.%t.%p
xdebug.remote_enable=true
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.remote_handler=dbgp

Instalando KCacheGrind

KCacheGrind es un programa de escritorio (en especial para diseñado para KDE, pero compatible para GNOME) escrito para visualizar los archivos de depuración generados por xdebug. Es un frontend de Callgrind, que a su vez usa en tiempo de ejecución al framework de Valgrind para simulación de caches y generación de las llamadas gráficas. Un aspecto a recalcar es que incluso las bibliotecas compartidas que son abiertas dinamicamente son perfiladas. Podemos abrir con el archivos de depuración con formato Callgrind generados con xdebug de forma bastante rápida.

Para instalarlo simplemente:

$ sudo apt-get install kcachegrind

Por ejemplo podemos crear un archivo con la información de PHP para ver un ejemplo sencillo de depuración y ejecutarlo para que se genere un archivo cachegrind.out, que podamos abrir con KCacheGrind:

# echo "<?php phpinfo(); ?>" >> /var/www/phpinfo.php

Como resultado, obtendríamos un grafo y los tiempos de ejecución globales y relativos implicados:

Ejemplo de depuración de phpinfo() con KCacheGrind

Es una aplicación bastante notable, pero en su lugar es más recomendable usar Webgrind como frontend web, que veremos a continuación.

Instalar webgrind

Webgrind es un perfilador gráfico de PHP via web escrito en PHP por Joakim Nygård y Jacob Oettinger. Tiene como dependencia Xdebug ya que es en realidad un wrapper web no completo de las funciones de KCacheGrind como reemplazo de frontend web de xdebug (existe incluso una proposición de Google Summer of Code aceptada por Chung-Yang Lee).

Su apareciencia es simple y agradable para depurar aplicaciones PHP. Es multiplataforma (al ser vía web) y permite seguimiento de los tiempos empleados por cada llamada de función, por coste de ellas misma o coste inclusivo (el coste inclusivo es el tiempo dentro de la función mas llamadas internas de esa función a otras).

También permite ver tiempo de llamadas internas y llamadas de usuario diciendo la llamada por la que fue invocada previamente.

Descargamos la aplicación de github, ya que en el momento de escribir este artículo están haciendo una migración de google code a github (también puedes hacer un checkout del repo, aunque puede ser inestable):

$ wget https://github.com/downloads/jokkedk/webgrind/webgrind-release-1.0.zip

Descomprimos:

$ unzip webgrind-release-1.0.zip

En ubuntu el DocumentRoot de Apache esta establecido por defecto en /var/www según /etc/apache2/sites-available/default

Luego, copiaremos los fuentes de webgrind y crearemos los archivos temporales que configuramos como directorio de salida en el perfilador de Xdebug:

$ sudo mv webgrind /var/www
$ sudo mkdir /var/www/webgrind/tmp
$ sudo chmod 777 /var/www/webgrind/tmp

Editamos el archivo de configuración e introducimos la ruta de directorio de salida de xdebug:

$ sudo vi /var/www/webgrind/config.php

Busca las variables $storageDir y $profilerDir y escribe:

static $storageDir = '/var/www/webgrind/tmp';
static $profilerDir = '/var/www/webgrind/tmp';

Nos aseguramos de todos los archivos tienen los permisos de grupo correctos de Apache:

$ sudo chown -R www-data.www-data /var/www/webgrind/

Si además queremos que no se creen archivos de depuración para el propio webgrind, creamos un archivo .htaccess en el directorio de webgrind desactivandolo:

php_flag xdebug.profiler_enable 0

Para entrar a la interfaz web de Webgrind, simplemente escribe en tu navegador http://localhost/webgrind/ y aparecerá disponible un listado de archivos cachegrind* donde podemos ver la traza y tiempos de depuración del perfilador:

Traza de depuración de phpinfo() con Webgrind

Salir de la versión móvil