Embora os desenvolvedores de apps clientes e da Web de front-end usem ferramentas como o Android Studio CPU Profiler ou as ferramentas de criação de perfil incluídas no Chrome para melhorar o desempenho do código, técnicas equivalentes não são tão acessíveis ou bem adotadas por quem trabalha com serviços de back-end. O Stackdriver Profiler oferece os mesmos recursos para desenvolvedores de serviços, independente de o código estar sendo executado no Google Cloud Platform ou em outro lugar.

A ferramenta coleta informações de uso de CPU e de alocação de memória dos aplicativos em produção. O Profiler atribui essas informações ao código-fonte do aplicativo para você identificar as partes que consomem mais recursos e conferir as características de desempenho do código. A baixa sobrecarga das técnicas de coleta usadas pela ferramenta a torna adequada para uso contínuo em ambientes de produção.
Neste codelab, você vai aprender a configurar o Stackdriver Profiler para um programa Go e conhecer os tipos de insights sobre o desempenho do aplicativo que a ferramenta pode apresentar.
O que você vai aprender
- Como configurar um programa Go para criação de perfil com o Stackdriver Profiler.
- Como coletar, visualizar e analisar os dados de desempenho com o Stackdriver Profiler.
O que é necessário
- Um projeto do Google Cloud Platform
- Um navegador, como o Chrome ou o Firefox
- Conhecer os editores de texto padrão do Linux, como vim, emacs ou nano
Como você vai usar este tutorial?
Como você classificaria sua experiência com o Google Cloud Platform?
Configuração de ambiente autoguiada
Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma. Faça login no Console do Google Cloud Platform (console.cloud.google.com) e crie um projeto:
Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID.
Em seguida, ative o faturamento no console do Cloud para usar os recursos do Google Cloud.
A execução por meio deste codelab terá um custo baixo, mas poderá ser mais se você decidir usar mais recursos ou se deixá-los em execução. Consulte a seção "limpeza" no final deste documento.
Novos usuários do Google Cloud Platform têm direito a uma avaliação sem custo financeiro de US$300.
Google Cloud Shell
Embora o Google Cloud possa ser operado remotamente em seu laptop, para simplificar a configuração neste codelab, vamos usar o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.
Ativar o Google Cloud Shell
No Console do GCP, clique no ícone do Cloud Shell na barra de ferramentas localizada no canto superior direito:
Em seguida, clique em "Start Cloud Shell":
O provisionamento e a conexão ao ambiente levarão apenas alguns instantes para serem concluídos:
Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Praticamente todo o seu trabalho neste laboratório pode ser feito em um navegador ou no seu Google Chromebook.
Depois de se conectar ao Cloud Shell, sua conta já estará autenticada e o projeto estará configurado com seu PROJECT_ID.
Execute o seguinte comando no Cloud Shell para confirmar se a conta está autenticada:
gcloud auth list
Resposta ao comando
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Resposta ao comando
[core] project = <PROJECT_ID>
Se o projeto não estiver configurado, configure-o usando este comando:
gcloud config set project <PROJECT_ID>
Resposta ao comando
Updated property [core/project].
No console do Cloud, acesse a interface do Profiler clicando em "Profiler" na barra de navegação à esquerda:

Como alternativa, use a barra de pesquisa do Console do Cloud para navegar até a interface do Profiler: basta digitar "Stackdriver Profiler" e selecionar o item encontrado. De qualquer forma, a interface do Profiler vai aparecer com a mensagem "Sem dados para mostrar", como abaixo. O projeto é novo, então ainda não tem dados de criação de perfil coletados.

Agora é hora de criar um perfil de algo.
Vamos usar um aplicativo Go sintético simples disponível no GitHub. No terminal do Cloud Shell que ainda está aberto (e enquanto a mensagem "Nenhum dado para mostrar" ainda aparece na interface do Profiler), execute o seguinte comando:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
Em seguida, mude para o diretório do aplicativo:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
O diretório contém o arquivo "main.go", que é um app sintético com o agente de criação de perfil ativado:
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)
}
...
}Por padrão, o agente de criação de perfil coleta perfis de CPU, heap e linhas de execução. O código aqui permite a coleta de perfis de mutex (também conhecido como "contenção").
Agora, execute o programa:
$ go run main.go
À medida que o programa é executado, o agente de criação de perfil coleta periodicamente perfis dos cinco tipos configurados. A coleta é aleatória ao longo do tempo (com uma taxa média de um perfil por minuto para cada um dos tipos). Portanto, pode levar até três minutos para coletar cada um dos tipos. O programa informa quando um perfil é criado. As mensagens são ativadas pela flag DebugLogging na configuração acima. Caso contrário, o agente é executado silenciosamente:
$ 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 ...
A interface vai se atualizar logo após a coleta do primeiro perfil. Depois disso, ele não será atualizado automaticamente. Para ver os novos dados, atualize a interface do Profiler manualmente. Para fazer isso, clique duas vezes no botão "Agora" no seletor de intervalo de tempo:

Depois que a interface for atualizada, você verá algo assim:

O seletor de tipo de perfil mostra os cinco tipos disponíveis:

Agora vamos analisar cada um dos tipos de perfil e alguns recursos importantes da interface, além de realizar alguns experimentos. Nesta etapa, você não precisa mais do terminal do Cloud Shell. Para sair, pressione CTRL-C e digite "exit".
Agora que coletamos alguns dados, vamos analisá-los mais de perto. Estamos usando um app sintético (a fonte está disponível no GitHub) que simula comportamentos típicos de diferentes tipos de problemas de performance em produção.
Código com uso intensivo da CPU
Selecione o tipo de perfil de CPU. Depois que a interface carregar, você vai ver no gráfico de chamas os quatro blocos de folhas da função load, que representam todo o consumo de CPU:

Essa função foi escrita especificamente para consumir muitos ciclos de CPU executando um loop apertado:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}A função é chamada indiretamente de busyloop() por quatro caminhos de chamada: busyloop → {foo1, foo2} → {bar, baz} → load. A largura de uma caixa de função representa o custo relativo do caminho de chamada específico. Nesse caso, todos os quatro caminhos têm aproximadamente o mesmo custo. Em um programa real, você precisa se concentrar na otimização dos caminhos de chamada mais importantes em termos de performance. O gráfico de chamas, que enfatiza visualmente os caminhos mais caros com caixas maiores, facilita a identificação desses caminhos.
Você pode usar o filtro de dados do perfil para refinar ainda mais a exibição. Por exemplo, adicione um filtro "Mostrar rastreamentos de pilha" especificando "baz" como a string de filtro. Você vai ver algo como a captura de tela abaixo, em que apenas dois dos quatro caminhos de chamada para load() são mostrados. Esses dois caminhos são os únicos que passam por uma função com a string "baz" no nome. Essa filtragem é útil quando você quer se concentrar em uma subparte de um programa maior, por exemplo, porque você só tem parte dele.

Código com uso intensivo de memória
Agora mude para o tipo de perfil "Heap". Remova todos os filtros criados em experimentos anteriores. Agora, um gráfico de chamas vai aparecer, em que allocImpl, chamado por alloc, é mostrado como o principal consumidor de memória no app:

A tabela de resumo acima do gráfico de chamas indica que a quantidade total de memória usada no app é de aproximadamente 57,4 MiB, e a maior parte dela é alocada pela função allocImpl. Isso não é surpreendente, considerando a implementação dessa função:
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))
}
}A função é executada uma vez, alocando 64 MiB em partes menores e armazenando ponteiros para essas partes em uma variável global para protegê-las da coleta de lixo. A quantidade de memória mostrada como usada pelo criador de perfil é um pouco diferente de 64 MiB. O criador de perfil de heap do Go é uma ferramenta estatística, então as medições são de baixa sobrecarga, mas não precisas em bytes. Não se surpreenda ao ver uma diferença de cerca de 10% como essa.
Código com uso intensivo de E/S
Se você escolher "Threads" no seletor de tipo de perfil, a exibição vai mudar para um gráfico de chama em que a maior parte da largura é ocupada pelas funções wait e waitImpl:

No resumo acima do gráfico de chamas, é possível ver que há 100 gorrotinas que aumentam a pilha de chamadas da função wait. Isso está correto, já que o código que inicia essas esperas é assim:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}Esse tipo de perfil é útil para entender se o programa passa um tempo inesperado em espera (como E/S). Essas pilhas de chamadas normalmente não são amostradas pelo criador de perfil da CPU, já que não consomem uma parte significativa do tempo da CPU. Muitas vezes, é útil usar filtros "Ocultar pilhas" com perfis de threads. Por exemplo, para ocultar todas as pilhas que terminam com uma chamada para gopark,, já que geralmente são gorrotinas inativas e menos interessantes do que as que aguardam E/S.
O tipo de perfil de linhas de execução também pode ajudar a identificar pontos no programa em que as linhas de execução estão aguardando um mutex pertencente a outra parte do programa por um longo período, mas o tipo de perfil a seguir é mais útil para isso.
Código com uso intenso de contenção
O tipo de perfil de disputa identifica os bloqueios mais "desejados" no programa. Esse tipo de perfil está disponível para programas Go, mas precisa ser ativado explicitamente especificando "MutexProfiling: true" no código de configuração do agente. A coleta funciona registrando (na métrica "Disputas") o número de vezes em que um bloqueio específico, ao ser desbloqueado por uma gorrotina A, tinha outra gorrotina B aguardando o desbloqueio. Ele também registra (na métrica "Atraso") o tempo que a gorrotina bloqueada esperou pelo bloqueio. Neste exemplo, há uma única pilha de disputa e o tempo total de espera do bloqueio foi de 11,03 segundos:

O código que gera esse perfil consiste em quatro gorrotinas disputando um 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)
}
}Neste laboratório, você aprendeu a configurar um programa Go para usar com o Stackdriver Profiler. Você também aprendeu a coletar, visualizar e analisar os dados de performance com essa ferramenta. Agora você pode aplicar sua nova habilidade aos serviços reais que executa no Google Cloud Platform.
Você aprendeu a configurar e usar o Stackdriver Profiler.
Saiba mais
- Stackdriver Profiler: https://cloud.google.com/profiler/
- Pacote Go runtime/pprof usado pelo Stackdriver Profiler: https://golang.org/pkg/runtime/pprof/
Licença
Este conteúdo está sob a licença Atribuição 2.0 Genérica da Creative Commons.