Souvent oubliées quand on parle de performance, les commandes Artisan sont une partie importante dans nos projets Laravel. Votre application en contient probablement plusieurs dizaines pour effectuer diverses tâches, le plus souvent d'exploitation.
Une commande volumineuse, fortement consommatrice en ressources, peut vite devenir un problème pour la stabilité de votre serveur ... avez-vous déjà lancé une commande exécutant, malgré vous, des milliers de requêtes mettant à mal votre base de données ?
Mais comment faire pour benchmark une commande Artisan ?
Une approche simpliste consisterait à utiliser la commande time présente sur les machines Unix :
1time php artisan test
Simple et rapide à utiliser, l'ajout de time devant votre commande vous permettra d'obtenir la durée d'exécution et la consommation en CPU de celle-ci.
1$ time php artisan inspire20,15s user 0,03s system 82% cpu 0,221 total
Pour des résultats plus complets et une approche plus analytique, nous vous conseillons le package phpbench, que nous vous présentons dans cet article.
Le package phpbench vous permettra de lancer de véritables benchmarks, d'écrire des tests vous protégeant des régressions et vous aidera à optimiser votre code.
Cependant, mettre en place ces tests est un exercice relativement lourd et laborieux ... tandis que la commande time, comme nous venons de le voir, est simple à utiliser mais relativement limitée dans ses résultats.
Aujourd’hui, nous vous proposons de découvrir le package artisan-benchmark, qui se positionne à mi-chemin entre les deux approches !
Lancer un benchmark
artisan-benchmark est développé par Christoph Rumpel, un membre de la core team de Laravel que vous connaissez peut-être pour sa série de vidéos sur le compte YouTube officiel de Laravel.
Le package est semblable à la commande time de par sa simplicité, il vous suffira de lancer la commande artisan benchmark suivie de la commande que vous souhaitez tester.
Par exemple, si vous souhaitez analyser la commande inspire
:
1php artisan benchmark inspire
Le résultat du benchmark sera constitué des trois valeurs suivantes :
- Le temps d'exécution en ms
- La consommation en mémoire en MO
- Le nombre de requêtes effectuées
Si vous souhaitez transmettre des arguments à la commande analysée, vous pouvez le faire de la maniere suivante :
1php artisan benchmark "inspire 1 2 3"
Enfin, si vous souhaitez surveiller les requêtes effectuées par la commande sur une table spécifique :
1php artisan benchmark inspire --tableToWatch=users
Maintenant que vous savez lancer un benchmark, nous allons voir un peu plus en profondeur comment le package fonctionne et les limites de son utilisation.
Analyse du package
Tout d'abord, concernant la consommation en ressources, le package se contente de visualiser l'empreinte mémoire de la commande à l'aide d'un memory_get_usage et non sa consommation maximale.
1protected function startBenchmark(): void 2{ 3 $this->benchmarkStartTime = microtime(true); 4 $this->benchmarkStartMemory = memory_get_usage(); 5 6 if ($tableToWatch = $this->option('tableToWatch')) { 7 $this->tableToWatchBeginCount = DB::table($tableToWatch)->count(); 8 } 9 10 Event::listen(QueryExecuted::class, fn () => $this->queryCount++);11}
Cette nuance est importante, car l'empreinte mémoire représente la différence de mémoire consommée par PHP entre le début et la fin de l'exécution de la commande.
Pour y voir plus clair, posez vous la question suivante :
Avant de lancer la commande, PHP consomme 20 Mo ... Après le lancement, PHP consomme 30 Mo : ma commande a donc consommé ?
À cette question, vous seriez tenté de répondre simplement ... 10 mo ?
Ce chiffre ne reflète que partiellement la réalité car l’analyse se limite ici à la mémoire occupée par les variables, les objets instanciés encore en mémoire à la fin de l'exécution du script.
Cependant, pendant son exécution, la commande peut créer puis détruire de nombreuses variables entrainement une consommation de mémoire qui ne sera pas capturée par le package, faussant l’estimation réelle des ressources utilisées.
Une analyse plus poussée, à l'aide de la fonction memory_get_peak_usage, permettrait de connaitre la consommation de mémoire maximale atteinte par la commande durant son exécution !
Avant de lancer la commande, PHP consomme 20 mo, pendant l'exécution PHP a consommé jusqu'à 60mo ... après le lancement, PHP consomme 30 mo
Comme vous pouvez le voir, l'empreinte mémoire est bien de 10 Mo alors que la consommation maximale est de 40 Mo ! Le résultat du benchmark est donc bien différent de la véritable consommation de la commande.

Un second point intéressant, en visualisant le code source du package, vous remarquerez qu'il utilise la fonction microtime pour calculer le temps d'exécution.
1protected function startBenchmark(): void 2{ 3 $this->benchmarkStartTime = microtime(true); 4 $this->benchmarkStartMemory = memory_get_usage(); 5 6 if ($tableToWatch = $this->option('tableToWatch')) { 7 $this->tableToWatchBeginCount = DB::table($tableToWatch)->count(); 8 } 9 10 Event::listen(QueryExecuted::class, fn () => $this->queryCount++);11}
Historiquement, cette fonction a toujours été plébiscitée pour calculer le temps d'exécution et vous trouverez une multitude d'articles à son propos sur internet.
Cependant, il existe d'autres méthodes en PHP et microtime présente certaines limites.
Depuis Laravel 10, le framework dispose d'une classe Benchmark permettant d'analyser de courtes sections de code. Cette dernière utilise la fonction hrtime pour calculer la durée d'exécution, plus fiable car moins sensible aux variations temporelles pouvant affecter votre serveur
Contrairement à microtime, qui est basé sur l'horloge système, hrtime mesure le temps écoulé depuis un point de départ arbitraire, ce qui lui permet d'obtenir des mesures plus précises sans être affecté par les ajustements de l'horloge du serveur.
Des ajustements qui peuvent survenir, entre autres, en cas de surcharge du serveur.
L'utilisation de microtime pourrait conduire à des résultats légèrement faussés, mais reste tout à fait acceptable pour un benchmark de ce type, étant donné que les commandes Artisan sont généralement relativement "lentes" en raison des tâches lourdes qu'elles accomplissent.
1protected function startBenchmark(): void 2{ 3 $this->benchmarkStartTime = microtime(true); 4 $this->benchmarkStartMemory = memory_get_usage(); 5 6 if ($tableToWatch = $this->option('tableToWatch')) { 7 $this->tableToWatchBeginCount = DB::table($tableToWatch)->count(); 8 } 9 10 Event::listen(QueryExecuted::class, fn () => $this->queryCount++); 11}
Pour finir, si vous vous demandez comment le package parvient à visualiser les requêtes exécutées, c’est tout simplement en écoutant l’événement QueryExecuted
que Laravel déclenche à chaque exécution de requêtes !
A lire
Autres articles de la même catégorie

Des mocks avec Prophecy
Prophecy est un package permettant de créer des puissants et flexibles mock dans vos tests.

Mathieu De Gracia

Se débarrasser des adresses jetables avec Laravel
Débarrassez votre application Laravel des mails jetables !

Laravel Jutsu

Une façon différente d’organiser son application avec Laravel Actions
Présentation du paquet lorisleiva/laravel-actions mettant en avant une façon différente d'organiser le code

Marc COLLET