diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml index 6d1c003802e8..b5128af7aeb0 100644 --- a/google-cloud-bigtable/pom.xml +++ b/google-cloud-bigtable/pom.xml @@ -61,6 +61,10 @@ + + com.google.cloud + google-cloud-bigtable-stats + com.google.api diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index d8daaa80e67c..b6d17baadf7f 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -70,6 +70,7 @@ import com.google.cloud.bigtable.data.v2.models.RowAdapter; import com.google.cloud.bigtable.data.v2.models.RowMutation; import com.google.cloud.bigtable.data.v2.models.RowMutationEntry; +import com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsTracerFactory; import com.google.cloud.bigtable.data.v2.stub.metrics.CompositeTracerFactory; import com.google.cloud.bigtable.data.v2.stub.metrics.HeaderTracerStreamingCallable; import com.google.cloud.bigtable.data.v2.stub.metrics.HeaderTracerUnaryCallable; @@ -194,6 +195,12 @@ public static EnhancedBigtableStubSettings finalizeSettings( RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID, TagValue.create(settings.getAppProfileId())) .build(); + ImmutableMap builtinAttributes = + ImmutableMap.builder() + .put("project_id", settings.getProjectId()) + .put("instance_id", settings.getInstanceId()) + .put("app_profile", settings.getAppProfileId()) + .build(); // Inject Opencensus instrumentation builder.setTracerFactory( new CompositeTracerFactory( @@ -218,6 +225,7 @@ public static EnhancedBigtableStubSettings finalizeSettings( .build()), // Add OpenCensus Metrics MetricsTracerFactory.create(tagger, stats, attributes), + BuiltinMetricsTracerFactory.create(builtinAttributes), // Add user configured tracer settings.getTracerFactory()))); return builder.build(); @@ -466,7 +474,7 @@ private UnaryCallable> createBulkReadRowsCallable( new TracedBatcherUnaryCallable<>(readRowsUserCallable.all()); UnaryCallable> withHeaderTracer = - new HeaderTracerUnaryCallable(tracedBatcher); + new HeaderTracerUnaryCallable<>(tracedBatcher); UnaryCallable> traced = new TracedUnaryCallable<>(withHeaderTracer, clientContext.getTracerFactory(), span); @@ -594,11 +602,11 @@ private UnaryCallable createBulkMutateRowsCallable() { SpanName spanName = getSpanName("MutateRows"); - UnaryCallable tracedBatcher = new TracedBatcherUnaryCallable<>(userFacing); + UnaryCallable tracedBatcherUnaryCallable = + new TracedBatcherUnaryCallable<>(userFacing); UnaryCallable withHeaderTracer = - new HeaderTracerUnaryCallable<>(tracedBatcher); - + new HeaderTracerUnaryCallable<>(tracedBatcherUnaryCallable); UnaryCallable traced = new TracedUnaryCallable<>(withHeaderTracer, clientContext.getTracerFactory(), spanName); 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 3d7707cc4c71..2640cc1ced29 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 @@ -25,7 +25,7 @@ * A Bigtable specific {@link ApiTracer} that includes additional contexts. This class is a base * implementation that does nothing. */ -@BetaApi("This surface is stable yet it might be removed in the future.") +@BetaApi("This surface is not stable yet it might be removed in the future.") public class BigtableTracer extends BaseApiTracer { private volatile int attempt = 0; @@ -35,6 +35,23 @@ public void attemptStarted(int attemptNumber) { this.attempt = attemptNumber; } + /** annotate when onRequest is called. This will be called in BuiltinMetricsTracer. */ + public void onRequest(int requestCount) { + // noop + } + + /** + * annotate when automatic flow control is disabled. This will be called in BuiltinMetricsTracer. + */ + public void disableFlowControl() { + // noop + } + + /** annotate after the callback from onResponse. This will be called in BuiltinMetricsTracer. */ + public void afterResponse(long applicationLatency) { + // noop + } + /** * Get the attempt number of the current call. Attempt number for the current call is passed in * and should be recorded in {@link #attemptStarted(int)}. With the getter we can access it from @@ -57,4 +74,12 @@ public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwa 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 + } } 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 new file mode 100644 index 000000000000..2148c674e3f8 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracer.java @@ -0,0 +1,246 @@ +/* + * Copyright 2022 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.metrics; + +import static com.google.api.gax.tracing.ApiTracerFactory.OperationType; + +import com.google.api.gax.tracing.SpanName; +import com.google.cloud.bigtable.stats.StatsRecorderWrapper; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.math.IntMath; +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 javax.annotation.Nullable; +import org.threeten.bp.Duration; + +/** + * A {@link BigtableTracer} that records built-in metrics and publish under the + * bigtable.googleapis.com/client namespace + */ +class BuiltinMetricsTracer extends BigtableTracer { + + private final StatsRecorderWrapper recorder; + + private final OperationType operationType; + private final SpanName spanName; + + // Operation level metrics + private final AtomicBoolean opFinished = new AtomicBoolean(); + private final Stopwatch operationTimer = Stopwatch.createStarted(); + private final Stopwatch firstResponsePerOpTimer = Stopwatch.createStarted(); + + // Attempt level metrics + private int attemptCount = 0; + private Stopwatch attemptTimer; + private volatile int attempt = 0; + + // Total server latency needs to be atomic because it's accessed from different threads. E.g. + // request() from user thread and attempt failed from grpc thread. We're only measuring the extra + // time application spent blocking grpc buffer, which will be operationLatency - serverLatency. + private final AtomicLong totalServerLatency = new AtomicLong(0); + // Stopwatch is not thread safe so this is a workaround to check if the stopwatch changes is + // flushed to memory. + private final Stopwatch serverLatencyTimer = Stopwatch.createUnstarted(); + private final AtomicBoolean serverLatencyTimerIsRunning = new AtomicBoolean(); + + private boolean flowControlIsDisabled = false; + + private AtomicInteger requestLeft = new AtomicInteger(0); + + // Monitored resource labels + private String tableId = "undefined"; + private String zone = "undefined"; + private String cluster = "undefined"; + + // gfe stats + private AtomicLong gfeMissingHeaders = new AtomicLong(0); + + @VisibleForTesting + BuiltinMetricsTracer( + OperationType operationType, SpanName spanName, StatsRecorderWrapper recorder) { + this.operationType = operationType; + this.spanName = spanName; + this.recorder = recorder; + } + + @Override + public Scope inScope() { + return new Scope() { + @Override + public void close() {} + }; + } + + @Override + public void operationSucceeded() { + recordOperationCompletion(null); + } + + @Override + public void operationCancelled() { + recordOperationCompletion(new CancellationException()); + } + + @Override + public void operationFailed(Throwable error) { + recordOperationCompletion(error); + } + + @Override + public void attemptStarted(int attemptNumber) { + attemptStarted(null, attemptNumber); + } + + @Override + public void attemptStarted(Object request, int attemptNumber) { + this.attempt = attemptNumber; + attemptCount++; + attemptTimer = Stopwatch.createStarted(); + if (request != null) { + this.tableId = Util.extractTableId(request); + } + if (!flowControlIsDisabled) { + if (serverLatencyTimerIsRunning.compareAndSet(false, true)) { + serverLatencyTimer.start(); + } + } + } + + @Override + public void attemptSucceeded() { + recordAttemptCompletion(null); + } + + @Override + public void attemptCancelled() { + recordAttemptCompletion(new CancellationException()); + } + + @Override + public void attemptFailed(Throwable error, Duration delay) { + recordAttemptCompletion(error); + } + + @Override + public void onRequest(int requestCount) { + requestLeft.accumulateAndGet(requestCount, IntMath::saturatedAdd); + if (flowControlIsDisabled) { + // On request is only called when auto flow control is disabled. When auto flow control is + // disabled, server latency is measured between onRequest and onResponse. + if (serverLatencyTimerIsRunning.compareAndSet(false, true)) { + serverLatencyTimer.start(); + } + } + } + + @Override + public void responseReceived() { + // When auto flow control is enabled, server latency is measured between afterResponse and + // responseReceived. + // When auto flow control is disabled, server latency is measured between onRequest and + // responseReceived. + // When auto flow control is disabled and application requested multiple responses, server + // latency is measured between afterResponse and responseReceived. + // In all the cases, we want to stop the serverLatencyTimer here. + if (serverLatencyTimerIsRunning.compareAndSet(true, false)) { + totalServerLatency.addAndGet(serverLatencyTimer.elapsed(TimeUnit.MILLISECONDS)); + serverLatencyTimer.reset(); + } + } + + @Override + public void afterResponse(long applicationLatency) { + if (!flowControlIsDisabled || requestLeft.decrementAndGet() > 0) { + // When auto flow control is enabled, request will never be called, so server latency is + // measured between after the last response is processed and before the next response is + // received. If flow control is disabled but requestLeft is greater than 0, + // also start the timer to count the time between afterResponse and responseReceived. + if (serverLatencyTimerIsRunning.compareAndSet(false, true)) { + serverLatencyTimer.start(); + } + } + } + + @Override + public int getAttempt() { + return attempt; + } + + @Override + public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwable) { + // Record the metrics and put in the map after the attempt is done, so we can have cluster and + // zone information + if (latency != null) { + recorder.putGfeLatencies(latency); + } else { + gfeMissingHeaders.incrementAndGet(); + } + recorder.putGfeMissingHeaders(gfeMissingHeaders.get()); + } + + @Override + public void setLocations(String zone, String cluster) { + this.zone = zone; + this.cluster = cluster; + } + + @Override + public void batchRequestThrottled(long throttledTimeMs) { + recorder.putBatchRequestThrottled(throttledTimeMs); + } + + @Override + public void disableFlowControl() { + flowControlIsDisabled = true; + } + + private void recordOperationCompletion(@Nullable Throwable status) { + if (!opFinished.compareAndSet(false, true)) { + return; + } + operationTimer.stop(); + long operationLatency = operationTimer.elapsed(TimeUnit.MILLISECONDS); + + recorder.putRetryCount(attemptCount - 1); + + // serverLatencyTimer should already be stopped in recordAttemptCompletion + recorder.putOperationLatencies(operationLatency); + recorder.putApplicationLatencies(operationLatency - totalServerLatency.get()); + + if (operationType == OperationType.ServerStreaming + && spanName.getMethodName().equals("ReadRows")) { + recorder.putFirstResponseLatencies(firstResponsePerOpTimer.elapsed(TimeUnit.MILLISECONDS)); + } + + recorder.record(Util.extractStatus(status), tableId, zone, cluster); + } + + private void recordAttemptCompletion(@Nullable Throwable status) { + // If the attempt failed, the time spent in retry should be counted in application latency. + // Stop the stopwatch and decrement requestLeft. + if (serverLatencyTimerIsRunning.compareAndSet(true, false)) { + requestLeft.decrementAndGet(); + totalServerLatency.addAndGet(serverLatencyTimer.elapsed(TimeUnit.MILLISECONDS)); + serverLatencyTimer.reset(); + } + recorder.putAttemptLatencies(attemptTimer.elapsed(TimeUnit.MILLISECONDS)); + recorder.record(Util.extractStatus(status), tableId, zone, cluster); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerFactory.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerFactory.java new file mode 100644 index 000000000000..794997071df9 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 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.metrics; + +import com.google.api.core.InternalApi; +import com.google.api.gax.tracing.ApiTracer; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.BaseApiTracerFactory; +import com.google.api.gax.tracing.SpanName; +import com.google.cloud.bigtable.stats.StatsWrapper; +import com.google.common.collect.ImmutableMap; + +/** + * {@link ApiTracerFactory} that will generate OpenCensus metrics by using the {@link ApiTracer} + * api. + */ +@InternalApi("For internal use only") +public class BuiltinMetricsTracerFactory extends BaseApiTracerFactory { + + private final ImmutableMap statsAttributes; + + public static BuiltinMetricsTracerFactory create(ImmutableMap statsAttributes) { + return new BuiltinMetricsTracerFactory(statsAttributes); + } + + private BuiltinMetricsTracerFactory(ImmutableMap statsAttributes) { + this.statsAttributes = statsAttributes; + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + return new BuiltinMetricsTracer( + operationType, + spanName, + StatsWrapper.createRecorder(operationType, spanName, statsAttributes)); + } +} 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 5f4580743be5..271782c2f6f6 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 @@ -92,9 +92,14 @@ public void connectionSelected(String id) { @Override public void attemptStarted(int attemptNumber) { + attemptStarted(null, attemptNumber); + } + + @Override + public void attemptStarted(Object request, int attemptNumber) { this.attempt = attemptNumber; for (ApiTracer child : children) { - child.attemptStarted(attemptNumber); + child.attemptStarted(request, attemptNumber); } } @@ -185,4 +190,32 @@ public void batchRequestThrottled(long throttledTimeMs) { tracer.batchRequestThrottled(throttledTimeMs); } } + + @Override + public void setLocations(String zone, String cluster) { + for (BigtableTracer tracer : bigtableTracers) { + tracer.setLocations(zone, cluster); + } + } + + @Override + public void onRequest(int requestCount) { + for (BigtableTracer tracer : bigtableTracers) { + tracer.onRequest(requestCount); + } + } + + @Override + public void disableFlowControl() { + for (BigtableTracer tracer : bigtableTracers) { + tracer.disableFlowControl(); + } + } + + @Override + public void afterResponse(long applicationLatency) { + for (BigtableTracer tracer : bigtableTracers) { + tracer.afterResponse(applicationLatency); + } + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java index 31c5cf196025..f73511dc4c71 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java @@ -22,7 +22,9 @@ import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.StreamController; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import io.grpc.Metadata; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; /** @@ -55,7 +57,7 @@ public void call( RequestT request, ResponseObserver responseObserver, ApiCallContext context) { final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata(); // tracer should always be an instance of bigtable tracer - if (RpcViews.isGfeMetricsRegistered() && context.getTracer() instanceof BigtableTracer) { + if (context.getTracer() instanceof BigtableTracer) { HeaderTracerResponseObserver innerObserver = new HeaderTracerResponseObserver<>( responseObserver, (BigtableTracer) context.getTracer(), responseMetadata); @@ -82,12 +84,15 @@ private class HeaderTracerResponseObserver implements ResponseObserve @Override public void onStart(final StreamController controller) { - outerObserver.onStart(controller); + TracedStreamController tracedController = new TracedStreamController(controller, tracer); + outerObserver.onStart(tracedController); } @Override public void onResponse(ResponseT response) { + Stopwatch stopwatch = Stopwatch.createStarted(); outerObserver.onResponse(response); + tracer.afterResponse(stopwatch.elapsed(TimeUnit.MILLISECONDS)); } @Override @@ -108,4 +113,31 @@ public void onComplete() { outerObserver.onComplete(); } } + + private class TracedStreamController implements StreamController { + private final StreamController innerController; + private final BigtableTracer tracer; + + TracedStreamController(StreamController innerController, BigtableTracer tracer) { + this.innerController = innerController; + this.tracer = tracer; + } + + @Override + public void cancel() { + innerController.cancel(); + } + + @Override + public void disableAutoInboundFlowControl() { + tracer.disableFlowControl(); + innerController.disableAutoInboundFlowControl(); + } + + @Override + public void request(int i) { + tracer.onRequest(i); + innerController.request(i); + } + } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java index 6335b433efc5..adbb6c84a9be 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java @@ -54,7 +54,7 @@ public HeaderTracerUnaryCallable(@Nonnull UnaryCallable inn @Override public ApiFuture futureCall(RequestT request, ApiCallContext context) { // tracer should always be an instance of BigtableTracer - if (RpcViews.isGfeMetricsRegistered() && context.getTracer() instanceof BigtableTracer) { + if (context.getTracer() instanceof BigtableTracer) { final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata(); final ApiCallContext contextWithResponseMetadata = responseMetadata.addHandlers(context); HeaderTracerUnaryCallback callback = 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 f28b07c0cb67..3c63b1b5f795 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 @@ -118,16 +118,13 @@ private void recordOperationCompletion(@Nullable Throwable throwable) { TagContextBuilder tagCtx = newTagCtxBuilder() - .putLocal(RpcMeasureConstants.BIGTABLE_STATUS, Util.extractStatus(throwable)); + .putLocal( + RpcMeasureConstants.BIGTABLE_STATUS, + TagValue.create(Util.extractStatus(throwable))); measures.record(tagCtx.build()); } - @Override - public void connectionSelected(String s) { - // noop: cardinality for connection ids is too high to use as tags - } - @Override public void attemptStarted(int attemptNumber) { attempt = attemptNumber; @@ -171,21 +168,13 @@ private void recordAttemptCompletion(@Nullable Throwable throwable) { TagContextBuilder tagCtx = newTagCtxBuilder() - .putLocal(RpcMeasureConstants.BIGTABLE_STATUS, Util.extractStatus(throwable)); + .putLocal( + RpcMeasureConstants.BIGTABLE_STATUS, + TagValue.create(Util.extractStatus(throwable))); measures.record(tagCtx.build()); } - @Override - public void lroStartFailed(Throwable throwable) { - // noop - } - - @Override - public void lroStartSucceeded() { - // noop - } - @Override public void responseReceived() { if (firstResponsePerOpTimer.isRunning()) { @@ -195,16 +184,6 @@ public void responseReceived() { operationResponseCount++; } - @Override - public void requestSent() { - // noop: no operations are client streaming - } - - @Override - public void batchRequestSent(long elementCount, long requestSize) { - // noop - } - @Override public int getAttempt() { return attempt; @@ -222,7 +201,8 @@ public void recordGfeMetadata(@Nullable Long latency, @Nullable Throwable throwa } measures.record( newTagCtxBuilder() - .putLocal(RpcMeasureConstants.BIGTABLE_STATUS, Util.extractStatus(throwable)) + .putLocal( + RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create(Util.extractStatus(throwable))) .build()); } 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 00995b717a87..044002902719 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 @@ -15,10 +15,19 @@ */ package com.google.cloud.bigtable.data.v2.stub.metrics; +import com.google.api.core.InternalApi; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.StatusCode.Code; +import com.google.bigtable.v2.CheckAndMutateRowRequest; +import com.google.bigtable.v2.MutateRowRequest; +import com.google.bigtable.v2.MutateRowsRequest; +import com.google.bigtable.v2.ReadModifyWriteRowRequest; +import com.google.bigtable.v2.ReadRowsRequest; +import com.google.bigtable.v2.SampleRowKeysRequest; +import com.google.bigtable.v2.TableName; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import io.grpc.Metadata; import io.grpc.Status; @@ -38,7 +47,8 @@ import javax.annotation.Nullable; /** Utilities to help integrating with OpenCensus. */ -class Util { +@InternalApi("For internal use only") +public class Util { static final Metadata.Key ATTEMPT_HEADER_KEY = Metadata.Key.of("bigtable-attempt", Metadata.ASCII_STRING_MARSHALLER); static final Metadata.Key ATTEMPT_EPOCH_KEY = @@ -48,14 +58,14 @@ class Util { Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER); private static final Pattern SERVER_TIMING_HEADER_PATTERN = Pattern.compile(".*dur=(?\\d+)"); - private static final TagValue OK_STATUS = TagValue.create(StatusCode.Code.OK.toString()); + static final String TRAILER_KEY = "x-goog-ext-425905942-bin"; - /** Convert an exception into a value that can be used as an OpenCensus tag value. */ - static TagValue extractStatus(@Nullable Throwable error) { + /** Convert an exception into a value that can be used to create an OpenCensus tag value. */ + static String extractStatus(@Nullable Throwable error) { final String statusString; if (error == null) { - return OK_STATUS; + return StatusCode.Code.OK.toString(); } else if (error instanceof CancellationException) { statusString = Status.Code.CANCELLED.toString(); } else if (error instanceof ApiException) { @@ -68,14 +78,14 @@ static TagValue extractStatus(@Nullable Throwable error) { statusString = Code.UNKNOWN.toString(); } - return TagValue.create(statusString); + 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 extractStatus(Future future) { + static TagValue extractStatusFromFuture(Future future) { Throwable error = null; try { @@ -88,7 +98,25 @@ static TagValue extractStatus(Future future) { } catch (RuntimeException e) { error = e; } - return extractStatus(error); + return TagValue.create(extractStatus(error)); + } + + static String extractTableId(Object request) { + String tableName = null; + if (request instanceof ReadRowsRequest) { + tableName = ((ReadRowsRequest) request).getTableName(); + } else if (request instanceof MutateRowsRequest) { + tableName = ((MutateRowsRequest) request).getTableName(); + } else if (request instanceof MutateRowRequest) { + tableName = ((MutateRowRequest) request).getTableName(); + } else if (request instanceof SampleRowKeysRequest) { + tableName = ((SampleRowKeysRequest) request).getTableName(); + } else if (request instanceof CheckAndMutateRowRequest) { + tableName = ((CheckAndMutateRowRequest) request).getTableName(); + } else if (request instanceof ReadModifyWriteRowRequest) { + tableName = ((ReadModifyWriteRowRequest) request).getTableName(); + } + return !Strings.isNullOrEmpty(tableName) ? TableName.parse(tableName).getTable() : "undefined"; } /** 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 new file mode 100644 index 000000000000..a48df9225447 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsTracerTest.java @@ -0,0 +1,414 @@ +/* + * Copyright 2022 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.metrics; + +import static com.google.api.gax.tracing.ApiTracerFactory.OperationType; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.util.Lists; +import com.google.api.core.SettableApiFuture; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.StreamController; +import com.google.api.gax.tracing.SpanName; +import com.google.bigtable.v2.BigtableGrpc; +import com.google.bigtable.v2.MutateRowRequest; +import com.google.bigtable.v2.MutateRowResponse; +import com.google.bigtable.v2.ReadRowsRequest; +import com.google.bigtable.v2.ReadRowsResponse; +import com.google.cloud.bigtable.data.v2.BigtableDataSettings; +import com.google.cloud.bigtable.data.v2.FakeServiceBuilder; +import com.google.cloud.bigtable.data.v2.models.Query; +import com.google.cloud.bigtable.data.v2.models.Row; +import com.google.cloud.bigtable.data.v2.models.RowMutation; +import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; +import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; +import com.google.cloud.bigtable.stats.StatsRecorderWrapper; +import com.google.common.base.Stopwatch; +import com.google.common.collect.Range; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.StringValue; +import io.grpc.ForwardingServerCall; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.ServerCallStreamObserver; +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class BuiltinMetricsTracerTest { + private static final String PROJECT_ID = "fake-project"; + private static final String INSTANCE_ID = "fake-instance"; + private static final String APP_PROFILE_ID = "default"; + private static final String TABLE_ID = "fake-table"; + private static final String UNDEFINED = "undefined"; + private static final long FAKE_SERVER_TIMING = 50; + private static final long SERVER_LATENCY = 100; + private static final long APPLICATION_LATENCY = 200; + + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + private FakeService fakeService; + private Server server; + + private EnhancedBigtableStub stub; + + @Mock private BuiltinMetricsTracerFactory mockFactory; + @Mock private StatsRecorderWrapper statsRecorderWrapper; + + @Captor private ArgumentCaptor status; + @Captor private ArgumentCaptor tableId; + @Captor private ArgumentCaptor zone; + @Captor private ArgumentCaptor cluster; + + @Before + public void setUp() throws Exception { + fakeService = new FakeService(); + + // Add an interceptor to add server-timing in headers + ServerInterceptor trailersInterceptor = + new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall serverCall, + Metadata metadata, + ServerCallHandler serverCallHandler) { + return serverCallHandler.startCall( + new ForwardingServerCall.SimpleForwardingServerCall(serverCall) { + @Override + public void sendHeaders(Metadata headers) { + headers.put( + Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER), + String.format("gfet4t7; dur=%d", FAKE_SERVER_TIMING)); + super.sendHeaders(headers); + } + }, + metadata); + } + }; + + server = FakeServiceBuilder.create(fakeService).intercept(trailersInterceptor).start(); + + BigtableDataSettings settings = + BigtableDataSettings.newBuilderForEmulator(server.getPort()) + .setProjectId(PROJECT_ID) + .setInstanceId(INSTANCE_ID) + .setAppProfileId(APP_PROFILE_ID) + .build(); + EnhancedBigtableStubSettings.Builder stubSettingsBuilder = + settings.getStubSettings().toBuilder(); + stubSettingsBuilder + .mutateRowSettings() + .retrySettings() + .setInitialRetryDelay(Duration.ofMillis(200)); + stubSettingsBuilder.setTracerFactory(mockFactory); + + EnhancedBigtableStubSettings stubSettings = stubSettingsBuilder.build(); + stub = new EnhancedBigtableStub(stubSettings, ClientContext.create(stubSettings)); + } + + @After + public void tearDown() { + stub.close(); + server.shutdown(); + } + + @Test + public void testOperationLatencies() { + when(mockFactory.newTracer(any(), any(), any())) + .thenAnswer( + (Answer) + invocationOnMock -> + new BuiltinMetricsTracer( + OperationType.ServerStreaming, + SpanName.of("Bigtable", "ReadRows"), + statsRecorderWrapper)); + ArgumentCaptor operationLatency = ArgumentCaptor.forClass(Long.class); + + Stopwatch stopwatch = Stopwatch.createStarted(); + Lists.newArrayList(stub.readRowsCallable().call(Query.create(TABLE_ID)).iterator()); + long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS); + + verify(statsRecorderWrapper).putOperationLatencies(operationLatency.capture()); + + assertThat(operationLatency.getValue()).isIn(Range.closed(SERVER_LATENCY, elapsed)); + } + + @Test + public void testGfeMetrics() { + when(mockFactory.newTracer(any(), any(), any())) + .thenAnswer( + (Answer) + invocationOnMock -> + new BuiltinMetricsTracer( + OperationType.ServerStreaming, + SpanName.of("Bigtable", "ReadRows"), + statsRecorderWrapper)); + ArgumentCaptor gfeLatency = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor gfeMissingHeaders = ArgumentCaptor.forClass(Long.class); + + Lists.newArrayList(stub.readRowsCallable().call(Query.create(TABLE_ID))); + + // The request was retried and gfe latency is only recorded in the retry attempt + verify(statsRecorderWrapper).putGfeLatencies(gfeLatency.capture()); + assertThat(gfeLatency.getValue()).isEqualTo(FAKE_SERVER_TIMING); + + // The first time the request was retried, it'll increment missing header counter + verify(statsRecorderWrapper, times(fakeService.getAttemptCounter().get())) + .putGfeMissingHeaders(gfeMissingHeaders.capture()); + assertThat(gfeMissingHeaders.getValue()).isEqualTo(1); + } + + @Test + public void testReadRowsApplicationLatencyWithAutoFlowControl() throws Exception { + when(mockFactory.newTracer(any(), any(), any())) + .thenAnswer( + (Answer) + invocationOnMock -> + new BuiltinMetricsTracer( + OperationType.ServerStreaming, + SpanName.of("Bigtable", "ReadRows"), + statsRecorderWrapper)); + + ArgumentCaptor applicationLatency = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor operationLatency = ArgumentCaptor.forClass(Long.class); + + final SettableApiFuture future = SettableApiFuture.create(); + final AtomicInteger counter = new AtomicInteger(0); + // For auto flow control, application latency is the time application spent in onResponse. + stub.readRowsCallable() + .call( + Query.create(TABLE_ID), + new ResponseObserver() { + @Override + public void onStart(StreamController streamController) {} + + @Override + public void onResponse(Row row) { + try { + counter.getAndIncrement(); + Thread.sleep(APPLICATION_LATENCY); + } catch (InterruptedException e) { + } + } + + @Override + public void onError(Throwable throwable) { + future.setException(throwable); + } + + @Override + public void onComplete() { + future.set(null); + } + }); + future.get(); + + verify(statsRecorderWrapper).putApplicationLatencies(applicationLatency.capture()); + verify(statsRecorderWrapper).putOperationLatencies(operationLatency.capture()); + + assertThat(counter.get()).isEqualTo(fakeService.getResponseCounter().get()); + assertThat(applicationLatency.getValue()).isAtLeast(APPLICATION_LATENCY * counter.get()); + assertThat(applicationLatency.getValue()) + .isAtMost(operationLatency.getValue() - SERVER_LATENCY); + } + + @Test + public void testReadRowsApplicationLatencyWithManualFlowControl() throws Exception { + when(mockFactory.newTracer(any(), any(), any())) + .thenAnswer( + (Answer) + invocationOnMock -> + new BuiltinMetricsTracer( + OperationType.ServerStreaming, + SpanName.of("Bigtable", "ReadRows"), + statsRecorderWrapper)); + + ArgumentCaptor applicationLatency = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor operationLatency = ArgumentCaptor.forClass(Long.class); + int counter = 0; + + Iterator rows = stub.readRowsCallable().call(Query.create(TABLE_ID)).iterator(); + + while (rows.hasNext()) { + counter++; + Thread.sleep(APPLICATION_LATENCY); + rows.next(); + } + + verify(statsRecorderWrapper).putApplicationLatencies(applicationLatency.capture()); + verify(statsRecorderWrapper).putOperationLatencies(operationLatency.capture()); + + // For manual flow control, the last application latency shouldn't count, because at that point + // the server already sent back all the responses. + assertThat(counter).isEqualTo(fakeService.getResponseCounter().get()); + assertThat(applicationLatency.getValue()) + .isAtLeast(APPLICATION_LATENCY * (counter - 1) - SERVER_LATENCY); + assertThat(applicationLatency.getValue()) + .isAtMost(operationLatency.getValue() - SERVER_LATENCY); + } + + @Test + public void testRetryCount() { + when(mockFactory.newTracer(any(), any(), any())) + .thenAnswer( + (Answer) + invocationOnMock -> + new BuiltinMetricsTracer( + OperationType.ServerStreaming, + SpanName.of("Bigtable", "ReadRows"), + statsRecorderWrapper)); + + ArgumentCaptor retryCount = ArgumentCaptor.forClass(Integer.class); + + stub.mutateRowCallable() + .call(RowMutation.create(TABLE_ID, "random-row").setCell("cf", "q", "value")); + + verify(statsRecorderWrapper).putRetryCount(retryCount.capture()); + + assertThat(retryCount.getValue()).isEqualTo(fakeService.getAttemptCounter().get() - 1); + } + + @Test + public void testMutateRowAttempts() { + when(mockFactory.newTracer(any(), any(), any())) + .thenReturn( + new BuiltinMetricsTracer( + OperationType.Unary, SpanName.of("Bigtable", "MutateRow"), statsRecorderWrapper)); + + stub.mutateRowCallable() + .call(RowMutation.create(TABLE_ID, "random-row").setCell("cf", "q", "value")); + + // record() will get called 4 times, 3 times for attempts and 1 for recording operation level + // metrics. Also set a timeout to reduce flakiness of this test. BasicRetryingFuture will set + // attempt succeeded and set the response which will call complete() in AbstractFuture which + // calls releaseWaiters(). onOperationComplete() is called in TracerFinisher which will be + // called after the mutateRow call is returned. So there's a race between when the call returns + // and when the record() is called in onOperationCompletion(). + verify(statsRecorderWrapper, timeout(10).times(fakeService.getAttemptCounter().get() + 1)) + .record(status.capture(), tableId.capture(), zone.capture(), cluster.capture()); + assertThat(zone.getAllValues()).containsExactly(UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED); + assertThat(cluster.getAllValues()).containsExactly(UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED); + assertThat(status.getAllValues()).containsExactly("UNAVAILABLE", "UNAVAILABLE", "OK", "OK"); + } + + private static class FakeService extends BigtableGrpc.BigtableImplBase { + + static List createFakeResponse() { + List responses = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + responses.add( + ReadRowsResponse.newBuilder() + .addChunks( + ReadRowsResponse.CellChunk.newBuilder() + .setRowKey(ByteString.copyFromUtf8("fake-key-" + i)) + .setFamilyName(StringValue.of("cf")) + .setQualifier( + BytesValue.newBuilder().setValue(ByteString.copyFromUtf8("q"))) + .setTimestampMicros(1_000) + .setValue( + ByteString.copyFromUtf8( + String.join("", Collections.nCopies(1024 * 1024, "A")))) + .setCommitRow(true)) + .build()); + } + return responses; + } + + private final AtomicInteger attemptCounter = new AtomicInteger(0); + private final AtomicInteger responseCounter = new AtomicInteger(0); + private final Iterator source = createFakeResponse().listIterator(); + + @Override + public void readRows( + ReadRowsRequest request, StreamObserver responseObserver) { + final AtomicBoolean done = new AtomicBoolean(); + final ServerCallStreamObserver target = + (ServerCallStreamObserver) responseObserver; + try { + Thread.sleep(SERVER_LATENCY); + } catch (InterruptedException e) { + } + if (attemptCounter.getAndIncrement() == 0) { + target.onError(new StatusRuntimeException(Status.UNAVAILABLE)); + return; + } + + // Only return the next response when the buffer is emptied for testing manual flow control. + // The fake service won't keep calling onNext unless it received an onRequest event from + // the application thread + target.setOnReadyHandler( + () -> { + while (target.isReady() && source.hasNext()) { + responseCounter.getAndIncrement(); + target.onNext(source.next()); + } + if (!source.hasNext() && done.compareAndSet(false, true)) { + target.onCompleted(); + } + }); + } + + @Override + public void mutateRow( + MutateRowRequest request, StreamObserver responseObserver) { + if (attemptCounter.getAndIncrement() < 2) { + responseObserver.onError(new StatusRuntimeException(Status.UNAVAILABLE)); + return; + } + responseObserver.onNext(MutateRowResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + public AtomicInteger getAttemptCounter() { + return attemptCounter; + } + + public AtomicInteger getResponseCounter() { + return responseCounter; + } + } +} 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 69a741d0e371..0de14636c664 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 @@ -23,6 +23,7 @@ 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.misc_utilities.MethodComparator; import com.google.common.collect.ImmutableList; import io.grpc.Status; @@ -118,11 +119,12 @@ public void testConnectionSelected() { @Test public void testAttemptStarted() { - compositeTracer.attemptStarted(3); - verify(child1, times(1)).attemptStarted(3); - verify(child2, times(1)).attemptStarted(3); - verify(child3, times(1)).attemptStarted(3); - verify(child4, times(1)).attemptStarted(3); + ReadRowsRequest request = ReadRowsRequest.getDefaultInstance(); + compositeTracer.attemptStarted(request, 3); + verify(child1, times(1)).attemptStarted(request, 3); + verify(child2, times(1)).attemptStarted(request, 3); + verify(child3, times(1)).attemptStarted(request, 3); + verify(child4, times(1)).attemptStarted(request, 3); } @Test diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java index d6dbb969f111..d93859bbadef 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java @@ -56,7 +56,6 @@ import io.grpc.stub.StreamObserver; import io.opencensus.impl.stats.StatsComponentImpl; import io.opencensus.stats.StatsComponent; -import io.opencensus.stats.ViewData; import io.opencensus.tags.TagKey; import io.opencensus.tags.TagValue; import io.opencensus.tags.Tags; @@ -383,24 +382,6 @@ public void testMetricsWithErrorResponse() throws InterruptedException { assertThat(missingCount).isEqualTo(attempts); } - @Test - public void testCallableBypassed() throws InterruptedException { - RpcViews.setGfeMetricsRegistered(false); - stub.readRowsCallable().call(Query.create(TABLE_ID)); - Thread.sleep(WAIT_FOR_METRICS_TIME_MS); - ViewData headerMissingView = - localStats - .getViewManager() - .getView(RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW.getName()); - ViewData latencyView = - localStats.getViewManager().getView(RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW.getName()); - // Verify that the view is registered by it's not collecting metrics - assertThat(headerMissingView).isNotNull(); - assertThat(latencyView).isNotNull(); - assertThat(headerMissingView.getAggregationMap()).isEmpty(); - assertThat(latencyView.getAggregationMap()).isEmpty(); - } - private class FakeService extends BigtableImplBase { private final String defaultTableName = NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID); diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java index 1176214de38a..b1b966ee9da5 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java @@ -42,7 +42,6 @@ import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; import com.google.cloud.bigtable.data.v2.stub.mutaterows.MutateRowsBatchingDescriptor; -import com.google.cloud.bigtable.misc_utilities.MethodComparator; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -59,8 +58,6 @@ import io.opencensus.tags.TagKey; import io.opencensus.tags.TagValue; import io.opencensus.tags.Tags; -import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Iterator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -485,15 +482,6 @@ public Object answer(InvocationOnMock invocation) { assertThat(throttledTimeMetric).isAtLeast(throttled); } - @Test - public void testMethodsOverride() { - Method[] baseMethods = BigtableTracer.class.getDeclaredMethods(); - Method[] metricsTracerMethods = MetricsTracer.class.getDeclaredMethods(); - assertThat(Arrays.asList(metricsTracerMethods)) - .comparingElementsUsing(MethodComparator.METHOD_CORRESPONDENCE) - .containsAtLeastElementsIn(baseMethods); - } - @SuppressWarnings("unchecked") private static StreamObserver anyObserver(Class returnType) { return (StreamObserver) any(returnType); 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 efef3b67d298..3c0fb4e6175c 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 @@ -30,13 +30,13 @@ public class UtilTest { @Test public void testOk() { - TagValue tagValue = Util.extractStatus((Throwable) null); + TagValue tagValue = TagValue.create(Util.extractStatus((Throwable) null)); assertThat(tagValue.asString()).isEqualTo("OK"); } @Test public void testOkFuture() { - TagValue tagValue = Util.extractStatus(Futures.immediateFuture(null)); + TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateFuture(null)); assertThat(tagValue.asString()).isEqualTo("OK"); } @@ -45,7 +45,7 @@ public void testError() { DeadlineExceededException error = new DeadlineExceededException( "Deadline exceeded", null, GrpcStatusCode.of(Status.Code.DEADLINE_EXCEEDED), true); - TagValue tagValue = Util.extractStatus(error); + TagValue tagValue = TagValue.create(Util.extractStatus(error)); assertThat(tagValue.asString()).isEqualTo("DEADLINE_EXCEEDED"); } @@ -54,13 +54,13 @@ public void testErrorFuture() { DeadlineExceededException error = new DeadlineExceededException( "Deadline exceeded", null, GrpcStatusCode.of(Status.Code.DEADLINE_EXCEEDED), true); - TagValue tagValue = Util.extractStatus(Futures.immediateFailedFuture(error)); + TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateFailedFuture(error)); assertThat(tagValue.asString()).isEqualTo("DEADLINE_EXCEEDED"); } @Test public void testCancelledFuture() { - TagValue tagValue = Util.extractStatus(Futures.immediateCancelledFuture()); + TagValue tagValue = Util.extractStatusFromFuture(Futures.immediateCancelledFuture()); assertThat(tagValue.asString()).isEqualTo("CANCELLED"); } }