Merekam metrik sisi klien kustom menggunakan OpenTelemetry

Dokumen ini menjelaskan cara merekam metrik klien kustom menggunakan OpenTelemetry. Metrik klien kustom tersedia menggunakan library klien Java dan Go.

Metrik sisi klien kustom dapat membantu Anda menemukan sumber latensi di sistem. Untuk informasi selengkapnya, lihat Titik latensi dalam permintaan Spanner.

Library klien Spanner juga menyediakan statistik dan trace menggunakan framework visibilitas OpenTelemetry. Untuk mengetahui informasi selengkapnya, lihat Menyiapkan pengumpulan rekaman aktivitas menggunakan OpenTelemetry.

OpenTelemetry adalah framework dan toolkit observabilitas open source yang memungkinkan Anda membuat dan mengelola data telemetri seperti trace, metrik, dan log.

Sebelum memulai

Anda perlu mengonfigurasi OpenTelemetry SDK dengan opsi yang sesuai untuk mengekspor data telemetri. Sebaiknya gunakan eksportir OpenTelemetry Protocol (OTLP).

Untuk menyiapkan metrik sisi klien kustom menggunakan OpenTelemetry, Anda perlu mengonfigurasi OpenTelemetry SDK dan pengekspor OTLP:

  1. Tambahkan dependensi yang diperlukan ke aplikasi Anda menggunakan kode berikut:

    Java

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

    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. Buat objek OpenTelemetry dengan pengekspor OTLP dan masukkan ke dalam Spanner menggunakan 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();
    

    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"),
    		))
    }
    

Merekam latensi GFE

Latensi Google Front End (GFE) adalah durasi dalam milidetik antara saat jaringan Google menerima panggilan prosedur jarak jauh dari klien dan saat GFE menerima byte pertama respons.

Anda dapat merekam latensi GFE menggunakan kode berikut:

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
		}
	}
}

Contoh kode menambahkan string spanner/gfe_latency ke nama metrik saat diekspor ke Cloud Monitoring. Anda dapat menelusuri metrik ini di Cloud Monitoring menggunakan string yang ditambahkan.

Merekam latensi permintaan Cloud Spanner API

Latensi permintaan Cloud Spanner API adalah waktu dalam detik antara byte pertama permintaan klien yang diterima frontend Cloud Spanner API dan byte terakhir respons yang dikirim frontend Cloud Spanner API.

Metrik latensi ini tersedia sebagai bagian dari metrik Cloud Monitoring.

Merekam latensi bolak-balik klien

Latensi bolak-balik klien adalah durasi dalam milidetik antara byte pertama permintaan Cloud Spanner API yang dikirim klien ke database (melalui frontend GFE dan Cloud Spanner API), dan byte terakhir respons yang diterima klien dari database.

Metrik latensi bolak-balik klien Spanner tidak didukung menggunakan OpenTelemetry. Sebagai gantinya, Anda dapat melihat metrik sisi klien latensi operasi. Untuk mengetahui informasi selengkapnya, lihat Deskripsi metrik sisi klien.

Anda juga dapat melakukan instrumentasi metrik menggunakan OpenCensus dengan bridge dan memigrasikan data ke OpenTelemetry.

Menangkap latensi kueri

Latensi kueri adalah durasi dalam milidetik untuk menjalankan kueri SQL di database Spanner.

Anda dapat merekam latensi kueri menggunakan kode berikut:

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
		}
	}
}

Contoh kode menambahkan string spanner/query_stats_elapsed ke nama metrik saat diekspor ke Cloud Monitoring. Anda dapat menelusuri metrik ini di Cloud Monitoring menggunakan string yang ditambahkan.

Melihat metrik di Metrics Explorer

  1. Di konsol Google Cloud, buka halaman Metrics Explorer.

    Buka Metrics Explorer

  2. Pilih project Anda.

  3. Klik Select a metric.

  4. Telusuri metrik latensi menggunakan string berikut:

    • roundtrip_latency: untuk metrik latensi bolak-balik klien.
    • spanner/gfe_latency: untuk metrik latensi GFE.
    • spanner/query_stats_elapsed: untuk metrik latensi kueri.
  5. Pilih metrik, lalu klik Terapkan.

Untuk informasi selengkapnya tentang cara mengelompokkan atau menggabungkan metrik, lihat Mem-build kueri menggunakan menu.

Langkah selanjutnya