Benchmark des application web avec Httperf

Publié le 20 février 2013 par Nicolas Cavigneaux | outils

Cet article est publié sous licence CC BY-NC-SA

Httperf ?

Httperf est un outil Unix dont le but est de vous aider à mesurer les performances de vos applications web.

Httperf vous permettra donc par exemple de connaître les temps de réponse dans diverses situations mais aussi de générer un trafic suffisant pour tester les montées en charge de votre application pour en découvrir les limites.

Cet outil supporte les sessions, le protocole SSL, les différents verbes HTTP, les connexions multiples, …

Installation

Httperf est un outil qui existe depuis longtemps et qui est assez connu. Il est donc disponible très facilement pour le peu que vous soyez sous Unix. J’utilise pour ma part un Mac avec Brew pour la gestion des paquets. Si vous êtes sous Debian ou encore RedHat, vous n’aurez aucun mal à trouver cet outils via APT ou RPM.

Voici la commande pour les utilisateurs de Brew :

$ brew install httperf

Mesures et méthodes de calcul

Préparatifs

Pour mesurer les performances d’une application, il faut être au plus près possible des conditions de production. Il faudra donc penser à lancer l’application en mode production. Une application lancée en mode production pourra par exemple bénéficier des mécanismes de cache ou d’autres optimisation non-activées en environnement de développement.

Les tests peuvent se faire en local mais seront très largement impactés par le fait qu’une seule machine face le travail de client et serveur.

L’idéal est d’avoir une machine de staging qui réplique à l’identique la production et qui se trouve sur le même réseau. En effet l’emplacement physique du serveur pourra jouer pour beaucoup sur les temps de réponse du fait de son éloignement par rapport au point de test ou encore à cause de la bande passante disponible.

Il est également important de tester l’application avec un jeu de données réaliste. Une page de résultat d’un moteur de recherche ne mettra pas forcément le même temps à générer ses réponses avec une base presque vide et une remplie de milliers d’enregistrements.

Relevé et calculs

Nous verrons dans la suite le détail des éléments de réponse fournies par httperf mais deux valeurs seront à surveiller tout particulièrement. Ces valeurs sont la moyenne du taux de réponse (Reply rate) en nombre de réponses par secondes ainsi que sa valeur de déviation standard.

La déviation standard est un moyen de présenter l’écart moyen du temps de réponse au cours du temps. Plus l’écart moyen est faible, plus les temps de réponse sont similaires entre les requêtes tout au long du test. Il est donc important d’éviter les valeurs élevées de déviation standard qui dénoterai de grosses variations dans les temps de réponse.

Une grande déviation standard est souvent le signe d’un morceau de code suspect ou d’un problème d’infrastructure réseau / serveur.

Outils complémentaires

Lorsqu’une application est en production, il peut être intéressant d’analyser son comportement en situation réelle. Pour ça, rien de mieux que de se pencher sur les logs de production pour les analyser.

Le gem production_log_analyser peut vous aider dans cette tâche. Il suffit de lui fournir un log de production (Ruby on Rails uniquement) pour qu’il génère une analyse détaillée des temps de réponse avec valeurs minimum, moyenne, maximum et même déviations standard.

Il mettra également en avant les actions les plus lentes. Ces mêmes infos seront disponibles pour le temps passé dans la base de données ou encore le temps nécessaire au rendu des vues.

C’est donc une analyse très détaillée qui peut être d’un grand secours. Il faut toutefois garder en tête que les mesures prises ne sont pas faîtes de bout à bout. Le calcul commence ici quand l’application a bel et bien reçue la requête HTTP et s’arrête dès lors que l’application a répondu à cette même requête. Ce n’est donc pas le temps réel que perçoit l’utilisateur puisque les accès réseau n’entre pas en ligne de compte. Seul le temps passé dans l’application est calculé.

Des services en ligne comme NewRelic permettent de faire cette même analyse sans avoir à récupérer les logs mais simplement depuis votre navigateur. Ce service va plus loin qu’un simple tableau de chiffres et mérite d’être essayé.

Utilisation

En fonction de vos besoins, n’hésitez pas à moduler vos tests en activant / désactivant le cache pour mesurer son impact. Si vous souhaitez tester le comportement final de l’application approchez vous au plus près de l’environnement de production. Si au contraire vous voulez tester des comportements particuliers reflétant une situation précise, cela reste évidemment possible. L’important est de bien comprendre ce que vous testez avant de lancer vos tests et donc de préparer l’environnement en fonction.

Il est recommandé d’utiliser au moins deux machines pour effectuer vos tests. L’une servira de machine cliente pour lancer httperf. On coupera tous les autres services de cette machine qui peuvent utiliser du CPU ou de la bande passante. La deuxième machine servira quant à elle de serveur pour faire tourner l’application dans les conditions de test souhaitées.

On peut donc imaginer une séquence de test de temps de réponse / montée en charge réalisée avant même que l’application ait générée son cache. Puis une deuxième, avec cache généré pour mesurer la différence de performance.

Avant de lancer votre test via httperf, assurez vous d’avoir déjà contacté une fois le serveur pour être sûr qu’il fonctionne et qu’il est prêt à recevoir les requêtes. Typiquement, pour une application en Rails qui vient d’être lancée (au travers d’Unicorn par exemple) mettra beaucoup plus longtemps à répondre à la première requête qu’au suivante.

Exemples

Voyons notre premier exemple d’appel, l’un des plus simple possible.

$ httperf --server blog.dev --port 8080 --uri /categories/ruby --num-conns 1000

Dans cet exemple nous fournissons quelques options indispensables : - l’hôte à contacter --server - l’éventuel port --port qui par défaut vaut 80 - l’URI à contacter --uri c’est donc le path vers votre action - et le nombre de requêtes --num-conns à exécuter

Httperf va nous retourner une réponse du type :

Total: connections 1000 requests 1000 replies 1000 test-duration 5.795 s

Connection rate: 172.6 conn/s (5.8 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 4.3 avg 5.8 max 15.5 median 5.5 stddev 1.4
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 1.000

Request rate: 172.6 req/s (5.8 ms/req)
Request size [B]: 61.0

Reply rate [replies/s]: min 172.2 avg 172.2 max 172.2 stddev 0.0 (1 samples)
Reply time [ms]: response 5.6 transfer 0.0
Reply size [B]: header 274.0 content 14831.0 footer 0.0 (total 15105.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

CPU time [s]: user 0.75 system 4.57 (user 13.0% system 78.9% total 91.9%)
Net I/O: 2555.6 KB/s (20.9*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

La première ligne nous donne le nombre de connexions HTTP établies, le nombre de requêtes envoyées et le nombre de réponses reçues. Pour finir cette même ligne nous donne le temps total qui a été nécessaire pour traiter l’ensemble des requêtes.

La durée totale est donc un bon indicateur de la réactivité de l’application sur cette action précise.

Le bloc “connection” nous donne lui plusieurs informations :

sur la première ligne :

  • le nombre de connexions faites par seconde
  • le temps moyen de connexion
  • le nombre de connexions concurrentes

la deuxième ligne nous donne elle :

  • le temps de connexion le plus court
  • le temps de connexion moyen
  • le temps de connexion le plus long
  • le temps de connexion médian
  • la déviation standard

Le “Request rate” est lui aussi une donnée importante puisqu’il nous donne le nombre de requêtes qui peut être traité en 1 sec suivi du temps moyen pour traiter une requête.

Le bloc “Reply rate” nous présente lui les statistiques concernant le nombre de réponses par seconde. Ce taux est mesuré toutes les 5 secondes, donc plus vous allongez votre test, plus le résultat sera significatif. N’ayant ici qu’un sample il est normal que la déviation standard soit à 0.

On note également dans ce bloc la répartition des statuts de réponse. On sait donc combien de réponses sont arrivées correctement (code 2xx), celles qui ont générées une erreur, etc.

Il est intéressant de regarder également l’avant dernière ligne qui nous donne le nombre d’erreurs et donc de requêtes qui n’ont pas abouties. Quand ce nombre grimpe c’est le signe que les limites du serveur ont été atteintes et qu’il n’arrive plus à délivrer les réponses aussi vite qu’elles le sont demandées.

Connaitre les limites de performance de votre application est très important. Il vaut mieux servir 1000 visiteurs dans de bonnes conditions plutôt que de vouloir en servir 10 000 et que tous soient pénalisés par un temps de réponse trop long.

Gestion des sessions

Par défaut httperf crée une session à chaque requête. Ce n’est peut-être pas important pour vos tests ou peut-être même souhaité. Si ce n’est pas le cas et que vous avez besoin d’un nombre de session limité pour vos tests, des options vous sont offertes.

Il s’agit tout d’abord d’activer la gestion des sessions en cookies via l’option --session-cookies pour ensuite préciser comment vous voulez contraindre les sessions :

--wsess=10,5,1

Cette option demande à httperf de créer au maximum 10 sessions, chaque session servira pour effectuer 5 appels, chaque appel étant séparé d’une seconde.

Connexion concurrente et stress de l’application

Dans la majorité des cas vous voudrez faire subir un test de stress à votre application. On peut donc demander à httperf d’utiliser plusieurs connexions en simultané pour simuler une situation plus réaliste où les demandes se font en parallèle et non les unes après les autres.

C’est particulièrement intéressant quand vous savez que votre application est utilisée dans certaines situations (en soirée, le week end…)

Pour les tests de stress et montée en charge, il est conseillé d’utiliser l’option --hog qui autorise httperf à ouvrir autant de ports que nécessaire en parallèle.

L’option qui va effectivement paralléliser les demandes est --rate qui définit le nombre de connexions qui doivent être faites par secondes ce qui implicitement permet de forcer les requêtes en parallèle.

$ httperf --server blog.dev --uri / --num-conns 1000 --hog --rate 150

Total: connections 1000 requests 1000 replies 1000 test-duration 6.667 s

Connection rate: 150.0 conn/s (6.7 ms/conn, <=12 concurrent connections)
Connection time [ms]: min 4.2 avg 8.3 max 72.1 median 5.5 stddev 8.1
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 1.000

Request rate: 150.0 req/s (6.7 ms/req)
Request size [B]: 61.0

Reply rate [replies/s]: min 150.0 avg 150.0 max 150.0 stddev 0.0 (1 samples)
Reply time [ms]: response 8.2 transfer 0.1
Reply size [B]: header 274.0 content 14831.0 footer 0.0 (total 15105.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

CPU time [s]: user 0.90 system 5.21 (user 13.4% system 78.1% total 91.6%)
Net I/O: 2221.6 KB/s (18.2*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

On peut ainsi faire varier le nombre de connexions concurrentes et affiner les tests en fonction de son infrastructure serveur. On a dans l’exemple précédent, douze connexions qui ont été utilisées en parallèle pour faire monter le serveur en charge.

Il est important d’augmenter le nombre de connexion demandées (--num-conns) pour avoir des résultats plus probants. Si la durée de test est trop courte, le nombre de samples pour le taux de réponse (Reply rate) ne permettra pas d’avoir une déviation standard exploitable.

$ httperf --server blog.dev --uri / --num-conns 10000 --rate 170

Total: connections 10000 requests 10000 replies 10000 test-duration 58.881 s

Connection rate: 169.8 conn/s (5.9 ms/conn, <=17 concurrent connections)
Connection time [ms]: min 53.0 avg 60.4 max 102.8 median 59.5 stddev 3.1
Connection time [ms]: connect 29.5
Connection length [replies/conn]: 1.000

Request rate: 169.8 req/s (5.9 ms/req)
Request size [B]: 63.0

Reply rate [replies/s]: min 168.2 avg 169.8 max 170.2 stddev 0.6 (11 samples)
Reply time [ms]: response 30.9 transfer 0.0
Reply size [B]: header 197.0 content 185.0 footer 0.0 (total 382.0)
Reply status: 1xx=0 2xx=0 3xx=10000 4xx=0 5xx=0

CPU time [s]: user 3.20 system 54.90 (user 5.4% system 93.2% total 98.7%)
Net I/O: 73.8 KB/s (0.6*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

On obtient dans cette exemple, une déviation standard de 0.6 réponses/sec ce qui nous permet de savoir que les temps de réponses sont homogènes pour l’ensemble des requêtes. C’est un très bon point, on sait que l’action a un comportement consistant. Une déviation standard élevée mettrait en évidence un problème de consistance soit au niveau code ou infrastructure serveur.

Conclusion

En fonction des résultats obtenus vous pourrez affiner votre application ou votre infrastructure en fonction de vos besoins. Vous pourrez également repérer les zones de faiblesses et les limites de votre architecture actuelle.

Un autre logiciel libre assez similaire et lui aussi très bien fichu est ab. Il est livré avec apache et permet le même genre de manipulations, il peut notamment sortir les résultats sous forme de fichiers exploitables par Gnuplot.

Bon benchmarks à vous.

L’équipe Synbioz.

Libres d’être ensemble.