Vous êtes ici : Accueil / 2011 / Juin / Quelques outils pour localiser un problème de performances

Quelques outils pour localiser un problème de performances

écrit le 29/06/2011 Par Gaël Le Mignot
Cet article va passer en revue certains outils, parfois méconnus, utilisés par l'équipe de Pilot Systems pour localiser un problème de performances sur une application, que ce soit une application réalisée en interne, de la TMA ou un audit. Il expliquera aussi comment interpréter les résultats. Il n'est bien sûr pas exhaustif, et ne remplacera jamais une bonne connaissance du fonctionnement des technologies utilisées.

IO et mémoire virtuelle

Rappels théoriques

Dans un système d'exploitation moderne, la mémoire physique disponible peut être considérée comme un cache du disque. Toute opération sur le disque (lecture ou écriture) est conservée en mémoire, jusqu'à ce que la mémoire soit pleine. Alors une des trois options suivantes est choisie :

  1. Une page (l'unité de mémoire manipulée par le système, 4Ko en général) du cache disque est libérée (après avoir été écrite sur le disque, s'il s'agissait d'une écriture).
  2. Une page d'un fichier mmappé est libérée (par exemple, une page d'un exécutable ou d'une bibliothèque d'un programme s'exécutant actuellement).
  3. Une page de données d'un programme est mise sur le swap et libérée.

Le système choisit en fonction de différents paramètres (fréquence d'utilisation de la page, s'il doit faire une écrire ou non, ...).

free

La commande free donne une sortie du type :

             total       used       free     shared    buffers     cached
Mem:       3986840    3104616     882224          0     547160    1415768
-/+ buffers/cache:    1141688    2845152
Swap:      1951860      35960    1915900

Comment interpréter cette sortie ?

La première ligne indique la mémoire physique totale, utilisée et libre. La mémoire utilisée est utilisée par n'importe quelle source : les binaires des programmes ou bibliothèques, la mémoire interne utilisée par le noyau, les données des programmes, le cache disque.

La deuxième ligne indique la mémoire hors cache disque et tampons (utilisés pour le réseau par exemple).

La troisième ligne indique le swap total et utilisé.

Jusque là, rien de bien sorcier. Quelques précisions, qui peuvent ne pas être évidentes au premier abord, tout de même :

  1. Il est normal que le swap commence à être utilisé, lorsque la mémoire totale est pleine (première ligne), mais même lorsqu'une partie importante est utilisée pour les buffers et les caches. En effet, si une zone mémoire de données d'un programme n'est quasiment jamais utilisée, alors qu'une zone disque est souvent utilisée, il est plus performant de mettre la zone mémoire dans le swap, et la zone disque en cache.
  2. Il est normal que le swap puisse être utilisé alors qu'il y a de la mémoire y compris sur la première ligne (comme sur l'exemple donné). Cela signifie que pendant un moment la mémoire a été utilisée, et donc le système a utilisé le swap, puis qu'elle a été libérée. Tant que ce qui a été mis dans le swap n'est pas utilisé de nouveau, le système le laisse dans le swap : inutile de relire des données qui pour l'instant ne servent pas.

iostat

La commande iostat donne des informations, de manière périodique, sur l'activité disque. Elle s'utilise par exemple avec iostat -x 5 qui donnera toutes les 5 secondes une information du type :

 Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda1              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sda2              0.00   476.05    0.00  110.78     0.00  4779.24    43.14     9.99   96.76   2.80  31.06

Ainsi on peut voir sur cet exemple que sda1 n'est pas utilisé (il s'agit du swap), mais que sda2 subit des écritures de manière assez importantes, mais restant tout de même raisonnable (31% d'utilisation de la bande passante du disque).

Si jamais le taux d'utilisation dépasse les 90% alors la machine est saturée par les IO. C'est en général l'une des premières choses à regarder quand un serveur commence à ralentir : est-ce que les IOs sont saturées ?

vmstat

La commande vmstat elle donne un affichage du type :

procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 1  1  50820 393512 554120 2874504    0    1   556  1140  148   19 26  4 51 18

Elle permet de voir l'évolution des zones mémoires, ainsi que le nombre de pages lues depuis le swap (si), écrites sur le swap (so), autres lectures depuis le disque (bi) et autres écritures sur le disque (bo).

Solutions

Si on constate une activité importante sur le disque sur un serveur qui a des problèmes de performances, il y a un grand nombre de causes possibles, qu'il faut diagnostiquer.

Si les opérations sont surtout des lectures (ou des opérations sur le swap), il peut être très intéressant d'augmenter la quantité de RAM sur la machine. Dans le cas d'une base de données, il y a une différence très importante de performances lorsque l'ensemble de la base peut tenir en RAM. Dans ce cas, il peut même être intéressant de la précharger dans le cache disque au boot, avec une commande du type :

tar cf /dev/null /var/lib/postgresql

Si les opérations sont surtout des écritures, il faut chercher si ces écritures sont normales, ou si elles peuvent être optimisées. Par exemple, une requête SQL non optimisée peut forcer la base de données à utiliser des tables temporaires sur le disque, ou des fichiers temporaires pour faire du tri. Les commandes SQL explain ou explain analyze peuvent permettre d'avoir des détails sur ce genre d'opérations. Il est aussi possible de modifier les paramètres de la base de données, pour modifier le seuil au-delà duquel les tris seront effectués sur le disque et non en mémoire.

Utilisation CPU

uptime

La première commande à exécuter quand un serveur semble ramer est la commande uptime, qui va donner le load average. Il s'agit de la moyenne du nombre de processus en attente de ressources (accès au CPU ou au disque) pendant une période de temps (trois valeurs, pour 1 minute, 5 minutes, 15 minutes).

Deux informations sont importantes :

  1. Est-ce que le load est élevé, comparé au nombre de coeurs de la machine ? Tant que le load est inférieur au nombre de coeurs de la machine, sous réserve que les IO ne soient pas saturées, tout va bien. Par contre, s'il est supérieur, c'est que plus de ressources sont demandées qu'il n'y en a de disponible.
  2. Est-ce que les trois valeurs sont très différentes ? Si elles sont proches, c'est que la charge est continue, sur 15 minutes. Si elles varient beaucoup, c'est que la machine subit (ou a subit, suivant le sens de la variation) une charge élevée de manière ponctuelle.

top

La seconde commande pour analyser l'utilisation CPU est la commande 'top'. Elle indique la liste des processus, classés par l'utilisation CPU qu'ils font, ainsi que l'état total des différents coeurs.

L'état total des CPUs est réparti entre :

  • us : userspace : le temps d'exécution des programmes eux-mêmes ;
  • sy : system : le temps d'exécution du noyau (à la demande des programmes, la plupart du temps) ;
  • ni : nice : le temps passé à exécuter des programmes de faible priorité ;
  • id : idle : le temps passé à ne rien faire ;
  • wa : io wait : le temps passé à attendre qu'une opération d'IO (en général lecture ou écriture sur le disque) se termine ;
  • hi / si : interrupts : le temps passé à traiter les interruptions (devrait être négligeable) ;
  • st : steal : sur une machine virtuelle uniquement, le temps "volé" pour être donné à une autre machine virtuelle.

Note : attention à la commande top, elle est modérément gourmande en ressources, et peut aggraver la situation sur un serveur déjà très chargé.

Multicoeurs et threads

Un point important est à considérer pour les deux commandes précédentes : le traitement du multicoeur (ou du multicpu, ce qui revient au même).

Lorsque top liste les temps globaux, il s'agit d'une moyenne sur tous les CPUs : ainsi, si on a 2 coeurs, et que top indique 50% de userspace et 50% de idle, il s'agit probablement d'un coeur fonctionnant à fond, tandis qu'un autre coeur n'est pas utilisé.

En effet, un même programme ne peut pas s'exécuter sur deux CPU à la fois, à l'exception des programmes multithreadés. Et encore, pour ceux-là, il faut qu'il n'y ait pas de lock, comme le Global Interpreter Lock de Python (donc, un programme Python, même multithreadé, ne s'exécutera que sur un seul coeur).

Ajouter des coeurs ne sert à rien si les services ne sont pas suffisamment parallélisés.

Appels systèmes et réseau

Si le système semble au repos, mais que l'application met du temps à répondre, il faut se demander si l'application n'est pas bloquée par quelque chose d'externe, en général un service réseau qui mettrait du temps à répondre.

Pour cela deux commandes sont indispensables :

  1. netstat et en particulier sa forme netstat -anp | grep ESTABLISHED qui indique toutes les connexions réseaux ouvertes.
  2. strace qui permet de suivre les appels systèmes d'un programme en cours d'exécution. Un strace -p <PID> permet de voir ce que fait le programme, et s'il est bloqué sur un read, recv ou select, il faut alors regarder si le fichier en question n'est pas une socket.

Deadlock debugguer

Un autre outil extrêmement pratique pour analyser les raisons de la lenteur d'un Zope (ici, il s'agit d'un produit Zope, mais le concept peut être reproduit sur d'autres frameworks Web) : le DeadlockDebugger (http://plone.org/products/deadlockdebugger/ ).

Ce produit permet d'avoir, en temps réel, l'état (la pile d'exécution) de tous les threads d'un Zope, et donc de voir dans quelle partie du code il est "coincé".

Attention cependant : en raison du mode de fonctionnement de l'ordonnancement des threads Python, le DeadlockDebugguer prendra quasiment toujours la main lors d'un appel système. Les dernières lignes de la pile seront très souvent un lseek ou un read, mais ce n'est pas pourtant que ce sont réellement ces appels qui prennent le plus de temps. Il vaut mieux remonter quelques niveau de plus de la pile, et voir à un niveau applicatif quel est le code concerné (recherche catalogue, rendu d'un template, ...).

Conclusion

Cet article liste quelques outils, la manière d'interpréter leurs résultats, et des actions à mener pour résoudre un problème du type "mon service rame". Cependant, il faut bien se souvenir que rien ne remplacera un travail d'investigation, une analyse du code et des requêtes SQL s'il y en a, ...

Actions sur le document