Capture custom client-side metrics using OpenCensus Stay organized with collections Save and categorize content based on your preferences.
This document describes how to capture custom client-side metricsusingOpenCensus.Custom client-side metrics can help find the source of latency in your system.For more information, seeIdentify the latency point.
Note:OpenCensus is deprecated.We recommend using OpenTelemetry to capture and visualize Spannerobservability metrics. For more information, seeCapture custom metrics using OpenTelemetry.Spanner client libraries also provide statistics and traces usingthe OpenCensus observability framework. By default, the framework is disabled.
You need to be familiar withcustom metrics associated with OpenCensusand have the OpenCensus metrics libraries and the Google Cloud Observability exporter availableto your application before capturing custom metrics.
Capture client round-trip latency
Client round-trip latency is the duration in milliseconds between the first byteof the Spanner API request that the client sends to the databaseand the last byte of the response that the client receives from the database.The API request can be sent through the Google Front End (GFE) or theCloud Spanner API frontend.
You can capture client round-trip latency using the following code:
Java
staticvoidcaptureGrpcMetric(DatabaseClientdbClient){// Add io.grpc:grpc-census and io.opencensus:opencensus-exporter-stats-stackdriver// dependencies to enable gRPC metrics.// Register basic gRPC views.RpcViews.registerClientGrpcBasicViews();// Enable OpenCensus exporters to export metrics to Stackdriver Monitoring.// Exporters use Application Default Credentials to authenticate.// See https://developers.google.com/identity/protocols/application-default-credentials// for more details.try{StackdriverStatsExporter.createAndRegister();}catch(IOException|IllegalStateExceptione){System.out.println("Error during StackdriverStatsExporter");}try(ResultSetresultSet=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
import("context""fmt""io""regexp""cloud.google.com/go/spanner""google.golang.org/api/iterator""contrib.go.opencensus.io/exporter/stackdriver""go.opencensus.io/plugin/ocgrpc""go.opencensus.io/stats/view")varvalidDatabasePattern=regexp.MustCompile("^projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)/databases/(?P<database>[^/]+)$")funcqueryWithGRPCMetric(wio.Writer,dbstring)error{projectID,_,_,err:=parseDatabaseName(db)iferr!=nil{returnerr}ctx:=context.Background()client,err:=spanner.NewClient(ctx,db)iferr!=nil{returnerr}deferclient.Close()// Register OpenCensus views.iferr:=view.Register(ocgrpc.DefaultClientViews...);err!=nil{returnerr}// Create OpenCensus Stackdriver exporter.sd,err:=stackdriver.NewExporter(stackdriver.Options{ProjectID:projectID,})iferr!=nil{returnerr}// It is imperative to invoke flush before your main function exitsdefersd.Flush()// Start the metrics exportersd.StartMetricsExporter()defersd.StopMetricsExporter()stmt:=spanner.Statement{SQL:`SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}iter:=client.Single().Query(ctx,stmt)deferiter.Stop()for{row,err:=iter.Next()iferr==iterator.Done{returnnil}iferr!=nil{returnerr}varsingerID,albumIDint64varalbumTitlestringiferr:=row.Columns(&singerID,&albumID,&albumTitle);err!=nil{returnerr}fmt.Fprintf(w,"%d %d %s\n",singerID,albumID,albumTitle)}}funcparseDatabaseName(databaseUristring)(project,instance,databasestring,errerror){matches:=validDatabasePattern.FindStringSubmatch(databaseUri)iflen(matches)==0{return"","","",fmt.Errorf("failed to parse database name from %q according to pattern %q",databaseUri,validDatabasePattern.String())}returnmatches[1],matches[2],matches[3],nil}The code sample appends the stringroundtrip_latency to the metric name whenit's exported to Cloud Monitoring. You can search for this metric inCloud Monitoring using the appended string.
Capture GFE latency
GFE latency is the duration in milliseconds betweenwhen the Google network receives a remote procedure call from the client andwhen the GFE receives the first byte of the response.
You can capture GFE latency using the following code:
Java
staticvoidcaptureGfeMetric(DatabaseClientdbClient){// Capture GFE Latency.SpannerRpcViews.registerGfeLatencyView();// Capture GFE Latency and GFE Header missing count.// SpannerRpcViews.registerGfeLatencyAndHeaderMissingCountViews();// Capture only GFE Header missing count.// SpannerRpcViews.registerGfeHeaderMissingCountView();// Enable OpenCensus exporters to export metrics to Stackdriver Monitoring.// Exporters use Application Default Credentials to authenticate.// See https://developers.google.com/identity/protocols/application-default-credentials// for more details.try{StackdriverStatsExporter.createAndRegister();}catch(IOException|IllegalStateExceptione){System.out.println("Error during StackdriverStatsExporter");}try(ResultSetresultSet=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
// We are in the process of adding support in the Cloud Spanner Go Client Library// to capture the gfe_latency metric.import("context""fmt""io""strconv""strings"spanner"cloud.google.com/go/spanner/apiv1"sppb"cloud.google.com/go/spanner/apiv1/spannerpb"gax"github.com/googleapis/gax-go/v2""google.golang.org/grpc""google.golang.org/grpc/metadata""contrib.go.opencensus.io/exporter/stackdriver""go.opencensus.io/stats""go.opencensus.io/stats/view""go.opencensus.io/tag")// OpenCensus Tag, Measure and View.var(KeyMethod=tag.MustNewKey("grpc_client_method")GFELatencyMs=stats.Int64("cloud.google.com/go/spanner/gfe_latency","Latency between Google's network receives an RPC and reads back the first byte of the response","ms")GFELatencyView=view.View{Name:"cloud.google.com/go/spanner/gfe_latency",Measure:GFELatencyMs,Description:"Latency between Google's network receives an RPC and reads back the first byte of the response",Aggregation:view.Distribution(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),TagKeys:[]tag.Key{KeyMethod}})funcqueryWithGFELatency(wio.Writer,dbstring)error{projectID,_,_,err:=parseDatabaseName(db)iferr!=nil{returnerr}ctx:=context.Background()client,err:=spanner.NewClient(ctx)iferr!=nil{returnerr}deferclient.Close()// Register OpenCensus views.err=view.Register(&GFELatencyView)iferr!=nil{returnerr}// Create OpenCensus Stackdriver exporter.sd,err:=stackdriver.NewExporter(stackdriver.Options{ProjectID:projectID,})iferr!=nil{returnerr}// It is imperative to invoke flush before your main function exitsdefersd.Flush()// Start the metrics exportersd.StartMetricsExporter()defersd.StopMetricsExporter()// Create a session.req:=&sppb.CreateSessionRequest{Database:db}session,err:=client.CreateSession(ctx,req)iferr!=nil{returnerr}// Execute a SQL query and retrieve the GFE server-timing header in gRPC metadata.req2:=&sppb.ExecuteSqlRequest{Session:session.Name,Sql:`SELECT SingerId, AlbumId, AlbumTitle FROM Albums`,}varmdmetadata.MDresultSet,err:=client.ExecuteSql(ctx,req2,gax.WithGRPCOptions(grpc.Header(&md)))iferr!=nil{returnerr}for_,row:=rangeresultSet.GetRows(){for_,value:=rangerow.GetValues(){fmt.Fprintf(w,"%s ",value.GetStringValue())}fmt.Fprintf(w,"\n")}// The format is: "server-timing: gfet4t7; dur=[GFE latency in ms]"srvTiming:=md.Get("server-timing")[0]gfeLtcy,err:=strconv.Atoi(strings.TrimPrefix(srvTiming,"gfet4t7; dur="))iferr!=nil{returnerr}// Record GFE t4t7 latency with OpenCensus.ctx,err=tag.New(ctx,tag.Insert(KeyMethod,"ExecuteSql"))iferr!=nil{returnerr}stats.Record(ctx,GFELatencyMs.M(int64(gfeLtcy)))returnnil}The code sample appends the stringspanner/gfe_latency to the metric name whenit's exported to Cloud Monitoring. You can search for this metric inCloud Monitoring using the appended string.
Capture Cloud Spanner API request latency
Cloud Spanner API request latency is the time in secondsbetween the first byte of client request that the Cloud Spanner API frontendreceives and the last byte of response that the Cloud Spanner APIfrontend sends.
This latency metric is available as part ofSpanner metrics inCloud Monitoring.
Capture query latency
Query latency is the duration in milliseconds to run SQLqueries in the Spanner database.
You can capture query latency using the following code:
Java
privatestaticfinalStringMILLISECOND="ms";staticfinalList<Double>RPC_MILLIS_BUCKET_BOUNDARIES=Collections.unmodifiableList(Arrays.asList(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));staticfinalAggregationAGGREGATION_WITH_MILLIS_HISTOGRAM=Distribution.create(BucketBoundaries.create(RPC_MILLIS_BUCKET_BOUNDARIES));staticMeasureDoubleQUERY_STATS_ELAPSED=MeasureDouble.create("cloud.google.com/java/spanner/query_stats_elapsed","The execution of the query",MILLISECOND);// Register the view. It is imperative that this step exists,// otherwise recorded metrics will be dropped and never exported.staticViewQUERY_STATS_LATENCY_VIEW=View.create(Name.create("cloud.google.com/java/spanner/query_stats_elapsed"),"The execution of the query",QUERY_STATS_ELAPSED,AGGREGATION_WITH_MILLIS_HISTOGRAM,Collections.emptyList());staticViewManagermanager=Stats.getViewManager();privatestaticfinalStatsRecorderSTATS_RECORDER=Stats.getStatsRecorder();staticvoidcaptureQueryStatsMetric(DatabaseClientdbClient){manager.registerView(QUERY_STATS_LATENCY_VIEW);// Enable OpenCensus exporters to export metrics to Cloud Monitoring.// Exporters use Application Default Credentials to authenticate.// See https://developers.google.com/identity/protocols/application-default-credentials// for more details.try{StackdriverStatsExporter.createAndRegister();}catch(IOException|IllegalStateExceptione){System.out.println("Error during StackdriverStatsExporter");}try(ResultSetresultSet=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));}Valuevalue=resultSet.getStats().getQueryStats().getFieldsOrDefault("elapsed_time",Value.newBuilder().setStringValue("0 msecs").build());doubleelapasedTime=Double.parseDouble(value.getStringValue().replaceAll(" msecs",""));STATS_RECORDER.newMeasureMap().put(QUERY_STATS_ELAPSED,elapasedTime).record();}}Go
import("context""fmt""io""strconv""strings""cloud.google.com/go/spanner""google.golang.org/api/iterator""contrib.go.opencensus.io/exporter/stackdriver""go.opencensus.io/stats""go.opencensus.io/stats/view""go.opencensus.io/tag")// OpenCensus Tag, Measure and View.var(QueryStatsElapsed=stats.Float64("cloud.google.com/go/spanner/query_stats_elapsed","The execution of the query","ms")QueryStatsLatencyView=view.View{Name:"cloud.google.com/go/spanner/query_stats_elapsed",Measure:QueryStatsElapsed,Description:"The execution of the query",Aggregation:view.Distribution(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),TagKeys:[]tag.Key{}})funcqueryWithQueryStats(wio.Writer,dbstring)error{projectID,_,_,err:=parseDatabaseName(db)iferr!=nil{returnerr}ctx:=context.Background()client,err:=spanner.NewClient(ctx,db)iferr!=nil{returnerr}deferclient.Close()// Register OpenCensus views.err=view.Register(&QueryStatsLatencyView)iferr!=nil{returnerr}// Create OpenCensus Stackdriver exporter.sd,err:=stackdriver.NewExporter(stackdriver.Options{ProjectID:projectID,})iferr!=nil{returnerr}// It is imperative to invoke flush before your main function exitsdefersd.Flush()// Start the metrics exportersd.StartMetricsExporter()defersd.StopMetricsExporter()// Execute a SQL query and get the query stats.stmt:=spanner.Statement{SQL:`SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}iter:=client.Single().QueryWithStats(ctx,stmt)deferiter.Stop()for{row,err:=iter.Next()iferr==iterator.Done{// Record query execution time with OpenCensus.elapasedTime:=iter.QueryStats["elapsed_time"].(string)elapasedTimeMs,err:=strconv.ParseFloat(strings.TrimSuffix(elapasedTime," msecs"),64)iferr!=nil{returnerr}stats.Record(ctx,QueryStatsElapsed.M(elapasedTimeMs))returnnil}iferr!=nil{returnerr}varsingerID,albumIDint64varalbumTitlestringiferr:=row.Columns(&singerID,&albumID,&albumTitle);err!=nil{returnerr}fmt.Fprintf(w,"%d %d %s\n",singerID,albumID,albumTitle)}}The code sample appends the stringspanner/query_stats_elapsed to the metric name whenit's exported to Cloud Monitoring. You can search for this metric onCloud Monitoring using the appended string.
View metrics in the Metrics Explorer
In the Google Cloud console, go to the Metrics Explorer page.
Select your project.
ClickSelect a metric.
Search for a latency metrics using the following strings:
roundtrip_latency: for the client round-trip latency metric.spanner/gfe_latency: for the GFE latency metric.spanner/query_stats_elapsed: for the query latency metric.
Select the metric, then clickApply.
For more information on grouping or aggregating your metric, seeBuild queries using menus.
What's next
Learn more aboutOpenCensus.
Learn how to usemetrics to diagnose latency.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-12-17 UTC.