Portail informatique 2019-2020

Systèmes d'exploitation

Le but de ce TP est de créer un outil permettant d'analyser les performances d'une application multithreadée utilisant des locks. Cet outil va intercepter les appels aux primitives de verrouillage (grâce à LD_PRELOAD) et, pour chaque appel, enregistrer dans un fichier un événement au début et à la fin de la fonction. Le fichier de trace généré peut être analysé post-mortem afin de détecter un éventuel problème de performance.

Pour commencer, récupérez la base du TP d'aujourd'hui:

  • git clone -b tp11_base https://stark2.int-evry.fr/asr3/csc4508_facebook/ csc4508_tp11

Le code fourni est composé de plusieurs fichiers:

  • liblock.h:
  • cond.c: intercepte les appels aux fonctions pthread_cond_* et appelle les fonctions enter_function et leave_function
  • mutex.c: intercepte les appels aux fonctions pthread_mutex_* et appelle les fonctions enter_function et leave_function
  • write_events.c: implémente les fonctions enter_function et leave_function
  • read_trace.c: programme permettant de lire une trace générée par write_event
  • test: répertoire contenant des programmes permettant de tester l'outil

Dans un premier temps, on souhaite compter le nombre d'appels à chacune des fonctions interceptées. Modifiez le fichier write_events.c pour définir un tableau d'entiers contenant le nombre d'appel à chaque fonction. Dans enter_function, incrémentez la case correspondant à la fonction appelée. Dans __write_events_conclude, affichez le nombre d'appels interceptés.

Mesurez le surcoût de l'outil à l'aide de l'application benchmark.

On souhaite maintenant générer un fichier permettant de retracer l'exécution de l'application. Pour cela, à chaque entrée/sortie de fonction, un struct lock_event doit être enregistré dans un fichier.

Modifiez votre implémentation pour qu'à chaque appel de fonction, un événement soit écrit sur le disque avec une IO non bufferisée.

Mesurez le surcoût de la génération de traces sur l'application benchmark.

Complétez le programme read_trace.c. Ce programme doit ouvrir le fichier de trace passé en paramètre et afficher le contenu de chacun des événements.

Modifiez votre implémentation de write_events.c pour utiliser des entrées/sorties bufferisées et mesurez le surcoût de l'outil.

Modifiez votre implémentation pour que les événements soient enregistrés dans un buffer préalloué (par exemple, en préallouant un buffer de 64Mo lors de l'initialisation). &Acute; la fin de l'application, le buffer est écrit sur le disque.

Mesurez le surcoût de l'outil.

Un problème se pose lorsque le buffer devient plein. Une solution est d'arrêter d'enregistrer des événements. Une autre solution consiste à vider le buffer pour continuer à enregistrer des événements.

Modifiez votre outil pour qu'il vide le buffer lorsque celui ci devient plein.

Mesurez le surcoût de l'outil.

Pour éviter de bloquer l'application lorsqu'on vide le buffer, on peut utiliser une technique de "double buffering": deux buffers A et B sont alloués, et les événements sont enregistrés dans le buffer A. Lorsqu'il devient plein, on démarre une écriture asynchrone, et on continue d'écrire dans le buffer B.

Mesurez le surcoût de l'outil.

Le corrigé de cet exercice est disponible dans la branch tp11_corrige du dépôt git.