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");
}
}