Instrumento para melhorar o desempenho do seu app em Go (parte 2: criador de perfil)

1. Introdução

e0509e8a07ad5537.png

Última atualização:14/07/2022

Observabilidade do aplicativo

Observabilidade e perfil contínuo

Observabilidade é o termo usado para descrever um atributo de um sistema. Um sistema com observabilidade permite que as equipes depurem ativamente o sistema. Nesse contexto, três pilares da observabilidade: registros, métricas e traces são a instrumentação fundamental para que o sistema adquira observabilidade.

Além dos três pilares da observabilidade, a criação de perfil contínua é outro componente principal para observabilidade e está expandindo a base de usuários no setor. O Cloud Profiler é um dos criadores e fornece uma interface fácil para detalhar as métricas de desempenho nas pilhas de chamadas do aplicativo.

Este codelab é a segunda parte da série e aborda a instrumentação de um agente de criação de perfil contínuo. A parte 1 aborda o rastreamento distribuído com o OpenTelemetry e o Cloud Trace, e você aprende a identificar melhor o gargalo dos microsserviços na parte 1.

O que você vai criar

Neste codelab, você vai instrumentar o agente de criação de perfil contínuo no serviço de servidor do aplicativo Shakespeare. (conhecido como Shakesapp) executado em um cluster do Google Kubernetes Engine. A arquitetura do Shakesapp é descrita abaixo:

44e243182ced442f.png

  • O Loadgen envia uma string de consulta ao cliente em HTTP
  • Os clientes passam a consulta do loadgen para o servidor no gRPC
  • O servidor aceita a consulta do cliente, busca todos os trabalhos do Shakespare em formato de texto no Google Cloud Storage, pesquisa as linhas que contêm a consulta e retorna o número da linha correspondente ao cliente

Na parte 1, você descobriu que o gargalo existe em algum lugar no serviço do servidor, mas não conseguiu identificar a causa exata.

O que você vai aprender

  • Como incorporar o agente do criador de perfil
  • Como investigar o gargalo da garrafa no Cloud Profiler

Este codelab explica como instrumentar um agente de criação de perfil contínuo no seu aplicativo.

O que é necessário

  • Conhecimento básico em Go.
  • Noções básicas do Kubernetes.

2. Configuração e requisitos

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 novo projeto.

Se você já tiver um projeto, clique no menu suspenso de seleção no canto superior esquerdo do console:

7a32e5469db69e9.png

e clique no botão "NEW PROJECT" na caixa de diálogo exibida para criar um novo projeto:

7136b3ee36ebaf89.png

Se você ainda não tiver um projeto, uma caixa de diálogo como esta será exibida para criar seu primeiro:

870a3cbd6541ee86.png

A caixa de diálogo de criação de projeto subsequente permite que você insira os detalhes do novo projeto:

affdc444517ba805.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.

Em seguida, você precisará ativar o faturamento no Developers Console para usar os recursos do Google Cloud e ativar a API Cloud Trace, caso ainda não tenha feito isso.

15d0ef27a8fbab27.png

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. Os preços do Google Cloud Trace, do Google Kubernetes Engine e do Google Artifact Registry estão indicados na documentação oficial.

Novos usuários do Google Cloud Platform estão qualificados para uma avaliação gratuita de US$ 300, o que torna este codelab totalmente gratuito.

Configuração do Google Cloud Shell

Embora o Google Cloud e o Google Cloud Trace possam ser operados remotamente do seu laptop, neste codelab vamos usar o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

O Cloud Shell é uma máquina virtual com base em Debian que 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. Isso significa que tudo que você precisa para este codelab é um navegador (sim, funciona em um Chromebook).

Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. O provisionamento e a conexão ao ambiente devem levar apenas alguns instantes.

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Screen Shot 2017-06-14 às 10.13.43 PM.png

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

gcloud auth list

Resposta ao comando

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Resposta ao comando

[core]
project = <PROJECT_ID>

Se, por algum motivo, o projeto não estiver definido, basta emitir o seguinte comando:

gcloud config set project <PROJECT_ID>

Quer encontrar seu PROJECT_ID? Veja qual ID você usou nas etapas de configuração ou procure-o no painel do Console do Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

O Cloud Shell também define algumas variáveis de ambiente por padrão, o que pode ser útil ao executar comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resposta ao comando

<PROJECT_ID>

Defina a zona padrão e a configuração do projeto:

gcloud config set compute/zone us-central1-f

É possível escolher uma variedade de zonas diferentes. Para mais informações, consulte Regiões e zonas.

Configuração de idioma do Go

Neste codelab, usamos Go para todo o código-fonte. Execute o comando a seguir no Cloud Shell e confirme se a versão do Go é 1.17 ou mais recente.

go version

Resposta ao comando

go version go1.18.3 linux/amd64

Configurar um cluster do Google Kubernetes

Neste codelab, você executará um cluster de microsserviços no Google Kubernetes Engine (GKE). O processo deste codelab é o seguinte:

  1. Faça o download do projeto de referência no Cloud Shell
  2. Crie microsserviços em contêineres
  3. Fazer upload de contêineres no Google Artifact Registry (GAR)
  4. Implantar contêineres no GKE
  5. Modificar o código-fonte dos serviços para instrumentação de rastreamento
  6. Ir para a etapa 2

Ativar o Kubernetes Engine

Primeiro, configuramos um cluster do Kubernetes em que o Shakesapp é executado no GKE, portanto, precisamos ativar o GKE. Acesse o menu "Kubernetes Engine". e pressione o botão ATIVAR.

548cfd95bc6d344d.png

Agora você já pode criar um cluster do Kubernetes.

Crie o cluster do Kubernetes

No Cloud Shell, execute o comando a seguir para criar um cluster do Kubernetes. Confirme se o valor da zona está na região que você vai usar para criar o repositório do Artifact Registry. Altere o valor da zona us-central1-f se a região do repositório não cobrir a zona.

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

Resposta ao comando

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

Configuração do Artifact Registry e do skaffold

Agora temos um cluster do Kubernetes pronto para implantação. Em seguida, preparamos um Container Registry para enviar e implantar contêineres. Para estas etapas, precisamos configurar um Artifact Registry (GAR) e skaffold para usá-lo.

Configuração do Artifact Registry

Navegue até o menu do "Artifact Registry". e pressione o botão ATIVAR.

45e384b87f7cf0db.png

Depois de alguns instantes, você verá o navegador do repositório do GAR. Clique em "CRIAR REPOSITÓRIO". e insira o nome do repositório.

d6a70f4cb4ebcbe3.png

Neste codelab, nomeio o novo repositório como trace-codelab. O formato do artefato é "Docker" e o tipo é "Região". Escolha a região próxima àquela que você definiu para a zona padrão do Google Compute Engine. Por exemplo, este exemplo escolhe "us-central1-f" acima, então aqui escolhemos "us-central1 (Iowa)". Em seguida, clique no botão "CRIAR" .

9c2d1ce65258ef70.png

Agora você vai encontrar o "trace-codelab" no navegador do repositório.

7a3c1f47346bea15.png

Vamos voltar aqui mais tarde para verificar o caminho do registro.

Configuração do Skaffold

O Skaffold é uma ferramenta útil para a criação de microsserviços executados no Kubernetes. Ele lida com o fluxo de trabalho de criação, envio e implantação de contêineres de aplicativos com um pequeno conjunto de comandos. Por padrão, o Skaffold usa o Docker Registry como o Container Registry. Portanto, é necessário configurar o skaffold para reconhecer o GAR ao enviar contêineres para ele.

Abra o Cloud Shell novamente e confirme se o skaffold está instalado. O Cloud Shell instala o skaffold no ambiente por padrão. Execute o comando a seguir e confira a versão do skaffold.

skaffold version

Resposta ao comando

v1.38.0

Agora, você pode registrar o repositório padrão para o skaffold usar. Para encontrar o caminho do registro, acesse o painel do Artifact Registry e clique no nome do repositório que você configurou na etapa anterior.

7a3c1f47346bea15.png

Em seguida, você verá trilhas de navegação estrutural na parte superior da página. Clique no ícone e157b1359c3edc06.png para copiar o caminho do registro para a área de transferência.

e0f2ae2144880b8b.png

Ao clicar no botão "Copiar", você vê a caixa de diálogo na parte inferior do navegador com a seguinte mensagem:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; foi copiado

Volte para o Cloud Shell. Execute o comando skaffold config set default-repo com o valor que você acabou de copiar do painel.

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

Resposta ao comando

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

Além disso, você precisa configurar o registro para o Docker. Execute este comando:

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

Resposta ao comando

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

Agora está tudo pronto para a próxima etapa de configuração de um contêiner do Kubernetes no GKE.

Resumo

Nesta etapa, você vai configurar seu ambiente do codelab:

  • Configurar o Cloud Shell
  • Criou um repositório do Artifact Registry para o Container Registry
  • Configurar o skaffold para usar o Container Registry
  • Criou um cluster do Kubernetes em que os microsserviços do codelab são executados

A seguir

Na próxima etapa, você vai instrumentar o agente de criação de perfil contínuo no serviço do servidor.

3. Criar, transferir e implantar os microsserviços

Faça o download do material do codelab

Na etapa anterior, configuramos todos os pré-requisitos para este codelab. Agora está tudo pronto para executar microsserviços inteiros neles. O material do codelab está hospedado no GitHub. Faça o download dele para o ambiente do Cloud Shell com o comando git abaixo.

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

A estrutura de diretórios do projeto é a seguinte:

.
├── README.md
├── step0
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step1
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step2
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step3
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step4
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step5
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
└── step6
    ├── manifests
    ├── proto
    ├── skaffold.yaml
    └── src
  • manifestos: arquivos de manifesto do Kubernetes
  • proto: definição proto para a comunicação entre cliente e servidor
  • src: diretórios para o código-fonte de cada serviço
  • skaffold.yaml: arquivo de configuração do skaffold

Neste codelab, você atualizará o código-fonte localizado na pasta step4. Também é possível consultar o código-fonte em pastas step[1-6] para conferir as mudanças desde o início. (A Parte 1 abrange as etapas 0 a 4, e a Parte 2 abrange as etapas 5 e 6)

Executar o comando skaffold

Por fim, você já pode criar, enviar e implantar todo o conteúdo no cluster do Kubernetes que acabou de criar. Parece que tem várias etapas, mas o skaffold na verdade faz tudo por você. Vamos tentar fazer isso com o seguinte comando:

cd step4
skaffold dev

Assim que executar o comando, você verá a saída do registro de docker build e poderá confirmar que eles foram enviados com sucesso para o registro.

Resposta ao comando

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

Após o envio de todos os contêineres de serviço, as implantações do Kubernetes são iniciadas automaticamente.

Resposta ao comando

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

Após a implantação, os registros do aplicativo serão emitidos para stdout em cada contêiner da seguinte forma:

Resposta ao comando

[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463

Observe que, neste momento, você quer ver todas as mensagens do servidor. Agora está tudo pronto para começar a instrumentar seu aplicativo com o OpenTelemetry para o rastreamento distribuído dos serviços.

Antes de começar a instrumentar o serviço, encerre seu cluster com Ctrl-C.

Resposta ao comando

...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
 - W0714 06:34:58.464305   28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
 - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Resumo

Nesta etapa, você preparou o material do codelab no seu ambiente e confirmou as execuções do skaffold conforme o esperado.

A seguir

Na próxima etapa, você vai modificar o código-fonte do serviço loadgen para instrumentar as informações de rastreamento.

4. Instrumentação do agente do Cloud Profiler

Conceito da criação contínua de perfis

Antes de explicar o conceito de criação de perfil contínua, precisamos entender isso primeiro. A criação de perfil é uma das maneiras de analisar o aplicativo dinamicamente (análise dinâmica do programa) e geralmente é executada durante o desenvolvimento do aplicativo, no processo de teste de carga e assim por diante. Essa é uma atividade de tomada única para medir as métricas do sistema, como uso de CPU e memória, durante o período específico. Depois de coletar os dados do perfil, os desenvolvedores os analisam fora do código.

A criação de perfil contínua é a abordagem estendida da criação de perfil normal: ele executa perfis de janela curta no aplicativo de longa duração periodicamente e coleta vários dados de perfil. Em seguida, ela gera automaticamente a análise estatística com base em um determinado atributo do aplicativo, como número da versão, zona de implantação, tempo de medição e assim por diante. Confira mais detalhes sobre o conceito na nossa documentação.

Como o destino é um aplicativo em execução, há uma maneira de coletar dados de perfil periodicamente e enviá-los para algum back-end que pós-processa os dados estatísticos. Esse é o agente do Cloud Profiler e você vai incorporá-lo ao serviço de servidor em breve.

Incorporar o agente do Cloud Profiler

Pressione o botão 776a11bfb2122549.png no canto superior direito para abrir o Editor do Cloud Shell. Abra step4/src/server/main.go no explorador no painel esquerdo e encontre a função principal.

step4/src/server/main.go

func main() {
        ...
        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

Na função main, há um código de configuração para o OpenTelemetry e o gRPC, o que foi feito na parte 1 do codelab. Agora você vai adicionar a instrumentação para o agente do Cloud Profiler aqui. Assim como fizemos com o initTracer(), é possível escrever uma função chamada initProfiler() para facilitar a leitura.

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Vamos dar uma olhada nas opções especificadas no objeto profiler.Config{}.

  • Serviço: o nome do serviço que pode ser selecionado e ativado no painel do criador de perfil
  • ServiceVersion: o nome da versão do serviço. Você pode comparar conjuntos de dados de perfil com base nesse valor.
  • NoHeapProfiling: desative a criação do perfil de consumo de memória.
  • NoAllocProfiling: desative o perfil de alocação de memória.
  • NoGoroutineProfiling: desative a criação de perfil do goroutine
  • NoCPUProfiling: desative a criação de perfil da CPU.

Neste codelab, vamos ativar apenas a criação de perfil de CPU.

Agora, basta chamar essa função na main. Importe o pacote do Cloud Profiler no bloco de importação.

step4/src/server/main.go

func main() {
        ...
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

Você está chamando a função initProfiler() com a palavra-chave go. Porque profiler.Start() fica bloqueado, então você precisa executá-lo em outra goroutine. Agora está tudo pronto para a criação. Execute go mod tidy antes da implantação.

go mod tidy

Agora implante o cluster com o novo serviço de servidor.

skaffold dev

Geralmente, esse gráfico leva alguns minutos para aparecer no Cloud Profiler. Digite "profiler" na caixa de pesquisa na parte de cima e clique no ícone do Profiler.

3d8ca8a64b267a40.png

Você verá o seguinte gráfico de chama.

7f80797dddc0128d.png

Resumo

Nesta etapa, você incorporou o agente do Cloud Profiler ao serviço do servidor e confirmou que ele gera um gráfico de chama.

A seguir

Na próxima etapa, você investigará a causa do gargalo no aplicativo com o gráfico de chama.

5. Analisar o gráfico de chama do Cloud Profiler

O que é o Flame Graph?

O gráfico de chama é uma das maneiras de visualizar os dados do perfil. Para uma explicação detalhada, consulte nosso documento, mas o resumo é:

  • Cada barra expressa a chamada de método/função no aplicativo
  • A direção vertical é a pilha de chamadas. a pilha de chamadas aumenta de cima para baixo
  • A direção horizontal é o uso de recursos. quanto mais tempo, pior.

Por isso, vamos analisar o gráfico de chama recebido.

7f80797dddc0128d.png

Como analisar o gráfico de chama

Na seção anterior, você aprendeu que cada barra no gráfico em degradê expressa a chamada da função/método, e o comprimento dela significa o uso de recursos na função/método. O gráfico de chama do Cloud Profiler classifica a barra na ordem decrescente ou o comprimento da esquerda para a direita. Você pode começar a olhar primeiro no canto superior esquerdo do gráfico.

6d90760c6c1183cd.png

No nosso caso, está explícito que grpc.(*Server).serveStreams.func1.2 está consumindo a maior parte do tempo de CPU e que, ao analisar a pilha de chamadas de cima para baixo, isso é gasto na maior parte do tempo em main.(*serverService).GetMatchCount, que é o gerenciador do servidor gRPC no serviço do servidor.

Em GetMatchCount, há uma série de funções regexp: regexp.MatchString e regexp.Compile. Eles são do pacote padrão, ou seja, precisam ser bem testados de muitos pontos de vista, incluindo o desempenho. Mas o resultado aqui mostra que o uso de recursos de tempo de CPU é alto no regexp.MatchString e no regexp.Compile. Considerando esses fatos, supomos que o uso de regexp.MatchString tem algo a ver com problemas de desempenho. Então, vamos ler o código-fonte onde a função é usada.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Esse é o lugar em que regexp.MatchString é chamado. Ao ler o código-fonte, você pode notar que a função é chamada dentro do loop for aninhado. Portanto, o uso dessa função pode estar incorreto. Vamos procurar o GoDoc do regexp.

80b8a4ba1931ff7b.png

De acordo com o documento, regexp.MatchString compila o padrão de expressão regular em cada chamada. A causa do grande consumo de recursos é assim.

Resumo

Nesta etapa, você presumiu a causa do consumo de recursos analisando o gráfico de chama.

A seguir

Na próxima etapa, você atualizará o código-fonte do serviço de servidor e confirmará a alteração da versão 1.0.0.

6. Atualizar o código-fonte e comparar os gráficos de chama

Atualizar o código-fonte

Na etapa anterior, você presumiu que o uso de regexp.MatchString tem algo a ver com o grande consumo de recursos. Vamos resolver isso. Abra o código e mude um pouco essa parte.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

Agora, o processo de compilação do padrão regexp é extraído do regexp.MatchString e removido do loop "for" aninhado.

Antes de implantar esse código, atualize a string da versão na função initProfiler().

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

Agora vamos conferir como isso funciona. Implante o cluster com o comando skaffold.

skaffold dev

Depois de um tempo, recarregue o painel do Cloud Profiler para ver o resultado.

283cfcd4c13716ad.png

Mude a versão para "1.1.0" para ver apenas os perfis da versão 1.1.0. Como você pode ver, o comprimento da barra de GetMatchCount foi reduzido e a proporção de uso do tempo de CPU (ou seja, a barra ficou mais curta).

e3a1456b4aada9a5.png

Além de analisar o gráfico em degradê de uma única versão, você também pode comparar as diferenças entre elas.

841dec77d8ba5595.png

Mudar o valor de "Comparar com" lista suspensa como "Versão". e alterar o valor de "Versão comparada" para "1.0.0", a versão original.

5553844292d6a537.png

Você verá esse tipo de gráfico de chama. A forma do gráfico é a mesma que 1.1.0, mas a cor é diferente. No modo de comparação, o que a cor significa é:

  • Azul: o valor (consumo de recursos) foi reduzido.
  • Laranja: o valor (consumo de recursos) ganho.
  • Cinza: neutro

Com base na legenda, vamos analisar a função mais de perto. Clique na barra em que você quer aumentar o zoom para ver mais detalhes na pilha. Clique em main.(*serverService).GetMatchCount barra. Além disso, ao passar o cursor sobre a barra, você verá os detalhes da comparação.

ca08d942dc1e2502.png

Ela informa que o tempo total de CPU é reduzido de 5,26s para 2,88s (o total é de 10s = janela de amostragem). Isso é uma grande melhoria!

Agora é possível melhorar o desempenho do aplicativo com a análise dos dados do perfil.

Resumo

Nesta etapa, você fez uma edição no serviço do servidor e confirmou a melhoria no modo de comparação do Cloud Profiler.

A seguir

Na próxima etapa, você atualizará o código-fonte do serviço de servidor e confirmará a alteração da versão 1.0.0.

7. Etapa extra: confirmar a melhoria na hierarquia do Trace

Diferença entre rastreamento distribuído e criação de perfil contínua

Na parte 1 do codelab, você confirmou que podia descobrir o serviço de gargalo nos microsserviços para um caminho de solicitação e que não conseguiu descobrir a causa exata do gargalo no serviço específico. Neste codelab da parte 2, você aprendeu que a criação de perfil contínua permite identificar o gargalo dentro do serviço único das pilhas de chamadas.

Nesta etapa, vamos revisar o gráfico de cascata do trace distribuído (Cloud Trace) e observar a diferença da criação de perfil contínua.

Este gráfico de cascata é um dos traces com a consulta "love". Está levando cerca de 6,7 s (6.700 ms) no total.

e2b7dec25926ee51.png

E isso vem após a melhoria para a mesma consulta. Como você sabe, a latência total agora é de 1,5 s (1.500 ms), o que é uma grande melhoria em relação à implementação anterior.

feeb7207f36c7e5e.png

O ponto importante aqui é que, no gráfico de cascata de rastreamento distribuído, as informações da pilha de chamadas não estão disponíveis, a menos que você instrumente os períodos em todos os lugares. Além disso, os traces distribuídos se concentram apenas na latência entre os serviços, enquanto a criação de perfil contínua se concentra nos recursos do computador (CPU, memória, linhas de execução do SO) de um único serviço.

Em outro aspecto, o trace distribuído é a base de eventos. O perfil contínuo é estatístico. Cada trace tem um gráfico de latência diferente, e você precisa de um formato diferente, como distribuição, para acompanhar a tendência das mudanças de latência.

Resumo

Nesta etapa, você verificou a diferença entre o trace distribuído e a criação de perfil contínua.

8. Parabéns

Você criou traces distribuídos com o OpenTelemery e confirmou as latências de solicitação no microsserviço no Google Cloud Trace.

Para exercícios prolongados, você pode experimentar os tópicos a seguir por conta própria.

  • A implementação atual envia todos os períodos gerados pela verificação de integridade. (grpc.health.v1.Health/Check) Como você filtra esses períodos do Cloud Trace? A dica está aqui.
  • Relacione logs de eventos com períodos e veja como isso funciona no Google Cloud Trace e no Google Cloud Logging. A dica está aqui.
  • Substitua alguns serviços por um em outro idioma e tente instrumentá-los com o OpenTelemetry para esse idioma.

Além disso, se você quiser saber mais sobre o criador de perfil depois disso, avance para a parte 2. Nesse caso, pule a seção de limpeza abaixo.

Limpeza

Após este codelab, interrompa o cluster do Kubernetes e exclua o projeto para não receber cobranças inesperadas no Google Kubernetes Engine, no Google Cloud Trace e no Google Artifact Registry.

Primeiro, exclua o cluster. Se você estiver executando o cluster com skaffold dev, basta pressionar Ctrl-C. Se você estiver executando o cluster com skaffold run, execute o seguinte comando:

skaffold delete

Resposta ao comando

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Depois de excluir o cluster, no painel de menu, selecione "IAM e Administrador" > "Configurações" e, em seguida, clique em "DESLIGAR" .

45aa37b7d5e1ddd1.png

Em seguida, insira o ID do projeto (não o nome do projeto) no formulário na caixa de diálogo e confirme o encerramento.