איסוף מדדים מותאמים אישית בצד הלקוח באמצעות OpenTelemetry

במסמך הזה מוסבר איך ללכוד מדדים מותאמים אישית של לקוחות באמצעות OpenTelemetry. מדדים מותאמים אישית של לקוחות זמינים באמצעות ספריות הלקוח של Java ושל Go.

מדדים מותאמים אישית בצד הלקוח יכולים לעזור לכם למצוא את מקור ההשהיה במערכת. מידע נוסף זמין במאמר בנושא נקודות השהיה בבקשת Spanner.

ספריות הלקוח של Spanner מספקות גם נתונים סטטיסטיים ועקבות באמצעות Framework הניתנות למעקב OpenTelemetry. מידע נוסף זמין במאמר בנושא הגדרת איסוף נתוני מעקב באמצעות OpenTelemetry.

‫OpenTelemetry הוא מסגרת וערכת כלים בקוד פתוח שמאפשרות לכם ליצור ולנהל נתוני טלמטריה כמו עקבות, מדדים ויומנים.

לפני שמתחילים

צריך להגדיר את OpenTelemetry SDK עם האפשרויות המתאימות לייצוא נתוני הטלמטריה. מומלץ להשתמש ב-exporter של OpenTelemetry Protocol‏ (OTLP).

כדי להגדיר מדדים מותאמים אישית בצד הלקוח באמצעות OpenTelemetry, צריך להגדיר את OpenTelemetry SDK ואת OTLP exporter:

  1. מוסיפים את יחסי התלות הנדרשים לאפליקציה באמצעות הקוד הבא:

    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.34.0
    go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
    go.opentelemetry.io/otel/metric v1.34.0
    go.opentelemetry.io/otel/sdk v1.34.0
    go.opentelemetry.io/otel/sdk/metric v1.34.0
  2. יוצרים אובייקט OpenTelemetry עם OTLP exporter ומזריקים אותו ל-Spanner באמצעות 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"),
    		))
    }
    

תיעוד זמן האחזור של GFE

זמן האחזור של ממשק הקצה של Google‏ (GFE) הוא משך הזמן באלפיות השנייה שחלף בין הרגע שבו הרשת של Google מקבלת קריאה לפרוצדורה מרוחקת מהלקוח לבין הרגע שבו GFE מקבל את הבייט הראשון של התגובה.

אפשר לתעד את זמן האחזור של GFE באמצעות הקוד הבא:

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

אפליקציית דוגמת הקוד מוסיפה את המחרוזת spanner/gfe_latency לשם המדד כשהוא מיוצא ל-Cloud Monitoring. אפשר לחפש את המדד הזה בדף 'מעקב' באמצעות המחרוזת שנוספה.

תיעוד של זמן האחזור של בקשות API ל-Cloud Spanner

זמן האחזור של בקשת Cloud Spanner API הוא הזמן בשניות שחלף בין הבייט הראשון של בקשת הלקוח שמתקבלת בחלק הקצה הקדמי של Cloud Spanner API לבין הבייט האחרון של התגובה שנשלחת מהחלק הקצה הקדמי של Cloud Spanner API.

מדד ההשהיה הזה זמין כחלק ממדדי המעקב.

תיעוד זמן האחזור הלוך ושוב של הלקוח

השהיית הלוך ושוב של הלקוח היא משך הזמן במילישניות בין הבייט הראשון של בקשת Cloud Spanner API שהלקוח שולח למסד הנתונים (דרך GFE וגם דרך ממשק הקצה של Cloud Spanner API), לבין הבייט האחרון של התגובה שהלקוח מקבל ממסד הנתונים.

אין תמיכה במדד זמן האחזור הלוך ושוב של לקוח Spanner באמצעות OpenTelemetry. במקום זאת, אפשר להציג את מדד זמן האחזור של הפעולה בצד הלקוח. מידע נוסף זמין במאמר תיאורים של מדדים מצד הלקוח.

אפשר גם להטמיע את המדד באמצעות OpenCensus עם גשר ולהעביר את הנתונים אל OpenTelemetry.

תיעוד זמן האחזור של השאילתה

זמן האחזור של השאילתה הוא משך הזמן באלפיות השנייה שנדרש להרצת שאילתות SQL במסד הנתונים של Spanner.

אפשר לתעד את זמן האחזור של השאילתה באמצעות הקוד הבא:

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

אפליקציית דוגמת הקוד מוסיפה את המחרוזת spanner/query_stats_elapsed לשם המדד כשהיא מיוצאת ל-Monitoring. אפשר לחפש את המדד הזה בדף 'מעקב' באמצעות המחרוזת שנוספה.

הצגת מדדים ב-Metrics Explorer

  1. נכנסים לדף Metrics Explorer במסוף Google Cloud .

    לדף Metrics Explorer

  2. בוחרים את הפרויקט הרצוי.

  3. לוחצים על בחירת מדד.

  4. אפשר לחפש מדדי זמן אחזור באמצעות המחרוזות הבאות:

    • roundtrip_latency: למדד זמן האחזור הלוך ושוב של הלקוח.
    • spanner/gfe_latency: למדד זמן האחזור של GFE.
    • spanner/query_stats_elapsed: למדד זמן האחזור של השאילתה.
  5. בוחרים את המדד ולוחצים על אישור.

מידע נוסף על קיבוץ או צבירת נתונים של המדד שלך, ראה יצירת שאילתות באמצעות תפריטים.

המאמרים הבאים