Quando “il carico è alto” non basta più come diagnosi
top e htop dicono che la CPU è occupata. Non dicono perché. Quando un server web risponde lentamente sotto carico, o un processo consuma più CPU del previsto senza una causa evidente, serve uno strumento che arrivi fino alle singole funzioni e, se necessario, alle istruzioni della CPU. Su Linux questo strumento è perf, il front-end user-space del sottosistema del kernel perf_events, capace di leggere contatori hardware, eventi software e tracepoint del kernel.
In questo articolo vediamo come installare perf, i comandi che un sistemista usa davvero (stat, top, record, report, trace), come interpretare le metriche di profiling della CPU, come generare un flame graph leggibile e quali sono le insidie più comuni — permessi, container, macchine virtuali e simboli di debug mancanti.
Installazione
perf è legato strettamente alla versione del kernel in uso, quindi la procedura di installazione varia per distribuzione:
# Debian/Ubuntu (serve il pacchetto per la specifica versione del kernel)
sudo apt install linux-tools-$(uname -r) linux-tools-generic
# Fedora / RHEL / CentOS
sudo dnf install perf
# Arch Linux
sudo pacman -S perf
Dopo l’installazione, una verifica rapida:
perf --version
Se la versione di perf non è perfettamente allineata a quella del kernel in esecuzione, è normale vedere avvisi di compatibilità: nella maggior parte dei casi lo strumento resta comunque utilizzabile.
I comandi fondamentali
perf stat: contare gli eventi
Il comando più semplice: esegue un programma (o si collega a un processo esistente) e conta gli eventi hardware e software rilevanti.
# Su un comando
perf stat ls /usr
# Su un processo già in esecuzione
sudo perf stat -p PID
# Su eventi specifici (ad esempio cache)
perf stat -e cache-references,cache-misses -p PID
perf top: la vista live
L’equivalente di top ma a livello di funzioni, non di processi: mostra in tempo reale quali funzioni del kernel e dello spazio utente stanno consumando più CPU in questo momento su tutto il sistema.
sudo perf top
sudo perf top -p PID
sudo perf top --cpu 0,1
Durante l’esecuzione, il tasto a permette di annotare il codice della funzione selezionata, utile per capire a quali righe corrisponde il tempo CPU osservato.
perf record e perf report: profilare e analizzare
perf record salva un profilo in un file (perf.data) che può essere analizzato in un secondo momento, anche su un’altra macchina:
# Tutta la macchina per 30 secondi
sudo perf record -g -a sleep 30
# Un processo specifico
sudo perf record -g -p PID
# Un comando dal suo avvio
sudo perf record -g ./myapp
L’opzione -g è quasi sempre da includere: cattura i call graph, cioè chi ha chiamato la funzione “calda” che si vede in cima al report. Senza -g si vede solo quale funzione consuma CPU, non il percorso che l’ha portata ad essere eseguita.
Per analizzare il file registrato:
# Modalità interattiva (TUI)
sudo perf report
# Output testuale, utile per script o CI
sudo perf report --stdio
# Call graph "piatto", più facile da leggere a colpo d'occhio
sudo perf report --call-graph flat
perf trace: tracciare le syscall con basso overhead
Simile a strace, ma pensato per avere un impatto molto più contenuto sulle performance del processo osservato — importante quando non si può permettere di rallentare un servizio in produzione solo per diagnosticarlo:
perf trace -p PID
perf trace -e openat,read,write -p PID
perf trace --summary -p PID sleep 10
perf list: scoprire gli eventi disponibili
perf list | grep cache
perf list | grep net
perf list | grep block
Come leggere le metriche di profiling della CPU
I numeri che perf stat restituisce hanno senso solo se si sa cosa cercare:
- IPC (instructions per cycle): valori bassi indicano spesso cache miss frequenti o stalli della pipeline, non necessariamente “poca CPU disponibile”.
- Branch misses: una percentuale elevata segnala che il predittore dei salti condizionali fatica a indovinare il percorso del codice — comune in codice con molte diramazioni dipendenti dai dati di input.
- Page faults: valori anomali possono indicare pressione sulla memoria, non un problema puramente computazionale.
- Task-clock: se il rapporto tra CPU utilizzata e tempo reale è inferiore a 1, il processo sta passando tempo in attesa (I/O, lock, scheduling), non calcolando.
- Rapporto cache-miss / cache-references: è forse l’indicatore più utile per distinguere un’applicazione CPU-bound da una memory-bound.
Su sistemi di produzione, la raccomandazione è di usare una frequenza di campionamento moderata con -F 99 (invece che il default, più aggressivo), per limitare sia l’overhead sia la dimensione dei file generati. Il valore dispari (99 invece di 100) non è un dettaglio estetico: evita che il campionamento si sincronizzi con i timer periodici del sistema, cosa che falserebbe la distribuzione statistica dei risultati.
Dalla registrazione al flame graph
Un report testuale con centinaia di funzioni è difficile da leggere. Il flame graph, ideato da Brendan Gregg, risolve il problema visualizzando gli stack di chiamata come blocchi sovrapposti, dove la larghezza di ogni blocco è proporzionale al tempo CPU consumato da quella funzione (e dai suoi discendenti). I blocchi larghi in cima alla pila sono, quasi sempre, i colli di bottiglia da investigare per primi.
Due modi per generarlo:
# Metodo classico con gli script FlameGraph di Brendan Gregg
perf script > out.perf
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > flamegraph.svg
# Dal kernel 5.8 in poi, integrato direttamente in perf
perf script report flamegraph
Il secondo metodo produce un file flamegraph.html interattivo direttamente, ma richiede il template d3-flame-graph disponibile sul sistema. In alternativa, l’applicazione GUI hotspot apre i file perf.data mostrando flame graph integrati senza passaggi intermedi, comoda per chi preferisce un’interfaccia desktop.
Un caso pratico: server web lento sotto carico
Un flusso di troubleshooting tipico per uno stack Nginx + PHP-FPM + MySQL sotto carico:
- Generare traffico realistico con
abowrkmentre si registra il profilo di sistema:sudo perf record -g -a sleep 60. - Analizzare il report con
perf reporte verificare quale componente domina il tempo CPU. - Se domina Nginx: controllare
worker_connections,keepalive_timeoute il costo del TLS. Molti simboli inlibcrypto/libsslnel profilo suggeriscono handshake TLS completi troppo frequenti: vale la pena verificare la configurazione della session resumption. - Se domina PHP-FPM: un valore alto su
execute_expunta a codice applicativo “pesante”; valori alti su_emallocozend_mm_alloc_*indicano allocazioni di memoria eccessive, spesso sintomo di strutture dati inefficienti o di troppi oggetti temporanei. - Se domina lo stack I/O del kernel: il tempo si concentra nel filesystem o nel block layer, tipicamente a causa di molte letture/scritture piccole e frequenti; i rimedi più comuni sono il batching delle operazioni, l’introduzione di cache applicative e l’ottimizzazione delle query.
Un limite da non sottovalutare: perf record campiona solo il codice effettivamente in esecuzione sulla CPU. Il tempo passato in attesa su disco (I/O wait) non compare nel profilo. Se la CPU risulta bassa ma il servizio è comunque lento, la causa probabile è altrove: conviene controllare iowait e l’output di iostat prima di incolpare il codice applicativo.
Anche perf trace può rivelare dettagli preziosi: una singola pread64 con latenza massima di 12 ms su una query verso un database è un segnale da approfondire — può indicare I/O lento sul disco, un buffer pool “freddo” dopo un riavvio, oppure indici mancanti sulla tabella interrogata.
Insidie comuni
- Permessi (
perf_event_paranoid): su molte distribuzioni derivate da Debian/Ubuntu questo parametro limita l’accesso ai contatori hardware per utenti non privilegiati. Per abilitare l’accesso temporaneamente:sudo sysctl -w kernel.perf_event_paranoid=1; per renderlo permanente si crea un file in/etc/sysctl.d/. È buona norma ripristinare il valore originale dopo la sessione di debug, non lasciarlo permanentemente permissivo su un sistema di produzione. - Container: il profilo seccomp predefinito di Docker blocca la syscall
perf_event_open. La soluzione più semplice è profilare dall’host specificando il PID del processo containerizzato:sudo perf top -p PID. - Macchine virtuali e cloud: i contatori hardware possono non essere esposti al guest. Per verificarlo:
perf stat -e cycles true— se l’output mostra<not supported>, bisogna ricorrere agli eventi software, che funzionano ovunque indipendentemente dalla virtualizzazione. - Simboli di debug mancanti: senza i simboli corretti, i report mostrano solo indirizzi esadecimali illeggibili. Servono pacchetti come
php8.3-fpm-dbgsymsu Ubuntu o l’equivalente ottenuto condebuginfo-installsu Fedora/RHEL; le versioni più recenti diperfpossono anche scaricare i simboli automaticamente da un server debuginfod. I simboli del kernel, invece, si leggono direttamente da/proc/kallsymse non richiedono pacchetti aggiuntivi (un pacchetto di debug del kernel serve solo per annotare le righe di codice sorgente). - Call graph senza frame pointer: per binari compilati senza frame pointer, serve l’opzione
--call-graph dwarf, che però produce file molto più grandi. Per questo motivo le registrazioni con questa opzione vanno tenute brevi.
Quick reference
perf stat COMANDO
sudo perf stat -p PID sleep 10
sudo perf top
sudo perf top -p PID
sudo perf record -g -a sleep 30
sudo perf report
sudo perf trace -p PID
sudo perf trace --summary -p PID sleep 10
perf list
Conclusione
perf è uno degli strumenti più potenti a disposizione di un sistemista Linux per capire cosa succede davvero dentro una CPU sotto stress, ma la sua potenza è anche la sua complessità: i numeri grezzi (IPC, cache miss, branch miss) hanno senso solo se interpretati nel contesto del workload specifico, e un profilo CPU da solo non racconta tutta la storia quando il collo di bottiglia è l’I/O. La combinazione vincente, nella pratica, è quasi sempre la stessa: perf stat per una prima diagnosi rapida, perf record + flame graph per individuare visivamente dove si concentra il tempo, e strumenti complementari come iostat o perf trace per coprire i casi in cui il problema non è la CPU ma l’attesa.
Fonte: linuxblog.io.