Capturar métricas personalizadas do lado do cliente usando o OpenTelemetry

Este documento descreve como capturar métricas personalizadas do cliente usando o OpenTelemetry. As métricas personalizadas do cliente estão disponíveis usando as bibliotecas de cliente Java e Go.

As métricas personalizadas do lado do cliente podem ajudar a encontrar a origem da latência no sistema. Para mais informações, consulte Pontos de latência em uma solicitação do Spanner.

As bibliotecas de cliente do Spanner também fornecem estatísticas e rastros usando o framework de observabilidade do OpenTelemetry. Para mais informações, consulte Configurar a coleta de rastros usando o OpenTelemetry.

O OpenTelemetry é um framework e um kit de ferramentas de observabilidade de código aberto que permite criar e gerenciar dados de telemetria, como traces, métricas e registros.

Antes de começar

É necessário configurar o SDK do OpenTelemetry com as opções adequadas para exportar seus dados de telemetria. Recomendamos o uso do exportador do protocolo do OpenTelemetry (OTLP, na sigla em inglês).

Para configurar métricas personalizadas do lado do cliente usando o OpenTelemetry, você precisa configurar o SDK do OpenTelemetry e o exportador OTLP:

  1. Adicione as dependências necessárias ao app usando o seguinte código:

    Java

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>com.google.cloud</groupId>
          <artifactId>libraries-bom</artifactId>
          <version>26.32.0</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
        <dependency>
          <groupId>io.opentelemetry</groupId>
          <artifactId>opentelemetry-bom</artifactId>
          <version>1.35.0</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-spanner</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-metrics</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-trace</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
      </dependency>
    </dependencies>

    Go

    go.opentelemetry.io/otel v1.24.0
    go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1
    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1
    go.opentelemetry.io/otel/metric v1.24.0
    go.opentelemetry.io/otel/sdk v1.24.0
    go.opentelemetry.io/otel/sdk/metric v1.23.1
  2. Crie um objeto do OpenTelemetry com o exportador OTLP e injete-o no Spanner usando SpannerOptions:

    Java

    // Enable OpenTelemetry metrics and traces before Injecting OpenTelemetry
    SpannerOptions.enableOpenTelemetryMetrics();
    SpannerOptions.enableOpenTelemetryTraces();
    
    // Create a new meter provider
    SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .registerMetricReader(
            PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build())
        .build();
    
    // Create a new tracer provider
    SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .addSpanProcessor(SimpleSpanProcessor.builder(OtlpGrpcSpanExporter
            .builder().build()).build())
            .build();
    
    // Configure OpenTelemetry object using Meter Provider and Tracer Provider
    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .setTracerProvider(sdkTracerProvider)
        .build();
    
    // Inject OpenTelemetry object via Spanner options or register as GlobalOpenTelemetry.
    SpannerOptions options = SpannerOptions.newBuilder()
        .setOpenTelemetry(openTelemetry)
        .build();
    Spanner spanner = options.getService();
    
    DatabaseClient dbClient = spanner
        .getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
    
    captureGfeMetric(dbClient);
    captureQueryStatsMetric(openTelemetry, dbClient);
    
    // Close the providers to free up the resources and export the data. */
    sdkMeterProvider.close();
    sdkTracerProvider.close();

    Go

    // Ensure that your Go runtime version is supported by the OpenTelemetry-Go compatibility policy before enabling OpenTelemetry instrumentation.
    // Refer to compatibility here https://github.com/googleapis/google-cloud-go/blob/main/debug.md#opentelemetry
    
    import (
    	"context"
    	"fmt"
    	"io"
    	"log"
    	"strconv"
    	"strings"
    
    	"cloud.google.com/go/spanner"
    	"go.opentelemetry.io/otel"
    	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    	"go.opentelemetry.io/otel/metric"
    	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    	"go.opentelemetry.io/otel/sdk/resource"
    	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
    	"google.golang.org/api/iterator"
    )
    
    func enableOpenTelemetryMetricsAndTraces(w io.Writer, db string) error {
    	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
    	ctx := context.Background()
    
    	// Create a new resource to uniquely identify the application
    	res, err := newResource()
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// Enable OpenTelemetry traces by setting environment variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive value "opentelemetry" before loading the client library.
    	// Enable OpenTelemetry metrics before injecting meter provider.
    	spanner.EnableOpenTelemetryMetrics()
    
    	// Create a new tracer provider
    	tracerProvider, err := getOtlpTracerProvider(ctx, res)
    	defer tracerProvider.ForceFlush(ctx)
    	if err != nil {
    		log.Fatal(err)
    	}
    	// Register tracer provider as global
    	otel.SetTracerProvider(tracerProvider)
    
    	// Create a new meter provider
    	meterProvider := getOtlpMeterProvider(ctx, res)
    	defer meterProvider.ForceFlush(ctx)
    
    	// Inject meter provider locally via ClientConfig when creating a spanner client or set globally via setMeterProvider.
    	client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{OpenTelemetryMeterProvider: meterProvider})
    	if err != nil {
    		return err
    	}
    	defer client.Close()
    	return nil
    }
    
    func getOtlpMeterProvider(ctx context.Context, res *resource.Resource) *sdkmetric.MeterProvider {
    	otlpExporter, err := otlpmetricgrpc.New(ctx)
    	if err != nil {
    		log.Fatal(err)
    	}
    	meterProvider := sdkmetric.NewMeterProvider(
    		sdkmetric.WithResource(res),
    		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(otlpExporter)),
    	)
    	return meterProvider
    }
    
    func getOtlpTracerProvider(ctx context.Context, res *resource.Resource) (*sdktrace.TracerProvider, error) {
    	traceExporter, err := otlptracegrpc.New(ctx)
    	if err != nil {
    		return nil, err
    	}
    
    	tracerProvider := sdktrace.NewTracerProvider(
    		sdktrace.WithResource(res),
    		sdktrace.WithBatcher(traceExporter),
    		sdktrace.WithSampler(sdktrace.AlwaysSample()),
    	)
    
    	return tracerProvider, nil
    }
    
    func newResource() (*resource.Resource, error) {
    	return resource.Merge(resource.Default(),
    		resource.NewWithAttributes(semconv.SchemaURL,
    			semconv.ServiceName("otlp-service"),
    			semconv.ServiceVersion("0.1.0"),
    		))
    }
    

Capturar a latência do GFE

A latência do Google Front End (GFE) é a duração em milissegundos entre o momento em que a rede do Google recebe uma chamada de procedimento remoto do cliente e quando o GFE recebe o primeiro byte da resposta.

É possível capturar a latência da GFE usando o seguinte código:

Java

static void captureGfeMetric(DatabaseClient dbClient) {
  // GFE_latency and other Spanner metrics are automatically collected
  // when OpenTelemetry metrics are enabled.

  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Go

// GFE_Latency and other Spanner metrics are automatically collected
// when OpenTelemetry metrics are enabled.
func captureGFELatencyMetric(ctx context.Context, client spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

O exemplo de código anexa a string spanner/gfe_latency ao nome da métrica quando ela é exportada para o Cloud Monitoring. É possível pesquisar essa métrica no Cloud Monitoring usando a string anexada.

Capturar a latência da solicitação da API Cloud Spanner

A latência de solicitação da API Cloud Spanner é o tempo em segundos entre o primeiro byte da solicitação do cliente que o front-end da API Cloud Spanner recebe e o último byte da resposta que o front-end da API Cloud Spanner envia.

Essa métrica de latência está disponível como parte das métricas do Cloud Monitoring.

Capturar a latência de ida e volta do cliente

A latência de ida e volta do cliente é a duração em milissegundos entre o primeiro byte da solicitação da API Cloud Spanner que o cliente envia para o banco de dados (pelo GFE e pelo front-end da API Cloud Spanner) e o último byte de resposta que o cliente recebe do banco de dados.

A métrica de latência de ida e volta do cliente do Spanner não é compatível com o OpenTelemetry. Você pode conferir a métrica de latência da operação do lado do cliente. Para mais informações, consulte Descrições de métricas do lado do cliente.

Também é possível instrumentar a métrica usando o OpenCensus com uma ponte e migrar os dados para o OpenTelemetry.

Capturar latência da consulta

A latência da consulta é a duração em milissegundos para executar consultas SQL no banco de dados do Spanner.

É possível capturar a latência da consulta usando o seguinte código:

Java

static void captureQueryStatsMetric(OpenTelemetry openTelemetry, DatabaseClient dbClient) {
  // Register query stats metric.
  // This should be done once before start recording the data.
  Meter meter = openTelemetry.getMeter("cloud.google.com/java");
  DoubleHistogram queryStatsMetricLatencies =
      meter
          .histogramBuilder("spanner/query_stats_elapsed")
          .setDescription("The execution of the query")
          .setUnit("ms")
          .build();

  // Capture query stats metric data.
  try (ResultSet resultSet = dbClient.singleUse()
      .analyzeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          QueryAnalyzeMode.PROFILE)) {

    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }

    String value = resultSet.getStats().getQueryStats()
        .getFieldsOrDefault("elapsed_time", Value.newBuilder().setStringValue("0 msecs").build())
        .getStringValue();
    double elapsedTime = value.contains("msecs")
        ? Double.parseDouble(value.replaceAll(" msecs", ""))
        : Double.parseDouble(value.replaceAll(" secs", "")) * 1000;
    queryStatsMetricLatencies.record(elapsedTime);
  }
}

Go

func captureQueryStatsMetric(ctx context.Context, mp metric.MeterProvider, client spanner.Client) error {
	meter := mp.Meter(spanner.OtInstrumentationScope)
	// Register query stats metric with OpenTelemetry to record the data.
	// This should be done once before start recording the data.
	queryStats, err := meter.Float64Histogram(
		"spanner/query_stats_elapsed",
		metric.WithDescription("The execution of the query"),
		metric.WithUnit("ms"),
		metric.WithExplicitBucketBoundaries(0.0, 0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0,
			16.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0,
			300.0, 400.0, 500.0, 650.0, 800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0,
			100000.0),
	)
	if err != nil {
		fmt.Print(err)
	}

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithStats(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			// Record query execution time with OpenTelemetry.
			elapasedTime := iter.QueryStats["elapsed_time"].(string)
			elapasedTimeMs, err := strconv.ParseFloat(strings.TrimSuffix(elapasedTime, " msecs"), 64)
			if err != nil {
				return err
			}
			queryStats.Record(ctx, elapasedTimeMs)
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

O exemplo de código anexa a string spanner/query_stats_elapsed ao nome da métrica quando ela é exportada para o Cloud Monitoring. É possível pesquisar essa métrica no Cloud Monitoring usando a string anexada.

Conferir métricas no Metrics Explorer

  1. No console Google Cloud , acesse a página Metrics Explorer.

    Acessar o Metrics Explorer

  2. Selecione o projeto.

  3. Clique em Selecionar uma métrica.

  4. Pesquise métricas de latência usando as seguintes strings:

    • roundtrip_latency: para a métrica de latência de ida e volta do cliente.
    • spanner/gfe_latency: para a métrica de latência do GFE.
    • spanner/query_stats_elapsed: para a métrica de latência da consulta.
  5. Selecione a métrica e clique em Aplicar.

Para mais informações sobre como agrupar ou agregar a métrica, consulte Criar consultas usando menus.

A seguir