Analisar o desempenho de produção com o Cloud Profiler

1. Visão geral

Embora os desenvolvedores Web de front-end e de apps clientes geralmente usem ferramentas como o CPU Profiler do Android Studio ou as ferramentas de criação de perfis do Chrome para melhorar o desempenho do código, técnicas equivalentes ainda não foram tão acessíveis ou bem adotadas por quem trabalha com serviços de back-end. O Cloud Profiler oferece esses mesmos recursos para desenvolvedores de serviços, independentemente de o código ser executado no Google Cloud Platform ou em outro lugar.

95c034c70c9cac22.png

A ferramenta coleta informações sobre uso de CPU e 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. O baixo overhead das técnicas de coleta empregadas pela ferramenta faz com que ela seja adequada para uso contínuo em ambientes de produção.

Neste codelab, você vai aprender a configurar o Cloud Profiler para um programa Go e se familiarizar com 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 Cloud Profiler.
  • Como coletar, visualizar e analisar os dados de desempenho com o Cloud 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?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com o Google Cloud Platform?

Iniciante Intermediário Proficiente

2. Configuração e requisitos

Configuração de ambiente autoguiada

  1. Faça login no console do Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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.

  1. Em seguida, será necessário ativar o faturamento no Console do Cloud para usar os recursos do Google Cloud.

A execução deste codelab não será muito cara, se for o caso. Siga todas as instruções na seção "Limpeza", que orienta você sobre como encerrar recursos para não incorrer em cobranças além deste tutorial. Novos usuários do Google Cloud estão qualificados para o programa de US$300 de teste sem custo financeiro.

Google Cloud Shell

Embora o Google Cloud possa ser operado remotamente do seu laptop, para simplificar a configuração neste codelab, usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

Ativar o Cloud Shell

  1. No Console do Cloud, clique em Ativar o Cloud Shell4292cbf4971c9786.png.

bce75f34b2c53987.png

Se você nunca iniciou o Cloud Shell antes, uma tela intermediária (abaixo da dobra) será exibida com a descrição dele. Se esse for o caso, clique em Continuar (e você não verá mais esse aviso). Esta é a aparência dessa tela única:

70f315d7b402b476.png

Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.

fbe3a0674c982259.png

Essa máquina virtual tem 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 codelab pode ser feito em um navegador ou no seu Chromebook.

Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto já estará configurado com seu ID do projeto.

  1. Execute o seguinte comando no Cloud Shell para confirmar que você está autenticado:
gcloud auth list

Resposta ao comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Execute o seguinte comando no Cloud Shell para confirmar que o comando gcloud sabe sobre seu projeto:
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].

3. Navegue até o Cloud Profiler

No console do Cloud, navegue até a interface do Profiler clicando em "Profiler". na barra de navegação à esquerda:

37ad0df7ddb2ad17.png

Como alternativa, use a barra de pesquisa do Console do Cloud para navegar até a interface do Profiler. Basta digitar "Cloud Profiler" e selecione o item encontrado. De qualquer forma, a interface do Profiler vai aparecer com a mensagem "No data to display" como abaixo. O projeto é novo, por isso ainda não tem dados de criação de perfil coletados.

d275a5f61ed31fb2.png

Chegou a hora de criar um perfil para você!

4. Criar o perfil do comparativo de mercado

Usaremos um aplicativo Go sintético simples disponível no GitHub (em inglês). No terminal do Cloud Shell que ainda está aberto (e enquanto a mensagem "No data to display" ainda é exibida na interface do Profiler), execute o seguinte comando:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

Em seguida, alterne 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)
        }
        ...
}

O agente de criação de perfil coleta perfis de CPU, heap e linha de execução por padrão. Este código ativa a coleção de perfis de mutex (também conhecido como “contenção”).

Agora, execute o programa:

$ go run main.go

Durante a execução do programa, o agente de criação de perfil coletará 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 tipo). Por isso, pode levar até três minutos para cada um dos tipos serem coletados. O programa informa quando cria um perfil. As mensagens são ativadas pela sinalização DebugLogging na configuração acima. Caso contrário, o agente será executado em silêncio:

$ 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 será atualizada logo após a coleta do primeiro dos perfis. Como ele não será atualizado automaticamente depois disso, você precisará atualizar a interface do Profiler manualmente para ver os novos dados. Para isso, clique duas vezes no botão "Agora" no seletor de intervalo de tempo:

650051097b651b91.png

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

47a763d4dc78b6e8.png

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

b5d7b4b5051687c9.png

Agora, vamos revisar cada um dos tipos de perfil e alguns recursos importantes da interface do usuário e, em seguida, realizar alguns experimentos. Você não precisa mais do terminal do Cloud Shell nesta etapa. Para sair, pressione CTRL+C e digite "exit".

5. analise os dados do Profiler

Agora que coletamos alguns dados, vamos analisá-los com mais detalhes. Estamos usando um app sintético (a fonte está disponível no GitHub) que simula comportamentos típicos de diferentes tipos de problemas de desempenho na produção.

Código com uso intensivo da CPU

Selecione o tipo de perfil de CPU. Depois que a interface carregar, você verá no gráfico de chama os quatro blocos de folhas para a função load, que, coletivamente, representam todo o consumo da CPU:

fae661c9fe6c58df.png

Essa função é especificamente escrita para consumir muitos ciclos de CPU ao executar uma repetição curta:

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, os quatro caminhos têm mais ou menos o mesmo custo. Em um programa real, você quer se concentrar na otimização dos caminhos de chamadas mais importantes em termos de performance. O gráfico de chama, 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, tente adicionar um filtro "Mostrar pilhas" filtro que especifica "baz" como a string de filtro. Você vai ver algo parecido com 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 dele. Essa filtragem é útil quando você quer se concentrar em uma subparte de um programa maior (por exemplo, porque você só possui parte dele).

eb1d97491782b03f.png

Código com uso intensivo de memória

Agora mude para "Heap" tipo de perfil. Remova todos os filtros criados em experimentos anteriores. Agora você verá um gráfico de chama em que allocImpl, chamado por alloc, é exibido como o principal consumidor de memória no app:

f6311c8c841d04c4.png

A tabela de resumo acima do gráfico de chama indica que a quantidade total de memória usada no app é, em média, de aproximadamente 57,4 MiB.A maior parte dela é alocada pela função allocImpl. Isso não é surpresa, dada a implementação desta 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 blocos menores e, em seguida, armazenando ponteiros para esses blocos em uma variável global para protegê-los de serem coletados como lixo. Observe que a quantidade de memória usada pelo criador de perfil é um pouco diferente de 64 MiB: o criador de perfil de heap do Go é uma ferramenta estatística. Portanto, as medições têm pouca sobrecarga, mas não precisam de precisão em bytes. Não se surpreenda ao ver uma diferença de aproximadamente 10% como essa.

Código com uso intensivo de E/S

Se você escolher "Threads" No seletor de tipo de perfil, a tela muda para um gráfico em degradê, em que a maior parte da largura é ocupada pelas funções wait e waitImpl:

ebd57fdff01dede9.png

No resumo acima do gráfico de chama, é possível ver que há 100 goroutines que aumentam a pilha de chamadas da função wait. Isso é verdade, já que o código que inicia essas esperas tem a seguinte aparência:

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 algum tempo inesperado em espera (como E/S). Normalmente, essas pilhas de chamadas não passariam por amostragem pelo CPU Profiler, porque não consomem nenhuma parte significativa do tempo de CPU. Muitas vezes, você usará "Ocultar pilhas" filtros com perfis de linhas de execução. Por exemplo, para ocultar todas as pilhas que terminam com uma chamada para gopark,, já que elas geralmente são goroutines 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 onde os threads 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 intensivo de contenção

O tipo de perfil Contenção identifica o perfil mais "desejado" fica bloqueado no programa. Este tipo de perfil está disponível para programas Go, mas deve ser explicitamente ativado especificando "MutexProfiling: true" no código de configuração do agente. A coleção funciona gravando (na métrica "Contenções") o número de vezes em que um bloqueio específico, ao ser desbloqueado por uma goroutine A, tinha outra goroutine B aguardando o desbloqueio. Ela também registra (na métrica "Delay") o tempo que a goroutine bloqueada aguardou o bloqueio. Neste exemplo, há uma única pilha de contenção e o tempo de espera total para o bloqueio foi de 10,5 segundos:

83f00dca4a0f768e.png

O código que gera esse perfil consiste em quatro goroutines lutando por 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)
        }
}

6. Resumo

Neste laboratório, você aprendeu como configurar um programa Go para uso com o Cloud Profiler. Você também aprendeu a coletar, visualizar e analisar os dados de desempenho com essa ferramenta. Agora você pode aplicar sua nova habilidade aos serviços reais que executa no Google Cloud Platform.

7. Parabéns!

Você aprendeu a configurar e usar o Cloud Profiler.

Saiba mais

Licença

Este conteúdo está sob a licença Atribuição 2.0 Genérica da Creative Commons.