Le code profiling, une technique pour améliorer les performances de votre PHP

le jeudi 1 octobre 2020

Bearstech | Dernière mise à jour : jeudi 1 octobre 2020

Le code profiling est une ressource essentielle pour l'optimisation de votre code PHP. Dans cette article, on vous explique comment améliorer la réactivité de vos applications en les analysant grâce au profiler XHProf couplé à XHGui.

Mème_les_experts

They say jump, you say how high

OK, votre code fonctionne sans erreur, répond au besoin, c'est le premier pas. Lao Tseu a dit : "D'abord ça marche, ensuite ça marche vite".

Donc, maintenant, il faut vous soucier des performances. L'expérience que va vivre votre utilisateur en naviguant sur votre site (ou en utilisant votre application), va dépendre entre autres des temps de réponses, aka la réactivité. La qualité de la connexion de l'utilisateur est souvent en dehors de votre scope, par contre, le temps de rendu de la page, c'est pour vous : une partie sera devant (HTML/CSS/JS), l'autre sera derrière, le temps de rendu de la page. D'autant plus qu'avec PHP (et toutes les technos synchrones), une page qui traîne, c'est un worker bloqué dans votre pool qui ne pourra pas servir d'autres clients.

Dans les performances, l'utilisation du CPU déterminera une partie du temps de réponse, mais il ne faut pas négliger la RAM, qui sera déterminante sur le nombre maximal de workers.

Anatomie

Mesurer le dehors

PHP-fpm va loguer gentiment les mesures de chaque appel et on peut ensuite ranger les logs en fonction des routes pour retrouver les contrôleurs. Avec ces mesures, on pourra affiner les réglages sur les limites mémoires des workers et commencer à discuter de ce qui est raisonnable. Pour des traitements coûteux en temps ou en mémoire, il faudra s'orienter vers du traitement asynchrone avec, par exemple, le concept de messager de Symfony, avec l'incontournable Redis.

Mesurer le dedans

Il est possible d'aller un cran plus loin dans la dissection d'une requête web et de savoir TOUT ce qu'il s'est passé.

Il y a longtemps, Facebook, grand mangeur de PHP, a écrit (et libéré) XHProf pour "profilage hiérarchique étendu". Bon, depuis, ils sont passés à autre chose, mais le module PHP est toujours maintenu et change de nom de temps en temps. La version contemporaine est maintenue par Tideways : php-tideways. L'approche est simple : le module s'accroche à zend_execute_ex et voit passer tous les appels de fonctions et il mesure tout ce qui passe. Simple, mais un peu brutal, les benchmarks parlent d'une baisse de 4% des performances.

XHProf a été conçu pour être utilisé sur un petit échantillon, on parle de 1 pour 1000, et pas sur l'ensemble des requêtes, ne serait-ce que pour le volume d'informations généré. Il est tout à fait possible de comparer deux profils, pour valider un changement ou surveiller une régression. XHProf, avec son approche bas niveau, permet de surveiller un code sans être intrusif : on rajoute un bout de code tout au début qui utilisera un register_shutdown_function tout à la fin pour exporter le profil généré. XHprof génère un profil en RAM, une variable PHP à sérialiser pour traiter ultérieurement.

Pour emballer tout ça, il existe un module composer : perftools/php-profiler, qui permet de configurer simplement le profiling, puis de téléverser le résultat vers un service qui permettra son analyse avec une jolie interface : XHGui.

Profiler son code

Pour essayer tout ça, je vais partir de la démo officielle de Symfony (le fleuron du PHP à la française).

composer create-project symfony/symfony-demo demo_xhprof

On va rajouter le module composer dans le projet :

composer require perftools/php-profiler

On peut maintenant modifier le index.php à partir de l'exemple de code fournit.

$config = array(
    // If defined, use specific profiler
    // otherwise use any profiler that's found
    'profiler' => \Xhgui\Profiler\Profiler::PROFILER_TIDEWAYS,

On utilise le PHP 7.3 fournit par le paquet Debian. Il faut donc utiliser la troisième réincarnation de XHProf : php-tideways.

    // This allows to configure, what profiling data to capture
    'profiler.flags' => array(
        \Xhgui\Profiler\ProfilingFlags::CPU,
        \Xhgui\Profiler\ProfilingFlags::MEMORY,
        \Xhgui\Profiler\ProfilingFlags::NO_BUILTINS,
        \Xhgui\Profiler\ProfilingFlags::NO_SPANS,
    ),

On peut choisir ce que l'on mesure. Là, j'ai ajouté la RAM pour faciliter le dimensionnement de mon application.

    // Saver to use.
    // Please note that 'pdo' and 'mongo' savers are deprecated
    // Prefer 'upload' or 'file' saver.
    'save.handler' => \Xhgui\Profiler\Profiler::SAVER_UPLOAD,

Le profil sera envoyé en HTTP vers une instance d'XHGui.

    // Saving profile data by upload is only recommended with HTTPS
    // endpoints that have IP whitelists applied.
    'save.handler.upload' => array(
        'uri' => $_SERVER['XHGUI_URI'],
        // The timeout option is in seconds and defaults to 3 if unspecified.
        'timeout' => 3,
        // the token must match 'upload.token' config in XHGui
        'token' => $_SERVER['XHGUI_TOKEN'],
    ),

XHGui est configuré via des variables d'environnement, avec son uri et son token.

    // Environment variables to exclude from profiling data
    'profiler.exclude-env' => array(
        'APP_DATABASE_PASSWORD',
        'PATH',
    ),

    'profiler.options' => array(
    ),

    /**
     * Determine whether profiler should run.
     * This default implementation just disables the profiler.
     * Override this with your custom logic in your config
     * @return bool
     */
    'profiler.enable' => function () {
        //return false;
        return true;
    },

C'est ici que l'on choisit la stratégie d'échantillonnage, avec du random ou en exigeant la présence d'un header. Là, c'est un paramétrage pour du dev : je mesure tout. En production, le paramétrage sera plus parcimonieux.

    /**
     * Creates a simplified URL given a standard URL.
     * Does the following transformations:
     *
     * - Remove numeric values after "=" in query string.
     *
     * @param string $url
     * @return string
     */
    'profiler.simple_url' => function($url) {
        return preg_replace('/=\d+/', '', $url);
    },

    /**
     * Enable this to clean up the url before submitting it to XHGui.
     * This way it is possible to remove sensitive data or discard any other data.
     *
     * The URL argument is the `REQUEST_URI` or `argv` value.
     *
     * @param string $url
     * @return string
     */
    'profiler.replace_url' => function($url) {
        return str_replace('token', '', $url);
    },
);

La configuration est faite, on va pouvoir instancier le profileur.

// Add this block inside some bootstrapper or other "early central point in execution"
try {
    /**
     * The constructor will throw an exception if the environment
     * isn't fit for profiling (extensions missing, other problems)
     */
    $profiler = new \Xhgui\Profiler\Profiler($config);

    // The profiler itself checks whether it should be enabled
    // for request (executes lambda function from config)
    $profiler->start();
} catch (Exception $e){
    // throw away or log error about profiling instantiation failure
    error_log("profile");
}

Voila, avec ça, on va envoyer des rapports dans son XHGui et l'on va pouvoir commencer à analyser le code.

Analyser ses rapports

XHGui range les rapports et propose de les trier, de chercher une url, et de sélectionner une plage de temps.

Liste XHGui

On peut ensuite avoir les détails d'une url qui va comparer les scores au fil du temps.

URL XHGui

On peut continuer l'exploration en affichant un rapport qui donne des informations sur le contexte de la page, et qui propose ce qu'il y a de pire avec un joli tableau qui affiche tous les appels de fonctions avec leurs métriques.

Détail XHGui

Pour finir sur le niveau de détail ultime : le graphique digne du plan de l'étoile noire. Pas de panique, le code couleur aide à naviguer, et à coup de zooms, on s'y retrouve rapidement.

Graphe XHGui

Il est possible de visualiser les rapports d'une session (filtré par fenêtre de temps, IP, cookie)

Waterfall XHGui

Aller plus loin

XHprof permet d'avoir un aperçu précis de ce que fait une page PHP, sans avoir à instrumenter son code, on est à la frontière de l'analyse en boîte noire. Par contre, XHprof ne doit pas être déployé en production, il manipule les entrailles du moteur Zend de PHP, ce serait dommage de rajouter des suspects pour le prochain bug.

XHprof fait le pari que tout le travail est fait par PHP, ce qui est de moins en moins vrai. Les pages sont de plus en plus consommatrices de services externes (ne serait-ce que la base de données) qui amènent leurs propres latences. Il est naïf de mesurer les temps passés dans le SQL (ou n'importe quel service) sans connaître la requête du service et savoir qui accuser de lenteur. Il va être compliqué de se passer d'annotations dans son code pour avoir de jolies traces, voir même des traces distribuées (trace de la requête HTTP avec les traces des services consommés). Ces annotations peuvent être rationnalisées, le framework utilisé le permet (avec la notion d'événements), ou taillées sur mesure.

XHprof est un très bel outil, parfait pour comparer et débroussailler, mais il n'est pas à la hauteur des APM (Application Performance Monitor) contemporains, sujet que l'on abordera un jour ou l'autre dans ce blog.

Service Audit de Performance web php

Bearstech vous propose ses services Audit de Performance web php

Découvrir ce service

Partager cet article

Flux RSS

flux rss

Partager cet article :