Date: Wed, 16 Dec 2020 19:49:58 -0500
Subject: [PATCH 09/38] feat: Surface the server-timing metric (#535)
* Extract server-timing trailer and create metrics for gfe latency
* Add more tests and refactor
* Refactor comments and imports
* reformatting
* Clean up comments
* Refactor, use GrpcMetadataResponse to get the trailer
* Fix based on the code review
* clean up HeaderTracerResponseObserver
* Add more tests for all the ops
* Improve documents, changes for directPath and more tests
* Small fixes in the doc
* small clean up
---
README.md | 14 +-
.../data/v2/BigtableDataSettings.java | 14 +
.../data/v2/stub/EnhancedBigtableStub.java | 98 ++--
.../v2/stub/EnhancedBigtableStubSettings.java | 23 +
.../data/v2/stub/metrics/HeaderTracer.java | 123 +++++
.../HeaderTracerStreamingCallable.java | 125 +++++
.../metrics/HeaderTracerUnaryCallable.java | 83 ++++
.../v2/stub/metrics/RpcMeasureConstants.java | 14 +
.../v2/stub/metrics/RpcViewConstants.java | 22 +
.../data/v2/stub/metrics/RpcViews.java | 34 ++
.../EnhancedBigtableStubSettingsTest.java | 36 +-
.../metrics/HeaderTracerCallableTest.java | 428 ++++++++++++++++++
.../v2/stub/metrics/HeaderTracerTest.java | 77 ++++
.../v2/stub/metrics/MetricsTracerTest.java | 121 ++---
.../data/v2/stub/metrics/StatsTestUtils.java | 79 ++++
15 files changed, 1161 insertions(+), 130 deletions(-)
create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracer.java
create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java
create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java
create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java
create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerTest.java
diff --git a/README.md b/README.md
index e9bb4b0f59..bf339adc4a 100644
--- a/README.md
+++ b/README.md
@@ -296,12 +296,22 @@ metrics will be tagged with:
each client RPC, tagged by operation name and the attempt status. Under normal
circumstances, this will be identical to op_latency. However, when the client
receives transient errors, op_latency will be the sum of all attempt_latencies
- and the exponential delays
+ and the exponential delays.
* `cloud.google.com/java/bigtable/attempts_per_op`: A distribution of attempts that
each operation required, tagged by operation name and final operation status.
Under normal circumstances, this will be 1.
+### GFE metric views:
+
+* `cloud.google.com/java/bigtable/gfe_latency`: A distribution of the latency
+ between Google's network receives an RPC and reads back the first byte of
+ the response.
+
+* `cloud.google.com/java/bigtable/gfe_header_missing_count`: A counter of the
+ number of RPC responses received without the server-timing header, which
+ indicates that the request probably never reached Google's network.
+
By default, the functionality is disabled. For example to enable metrics using
[Google Stackdriver](https://cloud.google.com/monitoring/docs/):
@@ -357,6 +367,8 @@ StackdriverStatsExporter.createAndRegister(
);
BigtableDataSettings.enableOpenCensusStats();
+// Enable GFE metric views
+BigtableDataSettings.enableGfeOpenCensusStats();
```
## Version Conflicts
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
index f4d63b5ec0..3002aa6113 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
@@ -175,6 +175,20 @@ public static void enableOpenCensusStats() {
// io.opencensus.contrib.grpc.metrics.RpcViews.registerClientGrpcBasicViews();
}
+ /**
+ * Enables OpenCensus GFE metric aggregations.
+ *
+ * This will register views for gfe_latency and gfe_header_missing_count metrics.
+ *
+ *
gfe_latency measures the latency between Google's network receives an RPC and reads back the
+ * first byte of the response. gfe_header_missing_count is a counter of the number of RPC
+ * responses received without the server-timing header.
+ */
+ @BetaApi("OpenCensus stats integration is currently unstable and may change in the future")
+ public static void enableGfeOpenCensusStats() {
+ com.google.cloud.bigtable.data.v2.stub.metrics.RpcViews.registerBigtableClientGfeViews();
+ }
+
/** Returns the target project id. */
public String getProjectId() {
return stubSettings.getProjectId();
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 d729d6244d..448390396f 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
@@ -66,6 +66,8 @@
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.CompositeTracerFactory;
+import com.google.cloud.bigtable.data.v2.stub.metrics.HeaderTracerStreamingCallable;
+import com.google.cloud.bigtable.data.v2.stub.metrics.HeaderTracerUnaryCallable;
import com.google.cloud.bigtable.data.v2.stub.metrics.MetricsTracerFactory;
import com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants;
import com.google.cloud.bigtable.data.v2.stub.mutaterows.BulkMutateRowsUserFacingCallable;
@@ -162,6 +164,15 @@ public static EnhancedBigtableStubSettings finalizeSettings(
.build());
}
+ ImmutableMap attributes =
+ ImmutableMap.builder()
+ .put(RpcMeasureConstants.BIGTABLE_PROJECT_ID, TagValue.create(settings.getProjectId()))
+ .put(
+ RpcMeasureConstants.BIGTABLE_INSTANCE_ID, TagValue.create(settings.getInstanceId()))
+ .put(
+ RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID,
+ TagValue.create(settings.getAppProfileId()))
+ .build();
// Inject Opencensus instrumentation
builder.setTracerFactory(
new CompositeTracerFactory(
@@ -187,23 +198,17 @@ public static EnhancedBigtableStubSettings finalizeSettings(
GaxProperties.getLibraryVersion(EnhancedBigtableStubSettings.class))
.build()),
// Add OpenCensus Metrics
- MetricsTracerFactory.create(
- tagger,
- stats,
- ImmutableMap.builder()
- .put(
- RpcMeasureConstants.BIGTABLE_PROJECT_ID,
- TagValue.create(settings.getProjectId()))
- .put(
- RpcMeasureConstants.BIGTABLE_INSTANCE_ID,
- TagValue.create(settings.getInstanceId()))
- .put(
- RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID,
- TagValue.create(settings.getAppProfileId()))
- .build()),
+ MetricsTracerFactory.create(tagger, stats, attributes),
// Add user configured tracer
settings.getTracerFactory())));
-
+ builder.setHeaderTracer(
+ builder
+ .getHeaderTracer()
+ .toBuilder()
+ .setStats(stats)
+ .setTagger(tagger)
+ .setStatsAttributes(attributes)
+ .build());
return builder.build();
}
@@ -268,11 +273,10 @@ public ServerStreamingCallable createReadRowsCallable(
ServerStreamingCallable readRowsUserCallable =
new ReadRowsUserCallable<>(readRowsCallable, requestContext);
+ SpanName span = getSpanName("ReadRows");
ServerStreamingCallable traced =
new TracedServerStreamingCallable<>(
- readRowsUserCallable,
- clientContext.getTracerFactory(),
- SpanName.of(CLIENT_NAME, "ReadRows"));
+ readRowsUserCallable, clientContext.getTracerFactory(), span);
return traced.withDefaultCallContext(clientContext.getDefaultCallContext());
}
@@ -315,6 +319,7 @@ public UnaryCallable createReadRowCallable(RowAdapter
* Upon receiving the response stream, it will merge the {@link
* com.google.bigtable.v2.ReadRowsResponse.CellChunk}s in logical rows. The actual row
* implementation can be configured by the {@code rowAdapter} parameter.
+ * Add header tracer for tracking GFE metrics.
* Retry/resume on failure.
* Filter out marker rows.
*
@@ -356,10 +361,14 @@ public Map extract(ReadRowsRequest readRowsRequest) {
ServerStreamingCallable watched =
Callables.watched(merging, innerSettings, clientContext);
+ ServerStreamingCallable withHeaderTracer =
+ new HeaderTracerStreamingCallable<>(
+ watched, settings.getHeaderTracer(), getSpanName("ReadRows").toString());
+
// Retry logic is split into 2 parts to workaround a rare edge case described in
// ReadRowsRetryCompletedCallable
ServerStreamingCallable retrying1 =
- new ReadRowsRetryCompletedCallable<>(watched);
+ new ReadRowsRetryCompletedCallable<>(withHeaderTracer);
ServerStreamingCallable retrying2 =
Callables.retrying(retrying1, innerSettings, clientContext);
@@ -380,6 +389,8 @@ public Map extract(ReadRowsRequest readRowsRequest) {
*
*/
private UnaryCallable> createSampleRowKeysCallable() {
+ String methodName = "SampleRowKeys";
+
ServerStreamingCallable base =
GrpcRawCallableFactory.createServerStreamingCallable(
GrpcCallSettings.newBuilder()
@@ -399,11 +410,15 @@ public Map extract(
UnaryCallable> spoolable = base.all();
+ UnaryCallable> withHeaderTracer =
+ new HeaderTracerUnaryCallable<>(
+ spoolable, settings.getHeaderTracer(), getSpanName(methodName).toString());
+
UnaryCallable> retryable =
- Callables.retrying(spoolable, settings.sampleRowKeysSettings(), clientContext);
+ Callables.retrying(withHeaderTracer, settings.sampleRowKeysSettings(), clientContext);
return createUserFacingUnaryCallable(
- "SampleRowKeys", new SampleRowKeysCallable(retryable, requestContext));
+ methodName, new SampleRowKeysCallable(retryable, requestContext));
}
/**
@@ -415,6 +430,7 @@ public Map extract(
*
*/
private UnaryCallable createMutateRowCallable() {
+ String methodName = "MutateRow";
UnaryCallable base =
GrpcRawCallableFactory.createUnaryCallable(
GrpcCallSettings.newBuilder()
@@ -431,11 +447,15 @@ public Map extract(MutateRowRequest mutateRowRequest) {
.build(),
settings.mutateRowSettings().getRetryableCodes());
+ UnaryCallable withHeaderTracer =
+ new HeaderTracerUnaryCallable<>(
+ base, settings.getHeaderTracer(), getSpanName(methodName).toString());
+
UnaryCallable retrying =
- Callables.retrying(base, settings.mutateRowSettings(), clientContext);
+ Callables.retrying(withHeaderTracer, settings.mutateRowSettings(), clientContext);
return createUserFacingUnaryCallable(
- "MutateRow", new MutateRowCallable(retrying, requestContext));
+ methodName, new MutateRowCallable(retrying, requestContext));
}
/**
@@ -459,11 +479,13 @@ private UnaryCallable createBulkMutateRowsCallable() {
UnaryCallable userFacing =
new BulkMutateRowsUserFacingCallable(baseCallable, requestContext);
+ SpanName spanName = getSpanName("MutateRows");
UnaryCallable traced =
- new TracedUnaryCallable<>(
- userFacing, clientContext.getTracerFactory(), SpanName.of(CLIENT_NAME, "MutateRows"));
+ new TracedUnaryCallable<>(userFacing, clientContext.getTracerFactory(), spanName);
+ UnaryCallable withHeaderTracer =
+ new HeaderTracerUnaryCallable<>(traced, settings.getHeaderTracer(), spanName.toString());
- return traced.withDefaultCallContext(clientContext.getDefaultCallContext());
+ return withHeaderTracer.withDefaultCallContext(clientContext.getDefaultCallContext());
}
/**
@@ -569,6 +591,7 @@ public Map extract(MutateRowsRequest mutateRowsRequest) {
*
*/
private UnaryCallable createCheckAndMutateRowCallable() {
+ String methodName = "CheckAndMutateRow";
UnaryCallable base =
GrpcRawCallableFactory.createUnaryCallable(
GrpcCallSettings.newBuilder()
@@ -586,11 +609,15 @@ public Map extract(
.build(),
settings.checkAndMutateRowSettings().getRetryableCodes());
+ UnaryCallable withHeaderTracer =
+ new HeaderTracerUnaryCallable<>(
+ base, settings.getHeaderTracer(), getSpanName(methodName).toString());
+
UnaryCallable retrying =
- Callables.retrying(base, settings.checkAndMutateRowSettings(), clientContext);
+ Callables.retrying(withHeaderTracer, settings.checkAndMutateRowSettings(), clientContext);
return createUserFacingUnaryCallable(
- "CheckAndMutateRow", new CheckAndMutateRowCallable(retrying, requestContext));
+ methodName, new CheckAndMutateRowCallable(retrying, requestContext));
}
/**
@@ -619,12 +646,16 @@ public Map extract(ReadModifyWriteRowRequest request) {
})
.build(),
settings.readModifyWriteRowSettings().getRetryableCodes());
+ String methodName = "ReadModifyWriteRow";
+ UnaryCallable withHeaderTracer =
+ new HeaderTracerUnaryCallable<>(
+ base, settings.getHeaderTracer(), getSpanName(methodName).toString());
UnaryCallable retrying =
- Callables.retrying(base, settings.readModifyWriteRowSettings(), clientContext);
+ Callables.retrying(withHeaderTracer, settings.readModifyWriteRowSettings(), clientContext);
return createUserFacingUnaryCallable(
- "ReadModifyWriteRow", new ReadModifyWriteRowCallable(retrying, requestContext));
+ methodName, new ReadModifyWriteRowCallable(retrying, requestContext));
}
/**
@@ -635,8 +666,7 @@ private UnaryCallable createUserFacin
String methodName, UnaryCallable inner) {
UnaryCallable traced =
- new TracedUnaryCallable<>(
- inner, clientContext.getTracerFactory(), SpanName.of(CLIENT_NAME, methodName));
+ new TracedUnaryCallable<>(inner, clientContext.getTracerFactory(), getSpanName(methodName));
return traced.withDefaultCallContext(clientContext.getDefaultCallContext());
}
@@ -686,6 +716,10 @@ public UnaryCallable readModifyWriteRowCallable() {
}
//
+ private SpanName getSpanName(String methodName) {
+ return SpanName.of(CLIENT_NAME, methodName);
+ }
+
@Override
public void close() {
for (BackgroundResource backgroundResource : clientContext.getBackgroundResources()) {
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java
index 72b22c35e2..eaea47f4ef 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java
@@ -38,6 +38,7 @@
import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
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.metrics.HeaderTracer;
import com.google.cloud.bigtable.data.v2.stub.mutaterows.MutateRowsBatchingDescriptor;
import com.google.cloud.bigtable.data.v2.stub.readrows.ReadRowsBatchingDescriptor;
import com.google.common.base.MoreObjects;
@@ -154,6 +155,7 @@ public class EnhancedBigtableStubSettings extends StubSettings primedTableIds;
+ private HeaderTracer headerTracer;
private final ServerStreamingCallSettings readRowsSettings;
private final UnaryCallSettings readRowSettings;
@@ -187,6 +189,7 @@ private EnhancedBigtableStubSettings(Builder builder) {
appProfileId = builder.appProfileId;
isRefreshingChannel = builder.isRefreshingChannel;
primedTableIds = builder.primedTableIds;
+ headerTracer = builder.headerTracer;
// Per method settings.
readRowsSettings = builder.readRowsSettings.build();
@@ -231,6 +234,11 @@ public List getPrimedTableIds() {
return primedTableIds;
}
+ /** Gets the tracer for capturing metrics in the header. */
+ HeaderTracer getHeaderTracer() {
+ return headerTracer;
+ }
+
/** Returns a builder for the default ChannelProvider for this service. */
public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() {
return BigtableStubSettings.defaultGrpcTransportProviderBuilder()
@@ -488,6 +496,7 @@ public static class Builder extends StubSettings.Builder primedTableIds;
+ private HeaderTracer headerTracer;
private final ServerStreamingCallSettings.Builder readRowsSettings;
private final UnaryCallSettings.Builder readRowSettings;
@@ -511,6 +520,7 @@ private Builder() {
this.appProfileId = SERVER_DEFAULT_APP_PROFILE_ID;
this.isRefreshingChannel = false;
primedTableIds = ImmutableList.of();
+ headerTracer = HeaderTracer.newBuilder().build();
setCredentialsProvider(defaultCredentialsProviderBuilder().build());
// Defaults provider
@@ -617,6 +627,7 @@ private Builder(EnhancedBigtableStubSettings settings) {
appProfileId = settings.appProfileId;
isRefreshingChannel = settings.isRefreshingChannel;
primedTableIds = settings.primedTableIds;
+ headerTracer = settings.headerTracer;
// Per method settings.
readRowsSettings = settings.readRowsSettings.toBuilder();
@@ -739,6 +750,17 @@ public List getPrimedTableIds() {
return primedTableIds;
}
+ /** Configure the header tracer for surfacing metrics in the header. */
+ Builder setHeaderTracer(HeaderTracer headerTracer) {
+ this.headerTracer = headerTracer;
+ return this;
+ }
+
+ /** Gets the header tracer that'll be used to surface metrics in the header. */
+ HeaderTracer getHeaderTracer() {
+ return headerTracer;
+ }
+
/** Returns the builder for the settings used for calls to readRows. */
public ServerStreamingCallSettings.Builder readRowsSettings() {
return readRowsSettings;
@@ -818,6 +840,7 @@ public String toString() {
.add("appProfileId", appProfileId)
.add("isRefreshingChannel", isRefreshingChannel)
.add("primedTableIds", primedTableIds)
+ .add("headerTracer", headerTracer)
.add("readRowsSettings", readRowsSettings)
.add("readRowSettings", readRowSettings)
.add("sampleRowKeysSettings", sampleRowKeysSettings)
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracer.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracer.java
new file mode 100644
index 0000000000..f3eb0ef1e2
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2020 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.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
+import io.grpc.Metadata;
+import io.opencensus.stats.MeasureMap;
+import io.opencensus.stats.Stats;
+import io.opencensus.stats.StatsRecorder;
+import io.opencensus.tags.TagContextBuilder;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.opencensus.tags.Tagger;
+import io.opencensus.tags.Tags;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+
+@InternalApi
+@AutoValue
+public abstract class HeaderTracer {
+
+ private static final Metadata.Key SERVER_TIMING_HEADER_KEY =
+ Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER);
+ private static final Pattern SERVER_TIMING_HEADER_PATTERN = Pattern.compile(".*dur=(?\\d+)");
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ //
+ public abstract Builder setTagger(@Nonnull Tagger tagger);
+
+ public abstract Builder setStats(@Nonnull StatsRecorder stats);
+
+ public abstract Builder setStatsAttributes(@Nonnull Map statsAttributes);
+
+ abstract HeaderTracer autoBuild();
+
+ public HeaderTracer build() {
+ HeaderTracer headerTracer = autoBuild();
+ return headerTracer;
+ }
+ //
+ }
+
+ public abstract Tagger getTagger();
+
+ public abstract StatsRecorder getStats();
+
+ public abstract Map getStatsAttributes();
+
+ /**
+ * If the header has a server-timing field, extract the metric and publish it to OpenCensus.
+ * Otherwise increment the gfe header missing counter by 1.
+ */
+ public void recordGfeMetadata(@Nonnull Metadata metadata, @Nonnull String spanName) {
+ MeasureMap measures = getStats().newMeasureMap();
+ if (metadata.get(SERVER_TIMING_HEADER_KEY) != null) {
+ String serverTiming = metadata.get(SERVER_TIMING_HEADER_KEY);
+ Matcher matcher = SERVER_TIMING_HEADER_PATTERN.matcher(serverTiming);
+ measures.put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 0L);
+ if (matcher.find()) {
+ long latency = Long.valueOf(matcher.group("dur"));
+ measures.put(RpcMeasureConstants.BIGTABLE_GFE_LATENCY, latency);
+ }
+ } else {
+ measures.put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 1L);
+ }
+ measures.record(newTagCtxBuilder(spanName).build());
+ }
+
+ public void recordGfeMissingHeader(@Nonnull String spanName) {
+ MeasureMap measures =
+ getStats().newMeasureMap().put(RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT, 1L);
+ measures.record(newTagCtxBuilder(spanName).build());
+ }
+
+ private TagContextBuilder newTagCtxBuilder(String span) {
+ TagContextBuilder tagContextBuilder = getTagger().currentBuilder();
+ if (span != null) {
+ tagContextBuilder.putLocal(RpcMeasureConstants.BIGTABLE_OP, TagValue.create(span));
+ }
+ // Copy client level tags in
+ for (Map.Entry entry : getStatsAttributes().entrySet()) {
+ tagContextBuilder.putLocal(entry.getKey(), entry.getValue());
+ }
+ return tagContextBuilder;
+ }
+
+ public static Builder newBuilder() {
+ return new AutoValue_HeaderTracer.Builder()
+ .setTagger(Tags.getTagger())
+ .setStats(Stats.getStatsRecorder())
+ .setStatsAttributes(Collections.emptyMap());
+ }
+
+ public abstract Builder toBuilder();
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("stats", getStats())
+ .add("tagger", getTagger())
+ .add("statsAttributes", getStatsAttributes())
+ .toString();
+ }
+}
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
new file mode 100644
index 0000000000..fdca9297aa
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerStreamingCallable.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2020 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.grpc.GrpcResponseMetadata;
+import com.google.api.gax.rpc.ApiCallContext;
+import com.google.api.gax.rpc.ResponseObserver;
+import com.google.api.gax.rpc.ServerStreamingCallable;
+import com.google.api.gax.rpc.StreamController;
+import com.google.common.base.Preconditions;
+import io.grpc.Metadata;
+import javax.annotation.Nonnull;
+
+/**
+ * This callable will inject a {@link GrpcResponseMetadata} to access the headers and trailers
+ * returned by gRPC methods upon completion. The {@link HeaderTracer} will process metrics that were
+ * injected in the header/trailer and publish them to OpenCensus. If {@link
+ * GrpcResponseMetadata#getMetadata()} returned null, it probably means that the request has never
+ * reached GFE, and it'll increment the gfe_header_missing_counter in this case.
+ *
+ * If GFE metrics are not registered in {@link RpcViews}, skip injecting GrpcResponseMetadata.
+ * This is for the case where direct path is enabled, all the requests won't go through GFE and
+ * therefore won't have the server-timing header.
+ *
+ *
This class is considered an internal implementation detail and not meant to be used by
+ * applications.
+ */
+@InternalApi
+public class HeaderTracerStreamingCallable
+ extends ServerStreamingCallable {
+
+ private final ServerStreamingCallable innerCallable;
+ private final HeaderTracer headerTracer;
+ private final String spanName;
+
+ public HeaderTracerStreamingCallable(
+ @Nonnull ServerStreamingCallable callable,
+ @Nonnull HeaderTracer headerTracer,
+ @Nonnull String spanName) {
+ this.innerCallable = Preconditions.checkNotNull(callable, "Inner callable must be set");
+ this.headerTracer = Preconditions.checkNotNull(headerTracer, "HeaderTracer must be set");
+ this.spanName = Preconditions.checkNotNull(spanName, "Span name must be set");
+ }
+
+ @Override
+ public void call(
+ RequestT request, ResponseObserver responseObserver, ApiCallContext context) {
+ final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata();
+ if (RpcViews.isGfeMetricsRegistered()) {
+ HeaderTracerResponseObserver innerObserver =
+ new HeaderTracerResponseObserver<>(
+ responseObserver, headerTracer, responseMetadata, spanName);
+ innerCallable.call(request, innerObserver, responseMetadata.addHandlers(context));
+ } else {
+ innerCallable.call(request, responseObserver, context);
+ }
+ }
+
+ private class HeaderTracerResponseObserver implements ResponseObserver {
+
+ private ResponseObserver outerObserver;
+ private HeaderTracer headerTracer;
+ private GrpcResponseMetadata responseMetadata;
+ private String spanName;
+
+ HeaderTracerResponseObserver(
+ ResponseObserver observer,
+ HeaderTracer headerTracer,
+ GrpcResponseMetadata metadata,
+ String spanName) {
+ this.outerObserver = observer;
+ this.headerTracer = headerTracer;
+ this.responseMetadata = metadata;
+ this.spanName = spanName;
+ }
+
+ @Override
+ public void onStart(final StreamController controller) {
+ outerObserver.onStart(controller);
+ }
+
+ @Override
+ public void onResponse(ResponseT response) {
+ outerObserver.onResponse(response);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ // server-timing metric will be added through GrpcResponseMetadata#onHeaders(Metadata),
+ // so it's not checking trailing metadata here.
+ Metadata metadata = responseMetadata.getMetadata();
+ if (metadata != null) {
+ headerTracer.recordGfeMetadata(metadata, spanName);
+ } else {
+ headerTracer.recordGfeMissingHeader(spanName);
+ }
+ outerObserver.onError(t);
+ }
+
+ @Override
+ public void onComplete() {
+ Metadata metadata = responseMetadata.getMetadata();
+ if (metadata != null) {
+ headerTracer.recordGfeMetadata(metadata, spanName);
+ } else {
+ headerTracer.recordGfeMissingHeader(spanName);
+ }
+ outerObserver.onComplete();
+ }
+ }
+}
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
new file mode 100644
index 0000000000..17d84b2a2e
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerUnaryCallable.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 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.ApiFuture;
+import com.google.api.core.InternalApi;
+import com.google.api.gax.grpc.GrpcResponseMetadata;
+import com.google.api.gax.rpc.ApiCallContext;
+import com.google.api.gax.rpc.UnaryCallable;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Metadata;
+import javax.annotation.Nonnull;
+
+/**
+ * This callable will inject a {@link GrpcResponseMetadata} to access the headers and trailers
+ * returned by gRPC methods upon completion. The {@link HeaderTracer} will process metrics that were
+ * injected in the header/trailer and publish them to OpenCensus. If {@link
+ * GrpcResponseMetadata#getMetadata()} returned null, it probably means that the request has never
+ * reached GFE, and it'll increment the gfe_header_missing_counter in this case.
+ *
+ * If GFE metrics are not registered in {@link RpcViews}, skip injecting GrpcResponseMetadata.
+ * This is for the case where direct path is enabled, all the requests won't go through GFE and
+ * therefore won't have the server-timing header.
+ *
+ *
This class is considered an internal implementation detail and not meant to be used by
+ * applications.
+ */
+@InternalApi
+public class HeaderTracerUnaryCallable
+ extends UnaryCallable {
+
+ private final UnaryCallable innerCallable;
+ private final HeaderTracer headerTracer;
+ private final String spanName;
+
+ public HeaderTracerUnaryCallable(
+ @Nonnull UnaryCallable innerCallable,
+ @Nonnull HeaderTracer headerTracer,
+ @Nonnull String spanName) {
+ this.innerCallable = Preconditions.checkNotNull(innerCallable, "Inner callable must be set");
+ this.headerTracer = Preconditions.checkNotNull(headerTracer, "HeaderTracer must be set");
+ this.spanName = Preconditions.checkNotNull(spanName, "Span name must be set");
+ }
+
+ @Override
+ public ApiFuture futureCall(RequestT request, ApiCallContext context) {
+ if (RpcViews.isGfeMetricsRegistered()) {
+ final GrpcResponseMetadata responseMetadata = new GrpcResponseMetadata();
+ ApiFuture future =
+ innerCallable.futureCall(request, responseMetadata.addHandlers(context));
+ future.addListener(
+ new Runnable() {
+ @Override
+ public void run() {
+ Metadata metadata = responseMetadata.getMetadata();
+ if (metadata != null) {
+ headerTracer.recordGfeMetadata(metadata, spanName);
+ } else {
+ headerTracer.recordGfeMissingHeader(spanName);
+ }
+ }
+ },
+ MoreExecutors.directExecutor());
+ return future;
+ } else {
+ return innerCallable.futureCall(request, context);
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcMeasureConstants.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcMeasureConstants.java
index 8c6e347a0f..e6e5c70db1 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcMeasureConstants.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcMeasureConstants.java
@@ -74,4 +74,18 @@ public class RpcMeasureConstants {
"cloud.google.com/java/bigtable/read_rows_first_row_latency",
"Time between request being sent to the first row received",
MILLISECOND);
+
+ /** GFE t4t7 latency extracted from server-timing header. */
+ public static final MeasureLong BIGTABLE_GFE_LATENCY =
+ MeasureLong.create(
+ "cloud.google.com/java/bigtable/gfe_latency",
+ "Latency between Google's network receives an RPC and reads back the first byte of the response",
+ MILLISECOND);
+
+ /** Number of responses without the server-timing header. */
+ public static final MeasureLong BIGTABLE_GFE_HEADER_MISSING_COUNT =
+ MeasureLong.create(
+ "cloud.google.com/java/bigtable/gfe_header_missing_count",
+ "Number of RPC responses received without the server-timing header, most likely means that the RPC never reached Google's network",
+ COUNT);
}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViewConstants.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViewConstants.java
index d21060c4ac..8a14c01b13 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViewConstants.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViewConstants.java
@@ -17,6 +17,8 @@
import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID;
import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_ATTEMPT_LATENCY;
+import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT;
+import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_GFE_LATENCY;
import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_INSTANCE_ID;
import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_OP;
import static com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants.BIGTABLE_OP_ATTEMPT_COUNT;
@@ -29,6 +31,7 @@
import io.opencensus.stats.Aggregation;
import io.opencensus.stats.Aggregation.Count;
import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.Sum;
import io.opencensus.stats.BucketBoundaries;
import io.opencensus.stats.View;
import java.util.Arrays;
@@ -36,6 +39,7 @@
class RpcViewConstants {
// Aggregations
private static final Aggregation COUNT = Count.create();
+ private static final Aggregation SUM = Sum.create();
private static final Aggregation AGGREGATION_WITH_MILLIS_HISTOGRAM =
Distribution.create(
@@ -124,4 +128,22 @@ class RpcViewConstants {
BIGTABLE_APP_PROFILE_ID,
BIGTABLE_OP,
BIGTABLE_STATUS));
+
+ static final View BIGTABLE_GFE_LATENCY_VIEW =
+ View.create(
+ View.Name.create("cloud.google.com/java/bigtable/gfe_latency"),
+ "Latency between Google's network receives an RPC and reads back the first byte of the response",
+ BIGTABLE_GFE_LATENCY,
+ AGGREGATION_WITH_MILLIS_HISTOGRAM,
+ ImmutableList.of(
+ BIGTABLE_INSTANCE_ID, BIGTABLE_PROJECT_ID, BIGTABLE_APP_PROFILE_ID, BIGTABLE_OP));
+
+ static final View BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW =
+ View.create(
+ View.Name.create("cloud.google.com/java/bigtable/gfe_header_missing_count"),
+ "Number of RPC responses received without the server-timing header, most likely means that the RPC never reached Google's network",
+ BIGTABLE_GFE_HEADER_MISSING_COUNT,
+ SUM,
+ ImmutableList.of(
+ BIGTABLE_INSTANCE_ID, BIGTABLE_PROJECT_ID, BIGTABLE_APP_PROFILE_ID, BIGTABLE_OP));
}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViews.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViews.java
index cc31539496..9e8f6084a2 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViews.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/RpcViews.java
@@ -33,15 +33,49 @@ public class RpcViews {
RpcViewConstants.BIGTABLE_ATTEMPT_LATENCY_VIEW,
RpcViewConstants.BIGTABLE_ATTEMPTS_PER_OP_VIEW);
+ private static final ImmutableSet GFE_VIEW_SET =
+ ImmutableSet.of(
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW);
+
+ private static boolean gfeMetricsRegistered = false;
+
/** Registers all Bigtable specific views. */
public static void registerBigtableClientViews() {
registerBigtableClientViews(Stats.getViewManager());
}
+ /**
+ * Register views for GFE metrics, including gfe_latency and gfe_header_missing_count. gfe_latency
+ * measures the latency between Google's network receives an RPC and reads back the first byte of
+ * the response. gfe_header_missing_count is a counter of the number of RPC responses without a
+ * server-timing header.
+ */
+ public static void registerBigtableClientGfeViews() {
+ registerBigtableClientGfeViews(Stats.getViewManager());
+ }
+
@VisibleForTesting
static void registerBigtableClientViews(ViewManager viewManager) {
for (View view : BIGTABLE_CLIENT_VIEWS_SET) {
viewManager.registerView(view);
}
}
+
+ @VisibleForTesting
+ static void registerBigtableClientGfeViews(ViewManager viewManager) {
+ for (View view : GFE_VIEW_SET) {
+ viewManager.registerView(view);
+ }
+ gfeMetricsRegistered = true;
+ }
+
+ static boolean isGfeMetricsRegistered() {
+ return gfeMetricsRegistered;
+ }
+
+ @VisibleForTesting
+ static void setGfeMetricsRegistered(boolean gfeMetricsRegistered) {
+ RpcViews.gfeMetricsRegistered = gfeMetricsRegistered;
+ }
}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java
index 2cd55a311c..10ba675826 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java
@@ -32,6 +32,7 @@
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.metrics.HeaderTracer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
@@ -75,6 +76,7 @@ public void settingsAreNotLostTest() {
CredentialsProvider credentialsProvider = Mockito.mock(CredentialsProvider.class);
WatchdogProvider watchdogProvider = Mockito.mock(WatchdogProvider.class);
Duration watchdogInterval = Duration.ofSeconds(12);
+ HeaderTracer headerTracer = Mockito.mock(HeaderTracer.class);
EnhancedBigtableStubSettings.Builder builder =
EnhancedBigtableStubSettings.newBuilder()
@@ -85,7 +87,8 @@ public void settingsAreNotLostTest() {
.setEndpoint(endpoint)
.setCredentialsProvider(credentialsProvider)
.setStreamWatchdogProvider(watchdogProvider)
- .setStreamWatchdogCheckInterval(watchdogInterval);
+ .setStreamWatchdogCheckInterval(watchdogInterval)
+ .setHeaderTracer(headerTracer);
verifyBuilder(
builder,
@@ -96,7 +99,8 @@ public void settingsAreNotLostTest() {
endpoint,
credentialsProvider,
watchdogProvider,
- watchdogInterval);
+ watchdogInterval,
+ headerTracer);
verifySettings(
builder.build(),
projectId,
@@ -106,7 +110,8 @@ public void settingsAreNotLostTest() {
endpoint,
credentialsProvider,
watchdogProvider,
- watchdogInterval);
+ watchdogInterval,
+ headerTracer);
verifyBuilder(
builder.build().toBuilder(),
projectId,
@@ -116,7 +121,8 @@ public void settingsAreNotLostTest() {
endpoint,
credentialsProvider,
watchdogProvider,
- watchdogInterval);
+ watchdogInterval,
+ headerTracer);
}
private void verifyBuilder(
@@ -128,7 +134,8 @@ private void verifyBuilder(
String endpoint,
CredentialsProvider credentialsProvider,
WatchdogProvider watchdogProvider,
- Duration watchdogInterval) {
+ Duration watchdogInterval,
+ HeaderTracer headerTracer) {
assertThat(builder.getProjectId()).isEqualTo(projectId);
assertThat(builder.getInstanceId()).isEqualTo(instanceId);
assertThat(builder.getAppProfileId()).isEqualTo(appProfileId);
@@ -137,6 +144,7 @@ private void verifyBuilder(
assertThat(builder.getCredentialsProvider()).isEqualTo(credentialsProvider);
assertThat(builder.getStreamWatchdogProvider()).isSameInstanceAs(watchdogProvider);
assertThat(builder.getStreamWatchdogCheckInterval()).isEqualTo(watchdogInterval);
+ assertThat(builder.getHeaderTracer()).isEqualTo(headerTracer);
}
private void verifySettings(
@@ -148,7 +156,8 @@ private void verifySettings(
String endpoint,
CredentialsProvider credentialsProvider,
WatchdogProvider watchdogProvider,
- Duration watchdogInterval) {
+ Duration watchdogInterval,
+ HeaderTracer headerTracer) {
assertThat(settings.getProjectId()).isEqualTo(projectId);
assertThat(settings.getInstanceId()).isEqualTo(instanceId);
assertThat(settings.getAppProfileId()).isEqualTo(appProfileId);
@@ -157,6 +166,7 @@ private void verifySettings(
assertThat(settings.getCredentialsProvider()).isEqualTo(credentialsProvider);
assertThat(settings.getStreamWatchdogProvider()).isSameInstanceAs(watchdogProvider);
assertThat(settings.getStreamWatchdogCheckInterval()).isEqualTo(watchdogInterval);
+ assertThat(settings.getHeaderTracer()).isEqualTo(headerTracer);
}
@Test
@@ -622,12 +632,26 @@ public void isRefreshingChannelFalseValueTest() {
assertThat(builder.build().toBuilder().isRefreshingChannel()).isFalse();
}
+ @Test
+ public void verifyDefaultHeaderTracerNotNullTest() {
+ String dummyProjectId = "my-project";
+ String dummyInstanceId = "my-instance";
+ EnhancedBigtableStubSettings.Builder builder =
+ EnhancedBigtableStubSettings.newBuilder()
+ .setProjectId(dummyProjectId)
+ .setInstanceId(dummyInstanceId);
+ assertThat(builder.getHeaderTracer()).isNotNull();
+ assertThat(builder.build().getHeaderTracer()).isNotNull();
+ assertThat(builder.build().toBuilder().getHeaderTracer()).isNotNull();
+ }
+
static final String[] SETTINGS_LIST = {
"projectId",
"instanceId",
"appProfileId",
"isRefreshingChannel",
"primedTableIds",
+ "headerTracer",
"readRowsSettings",
"readRowSettings",
"sampleRowKeysSettings",
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
new file mode 100644
index 0000000000..9538b6a135
--- /dev/null
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerCallableTest.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2020 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.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.api.gax.rpc.ClientContext;
+import com.google.api.gax.rpc.UnavailableException;
+import com.google.bigtable.v2.BigtableGrpc.BigtableImplBase;
+import com.google.bigtable.v2.CheckAndMutateRowRequest;
+import com.google.bigtable.v2.CheckAndMutateRowResponse;
+import com.google.bigtable.v2.MutateRowRequest;
+import com.google.bigtable.v2.MutateRowResponse;
+import com.google.bigtable.v2.MutateRowsRequest;
+import com.google.bigtable.v2.MutateRowsResponse;
+import com.google.bigtable.v2.ReadModifyWriteRowRequest;
+import com.google.bigtable.v2.ReadModifyWriteRowResponse;
+import com.google.bigtable.v2.ReadRowsRequest;
+import com.google.bigtable.v2.ReadRowsResponse;
+import com.google.bigtable.v2.SampleRowKeysRequest;
+import com.google.bigtable.v2.SampleRowKeysResponse;
+import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
+import com.google.cloud.bigtable.data.v2.FakeServiceHelper;
+import com.google.cloud.bigtable.data.v2.internal.NameUtil;
+import com.google.cloud.bigtable.data.v2.models.BulkMutation;
+import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
+import com.google.cloud.bigtable.data.v2.models.Mutation;
+import com.google.cloud.bigtable.data.v2.models.Query;
+import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
+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.common.collect.ImmutableMap;
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+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;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HeaderTracerCallableTest {
+ private FakeServiceHelper serviceHelper;
+ private FakeServiceHelper serviceHelperNoHeader;
+
+ private FakeService fakeService = new FakeService();
+
+ private final StatsComponent localStats = new StatsComponentImpl();
+ private EnhancedBigtableStub stub;
+ private EnhancedBigtableStub noHeaderStub;
+ private int attempts;
+
+ 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 long WAIT_FOR_METRICS_TIME_MS = 1_000;
+
+ private AtomicInteger fakeServerTiming;
+
+ @Before
+ public void setUp() throws Exception {
+ RpcViews.registerBigtableClientGfeViews(localStats.getViewManager());
+
+ // Create a server that'll inject a server-timing header with a random number and a stub that
+ // connects to this server.
+ fakeServerTiming = new AtomicInteger(new Random().nextInt(1000) + 1);
+ serviceHelper =
+ new FakeServiceHelper(
+ new ServerInterceptor() {
+ @Override
+ public ServerCall.Listener interceptCall(
+ ServerCall serverCall,
+ Metadata metadata,
+ ServerCallHandler serverCallHandler) {
+ return serverCallHandler.startCall(
+ new SimpleForwardingServerCall(serverCall) {
+ @Override
+ public void sendHeaders(Metadata headers) {
+ headers.put(
+ Metadata.Key.of("server-timing", Metadata.ASCII_STRING_MARSHALLER),
+ String.format("gfet4t7; dur=%d", fakeServerTiming.get()));
+ super.sendHeaders(headers);
+ }
+ },
+ metadata);
+ }
+ },
+ fakeService);
+ serviceHelper.start();
+
+ BigtableDataSettings settings =
+ BigtableDataSettings.newBuilderForEmulator(serviceHelper.getPort())
+ .setProjectId(PROJECT_ID)
+ .setInstanceId(INSTANCE_ID)
+ .setAppProfileId(APP_PROFILE_ID)
+ .build();
+ EnhancedBigtableStubSettings stubSettings =
+ EnhancedBigtableStub.finalizeSettings(
+ settings.getStubSettings(), Tags.getTagger(), localStats.getStatsRecorder());
+ attempts = stubSettings.readRowsSettings().getRetrySettings().getMaxAttempts();
+ stub = new EnhancedBigtableStub(stubSettings, ClientContext.create(stubSettings));
+
+ // Create another server without injecting the server-timing header and another stub that
+ // connects to it.
+ serviceHelperNoHeader = new FakeServiceHelper(fakeService);
+ serviceHelperNoHeader.start();
+
+ BigtableDataSettings noHeaderSettings =
+ BigtableDataSettings.newBuilderForEmulator(serviceHelperNoHeader.getPort())
+ .setProjectId(PROJECT_ID)
+ .setInstanceId(INSTANCE_ID)
+ .setAppProfileId(APP_PROFILE_ID)
+ .build();
+ EnhancedBigtableStubSettings noHeaderStubSettings =
+ EnhancedBigtableStub.finalizeSettings(
+ noHeaderSettings.getStubSettings(), Tags.getTagger(), localStats.getStatsRecorder());
+ noHeaderStub =
+ new EnhancedBigtableStub(noHeaderStubSettings, ClientContext.create(noHeaderStubSettings));
+ }
+
+ @After
+ public void tearDown() {
+ stub.close();
+ noHeaderStub.close();
+ serviceHelper.shutdown();
+ serviceHelperNoHeader.shutdown();
+ }
+
+ @Test
+ public void testGFELatencyMetricReadRows() throws InterruptedException {
+ stub.readRowsCallable().call(Query.create(TABLE_ID));
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFELatencyMetricMutateRow() throws InterruptedException {
+ stub.mutateRowCallable().call(RowMutation.create(TABLE_ID, "fake-key"));
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRow")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFELatencyMetricMutateRows() throws InterruptedException {
+ BulkMutation mutations =
+ BulkMutation.create(TABLE_ID)
+ .add("key", Mutation.create().setCell("fake-family", "fake-qualifier", "fake-value"));
+ stub.bulkMutateRowsCallable().call(mutations);
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRows")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFELatencySampleRowKeys() throws InterruptedException {
+ stub.sampleRowKeysCallable().call(TABLE_ID);
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.SampleRowKeys")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFELatencyCheckAndMutateRow() throws InterruptedException {
+ ConditionalRowMutation mutation =
+ ConditionalRowMutation.create(TABLE_ID, "fake-key")
+ .then(Mutation.create().setCell("fake-family", "fake-qualifier", "fake-value"));
+ stub.checkAndMutateRowCallable().call(mutation);
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.CheckAndMutateRow")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFELatencyReadModifyWriteRow() throws InterruptedException {
+ ReadModifyWriteRow request =
+ ReadModifyWriteRow.create(TABLE_ID, "fake-key")
+ .append("fake-family", "fake-qualifier", "suffix");
+ stub.readModifyWriteRowCallable().call(request);
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+ long latency =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadModifyWriteRow")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ assertThat(latency).isEqualTo(fakeServerTiming.get());
+ }
+
+ @Test
+ public void testGFEMissingHeaderMetric() throws InterruptedException {
+ // Make a few calls to the server which will inject the server-timing header and the counter
+ // should be 0.
+ stub.readRowsCallable().call(Query.create(TABLE_ID));
+ stub.mutateRowCallable().call(RowMutation.create(TABLE_ID, "key"));
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+ long mutateRowMissingCount =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW,
+ ImmutableMap.of(RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRow")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ long readRowsMissingCount =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+
+ assertThat(mutateRowMissingCount).isEqualTo(0);
+ assertThat(readRowsMissingCount).isEqualTo(0);
+
+ // Make a few more calls to the server which won't add the header and the counter should match
+ // the number of requests sent.
+ int readRowsCalls = new Random().nextInt(10) + 1;
+ int mutateRowCalls = new Random().nextInt(10) + 1;
+ for (int i = 0; i < mutateRowCalls; i++) {
+ noHeaderStub.mutateRowCallable().call(RowMutation.create(TABLE_ID, "fake-key" + i));
+ }
+ for (int i = 0; i < readRowsCalls; i++) {
+ noHeaderStub.readRowsCallable().call(Query.create(TABLE_ID));
+ }
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+
+ mutateRowMissingCount =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW,
+ ImmutableMap.of(RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRow")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ readRowsMissingCount =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+
+ assertThat(mutateRowMissingCount).isEqualTo(mutateRowCalls);
+ assertThat(readRowsMissingCount).isEqualTo(readRowsCalls);
+ }
+
+ @Test
+ public void testMetricsWithErrorResponse() throws InterruptedException {
+ try {
+ stub.readRowsCallable().call(Query.create("random-table-id")).iterator().next();
+ fail("readrows should throw exception");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(UnavailableException.class);
+ }
+
+ Thread.sleep(WAIT_FOR_METRICS_TIME_MS);
+ long missingCount =
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
+ RpcViewConstants.BIGTABLE_GFE_HEADER_MISSING_COUNT_VIEW,
+ ImmutableMap.of(RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
+ 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);
+
+ @Override
+ public void readRows(ReadRowsRequest request, StreamObserver observer) {
+ if (!request.getTableName().equals(defaultTableName)) {
+ observer.onError(new StatusRuntimeException(Status.UNAVAILABLE));
+ return;
+ }
+ observer.onNext(ReadRowsResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+
+ @Override
+ public void mutateRow(MutateRowRequest request, StreamObserver observer) {
+ observer.onNext(MutateRowResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+
+ @Override
+ public void mutateRows(MutateRowsRequest request, StreamObserver observer) {
+ observer.onNext(MutateRowsResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+
+ @Override
+ public void sampleRowKeys(
+ SampleRowKeysRequest request, StreamObserver observer) {
+ observer.onNext(SampleRowKeysResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+
+ @Override
+ public void checkAndMutateRow(
+ CheckAndMutateRowRequest request, StreamObserver observer) {
+ observer.onNext(CheckAndMutateRowResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+
+ @Override
+ public void readModifyWriteRow(
+ ReadModifyWriteRowRequest request, StreamObserver observer) {
+ observer.onNext(ReadModifyWriteRowResponse.getDefaultInstance());
+ observer.onCompleted();
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerTest.java
new file mode 100644
index 0000000000..30e24dbfe3
--- /dev/null
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/HeaderTracerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 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.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import io.opencensus.impl.stats.StatsComponentImpl;
+import io.opencensus.stats.StatsComponent;
+import io.opencensus.stats.StatsRecorder;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.opencensus.tags.Tagger;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class HeaderTracerTest {
+
+ private final StatsComponent localStats = new StatsComponentImpl();
+
+ @Test
+ public void testDefaultBuilder() {
+ HeaderTracer.Builder builder = HeaderTracer.newBuilder();
+ HeaderTracer tracer = builder.build();
+ assertThat(tracer.getStats()).isNotNull();
+ assertThat(tracer.getTagger()).isNotNull();
+ assertThat(tracer.getStatsAttributes()).isNotNull();
+ assertThat(tracer.getStatsAttributes()).isEmpty();
+ }
+
+ @Test
+ public void testBuilder() {
+ HeaderTracer.Builder builder = HeaderTracer.newBuilder();
+ Map attrs =
+ ImmutableMap.of(TagKey.create("fake-key"), TagValue.create("fake-value"));
+ Tagger tagger = Mockito.mock(Tagger.class);
+ StatsRecorder stats = localStats.getStatsRecorder();
+ builder.setStats(stats).setStatsAttributes(attrs).setTagger(tagger);
+ HeaderTracer headerTracer = builder.build();
+ assertThat(headerTracer.getStats()).isEqualTo(stats);
+ assertThat(headerTracer.getTagger()).isEqualTo(tagger);
+ assertThat(headerTracer.getStatsAttributes()).isEqualTo(attrs);
+ }
+
+ @Test
+ public void testToBuilder() {
+ HeaderTracer.Builder builder = HeaderTracer.newBuilder();
+ Map attrs =
+ ImmutableMap.of(TagKey.create("fake-key"), TagValue.create("fake-value"));
+ Tagger tagger = Mockito.mock(Tagger.class);
+ StatsRecorder stats = localStats.getStatsRecorder();
+ builder.setStats(stats).setStatsAttributes(attrs).setTagger(tagger);
+ HeaderTracer headerTracer = builder.build();
+
+ HeaderTracer.Builder newBuilder = headerTracer.toBuilder();
+ assertThat(newBuilder.build().getStats()).isEqualTo(stats);
+ assertThat(newBuilder.build().getTagger()).isEqualTo(tagger);
+ assertThat(newBuilder.build().getStatsAttributes()).isEqualTo(attrs);
+ }
+}
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 4b025303e4..56d65f8298 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
@@ -39,24 +39,10 @@
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
-import io.opencensus.common.Function;
import io.opencensus.impl.stats.StatsComponentImpl;
-import io.opencensus.stats.AggregationData;
-import io.opencensus.stats.AggregationData.CountData;
-import io.opencensus.stats.AggregationData.DistributionData;
-import io.opencensus.stats.AggregationData.LastValueDataDouble;
-import io.opencensus.stats.AggregationData.LastValueDataLong;
-import io.opencensus.stats.AggregationData.SumDataDouble;
-import io.opencensus.stats.AggregationData.SumDataLong;
-import io.opencensus.stats.View;
-import io.opencensus.stats.ViewData;
import io.opencensus.tags.TagKey;
import io.opencensus.tags.TagValue;
import io.opencensus.tags.Tags;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
@@ -117,7 +103,6 @@ public void setUp() throws Exception {
EnhancedBigtableStubSettings stubSettings =
EnhancedBigtableStub.finalizeSettings(
settings.getStubSettings(), Tags.getTagger(), localStats.getStatsRecorder());
-
stub = new EnhancedBigtableStub(stubSettings, ClientContext.create(stubSettings));
}
@@ -155,11 +140,15 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(100);
long opLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_OP_LATENCY_VIEW,
ImmutableMap.of(
RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows"),
- RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")));
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
assertThat(opLatency).isIn(Range.closed(sleepTime, elapsed));
}
@@ -187,11 +176,15 @@ public Object answer(InvocationOnMock invocation) {
Thread.sleep(100);
long opLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_COMPLETED_OP_VIEW,
ImmutableMap.of(
RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows"),
- RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")));
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
assertThat(opLatency).isEqualTo(2);
}
@@ -225,9 +218,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(100);
long firstRowLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_READ_ROWS_FIRST_ROW_LATENCY_VIEW,
- ImmutableMap.of());
+ ImmutableMap.of(),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
// adding buffer time to the upper range to allow for a race between the emulator and the client
// recording the duration
@@ -267,11 +264,15 @@ public Object answer(InvocationOnMock invocation) {
Thread.sleep(100);
long opLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_ATTEMPTS_PER_OP_VIEW,
ImmutableMap.of(
RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows"),
- RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")));
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
assertThat(opLatency).isEqualTo(2);
}
@@ -312,11 +313,15 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(100);
long attemptLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_ATTEMPT_LATENCY_VIEW,
ImmutableMap.of(
RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.ReadRows"),
- RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")));
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("OK")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
// Average attempt latency will be just a single wait (as opposed to op latency which will be 2x
// sleeptime)
assertThat(attemptLatency).isIn(Range.closed(sleepTime, elapsed - sleepTime));
@@ -326,70 +331,4 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
private static StreamObserver anyObserver(Class returnType) {
return (StreamObserver) any(returnType);
}
-
- private long getAggregationValueAsLong(View view, ImmutableMap tags) {
- ViewData viewData = localStats.getViewManager().getView(view.getName());
- Map, AggregationData> aggregationMap =
- Objects.requireNonNull(viewData).getAggregationMap();
-
- List tagValues = new ArrayList<>();
-
- for (TagKey column : view.getColumns()) {
- if (RpcMeasureConstants.BIGTABLE_PROJECT_ID == column) {
- tagValues.add(TagValue.create(PROJECT_ID));
- } else if (RpcMeasureConstants.BIGTABLE_INSTANCE_ID == column) {
- tagValues.add(TagValue.create(INSTANCE_ID));
- } else if (RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID == column) {
- tagValues.add(TagValue.create(APP_PROFILE_ID));
- } else {
- tagValues.add(tags.get(column));
- }
- }
-
- AggregationData aggregationData = aggregationMap.get(tagValues);
-
- return aggregationData.match(
- new Function() {
- @Override
- public Long apply(SumDataDouble arg) {
- return (long) arg.getSum();
- }
- },
- new Function() {
- @Override
- public Long apply(SumDataLong arg) {
- return arg.getSum();
- }
- },
- new Function() {
- @Override
- public Long apply(CountData arg) {
- return arg.getCount();
- }
- },
- new Function() {
- @Override
- public Long apply(DistributionData arg) {
- return (long) arg.getMean();
- }
- },
- new Function() {
- @Override
- public Long apply(LastValueDataDouble arg) {
- return (long) arg.getLastValue();
- }
- },
- new Function() {
- @Override
- public Long apply(LastValueDataLong arg) {
- return arg.getLastValue();
- }
- },
- new Function() {
- @Override
- public Long apply(AggregationData arg) {
- throw new UnsupportedOperationException();
- }
- });
- }
}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/StatsTestUtils.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/StatsTestUtils.java
index ff37e75a87..6aede96161 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/StatsTestUtils.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/StatsTestUtils.java
@@ -23,9 +23,13 @@
import com.google.common.collect.Maps;
import io.grpc.Context;
import io.opencensus.common.Scope;
+import io.opencensus.stats.AggregationData;
import io.opencensus.stats.Measure;
import io.opencensus.stats.MeasureMap;
+import io.opencensus.stats.StatsComponent;
import io.opencensus.stats.StatsRecorder;
+import io.opencensus.stats.View;
+import io.opencensus.stats.ViewData;
import io.opencensus.tags.Tag;
import io.opencensus.tags.TagContext;
import io.opencensus.tags.TagContextBuilder;
@@ -35,9 +39,12 @@
import io.opencensus.tags.TagValue;
import io.opencensus.tags.Tagger;
import io.opencensus.tags.unsafe.ContextUtils;
+import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -264,4 +271,76 @@ private static ImmutableMap getTags(TagContext tags) {
? ((FakeTagContext) tags).getTags()
: ImmutableMap.of();
}
+
+ public static long getAggregationValueAsLong(
+ StatsComponent stats,
+ View view,
+ ImmutableMap tags,
+ String projectId,
+ String instanceId,
+ String appProfileId) {
+ ViewData viewData = stats.getViewManager().getView(view.getName());
+ Map, AggregationData> aggregationMap =
+ Objects.requireNonNull(viewData).getAggregationMap();
+
+ List tagValues = new ArrayList<>();
+
+ for (TagKey column : view.getColumns()) {
+ if (RpcMeasureConstants.BIGTABLE_PROJECT_ID == column) {
+ tagValues.add(TagValue.create(projectId));
+ } else if (RpcMeasureConstants.BIGTABLE_INSTANCE_ID == column) {
+ tagValues.add(TagValue.create(instanceId));
+ } else if (RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID == column) {
+ tagValues.add(TagValue.create(appProfileId));
+ } else {
+ tagValues.add(tags.get(column));
+ }
+ }
+
+ AggregationData aggregationData = aggregationMap.get(tagValues);
+
+ return aggregationData.match(
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.SumDataDouble arg) {
+ return (long) arg.getSum();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.SumDataLong arg) {
+ return arg.getSum();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.CountData arg) {
+ return arg.getCount();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.DistributionData arg) {
+ return (long) arg.getMean();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.LastValueDataDouble arg) {
+ return (long) arg.getLastValue();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData.LastValueDataLong arg) {
+ return arg.getLastValue();
+ }
+ },
+ new io.opencensus.common.Function() {
+ @Override
+ public Long apply(AggregationData arg) {
+ throw new UnsupportedOperationException();
+ }
+ });
+ }
}
From 8d7102003b54757b64fd598290301d3b24fd9c29 Mon Sep 17 00:00:00 2001
From: Mattie Fu
Date: Wed, 16 Dec 2020 19:54:32 -0500
Subject: [PATCH 10/38] fix: fix MutateRowsAttemptCallable to avoid NPE in
MetricTracer (#557)
---
.../mutaterows/MutateRowsAttemptCallable.java | 12 +++++++----
.../v2/stub/metrics/MetricsTracerTest.java | 20 +++++++++++++++++++
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsAttemptCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsAttemptCallable.java
index 22266b6b81..e85270f619 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsAttemptCallable.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsAttemptCallable.java
@@ -160,9 +160,17 @@ public void setExternalFuture(RetryingFuture externalFuture) {
@Override
public Void call() {
try {
+ // externalFuture is set from MutateRowsRetryingCallable before invoking this method. It
+ // shouldn't be null unless the code changed
Preconditions.checkNotNull(
externalFuture, "External future must be set before starting an attempt");
+ // attemptStared should be called at the very start of the operation. This will initialize
+ // variables in ApiTracer and avoid exceptions when the tracer marks the attempt as finished
+ callContext
+ .getTracer()
+ .attemptStarted(externalFuture.getAttemptSettings().getOverallAttemptCount());
+
Preconditions.checkState(
currentRequest.getEntriesCount() > 0, "Request doesn't have any mutations to send");
@@ -179,10 +187,6 @@ public Void call() {
return null;
}
- callContext
- .getTracer()
- .attemptStarted(externalFuture.getAttemptSettings().getOverallAttemptCount());
-
// Make the actual call
ApiFuture> innerFuture =
innerCallable.futureCall(currentRequest, currentCallContext);
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 56d65f8298..bc6166f79e 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
@@ -26,6 +26,7 @@
import com.google.bigtable.v2.ReadRowsResponse.CellChunk;
import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
import com.google.cloud.bigtable.data.v2.FakeServiceHelper;
+import com.google.cloud.bigtable.data.v2.models.BulkMutation;
import com.google.cloud.bigtable.data.v2.models.Query;
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub;
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings;
@@ -46,6 +47,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -327,6 +329,24 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
assertThat(attemptLatency).isIn(Range.closed(sleepTime, elapsed - sleepTime));
}
+ @Test
+ public void testInvalidRequest() throws InterruptedException {
+ try {
+ stub.bulkMutateRowsCallable().call(BulkMutation.create(TABLE_ID));
+ Assert.fail("Invalid request should throw exception");
+ } catch (IllegalStateException e) {
+ Thread.sleep(100);
+ // Verify that the latency is recorded with an error code (in this case UNKNOWN)
+ long attemptLatency =
+ getAggregationValueAsLong(
+ RpcViewConstants.BIGTABLE_ATTEMPT_LATENCY_VIEW,
+ ImmutableMap.of(
+ RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRows"),
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("UNKNOWN")));
+ assertThat(attemptLatency).isAtLeast(0);
+ }
+ }
+
@SuppressWarnings("unchecked")
private static StreamObserver anyObserver(Class returnType) {
return (StreamObserver) any(returnType);
From 23e97cb308403b35fbe972b08048d0e59423e694 Mon Sep 17 00:00:00 2001
From: Mattie Fu
Date: Thu, 17 Dec 2020 13:02:35 -0500
Subject: [PATCH 11/38] fix: fix MetricTracerTest to rebase on head (#581)
---
.../bigtable/data/v2/stub/metrics/MetricsTracerTest.java | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
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 bc6166f79e..eb7bdaa998 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
@@ -338,11 +338,15 @@ public void testInvalidRequest() throws InterruptedException {
Thread.sleep(100);
// Verify that the latency is recorded with an error code (in this case UNKNOWN)
long attemptLatency =
- getAggregationValueAsLong(
+ StatsTestUtils.getAggregationValueAsLong(
+ localStats,
RpcViewConstants.BIGTABLE_ATTEMPT_LATENCY_VIEW,
ImmutableMap.of(
RpcMeasureConstants.BIGTABLE_OP, TagValue.create("Bigtable.MutateRows"),
- RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("UNKNOWN")));
+ RpcMeasureConstants.BIGTABLE_STATUS, TagValue.create("UNKNOWN")),
+ PROJECT_ID,
+ INSTANCE_ID,
+ APP_PROFILE_ID);
assertThat(attemptLatency).isAtLeast(0);
}
}
From 93353cfa3cb4bc30146f592bb1d6cdaac1fefebc Mon Sep 17 00:00:00 2001
From: Mattie Fu
Date: Thu, 17 Dec 2020 13:22:48 -0500
Subject: [PATCH 12/38] chore: move gfe metrics readme (#580)
---
.readme-partials.yml | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/.readme-partials.yml b/.readme-partials.yml
index 62730df47d..3f62c8db5c 100644
--- a/.readme-partials.yml
+++ b/.readme-partials.yml
@@ -215,12 +215,20 @@ custom_content: |
each client RPC, tagged by operation name and the attempt status. Under normal
circumstances, this will be identical to op_latency. However, when the client
receives transient errors, op_latency will be the sum of all attempt_latencies
- and the exponential delays
+ and the exponential delays.
* `cloud.google.com/java/bigtable/attempts_per_op`: A distribution of attempts that
each operation required, tagged by operation name and final operation status.
Under normal circumstances, this will be 1.
+ ### GFE metric views:
+ * `cloud.google.com/java/bigtable/gfe_latency`: A distribution of the latency
+ between Google's network receives an RPC and reads back the first byte of
+ the response.
+
+ * `cloud.google.com/java/bigtable/gfe_header_missing_count`: A counter of the
+ number of RPC responses received without the server-timing header, which
+ indicates that the request probably never reached Google's network.
By default, the functionality is disabled. For example to enable metrics using
[Google Stackdriver](https://cloud.google.com/monitoring/docs/):
@@ -276,6 +284,8 @@ custom_content: |
);
BigtableDataSettings.enableOpenCensusStats();
+ // Enable GFE metric views
+ BigtableDataSettings.enableGfeOpenCensusStats();
```
## Version Conflicts
From 4dd763839e1bd6f350f5afaee578aac180322107 Mon Sep 17 00:00:00 2001
From: Yoshi Automation Bot
Date: Thu, 17 Dec 2020 10:40:04 -0800
Subject: [PATCH 13/38] chore: regenerate README (#582)
This PR was generated using Autosynth. :rainbow:
Log from Synthtool
```
2020-12-17 18:27:00,044 synthtool [DEBUG] > Executing /root/.cache/synthtool/java-bigtable/.github/readme/synth.py.
On branch autosynth-readme
nothing to commit, working tree clean
2020-12-17 18:27:00,999 synthtool [DEBUG] > Wrote metadata to .github/readme/synth.metadata/synth.metadata.
```
Full log will be available here:
https://source.cloud.google.com/results/invocations/a685e710-846a-4969-a779-fee6f22b33bf/targets
- [ ] To automatically regenerate this PR, check this box.
---
.github/readme/synth.metadata/synth.metadata | 4 ++--
README.md | 10 ++++------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/.github/readme/synth.metadata/synth.metadata b/.github/readme/synth.metadata/synth.metadata
index adbe5a4a5d..328fb8e9d0 100644
--- a/.github/readme/synth.metadata/synth.metadata
+++ b/.github/readme/synth.metadata/synth.metadata
@@ -4,14 +4,14 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/java-bigtable.git",
- "sha": "fa8f5ef216fcbcd9f57343475f03a7353b0e9ddd"
+ "sha": "93353cfa3cb4bc30146f592bb1d6cdaac1fefebc"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "d73e8dea90af1b463a7f9d4a33244cf4612ada7c"
+ "sha": "41e998d5afdc2c2143a23c9b044b9931936f7318"
}
}
]
diff --git a/README.md b/README.md
index bf339adc4a..ad5778e884 100644
--- a/README.md
+++ b/README.md
@@ -303,15 +303,13 @@ metrics will be tagged with:
Under normal circumstances, this will be 1.
### GFE metric views:
-
* `cloud.google.com/java/bigtable/gfe_latency`: A distribution of the latency
- between Google's network receives an RPC and reads back the first byte of
- the response.
+between Google's network receives an RPC and reads back the first byte of
+the response.
* `cloud.google.com/java/bigtable/gfe_header_missing_count`: A counter of the
- number of RPC responses received without the server-timing header, which
- indicates that the request probably never reached Google's network.
-
+number of RPC responses received without the server-timing header, which
+indicates that the request probably never reached Google's network.
By default, the functionality is disabled. For example to enable metrics using
[Google Stackdriver](https://cloud.google.com/monitoring/docs/):
From a9001a88f338fc2acf6bc48927765f29819124ee Mon Sep 17 00:00:00 2001
From: Dmitry <58846611+dmitry-fa@users.noreply.github.com>
Date: Mon, 21 Dec 2020 23:08:39 +0300
Subject: [PATCH 14/38] docs: Expand hello world snippet to show how to access
specific cells (#516)
* samples: Expand hello world snippet to show how to access specific cells by family & qualifier
* samples: Expand hello world snippet to show how to access specific cells by family & qualifier
---
.../com/m/examples/bigtable/HelloWorld.java | 53 ++++++++++++++++---
.../m/examples/bigtable/HelloWorldTest.java | 25 ++++++---
2 files changed, 62 insertions(+), 16 deletions(-)
diff --git a/samples/snippets/src/main/java/com/m/examples/bigtable/HelloWorld.java b/samples/snippets/src/main/java/com/m/examples/bigtable/HelloWorld.java
index 36e1fc08f9..9668cdd80d 100644
--- a/samples/snippets/src/main/java/com/m/examples/bigtable/HelloWorld.java
+++ b/samples/snippets/src/main/java/com/m/examples/bigtable/HelloWorld.java
@@ -12,7 +12,7 @@
* 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.m.examples.bigtable;
@@ -29,6 +29,8 @@
import com.google.cloud.bigtable.data.v2.models.RowCell;
import com.google.cloud.bigtable.data.v2.models.RowMutation;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
// [END bigtable_hw_imports_veneer]
@@ -48,7 +50,8 @@
public class HelloWorld {
private static final String COLUMN_FAMILY = "cf1";
- private static final String COLUMN_QUALIFIER = "greeting";
+ private static final String COLUMN_QUALIFIER_GREETING = "greeting";
+ private static final String COLUMN_QUALIFIER_NAME = "name";
private static final String ROW_KEY_PREFIX = "rowKey";
private final String tableId;
private final BigtableDataClient dataClient;
@@ -94,8 +97,13 @@ public void run() throws Exception {
createTable();
writeToTable();
readSingleRow();
+ readSpecificCells();
readTable();
deleteTable();
+ close();
+ }
+
+ public void close() {
dataClient.close();
adminClient.close();
}
@@ -119,13 +127,15 @@ public void writeToTable() {
// [START bigtable_hw_write_rows_veneer]
try {
System.out.println("\nWriting some greetings to the table");
- String[] greetings = {"Hello World!", "Hello Bigtable!", "Hello Java!"};
- for (int i = 0; i < greetings.length; i++) {
+ String[] names = {"World", "Bigtable", "Java"};
+ for (int i = 0; i < names.length; i++) {
+ String greeting = "Hello " + names[i] + "!";
RowMutation rowMutation =
RowMutation.create(tableId, ROW_KEY_PREFIX + i)
- .setCell(COLUMN_FAMILY, COLUMN_QUALIFIER, greetings[i]);
+ .setCell(COLUMN_FAMILY, COLUMN_QUALIFIER_NAME, names[i])
+ .setCell(COLUMN_FAMILY, COLUMN_QUALIFIER_GREETING, greeting);
dataClient.mutateRow(rowMutation);
- System.out.println(greetings[i]);
+ System.out.println(greeting);
}
} catch (NotFoundException e) {
System.err.println("Failed to write to non-existent table: " + e.getMessage());
@@ -134,7 +144,7 @@ public void writeToTable() {
}
/** Demonstrates how to read a single row from a table. */
- public void readSingleRow() {
+ public Row readSingleRow() {
// [START bigtable_hw_get_by_key_veneer]
try {
System.out.println("\nReading a single row by row key");
@@ -145,29 +155,56 @@ public void readSingleRow() {
"Family: %s Qualifier: %s Value: %s%n",
cell.getFamily(), cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
}
+ return row;
+ } catch (NotFoundException e) {
+ System.err.println("Failed to read from a non-existent table: " + e.getMessage());
+ return null;
+ }
+ // [END bigtable_hw_get_by_key_veneer]
+ }
+
+ /** Demonstrates how to access specific cells by family and qualifier. */
+ public List readSpecificCells() {
+ // [START bigtable_hw_get_by_key_veneer]
+ try {
+ System.out.println("\nReading specific cells by family and qualifier");
+ Row row = dataClient.readRow(tableId, ROW_KEY_PREFIX + 0);
+ System.out.println("Row: " + row.getKey().toStringUtf8());
+ List cells = row.getCells(COLUMN_FAMILY, COLUMN_QUALIFIER_NAME);
+ for (RowCell cell : cells) {
+ System.out.printf(
+ "Family: %s Qualifier: %s Value: %s%n",
+ cell.getFamily(), cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
+ }
+ return cells;
} catch (NotFoundException e) {
System.err.println("Failed to read from a non-existent table: " + e.getMessage());
+ return null;
}
// [END bigtable_hw_get_by_key_veneer]
}
/** Demonstrates how to read an entire table. */
- public void readTable() {
+ public List readTable() {
// [START bigtable_hw_scan_all_veneer]
try {
System.out.println("\nReading the entire table");
Query query = Query.create(tableId);
ServerStream rowStream = dataClient.readRows(query);
+ List tableRows = new ArrayList<>();
for (Row r : rowStream) {
System.out.println("Row Key: " + r.getKey().toStringUtf8());
+ tableRows.add(r);
for (RowCell cell : r.getCells()) {
System.out.printf(
"Family: %s Qualifier: %s Value: %s%n",
cell.getFamily(), cell.getQualifier().toStringUtf8(), cell.getValue().toStringUtf8());
}
}
+ return tableRows;
} catch (NotFoundException e) {
System.err.println("Failed to read a non-existent table: " + e.getMessage());
+ return null;
}
// [END bigtable_hw_scan_all_veneer]
}
diff --git a/samples/snippets/src/test/java/com/m/examples/bigtable/HelloWorldTest.java b/samples/snippets/src/test/java/com/m/examples/bigtable/HelloWorldTest.java
index 985b37bbd7..6904161829 100644
--- a/samples/snippets/src/test/java/com/m/examples/bigtable/HelloWorldTest.java
+++ b/samples/snippets/src/test/java/com/m/examples/bigtable/HelloWorldTest.java
@@ -12,11 +12,13 @@
* 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.m.examples.bigtable;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient;
@@ -24,7 +26,6 @@
import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;
import com.google.cloud.bigtable.data.v2.BigtableDataClient;
import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
-import com.google.cloud.bigtable.data.v2.models.Row;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@@ -32,7 +33,6 @@
import java.util.regex.Pattern;
import org.junit.After;
import org.junit.AfterClass;
-import org.junit.AssumptionViolatedException;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -86,10 +86,11 @@ public void setup() throws IOException {
}
@After
- public void after() {
+ public void teardown() {
if (adminClient.exists(tableId)) {
adminClient.deleteTable(tableId);
}
+ helloWorld.close();
}
@Test
@@ -103,18 +104,26 @@ public void testCreateAndDeleteTable() throws IOException {
// Deletes a table.
testHelloWorld.deleteTable();
assertTrue(!adminClient.exists(testTable));
+ testHelloWorld.close();
}
@Test
public void testWriteToTable() {
// Writes to a table.
+ assertNull(dataClient.readRow(tableId, "rowKey0"));
helloWorld.writeToTable();
- Row row = dataClient.readRow(tableId, "rowKey0");
- assertNotNull(row);
+ assertNotNull(dataClient.readRow(tableId, "rowKey0"));
}
- // TODO: add test for helloWorld.readSingleRow()
- // TODO: add test for helloWorld.readTable()
+ @Test
+ public void testReads() {
+ assertEquals(0, helloWorld.readTable().size());
+ helloWorld.writeToTable();
+
+ assertEquals(2, helloWorld.readSingleRow().getCells().size());
+ assertEquals(1, helloWorld.readSpecificCells().size());
+ assertEquals(3, helloWorld.readTable().size());
+ }
@Test
public void testRunDoesNotFail() throws Exception {
From cfc33522fa37c906cf9a588c89181df9bdd2aabe Mon Sep 17 00:00:00 2001
From: Yoshi Automation Bot
Date: Tue, 29 Dec 2020 11:53:48 -0800
Subject: [PATCH 15/38] chore(java): remove formatter action
Source-Author: Jeff Ching
Source-Date: Tue Dec 29 10:50:17 2020 -0800
Source-Repo: googleapis/synthtool
Source-Sha: 6133907dbb3ddab204a17a15d5c53ec0aae9b033
Source-Link: https://github.com/googleapis/synthtool/commit/6133907dbb3ddab204a17a15d5c53ec0aae9b033
---
.github/workflows/formatting.yaml | 25 -------------------------
synth.metadata | 5 ++---
2 files changed, 2 insertions(+), 28 deletions(-)
delete mode 100644 .github/workflows/formatting.yaml
diff --git a/.github/workflows/formatting.yaml b/.github/workflows/formatting.yaml
deleted file mode 100644
index 6844407b4d..0000000000
--- a/.github/workflows/formatting.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-on:
- pull_request_target:
- types: [opened, synchronize]
- branches:
- - master
-name: format
-jobs:
- format-code:
- runs-on: ubuntu-latest
- env:
- ACCESS_TOKEN: ${{ secrets.YOSHI_CODE_BOT_TOKEN }}
- steps:
- - uses: actions/checkout@v2
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/setup-java@v1
- with:
- java-version: 11
- - run: "mvn com.coveo:fmt-maven-plugin:format"
- - uses: googleapis/code-suggester@v1
- with:
- command: review
- pull_number: ${{ github.event.pull_request.number }}
- git_dir: '.'
diff --git a/synth.metadata b/synth.metadata
index 61bebbe9cf..76de2e57e9 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,7 +4,7 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/java-bigtable.git",
- "sha": "27304431b86d38b6292651cf3ee6b463b13050fd"
+ "sha": "a9001a88f338fc2acf6bc48927765f29819124ee"
}
},
{
@@ -19,7 +19,7 @@
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "3f67ceece7e797a5736a25488aae35405649b90b"
+ "sha": "6133907dbb3ddab204a17a15d5c53ec0aae9b033"
}
}
],
@@ -57,7 +57,6 @@
".github/workflows/approve-readme.yaml",
".github/workflows/auto-release.yaml",
".github/workflows/ci.yaml",
- ".github/workflows/formatting.yaml",
".github/workflows/samples.yaml",
".kokoro/build.bat",
".kokoro/build.sh",
From 64bb85f620ec26a4d6f2bad2a4df3ffe7383be11 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Thu, 7 Jan 2021 16:35:54 +0100
Subject: [PATCH 16/38] chore(deps): update dependency
com.google.cloud:libraries-bom to v16.2.1 (#587)
---
samples/snippets/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 045cef761b..adaefc4374 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -30,7 +30,7 @@
com.google.cloud
libraries-bom
- 16.2.0
+ 16.2.1
pom
import
From e89f3e778bbe29a0c221a38bd1785e1a78e23b46 Mon Sep 17 00:00:00 2001
From: Yoshi Automation Bot
Date: Thu, 7 Jan 2021 07:50:03 -0800
Subject: [PATCH 17/38] chore: regenerate README (#588)
This PR was generated using Autosynth. :rainbow:
Log from Synthtool
```
2021-01-07 15:38:14,137 synthtool [DEBUG] > Executing /root/.cache/synthtool/java-bigtable/.github/readme/synth.py.
On branch autosynth-readme
nothing to commit, working tree clean
2021-01-07 15:38:15,077 synthtool [DEBUG] > Wrote metadata to .github/readme/synth.metadata/synth.metadata.
```
Full log will be available here:
https://source.cloud.google.com/results/invocations/07c27323-e217-46f5-bcf3-45e28b592a16/targets
- [ ] To automatically regenerate this PR, check this box.
---
.github/readme/synth.metadata/synth.metadata | 4 ++--
README.md | 11 +++++++++--
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/.github/readme/synth.metadata/synth.metadata b/.github/readme/synth.metadata/synth.metadata
index 328fb8e9d0..4f2698011e 100644
--- a/.github/readme/synth.metadata/synth.metadata
+++ b/.github/readme/synth.metadata/synth.metadata
@@ -4,14 +4,14 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/java-bigtable.git",
- "sha": "93353cfa3cb4bc30146f592bb1d6cdaac1fefebc"
+ "sha": "64bb85f620ec26a4d6f2bad2a4df3ffe7383be11"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "41e998d5afdc2c2143a23c9b044b9931936f7318"
+ "sha": "a3e990f3545dc8ccd384a75d20ce9cb185ca6a28"
}
}
]
diff --git a/README.md b/README.md
index ad5778e884..6d887ff8d9 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file
com.google.cloud
libraries-bom
- 16.2.0
+ 16.2.1
pom
import
@@ -43,10 +43,17 @@ If you are using Maven without BOM, add this to your dependencies:
```
-If you are using Gradle, add this to your dependencies
+If you are using Gradle 5.x or later, add this to your dependencies
+```Groovy
+implementation platform('com.google.cloud:libraries-bom:16.2.1')
+
+compile 'com.google.cloud:google-cloud-bigtable'
+```
+If you are using Gradle without BOM, add this to your dependencies
```Groovy
compile 'com.google.cloud:google-cloud-bigtable:1.19.2'
```
+
If you are using SBT, add this to your dependencies
```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-bigtable" % "1.19.2"
From 5035ad0db01a9247634137050698c30da29722a6 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Tue, 12 Jan 2021 20:44:48 +0100
Subject: [PATCH 18/38] deps: update dependency
com.google.cloud:google-cloud-shared-dependencies to v0.17.1 (#590)
---
google-cloud-bigtable-deps-bom/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml
index d2e36fe8a7..dc6fa1a2ca 100644
--- a/google-cloud-bigtable-deps-bom/pom.xml
+++ b/google-cloud-bigtable-deps-bom/pom.xml
@@ -79,7 +79,7 @@
com.google.cloud
google-cloud-shared-dependencies
- 0.17.0
+ 0.17.1
pom
import
From c58b73a7d70c8da1581ac06d77b5e362648a0868 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Thu, 14 Jan 2021 01:48:15 +0100
Subject: [PATCH 19/38] deps: update dependency
com.google.cloud:google-cloud-shared-dependencies to v0.18.0 (#592)
[](https://renovatebot.com)
This PR contains the following updates:
| Package | Update | Change |
|---|---|---|
| [com.google.cloud:google-cloud-shared-dependencies](https://togithub.com/googleapis/java-shared-dependencies) | minor | `0.17.1` -> `0.18.0` |
---
### Release Notes
googleapis/java-shared-dependencies
### [`v0.18.0`](https://togithub.com/googleapis/java-shared-dependencies/blob/master/CHANGELOG.md#0180-httpswwwgithubcomgoogleapisjava-shared-dependenciescompare0171v0180-2021-01-13)
[Compare Source](https://togithub.com/googleapis/java-shared-dependencies/compare/v0.17.1...v0.18.0)
##### Features
- add commons-codec to dependencyManagement ([#251](https://www.github.com/googleapis/java-shared-dependencies/issues/251)) ([4ee990d](https://www.github.com/googleapis/java-shared-dependencies/commit/4ee990d79c9207c81155f6ee9279308a2d4d0f9d))
##### Dependencies
- update dependency com.google.errorprone:error_prone_annotations to v2.5.0 ([#247](https://www.github.com/googleapis/java-shared-dependencies/issues/247)) ([37c0861](https://www.github.com/googleapis/java-shared-dependencies/commit/37c0861cfb89f13a0682c98067c633b13b30b827))
##### [0.17.1](https://www.github.com/googleapis/java-shared-dependencies/compare/0.17.0...v0.17.1) (2021-01-12)
##### Dependencies
- update dependency com.fasterxml.jackson:jackson-bom to v2.12.1 ([#245](https://www.github.com/googleapis/java-shared-dependencies/issues/245)) ([5ffc8a0](https://www.github.com/googleapis/java-shared-dependencies/commit/5ffc8a0d173ea0222ac9610ece0ac2aeb1d17f27))
---
### Renovate configuration
:date: **Schedule**: At any time (no schedule defined).
:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
:recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this PR, check this box
---
This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-bigtable).
---
google-cloud-bigtable-deps-bom/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml
index dc6fa1a2ca..817a701484 100644
--- a/google-cloud-bigtable-deps-bom/pom.xml
+++ b/google-cloud-bigtable-deps-bom/pom.xml
@@ -79,7 +79,7 @@
com.google.cloud
google-cloud-shared-dependencies
- 0.17.1
+ 0.18.0
pom
import
From dfa4da75e5ac81cc941177462326f7c38f18bacd Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Thu, 14 Jan 2021 17:07:37 +0100
Subject: [PATCH 20/38] deps: update dependency
com.google.errorprone:error_prone_annotations to v2.5.0 (#591)
---
google-cloud-bigtable-deps-bom/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml
index 817a701484..9564ce32ad 100644
--- a/google-cloud-bigtable-deps-bom/pom.xml
+++ b/google-cloud-bigtable-deps-bom/pom.xml
@@ -98,7 +98,7 @@
com.google.errorprone
error_prone_annotations
- 2.4.0
+ 2.5.0