diff --git a/google-cloud-bigtable-stats/pom.xml b/google-cloud-bigtable-stats/pom.xml
index a8633473cb..a1c4ddee99 100644
--- a/google-cloud-bigtable-stats/pom.xml
+++ b/google-cloud-bigtable-stats/pom.xml
@@ -16,6 +16,10 @@
2.10.4-SNAPSHOT
Experimental project to shade OpenCensus dependencies.
+
+ 3.3.6
+
+
@@ -25,10 +29,60 @@
pom
import
+
+ com.google.cloud
+ google-cloud-monitoring-bom
+ ${cloud.monitoring.version}
+ pom
+ import
+
+
+
+ io.opencensus
+ opencensus-api
+
+
+ io.opencensus
+ opencensus-exporter-stats-stackdriver
+
+
+ io.opencensus
+ opencensus-impl
+ runtime
+
+
+
+
+ com.google.cloud
+ google-cloud-monitoring
+
+
+
+ com.google.http-client
+ google-http-client-gson
+
+
+ com.google.http-client
+ google-http-client
+
+
+
+
+ com.google.api.grpc
+ proto-google-cloud-monitoring-v3
+
+
+ com.google.api.grpc
+ proto-google-common-protos
+
+
+ com.google.auth
+ google-auth-library-credentials
+
com.google.api
gax
@@ -48,21 +102,35 @@
com.google.api
api-common
-
- io.opencensus
- opencensus-api
+ com.google.api
+ gax-grpc
+
+
+ com.google.protobuf
+ protobuf-java
com.google.guava
guava
+
+ org.threeten
+ threetenbp
+
+
+ com.google.code.findbugs
+ jsr305
+
+
- io.opencensus
- opencensus-impl
+ com.google.http-client
+ google-http-client
runtime
+
+
com.google.truth
truth
@@ -73,6 +141,11 @@
junit
test
+
+ org.mockito
+ mockito-core
+ test
+
@@ -111,6 +184,17 @@
org.apache.maven.plugins
maven-dependency-plugin
3.3.0
+
+
+
+
+
+
+
+ io.opencensus:opencensus-exporter-metrics-util:*
+ io.opencensus:opencensus-exporter-stats-stackdriver:*
+
+
org.codehaus.mojo
@@ -121,6 +205,31 @@
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+ enforce-version-consistency
+
+ enforce
+
+
+
+
+
+
+
+
+ io.opencensus:*:[0.31.1]
+ io.opencensus:opencensus-proto:[0.2.0]
+
+
+
+
+
+
+
diff --git a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporter.java b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporter.java
new file mode 100644
index 0000000000..ad2e76867c
--- /dev/null
+++ b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.cloud.bigtable.stats;
+
+import com.google.api.MonitoredResource;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.monitoring.v3.ProjectName;
+import io.opencensus.exporter.metrics.util.MetricExporter;
+import io.opencensus.metrics.export.Metric;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+final class BigtableCreateTimeSeriesExporter extends MetricExporter {
+ private static final Logger logger =
+ Logger.getLogger(BigtableCreateTimeSeriesExporter.class.getName());
+ private final MetricServiceClient metricServiceClient;
+ private final MonitoredResource monitoredResource;
+ private final String clientId;
+
+ BigtableCreateTimeSeriesExporter(
+ MetricServiceClient metricServiceClient, MonitoredResource monitoredResource) {
+ this.metricServiceClient = metricServiceClient;
+ this.monitoredResource = monitoredResource;
+ this.clientId = BigtableStackdriverExportUtils.getDefaultTaskValue();
+ }
+
+ public void export(Collection metrics) {
+ Map> projectToTimeSeries = new HashMap<>();
+
+ for (Metric metric : metrics) {
+ // only export bigtable metrics
+ if (!metric.getMetricDescriptor().getName().contains("bigtable")) {
+ continue;
+ }
+
+ try {
+ projectToTimeSeries =
+ metric.getTimeSeriesList().stream()
+ .collect(
+ Collectors.groupingBy(
+ timeSeries ->
+ BigtableStackdriverExportUtils.getProjectId(
+ metric.getMetricDescriptor(), timeSeries),
+ Collectors.mapping(
+ timeSeries ->
+ BigtableStackdriverExportUtils.convertTimeSeries(
+ metric.getMetricDescriptor(),
+ timeSeries,
+ clientId,
+ monitoredResource),
+ Collectors.toList())));
+
+ for (Map.Entry> entry :
+ projectToTimeSeries.entrySet()) {
+ ProjectName projectName = ProjectName.of(entry.getKey());
+ CreateTimeSeriesRequest request =
+ CreateTimeSeriesRequest.newBuilder()
+ .setName(projectName.toString())
+ .addAllTimeSeries(entry.getValue())
+ .build();
+ this.metricServiceClient.createServiceTimeSeries(request);
+ }
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Exception thrown when exporting TimeSeries.", e);
+ }
+ }
+ }
+}
diff --git a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverExportUtils.java b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverExportUtils.java
new file mode 100644
index 0000000000..bdca097050
--- /dev/null
+++ b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverExportUtils.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.cloud.bigtable.stats;
+
+import com.google.api.Distribution.BucketOptions;
+import com.google.api.Distribution.BucketOptions.Explicit;
+import com.google.api.Metric;
+import com.google.api.MetricDescriptor.MetricKind;
+import com.google.api.MonitoredResource;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.monitoring.v3.TimeInterval;
+import com.google.monitoring.v3.TypedValue;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.LabelKey;
+import io.opencensus.metrics.LabelValue;
+import io.opencensus.metrics.export.Distribution;
+import io.opencensus.metrics.export.Distribution.Bucket;
+import io.opencensus.metrics.export.Distribution.BucketOptions.ExplicitOptions;
+import io.opencensus.metrics.export.MetricDescriptor;
+import io.opencensus.metrics.export.MetricDescriptor.Type;
+import io.opencensus.metrics.export.Point;
+import io.opencensus.metrics.export.Summary;
+import io.opencensus.metrics.export.TimeSeries;
+import io.opencensus.metrics.export.Value;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+class BigtableStackdriverExportUtils {
+
+ private static final Logger logger =
+ Logger.getLogger(BigtableStackdriverExportUtils.class.getName());
+
+ private static final Function typedValueDoubleFunction =
+ arg -> {
+ TypedValue.Builder builder = TypedValue.newBuilder();
+ builder.setDoubleValue(arg);
+ return builder.build();
+ };
+ private static final Function typedValueLongFunction =
+ arg -> {
+ TypedValue.Builder builder = TypedValue.newBuilder();
+ builder.setInt64Value(arg);
+ return builder.build();
+ };
+ private static final Function typedValueDistributionFunction =
+ arg -> {
+ TypedValue.Builder builder = TypedValue.newBuilder();
+ return builder
+ .setDistributionValue(BigtableStackdriverExportUtils.createDistribution(arg))
+ .build();
+ };
+ private static final Function typedValueSummaryFunction =
+ arg -> {
+ TypedValue.Builder builder = TypedValue.newBuilder();
+ return builder.build();
+ };
+ private static final Function bucketOptionsExplicitFunction =
+ arg -> {
+ BucketOptions.Builder builder = BucketOptions.newBuilder();
+ Explicit.Builder explicitBuilder = Explicit.newBuilder();
+ explicitBuilder.addBounds(0.0D);
+ explicitBuilder.addAllBounds(arg.getBucketBoundaries());
+ builder.setExplicitBuckets(explicitBuilder.build());
+ return builder.build();
+ };
+
+ // promote the following metric labels to monitored resource labels
+ private static final Set PROMOTED_RESOURCE_LABELS =
+ ImmutableSet.of(
+ BuiltinMeasureConstants.PROJECT_ID.getName(),
+ BuiltinMeasureConstants.INSTANCE_ID.getName(),
+ BuiltinMeasureConstants.CLUSTER.getName(),
+ BuiltinMeasureConstants.ZONE.getName(),
+ BuiltinMeasureConstants.TABLE.getName());
+
+ private static final LabelKey CLIENT_UID_LABEL_KEY =
+ LabelKey.create(BuiltinMeasureConstants.CLIENT_UID.getName(), "client uid");
+
+ static com.google.monitoring.v3.TimeSeries convertTimeSeries(
+ MetricDescriptor metricDescriptor,
+ TimeSeries timeSeries,
+ String clientId,
+ MonitoredResource monitoredResource) {
+ String metricName = metricDescriptor.getName();
+ List labelKeys = metricDescriptor.getLabelKeys();
+ Type metricType = metricDescriptor.getType();
+
+ MonitoredResource.Builder monitoredResourceBuilder = monitoredResource.toBuilder();
+
+ List metricTagKeys = new ArrayList<>();
+ List metricTagValues = new ArrayList<>();
+
+ List labelValues = timeSeries.getLabelValues();
+ for (int i = 0; i < labelValues.size(); i++) {
+ // If the label is defined in the monitored resource, convert it to
+ // a monitored resource label. Otherwise, keep it as a metric label.
+ if (PROMOTED_RESOURCE_LABELS.contains(labelKeys.get(i).getKey())) {
+ monitoredResourceBuilder.putLabels(
+ labelKeys.get(i).getKey(), labelValues.get(i).getValue());
+ } else {
+ metricTagKeys.add(labelKeys.get(i));
+ metricTagValues.add(labelValues.get(i));
+ }
+ }
+ metricTagKeys.add(CLIENT_UID_LABEL_KEY);
+ metricTagValues.add(LabelValue.create(clientId));
+
+ com.google.monitoring.v3.TimeSeries.Builder builder =
+ com.google.monitoring.v3.TimeSeries.newBuilder();
+ builder.setResource(monitoredResourceBuilder.build());
+ builder.setMetric(createMetric(metricName, metricTagKeys, metricTagValues));
+ builder.setMetricKind(createMetricKind(metricType));
+ builder.setValueType(createValueType(metricType));
+ Timestamp startTimeStamp = timeSeries.getStartTimestamp();
+ for (Point point : timeSeries.getPoints()) {
+ builder.addPoints(createPoint(point, startTimeStamp));
+ }
+ return builder.build();
+ }
+
+ static String getProjectId(MetricDescriptor metricDescriptor, TimeSeries timeSeries) {
+ List labelKeys = metricDescriptor.getLabelKeys();
+ List labelValues = timeSeries.getLabelValues();
+ for (int i = 0; i < labelKeys.size(); i++) {
+ if (labelKeys.get(i).getKey().equals(BuiltinMeasureConstants.PROJECT_ID.getName())) {
+ return labelValues.get(i).getValue();
+ }
+ }
+ throw new IllegalStateException("Can't find project id for the current timeseries");
+ }
+
+ static String getDefaultTaskValue() {
+ // Something like '@'
+ final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
+ // If not the expected format then generate a random number.
+ if (jvmName.indexOf('@') < 1) {
+ String hostname = "localhost";
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ logger.log(Level.INFO, "Unable to get the hostname.", e);
+ }
+ // Generate a random number and use the same format "random_number@hostname".
+ return "java-" + new SecureRandom().nextInt() + "@" + hostname;
+ }
+ return "java-" + jvmName;
+ }
+
+ private static MetricKind createMetricKind(Type type) {
+ switch (type) {
+ case CUMULATIVE_DOUBLE:
+ case CUMULATIVE_INT64:
+ case CUMULATIVE_DISTRIBUTION:
+ return MetricKind.CUMULATIVE;
+ default:
+ return MetricKind.UNRECOGNIZED;
+ }
+ }
+
+ private static com.google.api.MetricDescriptor.ValueType createValueType(Type type) {
+ switch (type) {
+ case CUMULATIVE_DOUBLE:
+ return com.google.api.MetricDescriptor.ValueType.DOUBLE;
+ case CUMULATIVE_INT64:
+ return com.google.api.MetricDescriptor.ValueType.INT64;
+ case CUMULATIVE_DISTRIBUTION:
+ return com.google.api.MetricDescriptor.ValueType.DISTRIBUTION;
+ default:
+ return com.google.api.MetricDescriptor.ValueType.UNRECOGNIZED;
+ }
+ }
+
+ private static Metric createMetric(
+ String metricName, List labelKeys, List labelValues) {
+ Metric.Builder builder = Metric.newBuilder();
+ builder.setType(metricName);
+ Map stringTagMap = Maps.newHashMap();
+
+ for (int i = 0; i < labelValues.size(); ++i) {
+ String value = labelValues.get(i).getValue();
+ if (value != null) {
+ stringTagMap.put(labelKeys.get(i).getKey(), value);
+ }
+ }
+
+ builder.putAllLabels(stringTagMap);
+ return builder.build();
+ }
+
+ private static com.google.monitoring.v3.Point createPoint(Point point, Timestamp startTimestamp) {
+ com.google.monitoring.v3.TimeInterval.Builder timeIntervalBuilder = TimeInterval.newBuilder();
+ timeIntervalBuilder.setStartTime(convertTimestamp(startTimestamp));
+ timeIntervalBuilder.setEndTime(convertTimestamp(point.getTimestamp()));
+
+ com.google.monitoring.v3.Point.Builder builder = com.google.monitoring.v3.Point.newBuilder();
+ builder.setInterval(timeIntervalBuilder.build());
+ builder.setValue(createTypedValue(point.getValue()));
+ return builder.build();
+ }
+
+ private static TypedValue createTypedValue(Value value) {
+ return value.match(
+ typedValueDoubleFunction,
+ typedValueLongFunction,
+ typedValueDistributionFunction,
+ typedValueSummaryFunction,
+ Functions.throwIllegalArgumentException());
+ }
+
+ private static com.google.api.Distribution createDistribution(Distribution distribution) {
+ com.google.api.Distribution.Builder builder =
+ com.google.api.Distribution.newBuilder()
+ .setBucketOptions(createBucketOptions(distribution.getBucketOptions()))
+ .setCount(distribution.getCount())
+ .setMean(
+ distribution.getCount() == 0L
+ ? 0.0D
+ : distribution.getSum() / (double) distribution.getCount())
+ .setSumOfSquaredDeviation(distribution.getSumOfSquaredDeviations());
+ setBucketCounts(distribution.getBuckets(), builder);
+ return builder.build();
+ }
+
+ private static BucketOptions createBucketOptions(
+ @Nullable Distribution.BucketOptions bucketOptions) {
+ com.google.api.Distribution.BucketOptions.Builder builder = BucketOptions.newBuilder();
+ return bucketOptions == null
+ ? builder.build()
+ : bucketOptions.match(
+ bucketOptionsExplicitFunction, Functions.throwIllegalArgumentException());
+ }
+
+ private static void setBucketCounts(
+ List buckets, com.google.api.Distribution.Builder builder) {
+ builder.addBucketCounts(0L);
+
+ for (Bucket bucket : buckets) {
+ builder.addBucketCounts(bucket.getCount());
+ }
+ }
+
+ private static com.google.protobuf.Timestamp convertTimestamp(Timestamp censusTimestamp) {
+ return censusTimestamp.getSeconds() < 0L
+ ? com.google.protobuf.Timestamp.newBuilder().build()
+ : com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(censusTimestamp.getSeconds())
+ .setNanos(censusTimestamp.getNanos())
+ .build();
+ }
+}
diff --git a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverStatsExporter.java b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverStatsExporter.java
new file mode 100644
index 0000000000..8896e52d89
--- /dev/null
+++ b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BigtableStackdriverStatsExporter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.cloud.bigtable.stats;
+
+import com.google.api.MonitoredResource;
+import com.google.api.core.InternalApi;
+import com.google.api.gax.core.FixedCredentialsProvider;
+import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
+import com.google.auth.Credentials;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.cloud.monitoring.v3.MetricServiceSettings;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.opencensus.common.Duration;
+import io.opencensus.exporter.metrics.util.IntervalMetricReader;
+import io.opencensus.exporter.metrics.util.MetricReader;
+import io.opencensus.metrics.Metrics;
+import java.io.IOException;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+@InternalApi
+public class BigtableStackdriverStatsExporter {
+ static final Object lock = new Object();
+
+ @Nullable
+ @GuardedBy("lock")
+ private static BigtableStackdriverStatsExporter instance = null;
+
+ // Default export interval is 1 minute
+ private static final Duration EXPORT_INTERVAL = Duration.create(60, 0);
+ private static final String RESOURCE_TYPE = "bigtable_client_raw";
+
+ private final IntervalMetricReader intervalMetricReader;
+
+ private BigtableStackdriverStatsExporter(
+ MetricServiceClient metricServiceClient,
+ Duration exportInterval,
+ MonitoredResource monitoredResource) {
+ IntervalMetricReader.Options.Builder intervalMetricReaderOptionsBuilder =
+ IntervalMetricReader.Options.builder();
+ intervalMetricReaderOptionsBuilder.setExportInterval(exportInterval);
+ this.intervalMetricReader =
+ IntervalMetricReader.create(
+ new BigtableCreateTimeSeriesExporter(metricServiceClient, monitoredResource),
+ MetricReader.create(
+ MetricReader.Options.builder()
+ .setMetricProducerManager(
+ Metrics.getExportComponent().getMetricProducerManager())
+ .build()),
+ intervalMetricReaderOptionsBuilder.build());
+ }
+
+ public static void register(Credentials credentials) throws IOException {
+ synchronized (lock) {
+ Preconditions.checkState(
+ instance == null, "Bigtable Stackdriver stats exporter is already created");
+ // Default timeout for creating a client is 1 minute
+ MetricServiceClient client = createMetricServiceClient(credentials, Duration.create(60L, 0));
+ MonitoredResource resourceType =
+ MonitoredResource.newBuilder().setType(RESOURCE_TYPE).build();
+ instance = new BigtableStackdriverStatsExporter(client, EXPORT_INTERVAL, resourceType);
+ }
+ }
+
+ @GuardedBy("lock")
+ @VisibleForTesting
+ static MetricServiceClient createMetricServiceClient(Credentials credentials, Duration deadline)
+ throws IOException {
+ MetricServiceSettings.Builder settingsBuilder =
+ MetricServiceSettings.newBuilder()
+ .setTransportChannelProvider(InstantiatingGrpcChannelProvider.newBuilder().build());
+ settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(credentials));
+
+ org.threeten.bp.Duration stackdriverDuration =
+ org.threeten.bp.Duration.ofMillis(deadline.toMillis());
+ settingsBuilder.createTimeSeriesSettings().setSimpleTimeoutNoRetries(stackdriverDuration);
+ return MetricServiceClient.create(settingsBuilder.build());
+ }
+}
diff --git a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinMeasureConstants.java b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinMeasureConstants.java
index 06ca674ffc..2f51204d4b 100644
--- a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinMeasureConstants.java
+++ b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinMeasureConstants.java
@@ -23,12 +23,11 @@
class BuiltinMeasureConstants {
// Monitored resource TagKeys
static final TagKey PROJECT_ID = TagKey.create("project_id");
- static final TagKey INSTANCE_ID = TagKey.create("instance_id");
+ static final TagKey INSTANCE_ID = TagKey.create("instance");
static final TagKey CLUSTER = TagKey.create("cluster");
static final TagKey TABLE = TagKey.create("table");
static final TagKey ZONE = TagKey.create("zone");
- // Placeholder TagKey to be used in Stackdriver exporter
- static final TagKey CLIENT_ID = TagKey.create("client_id");
+ static final TagKey CLIENT_UID = TagKey.create("client_uid");
// Metrics TagKeys
static final TagKey APP_PROFILE = TagKey.create("app_profile");
diff --git a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinViewConstants.java b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinViewConstants.java
index beceeeab83..7c9dc34d78 100644
--- a/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinViewConstants.java
+++ b/google-cloud-bigtable-stats/src/main/java/com/google/cloud/bigtable/stats/BuiltinViewConstants.java
@@ -59,7 +59,7 @@ class BuiltinViewConstants {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 30.0, 40.0, 50.0,
100.0)));
- private static final Aggregation AGGREGATION_ERROR_COUNT = Sum.create();
+ private static final Aggregation AGGREGATION_COUNT = Sum.create();
static final View OPERATION_LATENCIES_VIEW =
View.create(
@@ -102,7 +102,7 @@ class BuiltinViewConstants {
View.Name.create("bigtable.googleapis.com/internal/client/retry_count"),
"The number of additional RPCs sent after the initial attempt.",
RETRY_COUNT,
- AGGREGATION_RETRY_COUNT,
+ AGGREGATION_COUNT,
ImmutableList.of(
PROJECT_ID,
INSTANCE_ID,
@@ -154,7 +154,7 @@ class BuiltinViewConstants {
View.Name.create("bigtable.googleapis.com/internal/client/connectivity_error_count"),
"Number of requests that failed to reach the Google datacenter. (Requests without google response headers).",
CONNECTIVITY_ERROR_COUNT,
- AGGREGATION_ERROR_COUNT,
+ AGGREGATION_COUNT,
ImmutableList.of(
PROJECT_ID,
INSTANCE_ID,
@@ -173,15 +173,7 @@ class BuiltinViewConstants {
APPLICATION_LATENCIES,
AGGREGATION_WITH_MILLIS_HISTOGRAM,
ImmutableList.of(
- PROJECT_ID,
- INSTANCE_ID,
- APP_PROFILE,
- METHOD,
- STREAMING,
- CLIENT_NAME,
- CLUSTER,
- ZONE,
- TABLE));
+ PROJECT_ID, INSTANCE_ID, APP_PROFILE, METHOD, CLIENT_NAME, CLUSTER, ZONE, TABLE));
static final View THROTTLING_LATENCIES_VIEW =
View.create(
diff --git a/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporterTest.java b/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporterTest.java
new file mode 100644
index 0000000000..26654c09af
--- /dev/null
+++ b/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/BigtableCreateTimeSeriesExporterTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2021 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.api.MonitoredResource;
+import com.google.api.gax.rpc.UnaryCallable;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.cloud.monitoring.v3.stub.MetricServiceStub;
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.protobuf.Empty;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.LabelKey;
+import io.opencensus.metrics.LabelValue;
+import io.opencensus.metrics.export.Metric;
+import io.opencensus.metrics.export.MetricDescriptor;
+import io.opencensus.metrics.export.Point;
+import io.opencensus.metrics.export.TimeSeries;
+import io.opencensus.metrics.export.Value;
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class BigtableCreateTimeSeriesExporterTest {
+
+ private static final String projectId = "fake-project";
+ private static final String instanceId = "fake-instance";
+ private static final String appProfileId = "default";
+ private static final String tableId = "fake-table";
+ private static final String zone = "us-east-1";
+ private static final String cluster = "cluster-1";
+
+ @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock private MetricServiceStub mockMetricServiceStub;
+ private MetricServiceClient fakeMetricServiceClient;
+ private BigtableCreateTimeSeriesExporter exporter;
+
+ @Before
+ public void setUp() {
+
+ fakeMetricServiceClient = new FakeMetricServiceClient(mockMetricServiceStub);
+
+ exporter =
+ new BigtableCreateTimeSeriesExporter(
+ fakeMetricServiceClient,
+ MonitoredResource.newBuilder().setType("bigtable-table").build());
+ }
+
+ @After
+ public void tearDown() {}
+
+ @Test
+ public void testTimeSeries() {
+ ArgumentCaptor argumentCaptor =
+ ArgumentCaptor.forClass(CreateTimeSeriesRequest.class);
+
+ UnaryCallable mockCallable = mock(UnaryCallable.class);
+ when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable);
+ when(mockCallable.call(argumentCaptor.capture())).thenReturn(Empty.getDefaultInstance());
+
+ double fakeValue = 10.0;
+ Metric fakeMetric =
+ Metric.create(
+ MetricDescriptor.create(
+ "bigtable/test",
+ "descritpion",
+ "ms",
+ MetricDescriptor.Type.CUMULATIVE_DOUBLE,
+ Arrays.asList(
+ LabelKey.create(BuiltinMeasureConstants.PROJECT_ID.getName(), ""),
+ LabelKey.create(BuiltinMeasureConstants.INSTANCE_ID.getName(), ""),
+ LabelKey.create(BuiltinMeasureConstants.TABLE.getName(), ""),
+ LabelKey.create(BuiltinMeasureConstants.CLUSTER.getName(), ""),
+ LabelKey.create(BuiltinMeasureConstants.ZONE.getName(), ""),
+ LabelKey.create(BuiltinMeasureConstants.APP_PROFILE.getName(), ""))),
+ Arrays.asList(
+ TimeSeries.create(
+ Arrays.asList(
+ LabelValue.create(projectId),
+ LabelValue.create(instanceId),
+ LabelValue.create(tableId),
+ LabelValue.create(cluster),
+ LabelValue.create(zone),
+ LabelValue.create(appProfileId)),
+ Arrays.asList(
+ Point.create(
+ Value.doubleValue(fakeValue),
+ Timestamp.fromMillis(System.currentTimeMillis()))),
+ Timestamp.fromMillis(System.currentTimeMillis()))));
+
+ exporter.export(Arrays.asList(fakeMetric));
+
+ CreateTimeSeriesRequest request = argumentCaptor.getValue();
+
+ assertThat(request.getTimeSeriesList()).hasSize(1);
+
+ com.google.monitoring.v3.TimeSeries timeSeries = request.getTimeSeriesList().get(0);
+
+ assertThat(timeSeries.getResource().getLabelsMap())
+ .containsExactly(
+ BuiltinMeasureConstants.PROJECT_ID.getName(), projectId,
+ BuiltinMeasureConstants.INSTANCE_ID.getName(), instanceId,
+ BuiltinMeasureConstants.TABLE.getName(), tableId,
+ BuiltinMeasureConstants.CLUSTER.getName(), cluster,
+ BuiltinMeasureConstants.ZONE.getName(), zone);
+
+ assertThat(timeSeries.getMetric().getLabelsMap()).hasSize(2);
+ assertThat(timeSeries.getMetric().getLabelsMap())
+ .containsAtLeast(BuiltinMeasureConstants.APP_PROFILE.getName(), appProfileId);
+ assertThat(timeSeries.getMetric().getLabelsMap())
+ .containsKey(BuiltinMeasureConstants.CLIENT_UID.getName());
+
+ assertThat(timeSeries.getPoints(0).getValue().getDoubleValue()).isEqualTo(fakeValue);
+ }
+
+ private class FakeMetricServiceClient extends MetricServiceClient {
+
+ protected FakeMetricServiceClient(MetricServiceStub stub) {
+ super(stub);
+ }
+ }
+}
diff --git a/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/ITBuiltinViewConstantsTest.java b/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/ITBuiltinViewConstantsTest.java
index 9b486f919f..929ee85f48 100644
--- a/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/ITBuiltinViewConstantsTest.java
+++ b/google-cloud-bigtable-stats/src/test/java/com/google/cloud/bigtable/stats/ITBuiltinViewConstantsTest.java
@@ -32,7 +32,7 @@ public void testBasicTagsExistForAllViews() {
assertWithMessage(view + " should have all basic tags")
.that(viewToTagMap.get(view))
.containsAtLeast(
- "project_id", "instance_id", "app_profile", "method", "zone", "cluster", "table");
+ "project_id", "instance", "app_profile", "method", "zone", "cluster", "table");
}
}
}
diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml
index 9d4e00c00d..d565f746de 100644
--- a/google-cloud-bigtable/pom.xml
+++ b/google-cloud-bigtable/pom.xml
@@ -64,6 +64,13 @@
com.google.cloud
google-cloud-bigtable-stats
+
+
+
+ io.opencensus
+ *
+
+
@@ -149,6 +156,17 @@
grpc-alts
runtime
+
+
+ com.google.http-client
+ google-http-client
+ runtime
+
+
+ com.google.http-client
+ google-http-client-gson
+ runtime
+