Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

Commit aa7e823

Browse files
committed
fix: Connectivity Error Metrics
1 parent 2349908 commit aa7e823

3 files changed

Lines changed: 86 additions & 7 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.spanner;
17+
18+
import io.grpc.ClientStreamTracer;
19+
import io.grpc.Metadata;
20+
import java.util.concurrent.atomic.AtomicBoolean;
21+
22+
/** Captures the event when a request is sent from the gRPC client. */
23+
public class SpannerGrpcStreamTracer extends ClientStreamTracer {
24+
25+
private final AtomicBoolean outBoundMessageSent = new AtomicBoolean(false);
26+
27+
public SpannerGrpcStreamTracer() {}
28+
29+
public boolean isOutBoundMessageSent() {
30+
return outBoundMessageSent.get();
31+
}
32+
33+
/** An outbound message has been serialized and sent to the transport. */
34+
@Override
35+
public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
36+
outBoundMessageSent.set(true);
37+
}
38+
39+
public static class Factory extends ClientStreamTracer.Factory {
40+
41+
SpannerGrpcStreamTracer spannerGrpcStreamTracer;
42+
43+
public Factory(SpannerGrpcStreamTracer spannerGrpcStreamTracer) {
44+
this.spannerGrpcStreamTracer = spannerGrpcStreamTracer;
45+
}
46+
47+
@Override
48+
public ClientStreamTracer newClientStreamTracer(
49+
ClientStreamTracer.StreamInfo info, Metadata headers) {
50+
return spannerGrpcStreamTracer;
51+
}
52+
}
53+
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,9 @@
2828
import com.google.common.cache.Cache;
2929
import com.google.common.cache.CacheBuilder;
3030
import com.google.spanner.admin.database.v1.DatabaseName;
31-
import io.grpc.CallOptions;
32-
import io.grpc.Channel;
33-
import io.grpc.ClientCall;
34-
import io.grpc.ClientInterceptor;
31+
import io.grpc.*;
3532
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
3633
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
37-
import io.grpc.Metadata;
38-
import io.grpc.MethodDescriptor;
3934
import io.grpc.alts.AltsContextUtil;
4035
import io.opencensus.stats.MeasureMap;
4136
import io.opencensus.stats.Stats;
@@ -50,6 +45,7 @@
5045
import java.util.HashMap;
5146
import java.util.Map;
5247
import java.util.concurrent.ExecutionException;
48+
import java.util.concurrent.atomic.AtomicBoolean;
5349
import java.util.logging.Level;
5450
import java.util.logging.Logger;
5551
import java.util.regex.Matcher;
@@ -102,7 +98,11 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
10298
ApiTracer tracer = callOptions.getOption(TRACER_KEY);
10399
CompositeTracer compositeTracer =
104100
tracer instanceof CompositeTracer ? (CompositeTracer) tracer : null;
105-
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
101+
final AtomicBoolean headersReceived = new AtomicBoolean(false);
102+
SpannerGrpcStreamTracer streamTracer = new SpannerGrpcStreamTracer();
103+
CallOptions newOptions =
104+
callOptions.withStreamTracerFactory(new SpannerGrpcStreamTracer.Factory(streamTracer));
105+
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, newOptions)) {
106106
@Override
107107
public void start(Listener<RespT> responseListener, Metadata headers) {
108108
try {
@@ -124,13 +124,33 @@ public void start(Listener<RespT> responseListener, Metadata headers) {
124124
new SimpleForwardingClientCallListener<RespT>(responseListener) {
125125
@Override
126126
public void onHeaders(Metadata metadata) {
127+
headersReceived.set(true);
127128
// Check if the call uses DirectPath by inspecting the ALTS context.
128129
boolean isDirectPathUsed = AltsContextUtil.check(getAttributes());
129130
addDirectPathUsedAttribute(compositeTracer, isDirectPathUsed);
130131
processHeader(
131132
metadata, tagContext, attributes, span, compositeTracer, isDirectPathUsed);
132133
super.onHeaders(metadata);
133134
}
135+
136+
@Override
137+
public void onClose(Status status, Metadata trailers) {
138+
// Check if RPC was sent from gRPC client, but no response headers were received.
139+
// This can happen in
140+
// case of a timeout, for example.
141+
if (streamTracer.isOutBoundMessageSent() && !headersReceived.get()) {
142+
if (compositeTracer != null) {
143+
compositeTracer.recordGfeHeaderMissingCount(1L);
144+
// Disable afe_connectivity_error_count metric as AFE header is disabled in
145+
// backend
146+
// currently.
147+
// if (GapicSpannerRpc.isEnableAFEServerTiming()) {
148+
// compositeTracer.recordAfeHeaderMissingCount(1L);
149+
// }
150+
}
151+
}
152+
super.onClose(status, trailers);
153+
}
134154
},
135155
headers);
136156
} catch (ExecutionException executionException) {

google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ public void testNoNetworkConnection() {
347347
// Attempt count should have a failed metric point for CreateSession.
348348
assertEquals(
349349
1, getAggregatedValue(attemptCountMetricData, expectedAttributesCreateSessionFailed), 0);
350+
351+
// Connectivity count will not increase as client did not attempt to send the request
352+
assertFalse(
353+
checkIfMetricExists(metricReader, BuiltInMetricsConstant.GFE_CONNECTIVITY_ERROR_NAME));
350354
}
351355

352356
@Test
@@ -390,6 +394,8 @@ public void testNoServerTimingHeader() throws IOException, InterruptedException
390394

391395
assertFalse(checkIfMetricExists(metricReader, BuiltInMetricsConstant.AFE_LATENCIES_NAME));
392396
assertFalse(checkIfMetricExists(metricReader, BuiltInMetricsConstant.GFE_LATENCIES_NAME));
397+
assertTrue(
398+
checkIfMetricExists(metricReader, BuiltInMetricsConstant.GFE_CONNECTIVITY_ERROR_NAME));
393399
// Metric is disabled currently
394400
assertFalse(
395401
checkIfMetricExists(metricReader, BuiltInMetricsConstant.AFE_CONNECTIVITY_ERROR_NAME));

0 commit comments

Comments
 (0)