Utilisation du débogueur PHP Xdebug

Image non disponible

Xdebug est une extension PHP que l'on ne présente plus. Débogueur, profileur (analyse de performances), analyseur de couverture ainsi que fonctionnalités avancées d'introspection PHP, sont autant de possibilités offertes par cette extension entièrement gratuite sous une licence compatible avec celle de PHP. En un mot : un indispensable. 2 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Xdebug a fait ses preuves : une maturité de quasiment dix ans (PHP4.0), des sorties de version souvent et surtout, une aide au débogage de scripts PHP très appréciée.
Nous allons présenter ici cette extension, de son installation à ses fonctionnalités classiques ou bien plus avancées. Nous tenterons de montrer en quoi il s'agit d'un indispensable en terme de développement PHP.

II. Installation

Cet article suppose PHP 5.3.x. Attention, sous PHP 5.3.x, seules les versions 2.1.x de Xdebug sont compatibles. Les versions 2.0.x peuvent s'installer parfois, mais elles fonctionneront mal ou planteront.
Xdebug s'installe presque comme toute extension PHP. La différence principale réside dans le fait qu'il s'agit d'une zend_extension, et non d'une extension.
Vous devrez donc charger son .so dans php.ini sous la forme zend_extension=/chemin/vers/xdebug.so. Notez aussi que les zend_extension vous obligent à passer le chemin complet vers le fichier so, et non pas le chemin relatif à extension_dir comme avec les extensions classiques.

Téléchargement et installation de Xdebug
Sélectionnez

> pecl install xdebug
> vi {chemin-vers-php.ini}
Editer php.ini pour activer xdebug
Sélectionnez

zend_extension = chemin/complet/depuis/la/racine/vers/xdebug.so

; Vérifiez aussi l'absence d'une éventuelle ligne extension=xdebug.so

Xdebug devrait être installé. Nous allons vérifier cela :

Vérification de l'installation de Xdebug
Sélectionnez

> php --ri xdebug

Si la ligne vous indique que xdebug n'est pas installé, activez display_startup_errors dans php.ini pour plus d'informations. Lisez aussi les logs éventuels.

Vous pouvez aussi utiliser un gestionnaire de paquets type apt ou aptitude. Vérifiez simplement la version de Xdebug, à ce jour on trouve encore des 2.0.x dans certains dépôts...

Conflits d'extensions : Attention, XDebug entre souvent en conflit avec d'autres débogueurs présents dans PHP. On pense souvent à ZendDebugger. N'activez jamais les deux en même temps.
Notez aussi qu'il est impossible de charger une zend_extension au moyen de la fonction PHP dl().

Performances : XDebug a un impact non négligeable sur les performances globales de PHP. N'installez jamais XDebug sur un serveur de production. Plus d'informations ici.

Mon article sur la compilation de PHP pourra vous éclairer sur l'outil pecl et le fonctionnement des extensions.

III. Fonctionnalités

Nous allons ici passer en revue une partie des fonctionnalités qu'offre XDebug. La plupart d'entre elles s'activent et/ou se configurent grâce à des options dans php.ini.
Nous essayerons autant que possible de ne pas recopier la documentation officielle de Xdebug déjà tellement bien écrite...
Nous supposons pour la suite l'utilisation d'une SAPI Web de PHP, c'est-à-dire typiquement lié à un serveur web et non pas utilisé en ligne de commandes (développement web).

III-A. Fonctionnalités générales

Parmi les fonctionnalités dites "générales", on pourra déja citer la redéfinition de la fonction var_dump() de PHP. Si xdebug.overload_var_dump est à 1 dans php.ini (cas par défaut) ; Xdebug remplace la fonction var_dump() de PHP par sa propre implémentation qui possède l'énorme avantage de présenter les données dans un HTML permettant leur parfaite visualisation.

var_dump() avec Xdebug activé
Sélectionnez
<?php
var_dump(array('foo', array(23, array(new DirectoryIterator(sys_get_temp_dir())))));
Image non disponible
var_dump() avec Xdebug

Si vous lisez la source HTML, vous remarquez la production d'un code similaire à celui-ci :

Code source généré par var_dump() de Xdebug
Sélectionnez

<pre class='xdebug-var-dump' dir='ltr'>
<b>array</b>
  0 <font color='#888a85'>=&gt;</font> <small>string</small> <font color='#cc0000'>'foo'</font> <i>(length=3)</i>
  1 <font color='#888a85'>=&gt;</font> 
    <b>array</b>

      0 <font color='#888a85'>=&gt;</font> <small>int</small> <font color='#4e9a06'>23</font>
      1 <font color='#888a85'>=&gt;</font> 
        <b>array</b>
          0 <font color='#888a85'>=&gt;</font> 
            <b>object</b>(<i>DirectoryIterator</i>)[<i>1</i>]

</pre>

C'est très pratique car la sortie de var_dump() est souvent très complexe, avec une donnée (souvent un objet) très lourde et comportant beaucoup d'enfants. Ca devient vite illisible avec PHP "classique".
On peut personnaliser cet affichage :

Paramètres php.ini relatifs au formatage de la sortie de var_dump()
  1. xdebug.var_display_max_children le nombre de clés de tableau ou d'attributs d'objets à afficher ;
  2. xdebug.var_display_max_data règle la taille en octets des chaines de caractères affichées. Au-delà, elles seront tronquées avec des '...' ;
  3. xdebug.var_display_max_depth la profondeur à afficher pour les tableaux/objets.

Je trouve personnellement que les valeurs par défaut de ces paramètres sont trop basses, j'ai tendance à les augmenter au prix d'un temps d'affichage plus long de mes variables très complexes.

Autre paramètre intéressant, la désactivation du caractère de suppression d'erreurs en PHP : '@'. Le paramètre xdebug.scream de php.ini (par défaut sur off) permet de faire en sorte que PHP ignore totalement les '@' éventuellement utilisés dans les scripts. Ils sont souvent considérés comme des mauvaises pratiques, et on en abuse aussi assez souvent.

Sachez aussi que tout message d'erreur concernant une erreur ou une exception PHP va être entièrement réécrit. Grâce à xdebug.collect_params mis à la valeur 4, vous avez un affichage très précis des variables qui ont été passées à la fonction courante :

Générons une erreur manifeste due à une exception non attrapée
Sélectionnez
<?php
function foo($a, $b= 3) {
    throw new Exception('Boom');
}
foo("Foobarbaz", 456);
Image non disponible
Une erreur, avec xdebug.collect_params=4

On voit nettement les paramètres qui ont été passés à la fonction courante, ainsi que leur valeur.

Il est aussi possible dans le cas d'appels successifs de fonctions/méthodes, de savoir à partir d'une fonction précise quelle est la fonction qui l'a appelée.
On peut aussi savoir cela en PHP au moyen de la fonction debug_backtrace(), mais l'on n'a pas accès de manière directe à cette information. Xdebug propose (entre autres) xdebug_call_function().
Dans le même genre, xdebug_get_stack_depth() donne la profondeur de la pile d'appel, c'est équivalent à un count(debug_backtrace()) à une unité près : xdebug compte le script principal ("main") avec :

Qui a invoqué cette fonction-là ?
Sélectionnez
<?php
function bar(array $arg) {
    echo xdebug_call_function(); // Affichera "foo" dans notre exemple
}

function foo($a, $b= 3) {
    bar(array($a));
}

foo("Foobarbaz", 456);

Nous verrons le profilage dans un chapitre ultérieur, simplement, lorsqu'on veut savoir combien de temps tel script PHP a mis à s'exécuter, on a souvent recours à un calcul avec microtime().
Là encore, xdebug_time_index() évite ce calcul et simplifie/accélère un peu les choses.

Mesurer le temps d'exécution d'un script en PHP
Sélectionnez
<?php
$time = microtime(1);

function make_a_break($seconds) {
    sleep($seconds);
}

make_a_break(3);
printf("Durée : %f", microtime(1) - $time); // Affiche (environ) Durée : 3.000518
Mesurer le temps d'exécution d'un script en PHP avec Xdebug
Sélectionnez
<?php
function make_a_break($seconds) {
    sleep($seconds);
}

make_a_break(3);
printf("Durée : %f", xdebug_time_index()); // Affiche (environ) Durée : 3.000518

Enfin, xdebug_debug_zval() peut être intéressant pour tracer les références et les symboles attachés à une variable donnée :

Tracer les symboles PHP liés à des variables, ainsi que les références éventuelles
Sélectionnez
<?php
$a = array("foo"=>"bar", 1 => 82);
$c = &$a[1];

xdebug_debug_zval('c'); // Affiche c: (refcount=2, is_ref=1),int 82

Pour comprendre à quoi sert xdebug_debug_zval() précisément, reportez-vous à Maitrise de la gestion des variables en PHP

III-B. Débogage

Voilà la partie qui intéresse le plus je pense : la capacité de déboguer pas à pas un script PHP. C'est possible via Xdebug et un serveur de débogage. En effet, la communication entre le client de débogage (Xdebug) et le serveur de débogage (n'importe lequel, mais on utilisera dans cet article "Eclipse PDT") se fait au moyen d'un protocole clairement défini dans sa documentation officielle. Ainsi, c'est à vous de choisir un serveur de débogage convenable, qui implémente correctement le protocole de débogage appelé "Dbgp". Nous utiliserons Eclipse PDT dans cet article, qui est connu pour avoir des bogues au niveau de son implémentation du protocole. Plus votre version sera à jour, mieux ce sera (Ca fonctionne très bien dans les dernières versions).

Le protocole est "human readable", majoritairement basé sur XML, et peut donc être intercepté en reniflant le trafic TCP sur le port considéré. Ceci est très pratique pour se renseigner ou "déboguer" le... débogueur !? Nous reverrons cela.
Il existe d'ailleurs à cet effet un serveur de débogage en ligne de commandes fourni avec l'extension Xdebug. Nous ne le verrons pas.
Pour les IDE, PHPStorm ou encore NetBeans fonctionnent très bien.

III-B-1. Client de débogage

On appelle client de débogage, la machine sur laquelle s'exécute PHP avec Xdebug. PHP peut fonctionner en ligne de commandes ou dans un serveur Web, nous nous baserons majoritairement sur l'utilisation avec un serveur Web. Il convient de configurer :

  1. la manière dont le client de débogage indiquera au serveur de débogage qu'une session de débogage a commencé, la connection entre les deux parties ;
  2. l'adresse IP et le port qu'écoute le serveur de débogage en attente de connexion pour une session de débogage (communication TCP).

Concernant la manière dont le client se connectera au serveur de débogage, il est recommandé d'utiliser "req", sinon le client de débogage n'entamera une connexion vers le serveur que lorsqu'une erreur PHP survient.
Tous ces paramètres se règlent via le php.ini :

Une configuration classique pour le client de débogage Xdebug
Sélectionnez

xdebug.remote_autostart     Off
xdebug.remote_connect_back  Off
xdebug.remote_enable    On
xdebug.remote_handler   dbgp
xdebug.remote_host     127.0.0.1
xdebug.remote_log      /tmp/xdebug.log
xdebug.remote_mode req
xdebug.remote_port 9000

Je ne vais pas répéter la doc officielle, la plupart des paramètres présentés ci-dessus ont comme valeur celle par défaut. Simplement, il faut bien noter ces points-là :

  1. xdebug.remote_autostart indique de ne pas déboguer toutes les pages (off), mais uniquement celles dont on en fera la demande explicitement (on va y revenir) ;
  2. xdebug.remote_enable active le serveur de débogage (on), si la valeur off est donnée, aucun débogage ne sera jamais possible ;
  3. xdebug.remote_handler indique quel protocole de débogage utiliser, dbgp dans notre cas (recommandé) ;
  4. xdebug.remote_host indique l'adresse IP du serveur qui recevra la requête de connexion pour le débogage ;
  5. xdebug.remote_mode indique que le débogueur doit fonctionner systématiquement (req), et non juste lorsqu'une erreur PHP est levée ;
  6. xdebug.remote_port indique le port sur le lequel le remote_host écoute pour accepter une session de débogage.

Si plusieurs serveurs (IDE) doivent pouvoir recevoir une session de débogage, il n'est pas possible de préciser plusieurs adresses IP dans remote_host. A ce moment deux solutions existent : utiliser un proxy de débogage qui distribuera les requêtes aux différents serveurs (voir en doc officielle), ou alors jouer avec xdebug.remote_connect_back qui indique au client que le serveur de débogage est celui qui l'a interrogé, et non pas celui indiqué dans remote_host (il renifle alors $_SERVER['REMOTE_ADDR'] pour trouver l'IP du serveur).

C'est tout :). C'est assez simple en réalité. Gardez en tête que nous avons dit au client de débogage (l'appli PHP) qu'un seul serveur fixe (un IDE) attendait des sessions de débogage, et que (autostart=off) nous allions nous-même indiquer au client quand nous souhaitons qu'il initie une session de débogage vers ce serveur-là. C'est ce point qui est important.

Pour demander au client de démarrer une session de débogage, il conviendra de naviguer avec un cookie du nom de XDEBUG_SESSION_START ayant comme valeur le nom de la session de débogage (un nom quelconque en réalité).
Ce n'est qu'en recevant ce cookie de la part d'un client web quelconque (n'importe quel navigateur) que Xdebug initiera alors une connection vers le serveur de débogage (qui n'est pas forcément le même que le client web ayant initialisé la requête, gardez bien ceci en tête, même si c'est très souvent le cas).
Comme il peut être pénible de créer le cookie à la main ou d'ajouter un paramètre GET ou POST XDEBUG_SESSION_START=session_name dans ses URL, FireFox et Chrome proposent une extension rajoutant automatiquement le cookie dans la requête par la simple pression d'un bouton dans l'interface graphique, c'est très pratique pour le débogage quotidien, je la recommande vivement ("EasyXdebug" ou "Xdebug Helper").

III-B-2. Serveur de débogage

Le serveur de débogage est le programme qui possède une adresse IP et écoute sur un port TCP en attente de l'initialisation de la session de débogage déclenchée côté client. Ce programme est souvent un IDE, et il est souvent présent sur la machine possédant le client HTTP initialisant la session de débogage. Pour configurer ce serveur, il faut simplement faire en sorte qu'il crée une socket en écoute, ainsi il faut indiquer :

  1. sur quel port il doit écouter et attendre les sessions de débogage en provenance du client ;
  2. ce qu'il doit faire lorsqu'il reçoit une session (l'ignorer, l'accepter, demander via l'interface graphique ce que l'utilisateur veut faire).

Concernant EclipsePDT ou ZendStudio, ceci se paramètre de la manière suivante : "Window"-"Preference"-"PHP"-"Debug", choisissez bien Xdebug et non pas ZendDebugger. Concernant "Server" et "PHP Executable", ça n'a aucune importance pour nos exemples.

Image non disponible
Choisir Xdebug comme débogueur dans Eclipse

Cliquez ensuite sur "Configure" à droite, puis selectionnez Xdebug et recliquez sur le bouton "Configure" à droite. Ce panneau apparaît :

Image non disponible
Configurer le client de débogage Xdebug d'Eclipse

Indiquez le port d'écoute, il doit correspondre au port indiqué dans xdebug.remote_port. Indiquez absolument "Accept remote session JIT" à "Any", en tout cas pas à "Never". Ce paramètre indique l'action à suivre lorsque le serveur dans Eclipse reçoit une initialisation de session de débogage sur son port d'écoute. Il est par défaut configuré pour l'ignorer ("Never"). Ne cochez pas "Use Proxy", c'est utilisé pour le débogage avec de multiples clients (voir doc officielle).

C'est prêt ! Vérifions que le port est bien en écoute :

Vérification de l'écoute du port par le client de débogage
Sélectionnez

> netstat -taupen | grep 9000

; Affiche quelque chose comme :

tcp6 0  0 :::9000  :::*  LISTEN  1000  209008  4793/java

O.K. c'est tout bon, il n'y a plus qu'à essayer tout ça. Nous allons utiliser Firefox et l'extension EasyXdebug. Le principe est alors très simple :

  1. surfer sur la page à déboguer, pour en afficher le résultat ou l'erreur PHP éventuellement ;
  2. ouvrir la page en question dans Eclipse, ou son projet. C'est important, ça ne fonctionnera pas sinon ;
  3. activer le débogage via EasyXdebug (le cookie magique sera alors envoyé dans les prochaines requêtes) ;
  4. rafraichir la page : le cookie est envoyé, la session débogage commence, Xdebug contacte le serveur (Eclipse ici) qui vous fait signe qu'il a vu et accepté une connection de débogage ;
  5. déboguer (pas suivant, pas d'entrée, point d'arrêt, suite, fin) ;
  6. le cas échéant, ne pas oublier de désactiver EasyXdebug sinon toute future requête déclenchera le débogueur.
Image non disponible
EasyXdebug n'est pas activé
Image non disponible
EasyXdebug est activé
Image non disponible
Une session de débogage en cours

N'oubliez pas que votre navigateur Web reste en attente pendant la session de débogage. Il est normal qu'il semble bloqué, puisque le serveur de débogage a pris la main : terminez la session de débogage pour rendre la main au navigateur.

Il est aussi possible de lancer la session de débogage depuis Eclipse directement (menus "debug" ou "run"). C'est simplement plus pénible, moins intuitif, et très galère lorsqu'il s'agit de déboguer avec le contexte client HTTP : la session, les cookies, les données POST... Passer par le navigateur web offre cet énorme avantage d'interagir avec une requête web forgée par le navigateur lui-même et non créée main.

Sous NetBeans ou PHPStorm, le principe reste le même : indiquer sur quel port écouter et lancer la socket en écoute. C'est souvent des boutons à cliquer dans l'interface graphique.
Vérifiez bien avec un netstat que le serveur est prêt à accepter des connection (état "LISTEN").

III-B-3. Introspection dans les commandes de débogage

En utilisant tcpdump, on peut voir le trafic que le client de débogage échange avec son serveur. C'est un vrai protocole de communication, basé sur XML :

Echanges entre le client de débogage et le serveur de débogage, protocole dbgp
Sélectionnez

> tcpdump 'tcp port 9000' -A -i lo

<?xml version="1.0" encoding="iso-8859-1"?>
<init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" fileuri="file:///media/www/labo/xdebug.php" language="PHP"
protocol_version="1.0" appid="1857" idekey="netbeans-xdebug">
<engine version="2.1.0"><![CDATA[Xdebug></engine><author><![CDATA[Derick Rethans></author><url><![CDATA[http://xdebug.org>
</url><copyright><![CDATA[Copyright (c) 2002-2010 by Derick Rethans></copyright></init>

<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="feature_set" transaction_id="1"
feature="show_hidden" success="1"></response>

<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="step_over" transaction_id="13" status="break" reason="ok">
<xdebug:message filename="file:///media/www/labo/xdebug.php" lineno="3"></xdebug:message></response>
...

Finalement, le débogage est à la portée de tous. Un serveur, un(des) client(s), une socket, un protocole... Rien de bien sorcier ou de bien compliqué, surtout que j'insiste sur le fait, tout est documenté et le protocole est ouvert.

III-C. Profilage

Le profilage de code permet d'analyser toutes les instructions d'un programme en vue de trouver les instructions les plus couteuses en ressources et notamment en temps d'exécution.
Xdebug permet de profiler le code PHP à la recherche des fonctions "lentes" en vue d'optimiser les algorithmes. De manière générale, le profiling avec Xdebug va permettre de renseigner sur :

  1. l'ensemble des fonctions (utilisateur ou PHP) appelées dans un script ;
  2. le nombre de fois que chaque fonction individuelle a été appelée ;
  3. le temps d'exécution de chaque fonction ;
  4. la trace des appels : "qui appelle qui ?".

Le tout sera représenté visuellement et graphiquement. Xdebug va générer des traces dans un fichier dans un format (texte, donc lisible) compatible avec celui de KCacheGrind.
C'est le logiciel qui sera utilisé pour avoir une représentation visuelle, d'autres logiciels existent (Wincachegrind, Webcachegrind... ), voyez la documentation pour plus d'infos.

Commençons par activer le profileur. Il est déconseillé de l'activer sans cesse, comme pour le débogage, il sera beaucoup plus pratique de le déclencher suivant le contexte (un cookie HTTP) :

Configuration du profileur
Sélectionnez

xdebug.profiler_enable =         0
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir =     /tmp

Ici, le profileur écrira un fichier de trace dans /tmp lorsque le serveur recevra un paramètre GET, POST ou un cookie XDEBUG_PROFILE. C'est exactement ce que fait l'extension EasyXdebug, mais il s'agit cette fois-ci de la petite icône carrée et non plus du petit bug. Comme d'habitude : rouge=activé, vert=désactivé.
Dès qu'il est activé, on rafraichit la page, et la trace est collectée dans le dossier précisé. Son nom dépend du PID du programme lancé, vous pouvez le personnaliser au moyen d'options dans php.ini.

Attention, générer une trace de profilage peut devenir une tâche très gourmande en ressources. N'oubliez pas d'éteindre le profileur si vous n'avez pas besoin de générer de trace.
N'oubliez pas que les temps analysés ne seront pas représentatifs du tout de temps réels, mais toutes les proportions vont être conservées, c'est ce qui va nous intéresser. Xdebug doit tracer dans PHP chaque entrée et sortie de fonction, c'est très lourd et très lent. Ainsi, il peut vous indiquer des temps en secondes, alors qu'en fait il s'agit de milisecondes, ce n'est pas grave, ce sont les proportions qui interessent, pas les temps bruts calculés.

Code que nous allons profiler
Sélectionnez
<?php
function display_data(array $data)
{
    $buf = '';
    foreach ($data as $k=>$v) {
        $buf .= sprintf("%s: %s \n", $k, $v);
    }
    return $buf;
}

$p = new PDO('mysql:host=127.0.0.1;dbname=dvp', 'julien', 'dvppassword');

if (isset($_GET['id'])) {
    $stmt  = $p->query(sprintf("SELECT * FROM membres WHERE num=%3d", $_GET['id']));
    $infos = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$infos) {
        printf("Utilisateur inconnu");
    } else {
        printf("<p>Bienvenue %s, Voici vos informations:</p><pre>%s</pre>", ucfirst(strtolower($infos['nom'])), nl2br(display_data($infos)));
    }
} else {
    echo 'Saisissez votre identifiant';
}

Bon, quelque chose de simple et concis. Lançons cette page dans FireFox, avec EasyXdebug et en activant le profilage (icône du petit carré en rouge). Nous supposons bien sûr une base de données présente, et pour nos essais nous passerons évidemment une valeur de 'id' donnant lieu à des résultats côté base de données.
Dès lors, une trace est apparue, dans /tmp (nous avions précisé ce dossier). Nous pouvons l'ouvrir au moyen de KCacheGrind.

Image non disponible
Résultat de profil KCacheGrind

La documentation de XDebug explique comment lire KCacheGrind. Ce n'est pas complexe du tout. Nous allons d'abord procéder à certains réglages.
KCacheGrind limite le nombre de fonctions qu'il affiche par défaut. Je conseille de monter cette limite au maximum possible (499) via le paramètre "Maximum number of items in list".
Ensuite, dans le Call Graph, je conseille aussi d'être plus large sur les limites. Faites un clic droit dans la fenêtre et passez "Caller Depth" et "Callee Depth" sur "unlimited". Puis enfin, passez les "Min node Cost" et "Min Call Cost" sur les valeurs les plus basses.

Le temps, est indiqué en microsecondes. Attention, n'oubliez pas qu'il n'est pas représentatif car le coût de XDebug est non négligeable dans son calcul. Si vous cochez l'option pourcentage, vous aurez le temps en pourcentage (main représentera donc 100%) et si vous cochez en plus "relative to parent", le pourcentage sera relatif à la fonction parente et non pris globalement.

Image non disponible
Temps en microsecondes
Image non disponible
Temps en pourcentage

La colonne de gauche renseigne sur les temps de chaque fonction, avec ou sans leurs enfants ('self'). On apprend ainsi que display_data() a pris 17.3% du temps total, mais elle-même (son code propre) n'a pris que 15.64%, les 1.6% d'écart ont été consommés par ses enfants, il n'y en a qu'un seul ici : printf().
"php::" signifie "fonction interne à PHP", et comme on peut le voir, printf(), nl2br(), ucfirst() ont des temps très négligeables par rapport au reste.

Le reste justement, parle de lui-même : la création d'un objet PDO a compté pour 41% du temps, quasiment la moitié. Normal, lorsqu'on sait que le constructeur de PDO va établir une connexion physique à la base, on voit maintenant très clairement que ceci est très consommateur (surtout sur un tout petit script comme celui-là).
L'exécution de la requête a pris 14.7% du temps, c'est un tout petit peu moins que la fonction display_data(), en général ce n'est pas le cas et les requêtes SQL (beaucoup plus complexes) souvent mal optimisées consomment beaucoup de temps.

Optimiser tout ça va être aussi simple que concis : inutile de faire appel à la pile TCP/IP lors d'une connexion sur l'hôte local, utilisons plutôt des sockets Unix moins gourmandes. Aussi, nous pourrions basculer la connexion en persistante, histoire de ne pas la renégocier à chaque appel.
Changeons, et profilons :

La seule ligne qui change dans le nouveau code
Sélectionnez
<?php
//...
$p = new PDO('mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=dvp', 'julien', 'dvppassword', array(PDO::ATTR_PERSISTENT=>true));
//...
Image non disponible
Nouveau graphe de profilage après nos modifs

C'est mieux : nous utilisions avant 1.81ms, maintenant on est à 1.57ms. La fonction incriminée (PDO::__construct()) a baissé de 747ms à 590ms. Encore une fois, ne notez pas ce temps comme étant absolu, ce qu'il faut noter, c'est qu'avec nos changements, on a gagné 14% de temps global. Ce ratio sera à très peu de chose près conservé sur le script final en production, en revanche les temps propres (réels) seront différents (absence de Xdebug qui fausse les temps, machine plus robuste en production, etc.).
Prochaine étape d'optimisation : mettre en cache le résultat de la base (dans un support rapide, tel que Memcache ou un système de fichiers si celui-ci n'est pas trop chargé) ; on a en tout cas ici l'outil qui permettra de nous indiquer si on est sur la bonne voie ou pas.

Rappel : Le profilage permet de cibler précisément le problème dans un script, et de quantifier les apports des corrections éventuelles apportées. Cela permet de savoir sur quelle partie de code travailler, et de mesurer le résultat de son travail.
Une bonne connaissance de l'architecture interne de PHP, du réseau et de Linux de manière générale, permettra de savoir quels correctifs apporter face à telle ou telle problématique.

On mesure ici le temps que met PHP à éxecuter ses instructions. S'il doit attendre une base de données qui calcule des milliards d'enregistrement, ce n'est pas sa faute s'il est lent.
De la même manière, s'il attends l'OS et particulièrement le système de fichiers, ce n'est pas sa faute aussi.
Enfin, le temps d'exécution du code PHP brut est souvent négligeable face aux temps de latence du réseau, à la charge sur la base de données ou encore aux I/O disque de la machine serveur.

III-D. Conclusions

Xdebug est un indispensable, un standard - gratuit mûr et efficace - que tout développeur doit connaitre et maitriser. Xdebug peut aussi servir pour l'analyse de la couverture de code lors de tests unitaires, PHPUnit l'utilise dans ce but d'ailleurs. N'oubliez pas de veiller : Xdebug évolue et gagne en fonctionnalités à chaque nouvelle version. Son manuel est à l'image de PHP : très bien fait, pratique et agréable.

Remerciements à ClaudeLELOUP pour la relecture.
Maitrise de la gestion des variables en PHP
Le développement piloté par les tests avec PHPUnit

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2011 . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.