1. Panoramica
Anche se gli sviluppatori web di app client e frontend usano in genere strumenti come Android Studio CPU Profiler o gli strumenti di profilazione inclusi in Chrome per migliorare le prestazioni del codice, le tecniche equivalenti non sono state quasi altrettanto accessibili o adottate dagli utenti che lavorano sui servizi di backend. Cloud Profiler offre queste stesse funzionalità agli sviluppatori di servizi, indipendentemente dal fatto che il loro codice sia in esecuzione su Google Cloud o altrove.
Lo strumento raccoglie informazioni sull'utilizzo della CPU e sull'allocazione della memoria dalle applicazioni di produzione. Attribuisce queste informazioni al codice sorgente dell'applicazione, aiutandoti a identificare le parti dell'applicazione che consumano più risorse e indicando le caratteristiche prestazionali del codice. Il basso overhead delle tecniche di raccolta impiegate dallo strumento lo rende adatto all'uso continuo in ambienti di produzione.
In questo codelab imparerai a configurare Cloud Profiler per un programma Go e acquisirai familiarità con il tipo di insight sulle prestazioni delle applicazioni che lo strumento può presentare.
Cosa imparerai a fare
- Come configurare un programma Go per la profilazione con Cloud Profiler.
- Come raccogliere, visualizzare e analizzare i dati delle prestazioni con Cloud Profiler.
Che cosa ti serve
- Un progetto Google Cloud
- Un browser, ad esempio Chrome o Firefox
- Familiarità con gli editor di testo standard di Linux, ad esempio Vim, EMAC o Nano.
Come utilizzerai questo tutorial?
Come giudichi la tua esperienza con la piattaforma Google Cloud?
2. Configurazione e requisiti
Configurazione dell'ambiente da seguire in modo autonomo
- Accedi alla console Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.
Ricorda l'ID progetto, un nome univoco in tutti i progetti Google Cloud (il nome precedente è già stato utilizzato e non funzionerà correttamente). Verrà indicato più avanti in questo codelab come PROJECT_ID
.
- Successivamente, dovrai abilitare la fatturazione in Cloud Console per utilizzare le risorse Google Cloud.
Eseguire questo codelab non dovrebbe costare molto. Assicurati di seguire le istruzioni nella sezione "Pulizia" in cui viene spiegato come arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial. I nuovi utenti di Google Cloud sono idonei al programma prova senza costi di 300$.
Google Cloud Shell
Anche se Google Cloud può essere utilizzato da remoto dal tuo laptop, per semplificare la configurazione in questo codelab utilizzeremo Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.
Attiva Cloud Shell
- Dalla console Cloud, fai clic su Attiva Cloud Shell .
Se non hai mai avviato Cloud Shell, ti viene mostrata una schermata intermedia (below the fold) che descrive di cosa si tratta. In tal caso, fai clic su Continua (e non la vedrai più). Ecco come appare quella singola schermata:
Il provisioning e la connessione a Cloud Shell dovrebbero richiedere solo qualche istante.
Questa macchina virtuale viene caricata con tutti gli strumenti di sviluppo di cui hai bisogno. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Gran parte, se non tutto, del lavoro in questo codelab può essere svolto semplicemente con un browser o Chromebook.
Una volta eseguita la connessione a Cloud Shell, dovresti vedere che il tuo account è già autenticato e il progetto è già impostato sul tuo ID progetto.
- Esegui questo comando in Cloud Shell per verificare che l'account sia autenticato:
gcloud auth list
Output comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Esegui questo comando in Cloud Shell per confermare che il comando gcloud è a conoscenza del tuo progetto:
gcloud config list project
Output comando
[core] project = <PROJECT_ID>
In caso contrario, puoi impostarlo con questo comando:
gcloud config set project <PROJECT_ID>
Output comando
Updated property [core/project].
3. Vai a Cloud Profiler
Nella console Cloud, passa alla UI di Profiler facendo clic su "Profiler". nella barra di navigazione a sinistra:
In alternativa, puoi utilizzare la barra di ricerca della console Cloud per accedere alla UI di Profiler: basta digitare "Cloud Profiler" e seleziona l'elemento trovato. In ogni caso, dovresti visualizzare l'interfaccia utente di Profiler con il messaggio "Nessun dato da visualizzare" come mostrato di seguito. Il progetto è nuovo, pertanto non sono ancora stati raccolti dati di profilazione.
È giunto il momento di profilare qualcosa.
4. Profila il benchmark
Utilizzeremo una semplice applicazione sintetica Go disponibile su GitHub. Nel terminale Cloud Shell ancora aperto (e anche se il messaggio "Nessun dato da visualizzare" è ancora visualizzato nella UI di Profiler), esegui questo comando:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
Quindi passa alla directory delle applicazioni:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
La directory contiene il file "main.go" un'app sintetica in cui è abilitato l'agente di profilazione:
main.go
...
import (
...
"cloud.google.com/go/profiler"
)
...
func main() {
err := profiler.Start(profiler.Config{
Service: "hotapp-service",
DebugLogging: true,
MutexProfiling: true,
})
if err != nil {
log.Fatalf("failed to start the profiler: %v", err)
}
...
}
L'agente di profilazione raccoglie i profili di CPU, heap e thread per impostazione predefinita. Il codice in questo campo consente la raccolta di profili mutex (noti anche come profili "contesa").
Ora esegui il programma:
$ go run main.go
Durante l'esecuzione del programma, l'agente di profilazione raccoglierà periodicamente profili dei cinque tipi configurati. La raccolta è randomizzata nel tempo (con una tariffa media di un profilo al minuto per ciascun tipo), pertanto la raccolta di ciascun tipo potrebbe richiedere fino a tre minuti. Il programma ti informa quando crea un profilo. I messaggi sono abilitati dal flag DebugLogging
nella configurazione precedente; altrimenti l'agente esegue in modalità silenziosa:
$ go run main.go 2018/03/28 15:10:24 profiler has started 2018/03/28 15:10:57 successfully created profile THREADS 2018/03/28 15:10:57 start uploading profile 2018/03/28 15:11:19 successfully created profile CONTENTION 2018/03/28 15:11:30 start uploading profile 2018/03/28 15:11:40 successfully created profile CPU 2018/03/28 15:11:51 start uploading profile 2018/03/28 15:11:53 successfully created profile CONTENTION 2018/03/28 15:12:03 start uploading profile 2018/03/28 15:12:04 successfully created profile HEAP 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:04 successfully created profile THREADS 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:25 successfully created profile HEAP 2018/03/28 15:12:25 start uploading profile 2018/03/28 15:12:37 successfully created profile CPU ...
La UI si aggiornerà poco dopo la raccolta del primo profilo. In seguito, non si aggiornerà automaticamente, quindi per vedere i nuovi dati dovrai aggiornare manualmente l'interfaccia utente di Profiler. Per farlo, fai clic due volte sul pulsante Ora nel selettore dell'intervallo di tempo:
Dopo l'aggiornamento dell'interfaccia utente, vedrai una schermata simile alla seguente:
Il selettore del tipo di profilo mostra i cinque tipi di profilo disponibili:
Esaminiamo ora tutti i tipi di profilo e alcune importanti funzionalità dell'interfaccia utente per poi condurre alcuni esperimenti. In questa fase non hai più bisogno del terminale Cloud Shell, quindi puoi chiuderlo premendo Ctrl-C e digitando "exit".
5. Analizza i dati del profiler
Ora che abbiamo raccolto alcuni dati, esaminiamoli più nel dettaglio. Stiamo usando un'app sintetica (il codice sorgente è disponibile su GitHub) che simula comportamenti tipici di diversi tipi di problemi di prestazioni in produzione.
Codice ad alta intensità di CPU
Seleziona il tipo di profilo CPU. Dopo che l'interfaccia utente è stata caricata, vedrai nel grafico a fiamme i quattro blocchi foglia per la funzione load
, che tengono conto collettivamente di tutto il consumo della CPU:
Questa funzione è scritta specificamente per consumare molti cicli della CPU eseguendo un stretto loop:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
La funzione viene chiamata indirettamente da busyloop
() tramite quattro percorsi di chiamata: busyloop
→ {foo1
, foo2
} → {bar
, baz
} → load
. La larghezza di un riquadro funzione rappresenta il costo relativo del percorso di chiamata specifico. In questo caso, tutti e quattro i percorsi hanno all'incirca lo stesso costo. In un programma reale, vuoi concentrarti sull'ottimizzazione dei percorsi di chiamata più importanti in termini di rendimento. Il grafico a fiamme, che mette in risalto visivamente i percorsi più costosi con scatole più grandi, rende questi percorsi facili da identificare.
Puoi utilizzare il filtro dei dati del profilo per perfezionare ulteriormente la visualizzazione. Ad esempio, prova ad aggiungere un "Mostra filtri" filtro che specifica "baz" come stringa di filtro. Dovrebbe essere visualizzato un risultato simile a quello nello screenshot riportato di seguito, in cui vengono mostrati solo due dei quattro percorsi di chiamata a load()
. Questi due percorsi sono gli unici che passano attraverso una funzione con la stringa "baz" nel nome. Questo filtro è utile quando vuoi concentrarti su una sottoparte di un programma più grande (ad esempio, perché ne possiedi solo una parte).
Codice che richiede molta memoria
Ora passa ad "Heap". tipo di profilo. Assicurati di rimuovere tutti i filtri che hai creato negli esperimenti precedenti. Ora dovresti vedere un grafico a fiamme in cui allocImpl
, chiamato da alloc
, è visualizzato come il principale consumer della memoria nell'app:
La tabella di riepilogo sopra il grafico a fiamme indica che la quantità totale di memoria utilizzata nell'app è in media di ~57,4 MiB, la maggior parte della quale viene allocata dalla funzione allocImpl
. Non sorprende, data l'implementazione di questa funzione:
main.go
func allocImpl() {
// Allocate 64 MiB in 64 KiB chunks
for i := 0; i < 64*16; i++ {
mem = append(mem, make([]byte, 64*1024))
}
}
La funzione viene eseguita una volta, allocando 64 MiB in blocchi più piccoli, quindi archiviando i puntatori a questi blocchi in una variabile globale per proteggerli dalla garbage collection. Tieni presente che la quantità di memoria mostrata come utilizzata dal profiler è leggermente diversa da 64 MiB: il profiler heap di Go è uno strumento statistico, quindi le misurazioni sono a basso overhead ma non precise in byte. Non sorprenderti quando vedi una differenza di circa il 10% come questa.
Codice ad alta intensità di IO
Se scegli "Thread" nel selettore del tipo di profilo, il display passerà a un grafico a fiamme in cui la maggior parte della larghezza viene occupata dalle funzioni wait
e waitImpl
:
Nel grafico a fiamme sopra riportato, puoi vedere che ci sono 100 goroutine che aumentano il loro stack di chiamate dalla funzione wait
. È esattamente così, dato che il codice che avvia queste attese è simile al seguente:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
Questo tipo di profilo è utile per capire se il programma prevede tempi di attesa imprevisti (come I/O). Questi stack di chiamate in genere non vengono campionati dal profiler della CPU, poiché non consumano alcuna parte significativa del tempo di CPU. Ti consigliamo di usare "Nascondi serie di filtri" filtri con profili Thread, ad esempio per nascondere tutti gli stack che terminano con una chiamata a gopark,
, poiché spesso si tratta di goroutine inattive e meno interessanti di quelle che attendono su I/O.
Il tipo di profilo dei thread può anche aiutare a identificare i punti del programma in cui i thread sono in attesa di un mutex di proprietà di un'altra parte del programma per un lungo periodo, ma il seguente tipo di profilo è più utile per questo.
Codice ad alta intensità di contesa
Il tipo di profilo Contesa identifica il tipo più "desiderato" i blocchi nel programma. Questo tipo di profilo è disponibile per i programmi Go, ma deve essere attivato esplicitamente specificando "MutexProfiling: true
" nel codice di configurazione dell'agente. La raccolta registra (sotto la metrica "Contese") il numero di volte in cui una specifica serratura, quando viene aperta da una goroutine A, ha un'altra goroutine B in attesa che la serratura venga aperta. Inoltre, registra (nella metrica "Ritardo") il tempo di attesa della goroutine bloccata per la serratura. In questo esempio è presente un singolo stack di contesa e il tempo di attesa totale per il blocco è stato di 10, 5 secondi:
Il codice che genera questo profilo è costituito da quattro goroutine che si contendono un mutex:
main.go
func contention(d time.Duration) {
contentionImpl(d)
}
func contentionImpl(d time.Duration) {
for {
mu.Lock()
time.Sleep(d)
mu.Unlock()
}
}
...
func main() {
...
for i := 0; i < 4; i++ {
go contention(time.Duration(i) * 50 * time.Millisecond)
}
}
6. Riepilogo
In questo lab hai imparato come configurare un programma Go per l'utilizzo con Cloud Profiler. Hai anche imparato a raccogliere, visualizzare e analizzare i dati sul rendimento con questo strumento. Ora puoi applicare la tua nuova competenza ai servizi reali in esecuzione sulla piattaforma Google Cloud.
7. Complimenti!
Hai imparato a configurare e utilizzare Cloud Profiler.
Scopri di più
- Cloud Profiler: https://cloud.google.com/profiler/
- Pacchetto Go runtime/pprof utilizzato da Cloud Profiler: https://golang.org/pkg/runtime/pprof/
Licenza
Questo lavoro è concesso in licenza ai sensi di una licenza Creative Commons Attribution 2.0 Generic.