Configurar o plug-in básico do OpenTelemetry em gRPC C++

1. Introdução

Neste codelab, você vai usar o gRPC para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em C++.

Ao final do tutorial, você terá um aplicativo gRPC HelloWorld simples instrumentado com o plug-in gRPC OpenTelemetry e poderá conferir as métricas de observabilidade exportadas no Prometheus.

O que você vai aprender

  • Como configurar o plug-in OpenTelemetry para um aplicativo gRPC C++ atual
  • Executar uma instância local do Prometheus
  • Exportar métricas para o Prometheus
  • Conferir métricas no painel do Prometheus

2. Antes de começar

O que é necessário

  • git
  • curl
  • build-essential
  • clang
  • bazel para criar exemplos neste codelab

Instale os pré-requisitos:

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y git curl build-essential clang

bazel pode ser instalado via bazelisk. A versão mais recente pode ser encontrada neste link.

Uma maneira simples de configurar é instalar como o binário do bazel no seu PATH da seguinte maneira:

sudo cp bazelisk-linux-amd64 /usr/local/bin/bazel
sudo chmod a+x /usr/local/bin/bazel

Como alternativa, você também pode usar o CMake. Confira as instruções para usar o CMake neste link.

Acessar o código

Para facilitar o aprendizado, este codelab oferece um scaffold de código-fonte pré-criado para ajudar você a começar. As etapas a seguir explicam como instrumentar o plug-in gRPC do OpenTelemetry em um aplicativo.

grpc-codelabs

O código-fonte do scaffold para este codelab está disponível neste diretório do GitHub. Se preferir não implementar o código por conta própria, o código-fonte concluído está disponível no diretório completed.

Primeiro, clone o repositório do codelab do grpc e cd na pasta grpc-cpp-opentelemetry:

git clone https://github.com/grpc-ecosystem/grpc-codelabs.git
cd grpc-codelabs/codelabs/grpc-cpp-opentelemetry/

Como alternativa, baixe o arquivo .zip que contém apenas o diretório do codelab e descompacte-o manualmente.

Crie a biblioteca gRPC usando o bazel:

bazel build start_here/...

3. Registrar o plug-in do OpenTelemetry

Precisamos de um aplicativo gRPC para adicionar o plug-in gRPC OpenTelemetry. Neste codelab, vamos usar um cliente e um servidor gRPC HelloWorld simples que vamos instrumentar com o plug-in gRPC OpenTelemetry.

A primeira etapa é registrar o plug-in do OpenTelemetry configurado com um exportador do Prometheus no cliente. Abra codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_client.cc com seu editor favorito e transforme main() para que fique assim:

int main(int argc, char **argv) {
  absl::ParseCommandLine(argc, argv);

  // Codelab Solution: Register a global gRPC OpenTelemetry plugin configured
  // with a prometheus exporter.
  opentelemetry::exporter::metrics::PrometheusExporterOptions opts;
  opts.url = absl::GetFlag(FLAGS_prometheus_endpoint);
  auto prometheus_exporter =
      opentelemetry::exporter::metrics::PrometheusExporterFactory::Create(opts);
  auto meter_provider =
      std::make_shared<opentelemetry::sdk::metrics::MeterProvider>();

  // The default histogram boundaries are not granular enough for RPCs. Override
  // the "grpc.client.attempt.duration" view as recommended by
  // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md.
  AddLatencyView(meter_provider.get(), "grpc.client.attempt.duration", "s");
  meter_provider->AddMetricReader(std::move(prometheus_exporter));
  auto status = grpc::OpenTelemetryPluginBuilder()
                    .SetMeterProvider(std::move(meter_provider))
                    .BuildAndRegisterGlobal();
  if (!status.ok()) {
    std::cerr << "Failed to register gRPC OpenTelemetry Plugin: "
              << status.ToString() << std::endl;
    return static_cast<int>(status.code());
  }

  // Continuously send RPCs.
  RunClient(absl::GetFlag(FLAGS_target));

  return 0;
}

A próxima etapa é adicionar o plug-in OpenTelemetry ao servidor. Abra codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_server.cc e transforme a função principal para que ela fique assim:

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  // Register a global gRPC OpenTelemetry plugin configured with a prometheus
  // exporter.
  opentelemetry::exporter::metrics::PrometheusExporterOptions opts;
  opts.url = absl::GetFlag(FLAGS_prometheus_endpoint);
  auto prometheus_exporter =
      opentelemetry::exporter::metrics::PrometheusExporterFactory::Create(opts);
  auto meter_provider =
      std::make_shared<opentelemetry::sdk::metrics::MeterProvider>();

  // The default histogram boundaries are not granular enough for RPCs. Override
  // the "grpc.server.call.duration" view as recommended by
  // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md.
  AddLatencyView(meter_provider.get(), "grpc.server.call.duration", "s");
  meter_provider->AddMetricReader(std::move(prometheus_exporter));
  auto status = grpc::OpenTelemetryPluginBuilder()
                    .SetMeterProvider(std::move(meter_provider))
                    .BuildAndRegisterGlobal();
  if (!status.ok()) {
    std::cerr << "Failed to register gRPC OpenTelemetry Plugin: "
              << status.ToString() << std::endl;
    return static_cast<int>(status.code());
  }
  RunServer(absl::GetFlag(FLAGS_port));
  return 0;
}

Os arquivos de cabeçalho e as dependências de build necessários já foram adicionados para sua conveniência.

#include "opentelemetry/exporters/prometheus/exporter_factory.h"
#include "opentelemetry/exporters/prometheus/exporter_options.h"
#include "opentelemetry/sdk/metrics/meter_provider.h"

#include <grpcpp/ext/otel_plugin.h>

As dependências de build também já foram adicionadas no arquivo BUILD:

cc_binary(
    name = "greeter_callback_client",
    srcs = ["greeter_callback_client.cc"],
    defines = ["BAZEL_BUILD"],
    deps = [
        "//util:util",
        "@com_github_grpc_grpc//:grpc++",
        "@com_github_grpc_grpc//:grpcpp_otel_plugin",
        "@com_google_absl//absl/flags:flag",
        "@com_google_absl//absl/flags:parse",
        "@io_opentelemetry_cpp//exporters/prometheus:prometheus_exporter",
        "@io_opentelemetry_cpp//sdk/src/metrics",
    ],
)

4. Executar o exemplo e conferir as métricas

Para executar o servidor, execute:

bazel run start_here:greeter_callback_server

Com uma configuração bem-sucedida, você verá a seguinte saída para o servidor:

Server listening on 0.0.0.0:50051

Enquanto o servidor estiver em execução, execute o cliente em outro terminal:

bazel run start_here:greeter_callback_client

Uma execução bem-sucedida será semelhante a esta:

Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world

Como configuramos o plug-in gRPC OpenTelemetry para exportar métricas usando o Prometheus. Essas métricas estarão disponíveis em localhost:9464 para o servidor e localhost:9465 para o cliente.

Para ver as métricas do cliente:

curl localhost:9465/metrics

O resultado seria assim:

# HELP exposer_transferred_bytes_total Transferred bytes to metrics services
# TYPE exposer_transferred_bytes_total counter
exposer_transferred_bytes_total 0
# HELP exposer_scrapes_total Number of times metrics were scraped
# TYPE exposer_scrapes_total counter
exposer_scrapes_total 0
# HELP exposer_request_latencies Latencies of serving scrape requests, in microseconds
# TYPE exposer_request_latencies summary
exposer_request_latencies_count 0
exposer_request_latencies_sum 0
exposer_request_latencies{quantile="0.5"} Nan
exposer_request_latencies{quantile="0.9"} Nan
exposer_request_latencies{quantile="0.99"} Nan
# HELP target Target metadata
# TYPE target gauge
target_info{otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",service_name="unknown_service",telemetry_sdk_version="1.13.0",telemetry_sdk_name="opentelemetry",telemetry_sdk_language="cpp"} 1 1721958543107
# HELP grpc_client_attempt_rcvd_total_compressed_message_size_bytes Compressed message bytes received per call attempt
# TYPE grpc_client_attempt_rcvd_total_compressed_message_size_bytes histogram
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev"} 1248 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="0"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="5"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="10"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="25"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="50"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="75"} 96 1721958543107

Da mesma forma, para as métricas do lado do servidor:

curl localhost:9464/metrics

5. Como visualizar métricas no Prometheus

Aqui, vamos configurar uma instância do Prometheus que vai extrair nosso cliente e servidor de exemplo do gRPC que estão exportando métricas usando o Prometheus.

Faça o download da versão mais recente do Prometheus para sua plataforma, extraia e execute:

tar xvfz prometheus-*.tar.gz
cd prometheus-*

Crie um arquivo de configuração do Prometheus com o seguinte:

cat > grpc_otel_cpp_prometheus.yml <<EOF
scrape_configs:
  - job_name: "prometheus"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: "grpc-otel-cpp"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9464", "localhost:9465"]
EOF

Inicie o Prometheus com a nova configuração:

./prometheus --config.file=grpc_otel_cpp_prometheus.yml

Isso vai configurar as métricas dos processos do codelab do cliente e do servidor para serem extraídas a cada 5 segundos.

Acesse http://localhost:9090/graph para conferir as métricas. Por exemplo, a consulta:

histogram_quantile(0.5, rate(grpc_client_attempt_duration_seconds_bucket[1m]))

mostra um gráfico com a latência mediana da tentativa usando 1 minuto como a janela de tempo para o cálculo do quantil.

Taxa de consultas:

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (Opcional) Exercício para o usuário

Nos painéis do Prometheus, você vai notar que o QPS está baixo. Veja se você consegue identificar um código suspeito no exemplo que está limitando o QPS.

Para os entusiastas, o código do cliente se limita a ter apenas uma RPC pendente em um determinado momento. Isso pode ser modificado para que o cliente envie mais RPCs sem esperar que os anteriores sejam concluídos. (A solução para isso não foi fornecida.)