Tubes nommés et redirections en parallèle

La ligne de commande est bien faite. Cette phrase est souvent entendue dans la bouche des informaticiens. J’en ai encore vérifié la véracité ses jours derniers.

Ma problèmatique était de constituer des statistiques sur des accès HTTP en consultant un fichier de log. La volumétrie des log étant de l’ordre de plusieurs gigaoctets et je ne disposais que de ma ligne de commande en bash.

Je me suis alors dirigé vers une serie de grep avec les bons motifs de recherche couplé à un bon vieux wc:

grep motif fichier.log | wc -l

Pour compliquer il y avait plusieurs motifs differents à compter et il fallait produire un fichier par jour avec le comptage de chaques motifs.

ACCES_COUNT=`grep -e motif_date -e motif_acces fichier.log | wc -l`

Autant de ligne de ce genre que de motif. L’inconvenient est que l’on parcourt la log autant de fois qu’il y a de motif d’acces pour une date donnée. Dans mon cas j’avais 15 motifs soit 1h30 pour obtenir les statistiques pour un jour donné.

Or, une fois que l’on est sur la bonne date dans le fichier de log c’est dommage de le reparcourir depuis le debut pour le motif d’accès suivant. D’où l’idée d’externaliser le parcours du fichier à la recherche de la bonne date puis de dispatcher la ligne aux grep qui compte les motifs d’accès.

tee

On a donc envie de faire un premier grep dont on va diriger la sortie sur d’autres grep en arborscence. La première pièce du puzzle est la commande tee. Voici l’idée :

grep -e motif_date fichier.log | tee -a entree_proc_grep1 entree_proc_grep2 … > /dev/null

Il manque la seconde piece qui est celle qui permet de definir un tube entre le premier grep et les autres qui doivent s’exécuter en parallèle. C’est là que les tube nommés interviennent.

named pipe

La commande mkfifo crée un tube nommé que l’on donne en entrée au grep de motif d’accès. Ces grep seront alors bloqués en attente de lire des donnés dans leurs tubes respectifs.

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

Autant de lignes de ce genre qu’il y a de motif d’accès à compter. Remarquons qu’à ce stade on à seulement créé des processus pour faire les grep de comptage et qu’ils sont tous bloqués en attentes sur leur tube. On alimente ensuite les tubes. Voici l’ordre

  1. créer les tube
  2. lancer en arrière plan les grep prenant les tubes en entrée
  3. lancer le grep sur la date pipé avec le tee vers les tubes

Subtilites

On ne peut plus récupérer la valeur des wc car c’est un sous shell qui l’exécute. On passe par un fichier temporaire.

On stocke la liste des pid des grep de comptage car le shell courant qui est leur parent doit attendre qu’ils soient tous finis avant d’aller lire les resultats dans les fichiers temporaires.

wait $PID_LIST

cat fichier_temp1

cat fichier_temp2

….

Dans mon cas particulier je suis passé de 1h30 de traitement pour avoir les statistiques d’un jour donné à seulement 3 minutes !

Finitions

Pour être propre il faut terminer en supprimant les fichiers temporaires (de comptage et les tubes).

Pour pouvoir lancer plusieurs instance du script sur des dates differentes en parallèle, ii faut ajouter le pid du script dans les noms des fichiers temporaires ( tubes nommés et comptage).

Dans mon cas particulier il me fallait les statistiques de plusieurs jours: chaque jour ayant sa propre stat. On peut lancer le script manuellement pour chaque jour ou bien intérer sur les jours, ce qui donne a peu près ceci:

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

…..

grep -e motif_date fichier.log | tee -a entre_proc_grep1 entree_proc_grep2 … > /dev/null

wait $PID_LIST

cat fichier_temp1 >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1

….

done

On remarque encore que pour chaque jour en parcourt l’ensemble de la log d’accès. Or dès le premier passage on a deja passé sur toutes les dates possibles. Une seule passe doit suffire ! L’idée est que le grep sur le motif de la date ne prenne plus le fichier de log en entrée mais un tube nommé…

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1_$jour

grep -e motif_acces < entree_proc_grep1_$jour | wc -l > fichier_temp1_$jour` &

PID_LIST+=” $!”

…..

mkfifo entree_proc_$jour

PROC_JOUR_LIST+=” entree_proc_$jour”

grep -e motif_date entree_proc_$jour| tee -a entre_proc_grep1_$jour entree_proc_grep2_$jour … > /dev/null &

PID_LIST+=” $!”

done

cat fichier_log | tee -a $PROC_JOUR_LIST > /dev/null

wait $PID_LIST

for jour in 1 2 3 4

do

cat fichier_temp1_$jour >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1_$jour

….

rm entree_proc_$jour

done

C’est un peu plus compliqué car pour distinguer tous ces tubes nommés on doit ajouter le numero de jour à leur nom.

Eviter ainsi de reparcourir la log depuis le début pour chaque jour permet d’aller plus vite. Dans mon cas particulier pour les statistiques de 3 jours je suis passé de 2mn50 à 0mn45s et pour les statistiques de 14 journées je suis passé de 22mn à 5mn !

Moralité de l’histoire, l’algorithmie alliée à la ligne de commande et la puissance du shell ça fait des étincelles.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.