diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml index 30d61cdc6a..bd4c6f0b63 100644 --- a/google-cloud-bigtable/pom.xml +++ b/google-cloud-bigtable/pom.xml @@ -136,10 +136,6 @@ com.google.protobuf protobuf-java-util - - com.google.code.gson - gson - io.opencensus opencensus-api @@ -147,7 +143,6 @@ io.grpc grpc-alts - runtime org.checkerframework diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index d1fe259ea1..6a9dcdfbec 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -922,7 +922,8 @@ private Builder() { .setReverseScans(true) .setLastScannedRowResponses(true) .setDirectAccessRequested(DIRECT_PATH_ENABLED) - .setTrafficDirectorEnabled(DIRECT_PATH_ENABLED); + .setTrafficDirectorEnabled(DIRECT_PATH_ENABLED) + .setPeerInfo(true); } private Builder(EnhancedBigtableStubSettings settings) { diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/MetadataExtractorInterceptor.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/MetadataExtractorInterceptor.java new file mode 100644 index 0000000000..5b43f57527 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/MetadataExtractorInterceptor.java @@ -0,0 +1,198 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.stub; + +import com.google.api.core.InternalApi; +import com.google.api.gax.grpc.GrpcCallContext; +import com.google.bigtable.v2.PeerInfo; +import com.google.bigtable.v2.ResponseParams; +import com.google.common.base.Strings; +import com.google.protobuf.InvalidProtocolBufferException; +import io.grpc.Attributes; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.alts.AltsContextUtil; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +@InternalApi +public class MetadataExtractorInterceptor implements ClientInterceptor { + private final SidebandData sidebandData = new SidebandData(); + + public GrpcCallContext injectInto(GrpcCallContext ctx) { + // TODO: migrate to using .withTransportChannel + // This will require a change on gax's side to expose the underlying ManagedChannel in + // GrpcTransportChannel (its currently package private). + return ctx.withChannel(ClientInterceptors.intercept(ctx.getChannel(), this)) + .withCallOptions(ctx.getCallOptions().withOption(SidebandData.KEY, sidebandData)); + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + return new ForwardingClientCall.SimpleForwardingClientCall( + channel.newCall(methodDescriptor, callOptions)) { + @Override + public void start(Listener responseListener, Metadata headers) { + sidebandData.reset(); + + super.start( + new ForwardingClientCallListener.SimpleForwardingClientCallListener( + responseListener) { + @Override + public void onHeaders(Metadata headers) { + sidebandData.onResponseHeaders(headers, getAttributes()); + super.onHeaders(headers); + } + + @Override + public void onClose(Status status, Metadata trailers) { + sidebandData.onClose(status, trailers); + super.onClose(status, trailers); + } + }, + headers); + } + }; + } + + public SidebandData getSidebandData() { + return sidebandData; + } + + public static class SidebandData { + private static final CallOptions.Key KEY = + CallOptions.Key.create("bigtable-sideband"); + + private static final Metadata.Key SERVER_TIMING_HEADER_KEY = + Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER); + private static final Pattern SERVER_TIMING_HEADER_PATTERN = + Pattern.compile(".*dur=(?\\d+)"); + private static final Metadata.Key LOCATION_METADATA_KEY = + Metadata.Key.of("x-goog-ext-425905942-bin", Metadata.BINARY_BYTE_MARSHALLER); + private static final Metadata.Key PEER_INFO_KEY = + Metadata.Key.of("bigtable-peer-info", Metadata.ASCII_STRING_MARSHALLER); + + @Nullable private volatile ResponseParams responseParams; + @Nullable private volatile PeerInfo peerInfo; + @Nullable private volatile Long gfeTiming; + + @Nullable + public ResponseParams getResponseParams() { + return responseParams; + } + + @Nullable + public PeerInfo getPeerInfo() { + return peerInfo; + } + + @Nullable + public Long getGfeTiming() { + return gfeTiming; + } + + private void reset() { + responseParams = null; + peerInfo = null; + gfeTiming = null; + } + + void onResponseHeaders(Metadata md, Attributes attributes) { + responseParams = extractResponseParams(md); + gfeTiming = extractGfeLatency(md); + peerInfo = extractPeerInfo(md, gfeTiming, attributes); + } + + void onClose(Status status, Metadata trailers) { + if (responseParams == null) { + responseParams = extractResponseParams(trailers); + } + } + + @Nullable + private static Long extractGfeLatency(Metadata metadata) { + String serverTiming = metadata.get(SERVER_TIMING_HEADER_KEY); + if (serverTiming == null) { + return null; + } + Matcher matcher = SERVER_TIMING_HEADER_PATTERN.matcher(serverTiming); + // this should always be true + if (matcher.find()) { + return Long.parseLong(matcher.group("dur")); + } + return null; + } + + @Nullable + private static PeerInfo extractPeerInfo( + Metadata metadata, Long gfeTiming, Attributes attributes) { + String encodedStr = metadata.get(PEER_INFO_KEY); + if (Strings.isNullOrEmpty(encodedStr)) { + return null; + } + + try { + byte[] decoded = Base64.getUrlDecoder().decode(encodedStr); + PeerInfo peerInfo = PeerInfo.parseFrom(decoded); + PeerInfo.TransportType effectiveTransport = peerInfo.getTransportType(); + + // TODO: remove this once transport_type is being sent by the server + // This is a temporary workaround to detect directpath until its available from the server + if (effectiveTransport == PeerInfo.TransportType.TRANSPORT_TYPE_UNKNOWN) { + boolean isAlts = AltsContextUtil.check(attributes); + if (isAlts) { + effectiveTransport = PeerInfo.TransportType.TRANSPORT_TYPE_DIRECT_ACCESS; + } else if (gfeTiming != null) { + effectiveTransport = PeerInfo.TransportType.TRANSPORT_TYPE_CLOUD_PATH; + } + } + if (effectiveTransport != PeerInfo.TransportType.TRANSPORT_TYPE_UNKNOWN) { + peerInfo = peerInfo.toBuilder().setTransportType(effectiveTransport).build(); + } + return peerInfo; + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to parse " + + PEER_INFO_KEY.name() + + " from the response header value: " + + encodedStr); + } + } + + @Nullable + private static ResponseParams extractResponseParams(Metadata metadata) { + byte[] responseParams = metadata.get(LOCATION_METADATA_KEY); + if (responseParams != null) { + try { + return ResponseParams.parseFrom(responseParams); + } catch (InvalidProtocolBufferException e) { + } + } + return null; + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableGrpcStreamTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableGrpcStreamTracer.java index a364adbc46..9b220c1de3 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableGrpcStreamTracer.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableGrpcStreamTracer.java @@ -15,10 +15,8 @@ */ package com.google.cloud.bigtable.data.v2.stub.metrics; -import com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsTracer.TransportAttrs; import io.grpc.ClientStreamTracer; import io.grpc.Metadata; -import io.grpc.Status; /** * Records the time a request is enqueued in a grpc channel queue. This a bridge between gRPC stream @@ -26,16 +24,9 @@ * asking gRPC to start an RPC and gRPC actually serializing that RPC. */ class BigtableGrpcStreamTracer extends ClientStreamTracer { - private static final String GRPC_LB_LOCALITY_KEY = "grpc.lb.locality"; - private static final String GRPC_LB_BACKEND_SERVICE_KEY = "grpc.lb.backend_service"; - - private final StreamInfo info; private final BigtableTracer tracer; - private volatile String backendService = null; - private volatile String locality = null; - public BigtableGrpcStreamTracer(StreamInfo info, BigtableTracer tracer) { - this.info = info; + public BigtableGrpcStreamTracer(BigtableTracer tracer) { this.tracer = tracer; } @@ -44,26 +35,6 @@ public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalU tracer.grpcMessageSent(); } - @Override - public void addOptionalLabel(String key, String value) { - switch (key) { - case GRPC_LB_LOCALITY_KEY: - this.locality = value; - break; - case GRPC_LB_BACKEND_SERVICE_KEY: - this.backendService = value; - break; - } - - super.addOptionalLabel(key, value); - } - - @Override - public void streamClosed(Status status) { - tracer.setTransportAttrs(TransportAttrs.create(locality, backendService)); - super.streamClosed(status); - } - static class Factory extends ClientStreamTracer.Factory { private final BigtableTracer tracer; @@ -75,7 +46,7 @@ static class Factory extends ClientStreamTracer.Factory { @Override public ClientStreamTracer newClientStreamTracer( ClientStreamTracer.StreamInfo info, Metadata headers) { - return new BigtableGrpcStreamTracer(info, tracer); + return new BigtableGrpcStreamTracer(tracer); } } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracer.java index 898d743cd9..a1a53b6089 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracer.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracer.java @@ -20,6 +20,7 @@ import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.BaseApiTracer; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import java.time.Duration; import javax.annotation.Nullable; @@ -70,36 +71,12 @@ public int getAttempt() { return attempt; } - /** - * Record the latency between Google's network receives the RPC and reads back the first byte of - * the response from server-timing header. If server-timing header is missing, increment the - * missing header count. - */ - public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwable) { - // noop - } - /** Adds an annotation of the total throttled time of a batch. */ public void batchRequestThrottled(long throttledTimeMs) { // noop } - /** - * Set the Bigtable zone and cluster so metrics can be tagged with location information. This will - * be called in BuiltinMetricsTracer. - */ - public void setLocations(String zone, String cluster) { - // noop - } - - /** Set the underlying transport used to process the attempt */ - public void setTransportAttrs(BuiltinMetricsTracer.TransportAttrs attrs) {} - - @Deprecated - /** - * @deprecated {@link #grpcMessageSent()} is called instead. - */ - public void grpcChannelQueuedLatencies(long queuedTimeMs) { + public void setSidebandData(MetadataExtractorInterceptor.SidebandData sidebandData) { // noop } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerStreamingCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerStreamingCallable.java index 13b832b8b1..3cdcdc374e 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerStreamingCallable.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerStreamingCallable.java @@ -16,11 +16,12 @@ package com.google.cloud.bigtable.data.v2.stub.metrics; import com.google.api.core.InternalApi; -import com.google.api.gax.grpc.GrpcResponseMetadata; +import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.ResponseObserver; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.StreamController; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.cloud.bigtable.data.v2.stub.SafeResponseObserver; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; @@ -28,19 +29,8 @@ import javax.annotation.Nonnull; /** - * This callable will - *
  • -Inject a {@link GrpcResponseMetadata} to access the headers returned by gRPC methods upon - * completion. The {@link BigtableTracer} will process metrics that were injected in the - * header/trailer and publish them to OpenCensus. If {@link GrpcResponseMetadata#getMetadata()} - * returned null, it probably means that the request has never reached GFE, and it'll increment - * the gfe_header_missing_counter in this case. - *
  • -This class will also access trailers from {@link GrpcResponseMetadata} to record zone and - * cluster ids. - *
  • -Call {@link BigtableTracer#onRequest(int)} to record the request events in a stream. - *
  • -This class will also inject a {@link BigtableGrpcStreamTracer} that'll record the time an - * RPC spent in a grpc channel queue. - *
  • This class is considered an internal implementation detail and not meant to be used by - * applications. + * This class is considered an internal implementation detail and not meant to be used by + * applications. */ @InternalApi public class BigtableTracerStreamingCallable @@ -56,40 +46,41 @@ public BigtableTracerStreamingCallable( @Override public void call( RequestT request, ResponseObserver responseObserver, ApiCallContext context) { - final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata(); + GrpcCallContext grpcCtx = (GrpcCallContext) context; + + MetadataExtractorInterceptor metadataExtractor = new MetadataExtractorInterceptor(); + grpcCtx = metadataExtractor.injectInto(grpcCtx); + // tracer should always be an instance of bigtable tracer if (context.getTracer() instanceof BigtableTracer) { BigtableTracer tracer = (BigtableTracer) context.getTracer(); + tracer.setSidebandData(metadataExtractor.getSidebandData()); + grpcCtx = + grpcCtx.withCallOptions( + grpcCtx + .getCallOptions() + .withStreamTracerFactory(new BigtableGrpcStreamTracer.Factory(tracer))); + BigtableTracerResponseObserver innerObserver = - new BigtableTracerResponseObserver<>(responseObserver, tracer, responseMetadata); + new BigtableTracerResponseObserver<>(responseObserver, tracer); if (context.getRetrySettings() != null) { tracer.setTotalTimeoutDuration(context.getRetrySettings().getTotalTimeoutDuration()); } - innerCallable.call( - request, - innerObserver, - Util.injectBigtableStreamTracer( - context, responseMetadata, (BigtableTracer) context.getTracer())); + innerCallable.call(request, innerObserver, grpcCtx); } else { - innerCallable.call(request, responseObserver, context); + innerCallable.call(request, responseObserver, grpcCtx); } } private class BigtableTracerResponseObserver extends SafeResponseObserver { - private final BigtableTracer tracer; private final ResponseObserver outerObserver; - private final GrpcResponseMetadata responseMetadata; - BigtableTracerResponseObserver( - ResponseObserver observer, - BigtableTracer tracer, - GrpcResponseMetadata metadata) { + BigtableTracerResponseObserver(ResponseObserver observer, BigtableTracer tracer) { super(observer); this.tracer = tracer; this.outerObserver = observer; - this.responseMetadata = metadata; } @Override @@ -107,13 +98,11 @@ protected void onResponseImpl(ResponseT response) { @Override protected void onErrorImpl(Throwable t) { - Util.recordMetricsFromMetadata(responseMetadata, tracer, t); outerObserver.onError(t); } @Override protected void onCompleteImpl() { - Util.recordMetricsFromMetadata(responseMetadata, tracer, null); outerObserver.onComplete(); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerUnaryCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerUnaryCallable.java index 37ba74bfdb..363a69af3d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerUnaryCallable.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerUnaryCallable.java @@ -16,29 +16,17 @@ package com.google.cloud.bigtable.data.v2.stub.metrics; import com.google.api.core.ApiFuture; -import com.google.api.core.ApiFutureCallback; -import com.google.api.core.ApiFutures; import com.google.api.core.InternalApi; -import com.google.api.gax.grpc.GrpcResponseMetadata; +import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.MoreExecutors; import javax.annotation.Nonnull; /** - * This callable will: - *
  • - Inject a {@link GrpcResponseMetadata} to access the headers returned by gRPC methods upon - * completion. The {@link BigtableTracer} will process metrics that were injected in the - * header/trailer and publish them to OpenCensus. If {@link GrpcResponseMetadata#getMetadata()} - * returned null, it probably means that the request has never reached GFE, and it'll increment - * the gfe_header_missing_counter in this case. - *
  • -This class will also access trailers from {@link GrpcResponseMetadata} to record zone and - * cluster ids. - *
  • -This class will also inject a {@link BigtableGrpcStreamTracer} that'll record the time an - * RPC spent in a grpc channel queue. - *
  • This class is considered an internal implementation detail and not meant to be used by - * applications. + * This class is considered an internal implementation detail and not meant to be used by + * applications. */ @InternalApi public class BigtableTracerUnaryCallable @@ -52,46 +40,24 @@ public BigtableTracerUnaryCallable(@Nonnull UnaryCallable i @Override public ApiFuture futureCall(RequestT request, ApiCallContext context) { + MetadataExtractorInterceptor interceptor = new MetadataExtractorInterceptor(); + GrpcCallContext grpcCtx = interceptor.injectInto((GrpcCallContext) context); + // tracer should always be an instance of BigtableTracer if (context.getTracer() instanceof BigtableTracer) { BigtableTracer tracer = (BigtableTracer) context.getTracer(); - final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata(); - BigtableTracerUnaryCallback callback = - new BigtableTracerUnaryCallback( - (BigtableTracer) context.getTracer(), responseMetadata); + tracer.setSidebandData(interceptor.getSidebandData()); + + grpcCtx = + grpcCtx.withCallOptions( + grpcCtx + .getCallOptions() + .withStreamTracerFactory(new BigtableGrpcStreamTracer.Factory(tracer))); + if (context.getRetrySettings() != null) { tracer.setTotalTimeoutDuration(context.getRetrySettings().getTotalTimeoutDuration()); } - ApiFuture future = - innerCallable.futureCall( - request, - Util.injectBigtableStreamTracer( - context, responseMetadata, (BigtableTracer) context.getTracer())); - ApiFutures.addCallback(future, callback, MoreExecutors.directExecutor()); - return future; - } else { - return innerCallable.futureCall(request, context); - } - } - - private class BigtableTracerUnaryCallback implements ApiFutureCallback { - - private final BigtableTracer tracer; - private final GrpcResponseMetadata responseMetadata; - - BigtableTracerUnaryCallback(BigtableTracer tracer, GrpcResponseMetadata responseMetadata) { - this.tracer = tracer; - this.responseMetadata = responseMetadata; - } - - @Override - public void onFailure(Throwable throwable) { - Util.recordMetricsFromMetadata(responseMetadata, tracer, throwable); - } - - @Override - public void onSuccess(ResponseT response) { - Util.recordMetricsFromMetadata(responseMetadata, tracer, null); } + return innerCallable.futureCall(request, grpcCtx); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracer.java index e6ebad367a..546ea41c9f 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracer.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracer.java @@ -34,27 +34,23 @@ import com.google.api.core.ObsoleteApi; import com.google.api.gax.retrying.ServerStreamingAttemptException; import com.google.api.gax.tracing.SpanName; -import com.google.auto.value.AutoValue; +import com.google.bigtable.v2.PeerInfo; import com.google.cloud.bigtable.Version; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; import com.google.common.math.IntMath; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; import io.grpc.Deadline; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleGauge; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.LongCounter; import java.time.Duration; -import java.util.Map; +import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -62,24 +58,6 @@ * bigtable.googleapis.com/client namespace */ class BuiltinMetricsTracer extends BigtableTracer { - @AutoValue - abstract static class TransportAttrs { - @Nullable - abstract String getLocality(); - - @Nullable - abstract String getBackendService(); - - static TransportAttrs create(@Nullable String locality, @Nullable String backendService) { - return new AutoValue_BuiltinMetricsTracer_TransportAttrs(locality, backendService); - } - } - - private static final Logger logger = Logger.getLogger(BuiltinMetricsTracer.class.getName()); - private static final Gson GSON = new Gson(); - private static final TypeToken> LOCALITY_TYPE = - new TypeToken>() {}; - private static final String NAME = "java-bigtable/" + Version.VERSION; private final OperationType operationType; private final SpanName spanName; @@ -108,22 +86,21 @@ static TransportAttrs create(@Nullable String locality, @Nullable String backend private final AtomicInteger requestLeft = new AtomicInteger(0); - // Monitored resource labels private String tableId = ""; - private String zone = "global"; - private String cluster = ""; private final AtomicLong totalClientBlockingTime = new AtomicLong(0); private final Attributes baseAttributes; - private Long serverLatencies = null; private final AtomicLong grpcMessageSentDelay = new AtomicLong(0); private Deadline operationDeadline = null; private volatile long remainingDeadlineAtAttemptStart = 0; - private TransportAttrs transportAttrs = null; + // TODO: ensure that this is never null and remove all of the checks + // Sideband data wrapper itself should never be null unless a callable chain forgets to + // add BigtableTracer{Streaming,Unary}Callable. Which would be considered a bug. + @Nullable private volatile MetadataExtractorInterceptor.SidebandData sidebandData = null; // OpenCensus (and server) histogram buckets use [start, end), however OpenTelemetry uses (start, // end]. To work around this, we measure all the latencies in nanoseconds and convert them @@ -328,21 +305,8 @@ public int getAttempt() { } @Override - public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwable) { - if (latency != null) { - serverLatencies = latency; - } - } - - @Override - public void setLocations(String zone, String cluster) { - this.zone = zone; - this.cluster = cluster; - } - - @Override - public void setTransportAttrs(TransportAttrs attrs) { - this.transportAttrs = attrs; + public void setSidebandData(MetadataExtractorInterceptor.SidebandData sidebandData) { + this.sidebandData = sidebandData; } @Override @@ -390,8 +354,8 @@ private void recordOperationCompletion(@Nullable Throwable status) { Attributes attributes = baseAttributes.toBuilder() .put(TABLE_ID_KEY, tableId) - .put(CLUSTER_ID_KEY, cluster) - .put(ZONE_ID_KEY, zone) + .put(CLUSTER_ID_KEY, Util.formatClusterIdMetricLabel(sidebandData)) + .put(ZONE_ID_KEY, Util.formatZoneIdMetricLabel(sidebandData)) .put(METHOD_KEY, spanName.toString()) .put(CLIENT_NAME_KEY, NAME) .put(STREAMING_KEY, isStreaming) @@ -445,8 +409,8 @@ private void recordAttemptCompletion(@Nullable Throwable status) { Attributes attributes = baseAttributes.toBuilder() .put(TABLE_ID_KEY, tableId) - .put(CLUSTER_ID_KEY, cluster) - .put(ZONE_ID_KEY, zone) + .put(CLUSTER_ID_KEY, Util.formatClusterIdMetricLabel(sidebandData)) + .put(ZONE_ID_KEY, Util.formatZoneIdMetricLabel(sidebandData)) .put(METHOD_KEY, spanName.toString()) .put(CLIENT_NAME_KEY, NAME) .put(STREAMING_KEY, isStreaming) @@ -459,29 +423,27 @@ private void recordAttemptCompletion(@Nullable Throwable status) { attemptLatenciesHistogram.record( convertToMs(attemptTimer.elapsed(TimeUnit.NANOSECONDS)), attributes); - String transportType = "cloudpath"; + String transportTypeStr = "cloudpath"; String transportRegion = ""; String transportZone = ""; String transportSubzone = ""; - try { - if (transportAttrs != null && !Strings.isNullOrEmpty(transportAttrs.getLocality())) { - // only directpath has locality - transportType = "directpath"; - Map localityMap = - GSON.fromJson(transportAttrs.getLocality(), LOCALITY_TYPE); - transportRegion = localityMap.getOrDefault("region", ""); - transportZone = localityMap.getOrDefault("zone", ""); - transportSubzone = localityMap.getOrDefault("sub_zone", ""); - } - } catch (RuntimeException e) { - logger.log( - Level.WARNING, "Failed to parse transport locality: " + transportAttrs.getLocality(), e); + if (sidebandData != null) { + transportTypeStr = Util.formatTransportTypeMetricLabel(sidebandData); + transportZone = + Optional.ofNullable(sidebandData.getPeerInfo()) + .map(PeerInfo::getApplicationFrontendZone) + .orElse(""); + transportSubzone = + Optional.ofNullable(sidebandData.getPeerInfo()) + .map(PeerInfo::getApplicationFrontendSubzone) + .orElse(""); } + attemptLatencies2Histogram.record( convertToMs(attemptTimer.elapsed(TimeUnit.NANOSECONDS)), attributes.toBuilder() - .put(TRANSPORT_TYPE, transportType) + .put(TRANSPORT_TYPE, transportTypeStr) .put(TRANSPORT_REGION, transportRegion) .put(TRANSPORT_ZONE, transportZone) .put(TRANSPORT_SUBZONE, transportSubzone) @@ -493,8 +455,8 @@ private void recordAttemptCompletion(@Nullable Throwable status) { remainingDeadlineHistogram.record(Math.max(0, remainingDeadlineAtAttemptStart), attributes); } - if (serverLatencies != null) { - serverLatenciesHistogram.record(serverLatencies, attributes); + if (sidebandData != null && sidebandData.getGfeTiming() != null) { + serverLatenciesHistogram.record(sidebandData.getGfeTiming(), attributes); connectivityErrorCounter.add(0, attributes); } else { connectivityErrorCounter.add(1, attributes); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracer.java index f6d0858459..fad00a6d91 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracer.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracer.java @@ -19,6 +19,7 @@ import com.google.api.core.ObsoleteApi; import com.google.api.gax.tracing.ApiTracer; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; @@ -197,13 +198,6 @@ public int getAttempt() { return attempt; } - @Override - public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwable) { - for (BigtableTracer tracer : bigtableTracers) { - tracer.recordGfeMetadata(latency, throwable); - } - } - @Override public void batchRequestThrottled(long throttledTimeMs) { for (BigtableTracer tracer : bigtableTracers) { @@ -212,16 +206,9 @@ public void batchRequestThrottled(long throttledTimeMs) { } @Override - public void setLocations(String zone, String cluster) { - for (BigtableTracer tracer : bigtableTracers) { - tracer.setLocations(zone, cluster); - } - } - - @Override - public void setTransportAttrs(BuiltinMetricsTracer.TransportAttrs attrs) { - for (BigtableTracer tracer : bigtableTracers) { - tracer.setTransportAttrs(attrs); + public void setSidebandData(MetadataExtractorInterceptor.SidebandData sidebandData) { + for (BigtableTracer bigtableTracer : bigtableTracers) { + bigtableTracer.setSidebandData(sidebandData); } } @@ -246,13 +233,6 @@ public void afterResponse(long applicationLatency) { } } - @Override - public void grpcChannelQueuedLatencies(long queuedTimeMs) { - for (BigtableTracer tracer : bigtableTracers) { - tracer.grpcChannelQueuedLatencies(queuedTimeMs); - } - } - @Override public void grpcMessageSent() { for (BigtableTracer tracer : bigtableTracers) { diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracer.java index c322b75df8..53b4ca87a8 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracer.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracer.java @@ -21,6 +21,7 @@ import com.google.api.gax.retrying.ServerStreamingAttemptException; import com.google.api.gax.tracing.ApiTracerFactory.OperationType; import com.google.api.gax.tracing.SpanName; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.common.base.Stopwatch; import io.opencensus.stats.MeasureMap; import io.opencensus.stats.StatsRecorder; @@ -63,6 +64,7 @@ class MetricsTracer extends BigtableTracer { private volatile boolean reportBatchingLatency = false; private volatile long batchThrottledLatency = 0; + private MetadataExtractorInterceptor.SidebandData sidebandData; MetricsTracer( OperationType operationType, @@ -187,6 +189,14 @@ private void recordAttemptCompletion(@Nullable Throwable throwable) { RpcMeasureConstants.BIGTABLE_ATTEMPT_LATENCY, attemptTimer.elapsed(TimeUnit.MILLISECONDS)); + if (sidebandData != null && sidebandData.getGfeTiming() != null) { + measures + .put(RpcMeasureConstants.BIGTABLE_GFE_LATENCY, sidebandData.getGfeTiming()) + .put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 0L); + } else { + measures.put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 1L); + } + if (reportBatchingLatency) { measures.put(RpcMeasureConstants.BIGTABLE_BATCH_THROTTLED_TIME, batchThrottledLatency); @@ -226,20 +236,8 @@ public int getAttempt() { } @Override - public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwable) { - MeasureMap measures = stats.newMeasureMap(); - if (latency != null) { - measures - .put(RpcMeasureConstants.BIGTABLE_GFE_LATENCY, latency) - .put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 0L); - } else { - measures.put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 1L); - } - measures.record( - newTagCtxBuilder() - .putLocal( - RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create(Util.extractStatus(throwable))) - .build()); + public void setSidebandData(MetadataExtractorInterceptor.SidebandData sidebandData) { + this.sidebandData = sidebandData; } @Override diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java index 9ba2d39c49..da7de371c3 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java @@ -16,8 +16,6 @@ package com.google.cloud.bigtable.data.v2.stub.metrics; import com.google.api.core.InternalApi; -import com.google.api.gax.grpc.GrpcCallContext; -import com.google.api.gax.grpc.GrpcResponseMetadata; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode; @@ -29,6 +27,7 @@ import com.google.bigtable.v2.MaterializedViewName; import com.google.bigtable.v2.MutateRowRequest; import com.google.bigtable.v2.MutateRowsRequest; +import com.google.bigtable.v2.PeerInfo; import com.google.bigtable.v2.ReadChangeStreamRequest; import com.google.bigtable.v2.ReadModifyWriteRowRequest; import com.google.bigtable.v2.ReadRowsRequest; @@ -36,15 +35,13 @@ import com.google.bigtable.v2.SampleRowKeysRequest; import com.google.bigtable.v2.TableName; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.InvalidProtocolBufferException; -import io.grpc.CallOptions; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.StatusException; import io.grpc.StatusRuntimeException; -import io.opencensus.tags.TagValue; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.SdkMeterProvider; @@ -57,13 +54,11 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nullable; /** Utilities to help integrating with OpenCensus. */ @@ -74,12 +69,6 @@ public class Util { static final Metadata.Key ATTEMPT_EPOCH_KEY = Metadata.Key.of("bigtable-client-attempt-epoch-usec", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key SERVER_TIMING_HEADER_KEY = - Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER); - private static final Pattern SERVER_TIMING_HEADER_PATTERN = Pattern.compile(".*dur=(?\\d+)"); - static final Metadata.Key LOCATION_METADATA_KEY = - Metadata.Key.of("x-goog-ext-425905942-bin", Metadata.BINARY_BYTE_MARSHALLER); - /** Convert an exception into a value that can be used to create an OpenCensus tag value. */ public static String extractStatus(@Nullable Throwable error) { final String statusString; @@ -101,26 +90,6 @@ public static String extractStatus(@Nullable Throwable error) { return statusString; } - /** - * Await the result of the future and convert it into a value that can be used as an OpenCensus - * tag value. - */ - static TagValue extractStatusFromFuture(Future future) { - Throwable error = null; - - try { - future.get(); - } catch (InterruptedException e) { - error = e; - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - error = e.getCause(); - } catch (RuntimeException e) { - error = e; - } - return TagValue.create(extractStatus(error)); - } - static String extractTableId(Object request) { String tableName = null; String authorizedViewName = null; @@ -179,84 +148,6 @@ static Map> createStatsHeaders(ApiCallContext apiCallContex return headers.build(); } - private static Long getGfeLatency(@Nullable Metadata metadata) { - if (metadata == null) { - return null; - } - String serverTiming = metadata.get(SERVER_TIMING_HEADER_KEY); - if (serverTiming == null) { - return null; - } - Matcher matcher = SERVER_TIMING_HEADER_PATTERN.matcher(serverTiming); - // this should always be true - if (matcher.find()) { - long latency = Long.valueOf(matcher.group("dur")); - return latency; - } - return null; - } - - private static ResponseParams getResponseParams(@Nullable Metadata metadata) { - if (metadata == null) { - return null; - } - byte[] responseParams = metadata.get(Util.LOCATION_METADATA_KEY); - if (responseParams != null) { - try { - return ResponseParams.parseFrom(responseParams); - } catch (InvalidProtocolBufferException e) { - } - } - return null; - } - - static void recordMetricsFromMetadata( - GrpcResponseMetadata responseMetadata, BigtableTracer tracer, Throwable throwable) { - Metadata metadata = responseMetadata.getMetadata(); - - // Get the response params from the metadata. Check both headers and trailers - // because in different environments the metadata could be returned in headers or trailers - @Nullable ResponseParams responseParams = getResponseParams(responseMetadata.getMetadata()); - if (responseParams == null) { - responseParams = getResponseParams(responseMetadata.getTrailingMetadata()); - } - // Set tracer locations if response params is not null - if (responseParams != null) { - tracer.setLocations(responseParams.getZoneId(), responseParams.getClusterId()); - } - - // server-timing metric will be added through GrpcResponseMetadata#onHeaders(Metadata), - // so it's not checking trailing metadata here. - @Nullable Long latency = getGfeLatency(metadata); - // For direct path, we won't see GFE server-timing header. However, if we received the - // location info, we know that there isn't a connectivity issue. Set the latency to - // 0 so gfe missing header won't get incremented. - if (responseParams != null && latency == null) { - latency = 0L; - } - // Record gfe metrics - tracer.recordGfeMetadata(latency, throwable); - } - - /** - * This method bridges gRPC stream tracing to bigtable tracing by adding a {@link - * io.grpc.ClientStreamTracer} to the callContext. - */ - static GrpcCallContext injectBigtableStreamTracer( - ApiCallContext context, GrpcResponseMetadata responseMetadata, BigtableTracer tracer) { - if (context instanceof GrpcCallContext) { - GrpcCallContext callContext = (GrpcCallContext) context; - CallOptions callOptions = callContext.getCallOptions(); - return responseMetadata.addHandlers( - callContext.withCallOptions( - callOptions.withStreamTracerFactory(new BigtableGrpcStreamTracer.Factory(tracer)))); - } else { - // context should always be an instance of GrpcCallContext. If not throw an exception - // so we can see what class context is. - throw new RuntimeException("Unexpected context class: " + context.getClass().getName()); - } - } - public static OpenTelemetrySdk newInternalOpentelemetry( EnhancedBigtableStubSettings settings, Credentials credentials, @@ -285,4 +176,33 @@ public static OpenTelemetrySdk newInternalOpentelemetry( .build()); return OpenTelemetrySdk.builder().setMeterProvider(meterProviderBuilder.build()).build(); } + + public static String formatTransportTypeMetricLabel( + MetadataExtractorInterceptor.SidebandData sidebandData) { + return Optional.ofNullable(sidebandData) + .flatMap(s -> Optional.ofNullable(s.getPeerInfo())) + .map(PeerInfo::getTransportType) + .orElse(PeerInfo.TransportType.TRANSPORT_TYPE_UNKNOWN) + .name() + .replace("TRANSPORT_TYPE_", "") + .toLowerCase(Locale.ENGLISH); + } + + public static String formatClusterIdMetricLabel( + @Nullable MetadataExtractorInterceptor.SidebandData sidebandData) { + return Optional.ofNullable(sidebandData) + .flatMap(d -> Optional.ofNullable(d.getResponseParams())) + .map(ResponseParams::getClusterId) + .filter(s -> !s.isEmpty()) + .orElse(""); + } + + public static String formatZoneIdMetricLabel( + @Nullable MetadataExtractorInterceptor.SidebandData sidebandData) { + return Optional.ofNullable(sidebandData) + .flatMap(d -> Optional.ofNullable(d.getResponseParams())) + .map(ResponseParams::getZoneId) + .filter(s -> !s.isEmpty()) + .orElse("global"); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerTest.java index df63ff8019..3d0e6425d9 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerTest.java @@ -136,6 +136,9 @@ @RunWith(JUnit4.class) public class BuiltinMetricsTracerTest { + private static final Metadata.Key LOCATION_METADATA_KEY = + Metadata.Key.of("x-goog-ext-425905942-bin", Metadata.BINARY_BYTE_MARSHALLER); + private static final String PROJECT_ID = "fake-project"; private static final String INSTANCE_ID = "fake-instance"; private static final String APP_PROFILE_ID = "default"; @@ -211,7 +214,7 @@ public void sendHeaders(Metadata headers) { ResponseParams params = ResponseParams.newBuilder().setZoneId(ZONE).setClusterId(CLUSTER).build(); byte[] byteArray = params.toByteArray(); - headers.put(Util.LOCATION_METADATA_KEY, byteArray); + headers.put(LOCATION_METADATA_KEY, byteArray); super.sendHeaders(headers); } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracerTest.java index 71a4728f9f..62c343f16c 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracerTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/CompositeTracerTest.java @@ -25,10 +25,9 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.ApiTracer.Scope; import com.google.bigtable.v2.ReadRowsRequest; +import com.google.cloud.bigtable.data.v2.stub.MetadataExtractorInterceptor; import com.google.cloud.bigtable.misc_utilities.MethodComparator; import com.google.common.collect.ImmutableList; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; import java.lang.reflect.Method; import java.util.Arrays; import org.junit.Assert; @@ -241,11 +240,12 @@ public void testGetAttempt() { } @Test - public void testRecordGfeLatency() { - Throwable t = new StatusRuntimeException(Status.UNAVAILABLE); - compositeTracer.recordGfeMetadata(20L, t); - verify(child3, times(1)).recordGfeMetadata(20L, t); - verify(child4, times(1)).recordGfeMetadata(20L, t); + public void testSidebandData() { + MetadataExtractorInterceptor.SidebandData sidebandData = + new MetadataExtractorInterceptor.SidebandData(); + compositeTracer.setSidebandData(sidebandData); + verify(child3, times(1)).setSidebandData(sidebandData); + verify(child4, times(1)).setSidebandData(sidebandData); } @Test @@ -264,13 +264,6 @@ public void testMethodsOverride() { .containsAtLeastElementsIn(baseMethods); } - @Test - public void testRequestBlockedOnChannel() { - compositeTracer.grpcChannelQueuedLatencies(5L); - verify(child3, times(1)).grpcChannelQueuedLatencies(5L); - verify(child4, times(1)).grpcChannelQueuedLatencies(5L); - } - @Test public void testGrpcMessageSent() { compositeTracer.grpcMessageSent(); diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/UtilTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/UtilTest.java index 3c0fb4e617..824d8be307 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/UtilTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/UtilTest.java @@ -19,7 +19,6 @@ import com.google.api.gax.grpc.GrpcStatusCode; import com.google.api.gax.rpc.DeadlineExceededException; -import com.google.common.util.concurrent.Futures; import io.grpc.Status; import io.opencensus.tags.TagValue; import org.junit.Test; @@ -34,12 +33,6 @@ public void testOk() { assertThat(tagValue.asString()).isEqualTo("OK"); } - @Test - public void testOkFuture() { - TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateFuture(null)); - assertThat(tagValue.asString()).isEqualTo("OK"); - } - @Test public void testError() { DeadlineExceededException error = @@ -48,19 +41,4 @@ public void testError() { TagValue tagValue = TagValue.create(Util.extractStatus(error)); assertThat(tagValue.asString()).isEqualTo("DEADLINE_EXCEEDED"); } - - @Test - public void testErrorFuture() { - DeadlineExceededException error = - new DeadlineExceededException( - "Deadline exceeded", null, GrpcStatusCode.of(Status.Code.DEADLINE_EXCEEDED), true); - TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateFailedFuture(error)); - assertThat(tagValue.asString()).isEqualTo("DEADLINE_EXCEEDED"); - } - - @Test - public void testCancelledFuture() { - TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateCancelledFuture()); - assertThat(tagValue.asString()).isEqualTo("CANCELLED"); - } }