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 +