Guillaume Fenollar DevOps et SysAdmin Freelance

Guillaume FENOLLAR

Ingénieur Linux/DevOps Indépendant

− Montpellier −

Mesurer les performances d'une connexion avec les stats de cURL

cURL est aux protocoles L7, et surtout HTTP, ce que Netcat et Socat sont aux sockets réseau. De son nom complet «client URL request library», il propose un nombre assez fou de fonctionnalités et d'utilité, et miracle, sauf conteneurs, il est en général installé partout. Raison déjà suffisante en soi pour s'habituer à l'utiliser à la moindre occasion. Aujourd'hui, pas de cours exhaustif sur la question, mais sur une fonctionnalité que j'utilise beaucoup en ce moment: le formatage des statistiques d'utilisation (ou timing).

cURL permet de tenter des connexion en plusieurs protocoles, HTTP, FTP, IMAP, LDAP, POP3, SCP, SFTP, SMB, SMTP, pour ne citer qu’eux. Tout ce qui va suivre va plutôt être adapté au HTTP et HTTPS seulement, car il s’agit aujourd’hui d’afficher des statistiques de connexion qui vont nous permettre de détecter d’où peuvent venir les lenteurs d’une connexion à un site web.

Il faut savoir que cURL, en bon petit soldat, réunit des statistiques de timing à chacune des étapes d’une connexion HTTP. Il suffit alors de les écrire quelque part pour pouvoir les analyser. L’option pour exporter ces variables est -w, ou --write-out en version longue. Cette option peut prendre une chaîne de caractères avec le nom des variables désirées, ou même un fichier, et c’est cette fonctionnalité qui m’intéresse d’aborder aujourd’hui.

Parmi ces variables, mon but numéro en tant qu’hébergeur de site web est d’obtenir un TTFB (Time To First Byte) satisfaisant. Il s’agit du temps écoulé entre le lancement d’une requête HTTP par un utilisateur, et la reception du premier octet de données. En effet, il se passe plein de choses entre ces deux étapes, notamment des redirections si besoin, une négociation HTTPS qui utilise un nombre conséquent de ressources (et plusieurs aller-retours), etc…

Chaque étape de l’ouverture d’une connexion peut-être améliorée, et récupérer des stats peut nous permettre d’appliquer de bonnes optimisations en un rien de temps.

Je vous ai adapté un de ces fichiers pas piqué des hannetons pour afficher des stats intéressantes sur une connexion HTTP.

Commencez par coller ce fichier quelque part :

 === HEAD\n
       http_version: %{http_version}\n
          http_code: %{http_code}\n
      num_redirects: %{num_redirects} (nombre de redirections)\n
      url_effective: %{url_effective} (denière URL utilisée (si redirection))\n
 === TIMING\n
    time_namelookup:  %{time_namelookup} (temps écoulé pour la résolution DNS)\n
       time_connect:  %{time_connect} (temps écoulé depuis le début jusqu'à la connexion TCP à l'hôte distant)\n
    time_appconnect:  %{time_appconnect} (temps écoulé jusqu'à la connexion, pour le protocole applicatif utilisé (https par exemple))\n
   time_pretransfer:  %{time_pretransfer} (temps écoulé jusqu'avant que la réponse soit envoyée par le serveur)\n
      time_redirect:  %{time_redirect} (temps écoulé depuis le début par toutes les redirections)\n
 time_starttransfer:  %{time_starttransfer} (le serveur envoie son premier octet)\n
 === STATS\n
     speed_download:  %{speed_download}B/s (Vitesse de téléchargement moyenne)\n
       speed_upload:  %{speed_upload}B/s (Vitesse de téléversement moyenne)\n
                    ----------\n
         time_total:  %{time_total}\n

Libre à vous d’omettre les commentaires en parenthèses si votre terminal a une résolution inférieure, il faut qu’un résultat s’affiche par ligne.

Enfin, lancez une commande curl prenant comme export ce fichier template:

curl -w "@curl-format.txt" -o /dev/null -s -L "https://guillaume.fenollar.fr/"

Cette commande, outre le -w dont j’ai déjà parlé, suit les redirections (-L), active la sourdine (-s), et exporte le rendu de la page dans la poubelle. Optionnellement, vous pouvez toujours rajouter -I pour afficher les headers HTTP de la page consulté, selon ce que vous essayez de faire. Voici un exemple de résultat :

 === HEAD
       http_version: 2
          http_code: 200
      num_redirects: 0 (nombre de redirections)
      url_effective: https://guillaume.fenollar.fr/ (denière URL utilisée (si redirection))
 === TIMING
    time_namelookup:  0,154078 (temps écoulé pour la résolution DNS)
       time_connect:  0,189461 (temps écoulé depuis le début jusqu'à la connexion TCP à l'hôte distant)
    time_appconnect:  0,250609 (temps écoulé jusqu'à la connexion, pour le protocole applicatif utilisé (https par exemple))
   time_pretransfer:  0,250705 (temps écoulé jusqu'avant que la réponse soit envoyée par le serveur)
      time_redirect:  0,000000 (temps écoulé depuis le début par toutes les redirections)
 time_starttransfer:  0,316192 (le serveur envoie son premier octet)
 === STATS
     speed_download:  21018,000B/s (Vitesse de téléchargement moyenne)
       speed_upload:  0,000B/s (Vitesse de téléversement moyenne)
                    ----------
         time_total:  0,322284

Bon j’ai un peu triché ici en prenant mon site statique comme essai, ce qui donne bien sûr de bonnes performances jusqu’au TTFB, malgré une résolution DNS qui a pris tout de même 154ms, ce qui est énorme (ahhh, l’Océanie), mais n’arrive qu’une fois par visite. En computant ces valeurs, on voit que le serveur a mis 66ms entre l’établissement de la connexion HTTPS et l’envoi du premier octet de données. Ce qui arrive ensuite est en général à voir du niveau de l’optimisation du site, des ressources qu’il va appeler, c’est pas cURL qui va nous aiguiller sur cela.

Mais voici un autre exemple d’un site dont je tairais le nom pour ne pas m’attirer les foudres de son créateur =)

 === HEAD
       http_version: 2
          http_code: 200
      num_redirects: 1 (nombre de redirections)
      url_effective: https://xxx/ (denière URL utilisée (si redirection))
 === TIMING
    time_namelookup:  0,000967 (temps écoulé pour la résolution DNS)
       time_connect:  0,035613 (temps écoulé depuis le début jusqu'à la connexion TCP à l'hôte distant)
    time_appconnect:  0,116959 (temps écoulé jusqu'à la connexion, pour le protocole applicatif utilisé (https par exemple))
   time_pretransfer:  0,117170 (temps écoulé jusqu'avant que la réponse soit envoyée par le serveur)
      time_redirect:  2,104149 (temps écoulé depuis le début par toutes les redirections)
 time_starttransfer:  4,098944 (le serveur envoie son premier octet)
 === STATS
     speed_download:  118421,000B/s (Vitesse de téléchargement moyenne)
       speed_upload:  0,000B/s (Vitesse de téléversement moyenne)
                    ----------
         time_total:  4,669374

Bien qu’ayant le nom de domaine en cache DNS (967 microsecondes), le premier octet envoyé par le serveur est arrivé après plus de 4 secondes, ce qui est assez effrayant, car le transfert des assets ne fait alors que commencer (et dans ce cas là il y en a un paquet, pour un chargement total de page de plus de 15 secondes).

On voit donc ici que le serveur a perdu plus de deux secondes avec un seule redirection, il y a sûrement de l’optimisation à faire à ce niveau là. Et bien sûr, encore plus de 2 secondes sont nécessaires pour que le serveur nous envoie un début de page. S’agissant d’un site en PHP, il y aura sûrement un bel intérêt à mettre en place du cache FastCGI par exemple.

Si on peut professionaliser tout ça, il sera assez facile, en quelques minutes, de récupérer ces statistiques et de les envoyer dans une base de données temps (InfluxDB ou Graphite par exemple), grâce à Statsd ou Telegraf, pour faire de jolis graphs stacked sur les performances des connexion, via par exemple un Grafana.