backend / go
Profiling avec pprof
Explication
Ce que vous allez apprendre
- Exposer les profils d'un service HTTP en une seule ligne avec
net/http/pprof - Capturer un profil CPU et un profil mémoire dans un programme court
- Analyser un profil avec
go tool pprof(top,list,web) - Détecter une fuite de goroutines en production
- Comprendre pourquoi profiler avant d'optimiser est une règle non négociable
Dans quel contexte ?
Un service Go en production consomme progressivement de plus en plus de mémoire au fil des heures, jusqu'à devoir être redémarré manuellement chaque nuit — un symptôme classique de fuite de ressources. Sans profiling, un développeur pourrait passer des jours à relire le code en cherchant "à l'œil" le problème. Avec pprof, la même investigation prend souvent moins d'une heure : le profil pointe directement vers la fonction ou la goroutine responsable.
D'abord, une ligne suffit pour instrumenter un service HTTP
import _ "net/http/pprof" est un import à effet de bord : il enregistre automatiquement une série d'endpoints (/debug/pprof/*) sur le serveur HTTP par défaut de Go, sans qu'aucun code supplémentaire ne soit nécessaire dans les handlers de l'application elle-même. C'est délibérément conçu pour être trivial à activer.
Pour un programme court qui ne tourne pas comme service HTTP, pprof.StartCPUProfile/pprof.WriteHeapProfile capturent un profil directement dans un fichier, exploitable ensuite hors ligne.
Une fois un profil capturé, comment l'exploiter concrètement
go tool pprof ouvre un shell interactif d'analyse. Trois commandes couvrent l'essentiel des besoins au quotidien.
| Commande pprof | Ce qu'elle montre | Quand l'utiliser |
|---|---|---|
top10 | Les 10 fonctions les plus coûteuses | Premier réflexe pour cibler un point chaud |
list nomFonction | Détail ligne par ligne du coût dans une fonction | Une fois la fonction coupable identifiée |
web | Graphe visuel des appels (nécessite Graphviz) | Comprendre la propagation du coût dans l'arbre d'appels |
Prérequis
Cette leçon s'appuie directement sur les benchmarks (leçon précédente) : profiler un benchmark avec -cpuprofile est souvent le point de départ le plus rapide avant même de déployer un service complet.
Il reste une distinction importante entre les types de profils disponibles
Le profil CPU montre où le processeur passe son temps ; le profil mémoire (heap) montre quelles allocations dominent l'usage de mémoire à un instant donné ; le profil goroutine liste toutes les goroutines actives avec leur pile d'appel — le principal outil pour détecter une fuite de goroutines qui ne se terminent jamais.
Piège fréquent
Une fuite de goroutines (des goroutines lancées qui ne se terminent jamais, souvent parce qu'elles attendent indéfiniment sur un channel qui ne recevra jamais rien) ne provoque aucune erreur visible immédiatement : la mémoire grimpe lentement jusqu'à un crash bien plus tard, parfois des jours après le déploiement. go tool pprof http://.../debug/pprof/goroutine?debug=2 révèle immédiatement leur nombre et leur pile d'appel exacte.
Enfin, une règle d'or de l'optimisation
Bonne pratique
Ne jamais optimiser une portion de code sur la seule base d'une intuition : l'intuition humaine sur les goulots d'étranglement d'un programme se trompe très souvent. Profile toujours en premier, identifie objectivement le vrai point chaud, puis optimise uniquement cette zone précise.
Maintenant que tu sais localiser un problème de performance, la prochaine leçon montre comment résoudre concrètement les cas les plus fréquents liés à la mémoire et aux allocations.
Commandes & code
Profiling avec pprof
Diagnostiquer précisément CPU, mémoire et goroutines en production comme en local.
package main
import (
"log"
"net/http"
_ "net/http/pprof" // effet de bord : enregistre les endpoints /debug/pprof/*
"os"
"runtime/pprof"
)
func calculIntensif(n int) int {
total := 0
for i := 0; i < n; i++ {
total += i * i
}
return total
}
func main() {
// Option 1 : exposer pprof via HTTP (recommande pour un service long-running)
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Option 2 : capturer un profil CPU dans un fichier programme court
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal(err)
}
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < 5; i++ {
calculIntensif(10_000_000)
}
// Profil memoire a un instant donne
memFile, _ := os.Create("mem.prof")
defer memFile.Close()
pprof.WriteHeapProfile(memFile)
}# Serveur avec pprof actif : recuperer les profils via HTTP
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 # profil CPU 30s
go tool pprof http://localhost:6060/debug/pprof/heap # snapshot memoire
go tool pprof http://localhost:6060/debug/pprof/goroutine # etat des goroutines
# Analyser un fichier de profil genere localement
go tool pprof cpu.prof
# dans le shell interactif pprof :
# top10 -> les 10 fonctions les plus couteuses
# list nomFonc -> detail ligne par ligne
# web -> graphe visuel (necessite graphviz)
# Generer un graphe SVG directement
go tool pprof -svg cpu.prof > cpu.svg
# Detecter les fuites de goroutines
go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2// Benchmark integre a pprof : profiler directement via go test
// go test -bench=. -cpuprofile=cpu.prof -memprofile=mem.prof
// go tool pprof monbinaire.test cpu.profRésumé
import _ "net/http/pprof"expose/debug/pprof/*en une ligne pour un service HTTP.pprof.StartCPUProfile/WriteHeapProfilecapturent des profils dans des programmes courts.go tool pprofavectop,list,weblocalise précisément les points chauds.- Toujours profiler AVANT d'optimiser : l'intuition sur les goulots d'étranglement est souvent fausse.
Exercices pratiques
Mission : un service qui doit être redémarré chaque nuit
Objectif : Diagnostiquer une fuite de goroutines avec pprof plutôt qu'à l'intuition, et savoir choisir le bon type de profil selon le symptôme observé.
Contexte
Le service de notifications consomme progressivement de plus en plus de mémoire au fil des heures en production, jusqu'à nécessiter un redémarrage manuel chaque nuit. Un développeur propose de commencer par relire tout le code source à la recherche du problème "à l'œil", ce qui prendrait plusieurs jours vu la taille du service.