Comment lire un flamegraph

Vous avez lancé un profiling, par exemple avec async-profiler, et vous voilà devant un flamegraph. C’est l’une des meilleures façons de comprendre où part le temps d’un programme, à condition de connaître la grille de lecture. Elle tient en quelques règles.

Ce que représente chaque axe

Un flamegraph agrège des milliers d’échantillons de stacks. Chaque rectangle est une frame, c’est-à-dire une méthode.

Le piège classique : l’horizontale n’est pas le temps

C’est l’erreur qu’on voit le plus souvent. De gauche à droite, il n’y a pas de chronologie. Les frames d’un même niveau sont juste triées par ordre alphabétique pour regrouper les piles identiques. Une méthode tout à droite ne s’exécute pas « après » celle de gauche. Un flamegraph répond à la question « où passe le temps ? », pas à « dans quel ordre ? ». Pour de la chronologie, il faut un autre outil, une trace ou une timeline.

En pratique : chercher les plateaux

La méthode qui marche :

  1. Partir du haut et repérer les frames larges. Le sommet d’une pile, c’est le code qui était réellement en train de tourner quand l’échantillon a été pris (son « self time »). Un large plateau en haut, c’est du temps brûlé directement là. Premier suspect.
  2. Redescendre pour comprendre le chemin. En suivant une frame large vers le bas, on voit qui a mené jusque-là. Bien souvent le coupable n’est pas la feuille elle-même, mais le fait qu’on l’appelle beaucoup trop, depuis plus haut.
  3. Ignorer les tours fines et isolées. Une pile étroite et très haute consomme peu : beaucoup d’appels imbriqués, mais peu de temps total. Pas une priorité.

La règle à garder en tête : la largeur dit combien ça coûte, la position en haut dit où c’est effectivement dépensé.

Quelques motifs qu’on reconnaît vite

Adapter sa lecture à l’événement profilé

Le même graphe ne se lit pas pareil selon ce qui a été mesuré. Sur un flamegraph CPU, une frame large, c’est du calcul à optimiser. Sur un flamegraph wall-clock, une frame large peut être une attente (I/O, lock, park), et l’objectif n’est alors pas d’accélérer le code mais de supprimer le blocage. Sur un flamegraph d’allocations, la largeur représente des octets alloués, pas du temps. Gardez toujours en tête ce que vous avez demandé à async-profiler de mesurer.

En résumé

Cherchez le large, partez du haut, ne lisez pas l’horizontale comme une horloge, et souvenez-vous de ce que l’événement profilé veut dire. Avec ça, un flamegraph qui paraissait illisible devient une carte assez directe de ce qu’il faut corriger en premier.

Si vous n’avez pas encore généré le vôtre : Profiler la JVM avec async-profiler.