Individuare con PerfView e dotnet-trace la causa di un rallentamento — introduzione pratica all'analisi delle prestazioni .NET

· · PerfView, dotnet-trace, ETW, Analisi delle prestazioni, .NET, CSharp, Indagine sui bug, Sviluppo Windows, Consulenza tecnica

Nel precedente articolo, Introduzione al registro eventi di Windows e a ETW, abbiamo spiegato ETW e la definizione di eventi personalizzati con EventSource, lasciando PerfView fuori ambito. Qui riprendiamo da quel punto. Vedremo come usare PerfView e dotnet-trace per individuare la funzione esatta responsabile quando un’app aziendale è lenta, satura la CPU o si blocca occasionalmente.

L’argomento è CPU e tempo di risposta. Per la memoria che cresce continuamente, vedere Distinguere pause GC e perdite di memoria in .NET; per i crash, Leggere crash dump con WinDbg + SOS.

1. Prima la conclusione

  • Esistono due tipi di lentezza: CPU satura, CPU bound, e CPU inattiva ma app in attesa, blocked. La prima richiede campionamento CPU; la seconda ThreadTime, cioè context switch. La raccolta CPU predefinita non mostra la causa del secondo caso.1
  • Se non siete sicuri, iniziate con dotnet-trace. È multipiattaforma e basato su EventPipe; lo stesso utente che avvia il processo può raccogliere senza privilegi di amministratore. I file .nettrace si aprono in PerfView e Visual Studio.23
  • Usate PerfView quando servono l’intera macchina, codice nativo o tempo bloccato. ETW include eventi kernel e stack nativi, ma avviare una sessione richiede privilegi di amministratore.3
  • Il campionamento CPU avviene per impostazione predefinita una volta ogni millisecondo, per processore. Leggete un campione come circa 1 ms di CPU e raccogliete almeno 1.000 campioni, idealmente circa 5.000, prima di concludere.1
  • La lettura dipende dalla differenza fra inclusive e exclusive. Exclusive indica dove la funzione stessa consuma CPU; inclusive indica che qualcosa più in basso nell’albero di chiamate è pesante.
  • Definite «lento» come una singola operazione misurabile. «L’app è generalmente lenta» non è una raccolta leggibile; fissate invece qualcosa come «questo report richiede 40 secondi» e misurate quella operazione.
Sintomo Primo strumento Cosa osservare
CPU costantemente alta dotnet-trace collect / CPU Stacks di PerfView Funzioni con exclusive alto
CPU bassa ma elaborazione lenta o bloccata PerfView con ThreadTime Quale thread attende che cosa
Si vuole prima una tendenza generale dotnet-counters CPU, frequenza GC, coda ThreadPool
Si sospetta troppo GC dotnet-counters → dotnet-trace, eventi GC Numero e pause GC, punti con più allocazioni
Si vuole localizzare una specifica operazione business Eventi EventSource + strumenti precedenti Tempo fra eventi e stack in quell’intervallo

2. ETW ed EventPipe: il rapporto fra gli strumenti

Gli strumenti sembrano molti, ma le fondamenta sono due.

  • ETW, Event Tracing for Windows: infrastruttura di traccia di tutto il sistema operativo. Registra kernel e applicazioni sulla stessa linea temporale, ma richiede privilegi di amministratore e funziona solo su Windows.3
  • EventPipe: meccanismo di traccia incorporato nel runtime .NET. Non richiede amministratore e si comporta allo stesso modo su tutti i sistemi, ma vede solo codice gestito e runtime: non cattura eventi kernel o stack nativi.2
  dotnet-trace, EventPipe PerfView, ETW
Privilegi amministrativi Non richiesti per un processo dello stesso utente Richiesti
Destinazione Un processo .NET specificato Intera macchina, processi e kernel
Stack nativi Non acquisibili Acquisibili
Tempo bloccato / context switch Non acquisibile Acquisibile con ThreadTime
Sistemi operativi Windows / Linux / macOS Solo Windows

Da qui deriva il flusso naturale: iniziare con una raccolta leggera in dotnet-trace e passare alla raccolta ETW completa con PerfView se i dati non bastano.

3. Prima della traccia — dieci minuti con dotnet-counters

Prima di raccogliere una traccia, spesso è più rapido osservare le metriche principali del runtime con dotnet-counters.4

dotnet tool install --global dotnet-counters
dotnet-counters ps
dotnet-counters monitor --process-id <PID> --counters System.Runtime

Osservate uso CPU, dimensione e numero di GC dell’heap, lunghezza della coda e numero di thread del ThreadPool, oltre al numero di eccezioni. Se vedete «GC più volte al secondo» o «coda ThreadPool in crescita», sapete se la raccolta successiva deve concentrarsi su allocazioni o blocchi. La lettura delle tendenze di memoria è spiegata in Distinguere pause GC e perdite di memoria in .NET.

4. Raccogliere una traccia con dotnet-trace

dotnet-trace è uno strumento EventPipe.5 Installazione e raccolta di base:

dotnet tool install --global dotnet-trace
dotnet-trace ps
dotnet-trace collect --process-id <PID> --duration 00:00:00:30

Senza opzioni, raccoglie con il profilo predefinito, che include gli eventi principali del runtime e il campionamento dei thread. Il vecchio profilo cpu-sampling è stato ritirato; oggi il predefinito combina dotnet-common e dotnet-sampled-thread-time.5 Potete anche scegliere --profile gc-verbose per GC e campionamento allocazioni, oppure --profile gc-collect per registrare soltanto le occorrenze GC con basso overhead.

Per raccogliere anche il provider EventSource personalizzato dell’articolo precedente, aggiungete --providers.

dotnet-trace collect --process-id <PID> --providers KomuraSoft-OrderService

Il risultato è un file .nettrace. Potete aprirlo in Visual Studio o PerfView, oppure convertirlo in formato speedscope e visualizzarlo nel browser.5

dotnet-trace convert trace.nettrace --format Speedscope

Il formato speedscope è un flame graph leggero visualizzabile in speedscope.app, utile per capire intuitivamente dove vada il tempo e sotto quale albero di chiamate. Per arrivare alla funzione esatta, lo stack viewer di PerfView è più potente.

5. Indagare la CPU con PerfView — leggere lo Stack Viewer

PerfView è uno strumento gratuito di analisi delle prestazioni pubblicato da Microsoft. Si avvia scaricando il singolo file PerfView.exe dalla pagina delle release GitHub, senza installazione.6

5.1 Raccolta

Nella GUI, aprite «Collect > Collect», fate clic su Start Collection, riproducete l’operazione bersaglio e poi fate clic su Stop Collection. Da riga di comando:

PerfView collect /nogui /acceptEULA /maxCollectSec:30

Eseguite come amministratore, perché PerfView deve avviare una sessione ETW.3 La raccolta predefinita include un campione CPU di 1 millisecondo, con stack di chiamate, su ogni processore e gli eventi CLR principali.1 L’overhead di questa configurazione è normalmente di pochi punti percentuali.7

5.2 Leggere CPU Stacks

Aprite il risultato raccolto, .etl.zip, selezionate il processo bersaglio in «CPU Stacks» e si apre lo stack viewer. Nella scheda By Name guardate subito queste colonne:

  • Exc, exclusive: numero di campioni in cui stava eseguendo la funzione stessa. È il luogo che consuma realmente CPU.
  • Inc, inclusive: totale della funzione e di tutto ciò che chiama. Mostra che qualcosa più in basso nell’albero è pesante.

Il metodo standard è ordinare per Exc e chiedersi dall’alto «è il mio codice o runtime/libreria?». Se il vostro codice è alto nella lista Exc, l’algoritmo o ciclo di quella funzione è il bersaglio diretto. Se è alta invece un’interna di System.String o un serializzatore JSON, risalite dalla scheda Callers usando Inc e individuate da dove nel vostro codice viene chiamata con tanta frequenza.

Controllate anche il numero di campioni. I campioni CPU sono statistiche a intervallo di 1 ms e, con un numero basso, domina il caso. Raccogliete almeno 1.000 campioni, idealmente circa 5.000. Se non bastano, prolungate la raccolta ripetendo l’operazione bersaglio.1

Se una funzione del vostro modulo compare solo come indirizzo, i simboli PDB non sono stati risolti. Conservare i PDB degli artefatti di build è ciò che rende possibile l’indagine. Vedere Che cos’è un PDB, Program Database?.

6. «Lento anche se la CPU è inattiva» — tempo bloccato con ThreadTime

Più della metà delle lentezze reali non riguarda la CPU, ma l’attesa: lock, risposta DB o HTTP, I/O di file, attesa simile a deadlock tramite Task.Result. Nei campioni CPU questa classe di problemi appare come tempo in cui la CPU non veniva usata, perciò la raccolta predefinita non ne rivela la causa.

Attivando Thread Time in PerfView si registrano anche gli eventi di context switch. Si può seguire nella stessa vista quanto tempo ogni thread usi CPU e quanto resti bloccato.1

PerfView collect /nogui /acceptEULA /threadTime /maxCollectSec:30

Per l’analisi aprite «Thread Time Stacks». È lo stesso stack viewer di CPU Stacks, ma i campioni includono BLOCKED_TIME, tempo bloccato, oltre al tempo CPU. Restringetevi all’intervallo dell’operazione lenta e guardate quale stack, cioè quale attesa, accumuli BLOCKED_TIME per il thread che svolgeva il lavoro. Potete arrivare così a una spiegazione come «35 dei 40 secondi totali sono stati spesi in attesa di questa chiamata HTTP».

Quando l’interfaccia smette di rispondere per alcuni secondi dopo un’azione, il nucleo del problema è quasi sempre il thread UI bloccato. Una volta identificato con ThreadTime ciò che lo blocca, la correzione definitiva è riprogettare l’attesa sincrona come asincrona. Per il lato progettuale, vedere Scheda sintetica su async e thread UI in WPF/WinForms.

Poiché ThreadTime registra un evento per ogni context switch, il volume di dati è sensibilmente maggiore della raccolta predefinita. Mantenete breve la finestra di raccolta ed evitate ThreadTime prolungato in produzione.

7. Unirlo a EventSource — capire in termini di business «quale sezione è lenta»

CPU Stacks e Thread Time sono aggregati sull’intero processo. Se volete suddividere una singola operazione di business, per esempio «dove va il tempo in questa esecuzione di elaborazione ordine», entrano in gioco gli eventi Start/Stop di EventSource dell’articolo precedente.

  • Nell’app, strumentate eventi come OrderProcessingStart e OrderProcessingStop; per l’implementazione vedere l’articolo precedente.
  • Verificate la temporizzazione dell’evento nella vista «Events» di PerfView e restringete l’intervallo dello stack viewer, filtro Start/End, a quella finestra.
  • Leggete in quel periodo la ripartizione fra CPU e tempo bloccato.

Così si analizza isolatamente «quella singola esecuzione lenta» senza sommergerla nel rumore delle esecuzioni normali. Gli eventi personalizzati possono essere raccolti anche con dotnet-trace; dopo avere aggiunto la strumentazione, la stessa tecnica funziona su sviluppo e produzione. Nella pratica, la difficoltà dell’indagine dipende meno dall’abilità con lo strumento che dalla presenza di punti di osservazione nell’app.

  • Il punto di partenza è dividere «lento» in CPU-bound e bloccato. Il primo appare nel campionamento CPU, 1 campione ≈ 1 ms; il secondo nella raccolta ThreadTime.
  • La divisione di base è dotnet-trace prima, senza privilegi di amministratore, e PerfView quando servono intera macchina, codice nativo o tempo bloccato. Raccogliere con dotnet-trace e leggere in PerfView è una combinazione standard.
  • Il nucleo della lettura dello stack viewer è la distinzione fra exclusive e inclusive; verificate sempre di avere abbastanza campioni.
  • Eventi EventSource personalizzati che segnino i confini di un’operazione business alzano ulteriormente la precisione dell’indagine.

Komura Software LLC svolge indagini sui problemi di prestazioni delle app aziendali Windows — lentezza, blocchi e CPU satura — oltre a consulenza sulla strumentazione e sul design che rendono un’app più misurabile. Se avete procedura di riproduzione e file di traccia, possiamo occuparci anche della sola analisi.

Articoli correlati

Ambiti di consulenza correlati

Riferimenti

  1. GitHub, PerfView User’s Guide. La raccolta predefinita usa un campione CPU con stack ogni millisecondo per processore; per concludere sono desiderabili circa 1.000-5.000 campioni; Thread Time raccoglie context switch e consente l’analisi del tempo bloccato.  2 3 4 5

  2. Microsoft Learn, EventPipe Overview. EventPipe traccia app .NET senza dipendere da un componente ad alto privilegio come l’amministratore; il suo ambito è codice gestito e runtime, non eventi kernel né stack nativi.  2

  3. Microsoft Learn, Collect and View EventSource Traces. La raccolta di tracce ETW richiede sempre privilegi di amministratore; Visual Studio e PerfView possono aprire file .nettrace.  2 3 4

  4. Microsoft Learn, dotnet-counters diagnostic tool. Monitoraggio di CPU, GC, ThreadPool e altre metriche di un processo in esecuzione tramite contatori System.Runtime. 

  5. Microsoft Learn, dotnet-trace diagnostic tool. Uso di collect, profili predefiniti dotnet-common / dotnet-sampled-thread-time, ritiro di cpu-sampling, profili gc-verbose / gc-collect e conversione in Speedscope / Chromium.  2 3

  6. GitHub, microsoft/perfview. PerfView è uno strumento gratuito per indagare problemi di prestazioni CPU e memoria; l’eseguibile è disponibile direttamente nella pagina delle release. 

  7. GitHub, microsoft/perfview Issue #598. Un maintainer di PerfView indica un overhead della raccolta predefinita intorno al 3% e la possibilità di ridurre il carico regolando l’intervallo con /CpuSampleMSec. 

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Devo usare PerfView o dotnet-trace?
Iniziate con dotnet-trace: non richiede privilegi di amministratore, può mirare a un solo processo e si usa da una riga di comando. Se servono stack di codice nativo, lo stato dell'intera macchina o il tempo bloccato per singolo context switch, occorre PerfView basato su ETW. È comune raccogliere con dotnet-trace e aprire poi il file .nettrace in PerfView.
Qual è il rapporto con il profiler di Visual Studio?
Se il problema si riproduce su una macchina di sviluppo, gli strumenti CPU e memoria di Visual Studio sono più comodi e spesso sufficienti. PerfView e dotnet-trace servono quando bisogna raccogliere su test o produzione senza Visual Studio, separare raccolta e analisi su macchine diverse o vedere eventi ETW, inclusi EventSource personalizzati e kernel. Visual Studio può aprire anche .nettrace.
È sicuro usarli in produzione?
Sono pensati anche per raccolte brevi in produzione, ma limitate la durata da decine di secondi a pochi minuti, provate prima in staging e verificate spazio disco, overhead e dimensione file. ThreadTime e il tracing delle allocazioni generano molti eventi. Una traccia può contenere argomenti della riga di comando: trattatela come un artefatto sensibile.
Dove si collocano WPR e WPA?
WPR e WPA sono strumenti più orientati al sistema operativo e basati sullo stesso ETW. Per analisi dettagliate di I/O disco, alimentazione e avvio dell'intera macchina sono più forti. Per CPU, GC e tempo bloccato di un'app .NET, PerfView è più leggibile perché ottimizzato per codice gestito.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog