Infrastructure
L’instrumentation de code est une technique puissante pour répondre à toutes
sortes de questions de performances. Dès lors qu’on chercher à connaître un
paramètre d’un programme qui n’est a priori défini qu’à l’exécution (par
exemple la taille typique de messages reçus via le réseau), la manière la plus
simple de répondre à la question est d’instrumenter le code concerné pour en
extraire cette information, fût-ce par un simple printf()
bien placé suivi
d’une recompilation.
Mais recompiler du code peut être laborieux, et faire des statistiques sur de
l’information issue d’un printf
dont le format est défini par l’utilisateur
peut l’être tout autant. C’est pourquoi Linux fournit un grand nombre d’outils
pour extraire de façon standardisée de l’information d’un code, avec ou sans
recompilation, et que le code ait été conçu pour ça à la base ou non.
Types d’instrumentation
Les principales formes d’instrumentation Linux accessible par le biais de perf
sont:
- Les tracepoint, dont nous avons déjà beaucoup parlé. Il s’agit
de points du noyau Linux qui ont été manuellement instrumentés par les
développeurs de celui-ci, afin de pouvoir suivre quand du code y fait appel
ainsi que la valeur de certains paramètres d’exécution (ex: taille d’une
écriture de fichier pour le tracepoint
syscalls:sys_exit_write
). - Les kprobes sont un mécanisme permettant d’injecter du code en un point arbitraire du source du noyau Linux, pour détecter quand on passe en ce point du code noyau et éventuellement en extraire des informations (contenu de variables, paramètres et résultats de fonctions…). Cela se fait “à chaud”, il n’est pas nécessaire de recompiler son noyau ou redémarrer.
- Les uprobes ont une flexibilité analogue aux kprobes, mais permettent d’instrumenter du code extérieur au noyau (exécutables, bibliothèques partagées…). Leur fonctionnement est analogue à celui d’un point d’arrêt dans un débogueur : le code est interrompu et passe la main au noyau, qui récupère l’information voulue avant de lui rendre la main.
- Les USDT probes, pour User Statically-Defined Tracing, sont un mécanisme basé sur les uprobes qui permet à une application ou une bibliothèque de définir manuellement des points d’instrumentation, à la manière des tracepoints dans le noyau.
Outre le fait que les deux premières ciblent le code noyau et les suivantes ciblent le code applicatif, ces formes d’instrumentation se distinguent par leur performance et leurs garanties de stabilité.
Stabilité
Les tracepoint et USDT probes sont définis par le développeur du code concerné, et sont normalement soumis aux mêmes garanties de stabilité que toute autre API. Par conséquent, on peut s’attendre à ce que l’instrumentation associée reste disponible pendant un certain temps, ce qui permet par exemple de créer des tutoriels ou utilitaires d’analyse de performance basés sur l’information fournie par l’instrumentation.
Les kprobes et uprobes, en revanche, sont définis par la personne qui souhaite instrumenter du code, sans accord préalable avec les développeurs. On n’est pas limité aux interfaces publiques, et on peut instrumenter aussi profond dans l’implémentation qu’on le souhaite. Il n’y a donc aucune garantie de stabilité, et même un petit correctif d’une application peut casser une instrumentation basée sur ces mécanismes. Ils ne sont donc adaptés que pour des analyses ponctuelles.
Performances
L’instrumentation rajoute du code CPU à exécuter à chaque fois que l’exécution passe en un certain point du programme instrumenté, ainsi que du trafic mémoire. Elle a donc un impact sur les performances du code que l’on étudie.
Selon “System Performance: Enterprise and the Cloud” de Brendan Gregg (2e édition, 2020)…
- Les tracepoints définis par le noyau n’ont pas d’impact significatif sur les performances jusqu’à environ ~100k événements par seconde (ce qui suggère un coût de l’ordre de 100ns).
- Le coût des kprobes dépend de leur emplacement dans la fonction noyau
concernée
- A l’entrée d’une fonction (ex: suivi des appels, lecture des paramètres), le coût est comparable à celui d’un tracepoint.
- A la sortie d’une fonction (kretprobe), c’est environ 2x plus cher.
- Au milieu d’une fonction, ça peut être plus cher encore (ex: boucles).
- Les uprobes sont plus coûteuses puisqu’elles nécessitent un point d’arrêt, donc une interruption et un basculement vers le code noyau. On parle de ~13x le coût d’un tracepoint pour une uprobe à l’entrée d’une fonction et ~19x en sortie d’une fonction.
- Les USDT probes sont implémentées via des uprobes et ont donc un coût similaire.
Conclusion
Voici un résumé des informations précédentes.
Instrumentation | Où? | Quand? | Stable? | Coût (Gregg2020) |
---|---|---|---|---|
tracepoint | Noyau | Compilation | Oui | ~100ns |
kprobe (entrée) | Noyau | Exécution | Non | ~100ns |
kretprobe | Noyau | Exécution | Non | ~200ns |
uprobe (entrée) | App/Lib | Exécution | Non | ~1.3µs |
uretprobe | App/Lib | Exécution | Non | ~1.9µs |
USDT | App/Lib | Compilation | Oui | = u(ret)probe |
Notons un dernier point concernant les privilèges et la sécurité. En raison de la très grande dangerosité de l’instrumentation de code arbitraire (risque de ralentir fortement le système, de créer des récursions infinies, de dévoiler des informations qui devraient rester secrètes…), la création de kprobes et uprobes est réservée au super-utilisateur. Vous aurez donc besoin d’un peu d’aide de l’administrateur pour certaines commandes de ce TP.
En revanche, l’administrateur peut rendre ces “sondes” accessibles à l’ensemble
des utilisateurs d’un groupe par une simple commande chown
de ce style :
# Nécessite des privilèges administrateur !
sudo chown -R root:perf /sys/kernel/tracing/events/xyz