Je vais revenir sur un problème informatique. Sa résolution technique est insignifiante, mais je veux illustrer le déroulé d'un diagnostic, l'arbre de décisions, les différentes possibilités techniques, les différentes contraintes, les erreurs de réflexion, etc. En parlant autour de moi, je me suis rendu compte que quelques-unes des notions et des commandes / variables que je vais présenter ci-dessous sont méconnues (apt-file
, apt-mark
, dpkg-divert
, ldconfig
, LD_LIBRARY_PATH
, PPID
, snoopy
, etc.).
Pour notre supervision, nous utilisons Xymon. Alors, oui, il est rustique, mais, pour avoir manipulé pas mal des standards du marché (Zabbix, Icinga, Centreon, Shinken, etc.), je peux affirmer qu'au moins il est KISS, flexible, et qu'il fonctionne. Je parle bien de la partie supervision, les graphes (via RRDtool) sont une plaie à configurer. L'écriture d'un test de supervision est facile : il suffit d'apeler un binaire en lui donnant un état + message, en gros. Il s'appelle de n'importe où, par n'importe qui, point d'authentification ni autres. Juste, ça fonctionne.
Le test interne nommé « memory » affiche une ligne « physical » (consommation totale de RAM ‒ processus + cache + tampons ‒), une ligne « actual » (consommation de la RAM par les processus, hors cache et tampons), et une ligne « swap ». Sur certains de nos serveurs, la ligne « actual » n'apparaît pas. On en avait viteuf déduit que ça se produit sur nos machines Debian >= 9.
Quel logiciel Xymon utilise-t-il pour connaître la consommation RAM ? Avec grep -Ri memory
, je cherche dans la configuration de Xymon (/etc/xymon) et dans les scripts et binaires (/usr/lib/xymon) aussi bien sur le serveur que sur un client. Je trouve rien. Cela doit se faire au sein d'un binaire, illisible par grep
.
Essayons de deviner le logiciel utilisé en sous-main par Xymon. Pour afficher la consommation RAM, je connais vmstat
(dont je sais qu'il est utilisé par Xymon, je l'ai déjà vu plein de fois dans une sortie ps faux
avec Xymon comme parent) et free
. J'utilise whereis
pour localiser ces deux binaires et chmod -x
pour les rendre inutilisables. J'attends la fréquence d'actualisation du test (5 minutes) et, par déduction, j'en arrive à la conclusion que Xymon utilise free
car la sortie du test est vide quand je bloque l'exécution de free
.
J'aurais pu utiliser snoopy logger afin de tracer les commandes lancées par Xymon, mais je ne suis pas sûr qu'il fonctionne avec un compte système sans shell (car c'est ce qu'utilise Xymon), il faut l'installer, et je me suis dit que c'était "too much" pour mon besoin.
À ce stade, remplacer /usr/bin/free par un script shell qui aurait juste exécuté un ps aux | grep $PPID > /tmp/debugXymon
aurait été utile : j'aurais appris quel composant précis de Xymon exécute la commande système free
($PPID : identifiant numérique du processus parent ;) ). Mais, sur le coup, je n'y ai pas pensé.
Qu'est-ce qui a changé dans free
entre Debian 8 et Debian 9 ? Le format de la sortie :
On a donc la raison qui explique notre constat : Xymon n'a pas été mis à jour pour tenir compte de ce changement dans l'affichage de free
(enfin, je croyais, mais je me trompe, voir ci-dessous). Il ne trouve plus la ligne « -/+ buffers/cache » donc il n'affiche plus sa ligne « actual ». Et ce qu'il nomme « physical », qui était toute la RAM consommée (processus + cache + tampons), prend le nouveau sens de la commande free
, à savoir la mémoire utilisée par les processus hors cache et tampons.
Est-ce un problème ? Puisqu'« actual » est, de fait, devenue la ligne « physical », tout va bien, on a l'info qu'on a toujours eu.
Sauf que je pense que la quantité de RAM utilisée par le cache et les tampons est une info intéressante. Pas forcément sur tout type de serveur. Mais moins on a de lectures depuis les supports de stockage, mieux on se porte. Elle m'a permis d'ajuster la quantité de RAM de notre serveur LDAP. De même, le cache est important sur un serveur de bases de données. Je pense que c'est aussi important sur des serveurs d'applications web (PHP, uwsgi/django, Tomcat, etc.
Pour me contredire, on peut citer les propos de tonton Torvalds : "toute RAM libre est du gâchis". Linux va toujours exploiter la RAM au maximum, y compris pour mettre en cache des trucs qui serviront qu'une fois genre une mise à jour aptitude. Donc, la quantité de cache va augmenter constamment. De ce fait, mesurer cette quantité est inintéressant. Ceci dit, ce n'est pas parce qu'on voit un chiffre augmenter dans sa supervision qu'on augmente automatiquement la quantité de RAM. En revanche, avoir l'historique de la consommation de RAM par le cache+tampons est intéressant (si au boot de la machine LDAP, le cache monte direct à 500 Mo, c'est qu'il est plutôt utile ; si un reboot libère 3 Go de cache sans y revenir avant 6 mois, c'est qu'il contenait nawak, donc il ne faut pas redimensionner la RAM).
Avant de me demander si j'agis, je trouve sain de me demander si le problème est temporaire ou définitif.
J'installe un agent / client Xymon sur une machine Debian 10 (oui, en supervision, un logiciel est installé sur les ordinateurs à surveiller, et un serveur de supervision centralise les remontées d'informations et présente les informations aux administrateurs). Je l'ajoute dans notre supervision. *Le problème demeure. En même temps, entre Stretch et Buster, on passe de la version 4.3.28-2 de Xymon à la 4.3.28-5… Version très mineure voire interne à Debian, donc il ne fallait pas en attendre grand-chose.
Peut-être que l'analyse des données se fait côté serveur Xymon (je ne le sais pas encore, mais c'est bien le cas) ? Nous ne prévoyons pas de le mettre à jour puisque nous allons migrer vers une autre solution de supervision. Quand ? Aucune idée, aucun projet à l'horizon. Cela justifie qu'on corrige temporairement ce problème.
Je pourrais coder un nouveau test qui surveille la quantité de RAM. J'ai pas trouvé comment désactiver le test « memory » intégré à Xymon. Je pense que cumuler deux tests qui font la même chose est confusant. De plus, il faudrait re-coder toutes les subtilités du test : les seuils d'alerte, le passage de paramètres depuis la configuration du serveur (chose qu'on ne fait pas dans nos autres scripts, d'où il faudra du temps pour chercher comment faire) ‒ car, sur certains de nos serveurs, on change les seuils, entre autres ‒, etc. Tout ça sera spécifique à Xymon… Dont nous voulons nous débarrasser à moyen-terme. Trop d'investissement pour pas grand-chose, à mon avis. Je laisse tomber cette piste.
Une piste vient à l'esprit : remplacer /usr/bin/free par un script. S'il est exécuté par Xymon, il lancera l'ancien binaire free
ou un script maison qui collectera les données et les affichera dans le même format que le faisait l'ancienne version de free
. S'il est lancé par quoi ou qui que ce soit d'autre, il lancera le vrai binaire free
que l'on déplacera en /usr/bin/free.new (par exemple). Afin de ne pas lever d'alerte ni d'écraser notre /usr/bin/free modifié sans pour autant se priver des mises à jour de free
, ce qui serait le cas si l'on empêchait sa mise à jour avec apt-mark hold procps
(je sais que free
est empaqueté dans ce paquet grâce à apt-file search -x '/usr/bin/free$'
), on utiliserait dpkg-divert
.
Je scp
le binaire free
depuis un Debian 8 sur un Debian 9. Il refuse de s'exécuter car il manque la bibliothèque de fonctions libprocps.so.3. En effet, c'est la version 6 de cette bibliothèque qui est empaquetée dans Debian Stretch / 9. Créons un lien symbolique afin de le duper : sudo ln -s /lib/x86_64-linux-gnu/libprocps.so.6 /lib/x86_64-linux-gnu/libprocps.so.3
. Cela fonctionne mais la ligne « -/+ buffers/cache » présente des nombres négatifs. Si je copie la version 3 depuis Debian 8, cela fonctionne. La bibliothèque a changé…
Je pourrais déployer la vieille version de free
et de libprocps sur nos Debian >= 9. Le risque que la vieille bibliothèque soit utilisée par d'autres binaires que notre vieux free
est proche de 0. Si je voulais m'en assurer, je pourrais la stocker dans une arborescence ignorée par ldconfig
(configuration du chargement à chaud des bibliothèques de fonctions) et utiliser LD_LIBRARY_PATH
pour forcer son utilisation quand free
est exécuté par Xymon. Sauf qu'à ce stade-là, je ne sais pas quel composant de Xymon exécute free
, donc il est inenvisageable de surcharger son appel à free
.
De toute façon, free
dépend d'autres bibliothèques de fonctions (libc, libdl) qui peuvent changer de version et/ou de comportement. Plus le temps (et les versions de Debian) passe, plus la probabilité que ça casse augmente. Utiliser une vieille version de free
est donc plutôt un mauvais choix.
D'autant que, de l'autre côté de la balance, écrire un script qui va chercher les mêmes informations que free
ne semble pas être difficile puisque le manuel de free
expose qu'il récupère les informations dans le fichier /proc/meminfo
.
Au début, je pars dans l'idée d'exécuter la version actuelle de free
, d'y ajouter une ligne « -/+ buffers/cache » et de remplacer l'intersection de la ligne « Mem » et de la colonne « used ». J'ai considéré que je change 3 valeurs (Mem+used + 2 valeurs sur la ligne « -/+ buffers/cache ») alors que free
en affiche 11 en tout. Les deux tiers des valeurs sont valides, donc pourquoi s'embêter à les récupérer nous-même ?
En pratique, je récupère les infos manquantes depuis /proc/meminfo et je les stocke dans des variables, je stocke les deux premières lignes de la sortie de free
avec head -n 2
dans une variable, la dernière ligne avec tail -n 1
dans une deuxième variable, je corrige Mem+used avec un awk -v memUsed="$memUsed" 'FNR==2 { $3=memUsed }; { print };'
(-v
permet de créer une variable interne à awk
et de lui attribuer la valeur d'une variable shell et FNR
est le numéro de la ligne, donc on remplace le troisième élément de la deuxième ligne par le contenu de la variable shell maison nommée memUsed), j'affiche tous les variables, et hop, ça fonctionne.
Ça fonctionne, mais c'est un mauvais raisonnement : afin de corriger un problème lié à un changement dans la sortie d'un programme, je crée un script qui dépend lui-même de la sortie d'un programme qui peut changer (il a même déjà changé, c'est l'origine du problème). Genius!
Je ré-écris tout. Je récupère toutes les valeurs depuis /proc/meminfo
et je les affiche. Le code est même plus simple : juste un cat
, des grep
, des additions / soustractions et des echo
. Comment sais-je quelles valeurs dans /proc/meminfo dois-je utiliser ? Je lis le code source de la commande free. Enfin… L'ancienne version, évidemment. Je cherche où est défini la fonction meminfo() qui semble collecter les données… Dans sysinfo.c. Ainsi, mémoire totale = kb_main_total dans le code = « MemTotal » dans /proc/meminfo, mémoire utilisée = « MemTotal » - « MemFree », mémoire réellement utilisée = mémoire utilisée - (« Buffers » + « Cached ») , etc.
Au final, ça donne ça :
#!/bin/bash
# On récupère les valeurs depuis procfs
meminfo=$(cat /proc/meminfo)
memTotal=$(grep '^MemTotal:' <<< "$meminfo" | awk '{ print $2 }')
memFree=$(grep '^MemFree:' <<< "$meminfo" | awk '{ print $2 }')
shared=$(grep '^Shmem:' <<< "$meminfo" | awk '{ print $2 }')
buffers=$(grep '^Buffers:' <<< "$meminfo" | awk '{ print $2 }')
cached=$(grep '^Cached:' <<< "$meminfo" | awk '{ print $2 }')
swapTotal=$(grep '^SwapTotal' <<< "$meminfo" | awk '{ print $2 }')
swapFree=$(grep '^SwapFree' <<< "$meminfo" | awk '{ print $2 }')
# On calcule ce qui nous manque
memUsed=$(( $memTotal - $memFree ))
swapUsed=$(( $swapTotal - $swapFree ))
# voir https://gitlab.com/procps-ng/procps/-/blob/099bf9a26a074a7f58442aa0c907980459afdc02/free.c
buffers_plus_cached=$(( $buffers + $cached ))
# +/- used = kb_main_used - buffers_plus_cached
# = memUsed - buffers_plus_cached
buffersUsed=$(( $memUsed - $buffers_plus_cached ))
# # +/- free = kb_main_free + buffers_plus_cached
# = memFree + buffers_plus_cached
buffersFree=$(( $memFree + $buffers_plus_cached ))
# Affichage
echo -e '\ttotal\t\tused\t\tfree\t\tshared\t\tbuffers\t\tcached'
echo -e "Mem:\t$memTotal\t\t$memUsed\t\t$memFree\t\t$shared\t\t$buffers\t\t$cached"
echo -e "-/+ buffers/cache:\t$buffersUsed\t\t$buffersFree"
echo -e "Swap:\t$swapTotal\t\t$swapUsed\t\t$swapFree"
exit 0
Alors, oui, les chiffres remontés à notre supervision seront moins fiables que la colonne « available » d'un free
moderne puisque le calcul de la mémoire utilisée pour le cache a changé (source : https://gitlab.com/procps-ng/procps/-/blob/13f20a48119894638a98b9a17a714704c69492d4/proc/sysinfo.c#L685 versus https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c#L781), mais on est à quelques dizaines de mégaoctets d'écart… Nous ne sommes pas la NASA : cette imprécision est largement tolérable.
Il me reste plus qu'à écrire le script qui prendra la place de /usr/bin/free et qui dirigera vers le bon binaire en fonction de qui l'exécute. Comment savoir qui exécute un script ? Il existe la variable shell USER
, ce qui rend envisageable une condition if [ "$USER" == 'xymon' ]
. La variable PPID
permet de connaître l'identifiant du processus parent, ce qui rend envisageable un if ps aux | grep $PPID | grep -q xymon
. Plein d'autres moyens existent.
Dans mon cas, USER
n'est pas provisionnée. Car Xymon est exécuté par un compte utilisateur système qui a /bin/false pour shell ? Je pourrais utiliser id -u
, mais je pars sur $PPID
, comme ça, sans raison. Mais… Attends… Je peux donc savoir quel composant de Xymon exécute free
: il suffit de remplacer /usr/bin/free par un script ps aux | grep $PPID > /tmp/debugXymon
. J'attends la fréquence d'actualisation du test « memory ». le fichier apparaît dans /tmp et je découvre que le processus Xymon qui exécute free
est /usr/lib/xymon/client/bin/xymonclient-linux.sh
.
Comment a-t-il échappé à mes grep
du début ? Parce qu'à ce stade-là, le test se nomme « free », pas « memory ». Je n'ai pas lancé une nouvelle campagne de grep
quand j'ai compris que c'était bien la commande free
qui était utilisée en sous-main, donc j'ai rien vu, forcément. Cela permet d'affirmer que le test définitif, « memory », est produit côté serveur Xymon à partir des données remontées par xymonclient-linux.sh
sous le nom de « free ». Comme pour l'espace disque. Comme pour les processus. Comme pour… De même, si j'avais percuté qu'une version de Xymon est sortit en 2019 et si j'avais lu le journal des changements j'aurais compris que le problème qui m'interroge a été corrigé dans la version 4.3.20 du serveur Xymon (notons que le fichier « README » de la version la plus récente ne contient pas tout l'historique, il faut donc remonter de version en version pour prendre connaissance de tous les changements…). Ce constat ne change pas la conclusion : le problème se situe côté serveur… que nous ne mettrons pas à jour.
Du coup, nul besoin de remplacer /usr/bin/free : il suffit de lancer notre script depuis xymonclient-linux.sh. Cela fonctionne. \o/
Avec Puppet, je déploie, sur nos Debian >=9 uniquement, mon script qui émule l'ancienne version de free
et une nouvelle version de xymonclient-linux.sh
qui exécute mon script au lieu de free
afin de remonter l'utilisation de la RAM.
Fin.