From 08cb1b89b7e381ba014119e768e5f0bd7e5acb28 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Fri, 26 May 2023 16:34:08 -0400 Subject: [PATCH 01/18] feat: migrate exporter to OTEL --- google-cloud-bigtable/pom.xml | 40 +++ .../BigtableCloudMonitoringExporter.java | 125 ++++++++++ .../stub/metrics/BigtableExporterUtils.java | 234 ++++++++++++++++++ .../metrics/BuiltinMetricsAttributes.java | 34 +++ .../BigtableCloudMonitoringExporterTest.java | 217 ++++++++++++++++ 5 files changed, 650 insertions(+) create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml index b293e75554..90d15cdacd 100644 --- a/google-cloud-bigtable/pom.xml +++ b/google-cloud-bigtable/pom.xml @@ -40,6 +40,7 @@ 1.55.1 3.23.2 + 1.26.0 @@ -332,6 +333,45 @@ mockito-core test + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + + + io.opentelemetry + opentelemetry-sdk-metrics + ${opentelemetry.version} + + + io.opentelemetry + opentelemetry-sdk-common + ${opentelemetry.version} + + + com.google.cloud + google-cloud-monitoring + + + + com.google.http-client + google-http-client-gson + + + com.google.http-client + google-http-client + + + + io.perfmark + perfmark-api + + + + + com.google.api.grpc + proto-google-cloud-monitoring-v3 + diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java new file mode 100644 index 0000000000..df9765fe3f --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -0,0 +1,125 @@ +/* + * Copyright 2023 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.MonitoredResource; +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.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.ProjectName; +import com.google.monitoring.v3.TimeSeries; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import org.threeten.bp.Duration; + +final class BigtableCloudMonitoringExporter implements MetricExporter { + + private static final Logger logger = + Logger.getLogger(BigtableCloudMonitoringExporter.class.getName()); + private final MetricServiceClient client; + private final String taskId; + private final MonitoredResource monitoredResource; + + private static final String RESOURCE_TYPE = "bigtable_client_raw"; + + BigtableCloudMonitoringExporter(Credentials credentials) throws Exception { + MetricServiceSettings.Builder settingsBuilder = + MetricServiceSettings.newBuilder() + .setTransportChannelProvider(InstantiatingGrpcChannelProvider.newBuilder().build()); + settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); + + org.threeten.bp.Duration timeout = Duration.ofMinutes(1); + settingsBuilder.createServiceTimeSeriesSettings().setSimpleTimeoutNoRetries(timeout); + this.client = MetricServiceClient.create(settingsBuilder.build()); + this.taskId = BigtableExporterUtils.getDefaultTaskValue(); + this.monitoredResource = MonitoredResource.newBuilder().setType(RESOURCE_TYPE).build(); + } + + @VisibleForTesting + BigtableCloudMonitoringExporter( + MetricServiceClient client, MonitoredResource monitoredResource, String taskId) { + this.client = client; + this.monitoredResource = monitoredResource; + this.taskId = taskId; + } + + @Override + public CompletableResultCode export(Collection collection) { + Map> projectToTimeSeries; + + for (MetricData metricData : collection) { + projectToTimeSeries = + metricData.getData().getPoints().stream() + .collect( + Collectors.groupingBy( + BigtableExporterUtils::getProjectId, + Collectors.mapping( + pointData -> + BigtableExporterUtils.convertPointToTimeSeries( + metricData, pointData, taskId, 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(); + + try { + this.client.createServiceTimeSeries(request); + } catch (Throwable e) { + logger.log( + Level.WARNING, + "Exception thrown when exporting TimeSeries for projectName=" + + projectName.getProject(), + e); + } + } + } + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + client.shutdown(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java new file mode 100644 index 0000000000..94f926eb29 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -0,0 +1,234 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.data.v2.stub.metrics; + +import static com.google.api.Distribution.BucketOptions; +import static com.google.api.Distribution.BucketOptions.Explicit; +import static com.google.api.MetricDescriptor.MetricKind; +import static com.google.api.MetricDescriptor.MetricKind.CUMULATIVE; +import static com.google.api.MetricDescriptor.MetricKind.GAUGE; +import static com.google.api.MetricDescriptor.MetricKind.UNRECOGNIZED; +import static com.google.api.MetricDescriptor.ValueType; +import static com.google.api.MetricDescriptor.ValueType.DISTRIBUTION; +import static com.google.api.MetricDescriptor.ValueType.DOUBLE; +import static com.google.api.MetricDescriptor.ValueType.INT64; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.CLIENT_UID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.CLUSTER_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.INSTANCE_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.PROJECT_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.TABLE_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.ZONE_ID; + +import com.google.api.Distribution; +import com.google.api.Metric; +import com.google.api.MonitoredResource; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.monitoring.v3.Point; +import com.google.monitoring.v3.TimeInterval; +import com.google.monitoring.v3.TimeSeries; +import com.google.monitoring.v3.TypedValue; +import com.google.protobuf.Timestamp; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.DoublePointData; +import io.opentelemetry.sdk.metrics.data.HistogramData; +import io.opentelemetry.sdk.metrics.data.HistogramPointData; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.opentelemetry.sdk.metrics.data.PointData; +import io.opentelemetry.sdk.metrics.data.SumData; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +class BigtableExporterUtils { + + private static final Logger logger = Logger.getLogger(BigtableExporterUtils.class.getName()); + private static final long NANO_PER_SECOND = (long) 1e9; + + 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-" + UUID.randomUUID() + jvmName; + } + + private static final Set> PROMOTED_RESOURCE_LABELS = + ImmutableSet.of(PROJECT_ID, INSTANCE_ID, TABLE_ID, CLUSTER_ID, ZONE_ID); + + static TimeSeries convertPointToTimeSeries( + MetricData metricData, + PointData pointData, + String taskId, + MonitoredResource monitoredResource) { + Map, Object> attributes = pointData.getAttributes().asMap(); + MonitoredResource.Builder monitoredResourceBuilder = monitoredResource.toBuilder(); + ImmutableMap.Builder metricLabels = new ImmutableMap.Builder<>(); + for (AttributeKey attributeKey : attributes.keySet()) { + if (PROMOTED_RESOURCE_LABELS.contains(attributeKey)) { + monitoredResourceBuilder.putLabels( + attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); + } else { + if (attributes.get(attributeKey) != null) { + metricLabels.put(attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); + } + } + } + metricLabels.put(CLIENT_UID.getKey(), taskId); + + TimeSeries.Builder builder = + TimeSeries.newBuilder() + .setResource(monitoredResourceBuilder.build()) + .setMetricKind(convertMetricKind(metricData)) + .setMetric( + Metric.newBuilder() + .setType(metricData.getName()) + .putAllLabels(metricLabels.build()) + .build()) + .setValueType(convertValueType(metricData.getType())); + + TimeInterval timeInterval = + TimeInterval.newBuilder() + .setStartTime(convertTimestamp(pointData.getStartEpochNanos())) + .setEndTime(convertTimestamp(pointData.getEpochNanos())) + .build(); + + builder.addPoints(createPoint(metricData.getType(), pointData, timeInterval)); + + return builder.build(); + } + + static String getProjectId(PointData pointData) { + return pointData.getAttributes().get(PROJECT_ID); + } + + private static MetricKind convertMetricKind(MetricData metricData) { + switch (metricData.getType()) { + case HISTOGRAM: + case EXPONENTIAL_HISTOGRAM: + return convertHistogramDataType(metricData.getHistogramData()); + case LONG_GAUGE: + case DOUBLE_GAUGE: + return GAUGE; + case LONG_SUM: + return convertSumDataType(metricData.getLongSumData()); + case DOUBLE_SUM: + return convertSumDataType(metricData.getDoubleSumData()); + default: + return UNRECOGNIZED; + } + } + + private static MetricKind convertHistogramDataType(HistogramData histogramData) { + if (histogramData.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) { + return CUMULATIVE; + } + return UNRECOGNIZED; + } + + private static MetricKind convertSumDataType(SumData sum) { + if (!sum.isMonotonic()) { + return GAUGE; + } + if (sum.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) { + return CUMULATIVE; + } + return UNRECOGNIZED; + } + + private static ValueType convertValueType(MetricDataType metricDataType) { + switch (metricDataType) { + case LONG_GAUGE: + case LONG_SUM: + return INT64; + case DOUBLE_GAUGE: + case DOUBLE_SUM: + return DOUBLE; + case HISTOGRAM: + case EXPONENTIAL_HISTOGRAM: + return DISTRIBUTION; + default: + return ValueType.UNRECOGNIZED; + } + } + + private static Timestamp convertTimestamp(long epochNanos) { + return Timestamp.newBuilder() + .setSeconds(epochNanos / NANO_PER_SECOND) + .setNanos((int) (epochNanos % NANO_PER_SECOND)) + .build(); + } + + private static Point createPoint( + MetricDataType type, PointData pointData, TimeInterval timeInterval) { + Point.Builder builder = Point.newBuilder().setInterval(timeInterval); + switch (type) { + case HISTOGRAM: + case EXPONENTIAL_HISTOGRAM: + return builder + .setValue( + TypedValue.newBuilder() + .setDistributionValue(convertHistogramData((HistogramPointData) pointData)) + .build()) + .build(); + case DOUBLE_GAUGE: + case DOUBLE_SUM: + return builder + .setValue( + TypedValue.newBuilder() + .setDoubleValue(((DoublePointData) pointData).getValue()) + .build()) + .build(); + case LONG_GAUGE: + case LONG_SUM: + return builder + .setValue(TypedValue.newBuilder().setInt64Value(((LongPointData) pointData).getValue())) + .build(); + default: + logger.log(Level.WARNING, "unsupported metric type"); + return builder.build(); + } + } + + private static Distribution convertHistogramData(HistogramPointData pointData) { + return Distribution.newBuilder() + .setCount(pointData.getCount()) + .setMean(pointData.getCount() == 0L ? 0.0D : pointData.getSum() / pointData.getCount()) + .setBucketOptions( + BucketOptions.newBuilder() + .setExplicitBuckets(Explicit.newBuilder().addAllBounds(pointData.getBoundaries()))) + .addAllBucketCounts(pointData.getCounts()) + .build(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java new file mode 100644 index 0000000000..070544cbd8 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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 io.opentelemetry.api.common.AttributeKey; + +class BuiltinMetricsAttributes { + + public static final AttributeKey PROJECT_ID = AttributeKey.stringKey("project_id"); + public static final AttributeKey INSTANCE_ID = AttributeKey.stringKey("instance"); + public static final AttributeKey TABLE_ID = AttributeKey.stringKey("table"); + public static final AttributeKey CLUSTER_ID = AttributeKey.stringKey("cluster"); + public static final AttributeKey ZONE_ID = AttributeKey.stringKey("zone"); + static final AttributeKey CLIENT_UID = AttributeKey.stringKey("client_uid"); + + static final AttributeKey APP_PROFILE = AttributeKey.stringKey("app_profile"); + static final AttributeKey STREAMING = AttributeKey.booleanKey("streaming"); + static final AttributeKey METHOD = AttributeKey.stringKey("method"); + static final AttributeKey STATUS = AttributeKey.stringKey("status"); + static final AttributeKey CLIENT_NAME = AttributeKey.stringKey("client_name"); +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java new file mode 100644 index 0000000000..16a4e64778 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java @@ -0,0 +1,217 @@ +/* + * Copyright 2023 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.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.APP_PROFILE; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.CLIENT_UID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.CLUSTER_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.INSTANCE_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.PROJECT_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.TABLE_ID; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsAttributes.ZONE_ID; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.Distribution; +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.common.collect.ImmutableList; +import com.google.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.TimeSeries; +import com.google.protobuf.Empty; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.HistogramPointData; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class BigtableCloudMonitoringExporterTest { + 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"; + + private static final String taskId = "fake-task-id"; + + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock private MetricServiceStub mockMetricServiceStub; + private MetricServiceClient fakeMetricServiceClient; + private BigtableCloudMonitoringExporter exporter; + + private Attributes attributes; + private Resource resource; + private InstrumentationScopeInfo scope; + + @Before + public void setUp() { + + fakeMetricServiceClient = new FakeMetricServiceClient(mockMetricServiceStub); + + exporter = + new BigtableCloudMonitoringExporter( + fakeMetricServiceClient, + MonitoredResource.newBuilder().setType("bigtable-table").build(), + taskId); + + attributes = + Attributes.builder() + .put(PROJECT_ID, projectId) + .put(INSTANCE_ID, instanceId) + .put(TABLE_ID, tableId) + .put(CLUSTER_ID, cluster) + .put(ZONE_ID, zone) + .put(APP_PROFILE, appProfileId) + .build(); + + resource = Resource.create(Attributes.empty()); + + scope = InstrumentationScopeInfo.create("bigtable"); + } + + @After + public void tearDown() {} + + @Test + public void testExportingSumData() { + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(CreateTimeSeriesRequest.class); + + UnaryCallable mockCallable = mock(UnaryCallable.class); + when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable); + when(mockCallable.call(argumentCaptor.capture())).thenReturn(Empty.getDefaultInstance()); + + long fakeValue = 11L; + + LongPointData longPointData = ImmutableLongPointData.create(0, 1, attributes, fakeValue); + + MetricData longData = + ImmutableMetricData.createLongSum( + resource, + scope, + "bigtable/test/long", + "description", + "1", + ImmutableSumData.create( + true, AggregationTemporality.CUMULATIVE, ImmutableList.of(longPointData))); + + exporter.export(Arrays.asList(longData)); + + CreateTimeSeriesRequest request = argumentCaptor.getValue(); + + assertThat(request.getTimeSeriesList()).hasSize(1); + + TimeSeries timeSeries = request.getTimeSeriesList().get(0); + + assertThat(timeSeries.getResource().getLabelsMap()) + .containsExactly( + PROJECT_ID.getKey(), projectId, + INSTANCE_ID.getKey(), instanceId, + TABLE_ID.getKey(), tableId, + CLUSTER_ID.getKey(), cluster, + ZONE_ID.getKey(), zone); + + assertThat(timeSeries.getMetric().getLabelsMap()).hasSize(2); + assertThat(timeSeries.getMetric().getLabelsMap()) + .containsAtLeast(APP_PROFILE.getKey(), appProfileId); + assertThat(timeSeries.getMetric().getLabelsMap()).containsAtLeast(CLIENT_UID.getKey(), taskId); + assertThat(timeSeries.getPoints(0).getValue().getInt64Value()).isEqualTo(fakeValue); + } + + @Test + public void testExportingHistogramData() { + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(CreateTimeSeriesRequest.class); + + UnaryCallable mockCallable = mock(UnaryCallable.class); + when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable); + when(mockCallable.call(argumentCaptor.capture())).thenReturn(Empty.getDefaultInstance()); + + HistogramPointData histogramPointData = + ImmutableHistogramPointData.create( + 0, + 1, + attributes, + 3d, + true, + 1d, // min + true, + 2d, // max + Arrays.asList(1.0), + Arrays.asList(1L, 2L)); + + MetricData histogramData = + ImmutableMetricData.createDoubleHistogram( + resource, + scope, + "bigtable/test/histogram", + "description", + "ms", + ImmutableHistogramData.create( + AggregationTemporality.CUMULATIVE, ImmutableList.of(histogramPointData))); + + exporter.export(Arrays.asList(histogramData)); + + CreateTimeSeriesRequest request = argumentCaptor.getValue(); + + assertThat(request.getTimeSeriesList()).hasSize(1); + + TimeSeries timeSeries = request.getTimeSeriesList().get(0); + + assertThat(timeSeries.getResource().getLabelsMap()) + .containsExactly( + PROJECT_ID.getKey(), projectId, + INSTANCE_ID.getKey(), instanceId, + TABLE_ID.getKey(), tableId, + CLUSTER_ID.getKey(), cluster, + ZONE_ID.getKey(), zone); + + assertThat(timeSeries.getMetric().getLabelsMap()).hasSize(2); + assertThat(timeSeries.getMetric().getLabelsMap()) + .containsAtLeast(APP_PROFILE.getKey(), appProfileId); + assertThat(timeSeries.getMetric().getLabelsMap()).containsAtLeast(CLIENT_UID.getKey(), taskId); + Distribution distribution = timeSeries.getPoints(0).getValue().getDistributionValue(); + assertThat(distribution.getCount()).isEqualTo(3); + } + + private static class FakeMetricServiceClient extends MetricServiceClient { + + protected FakeMetricServiceClient(MetricServiceStub stub) { + super(stub); + } + } +} From 26a648acb0140e9fa94cc7a81dbe28a90c482938 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Fri, 16 Jun 2023 11:44:55 -0400 Subject: [PATCH 02/18] address comments --- google-cloud-bigtable-deps-bom/pom.xml | 20 +++++ google-cloud-bigtable/pom.xml | 20 ----- .../BigtableCloudMonitoringExporter.java | 76 +++++++++---------- .../stub/metrics/BigtableExporterUtils.java | 45 +++++------ .../BigtableCloudMonitoringExporterTest.java | 1 + 5 files changed, 79 insertions(+), 83 deletions(-) diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml index acdd1b011b..fdca70aef5 100644 --- a/google-cloud-bigtable-deps-bom/pom.xml +++ b/google-cloud-bigtable-deps-bom/pom.xml @@ -84,6 +84,26 @@ pom import + + io.opentelemetry + opentelemetry-api + 1.26.0 + + + io.opentelemetry + opentelemetry-sdk-metrics + 1.26.0 + + + io.opentelemetry + opentelemetry-sdk-testing + 1.26.0 + + + io.opentelemetry + opentelemetry-sdk-common + 1.26.0 + diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml index 90d15cdacd..5cd52199c4 100644 --- a/google-cloud-bigtable/pom.xml +++ b/google-cloud-bigtable/pom.xml @@ -40,7 +40,6 @@ 1.55.1 3.23.2 - 1.26.0 @@ -336,37 +335,18 @@ io.opentelemetry opentelemetry-api - ${opentelemetry.version} io.opentelemetry opentelemetry-sdk-metrics - ${opentelemetry.version} io.opentelemetry opentelemetry-sdk-common - ${opentelemetry.version} com.google.cloud google-cloud-monitoring - - - - com.google.http-client - google-http-client-gson - - - com.google.http-client - google-http-client - - - - io.perfmark - perfmark-api - - com.google.api.grpc diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index df9765fe3f..07bbe005a7 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -17,11 +17,11 @@ import com.google.api.MonitoredResource; 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 com.google.monitoring.v3.CreateTimeSeriesRequest; import com.google.monitoring.v3.ProjectName; import com.google.monitoring.v3.TimeSeries; @@ -30,80 +30,78 @@ import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.io.IOException; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.threeten.bp.Duration; +/** Bigtable Cloud Monitoring OpenTelemetry Exporter. */ final class BigtableCloudMonitoringExporter implements MetricExporter { private static final Logger logger = Logger.getLogger(BigtableCloudMonitoringExporter.class.getName()); private final MetricServiceClient client; + + private final String projectId; private final String taskId; private final MonitoredResource monitoredResource; private static final String RESOURCE_TYPE = "bigtable_client_raw"; - BigtableCloudMonitoringExporter(Credentials credentials) throws Exception { - MetricServiceSettings.Builder settingsBuilder = - MetricServiceSettings.newBuilder() - .setTransportChannelProvider(InstantiatingGrpcChannelProvider.newBuilder().build()); + static BigtableCloudMonitoringExporter create(String projectId, Credentials credentials) + throws IOException { + MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder(); settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); org.threeten.bp.Duration timeout = Duration.ofMinutes(1); settingsBuilder.createServiceTimeSeriesSettings().setSimpleTimeoutNoRetries(timeout); - this.client = MetricServiceClient.create(settingsBuilder.build()); - this.taskId = BigtableExporterUtils.getDefaultTaskValue(); - this.monitoredResource = MonitoredResource.newBuilder().setType(RESOURCE_TYPE).build(); + return new BigtableCloudMonitoringExporter( + projectId, + MetricServiceClient.create(settingsBuilder.build()), + MonitoredResource.newBuilder().setType(RESOURCE_TYPE).build(), + BigtableExporterUtils.getDefaultTaskValue()); } @VisibleForTesting BigtableCloudMonitoringExporter( - MetricServiceClient client, MonitoredResource monitoredResource, String taskId) { + String projectId, + MetricServiceClient client, + MonitoredResource monitoredResource, + String taskId) { this.client = client; this.monitoredResource = monitoredResource; this.taskId = taskId; + this.projectId = projectId; } @Override public CompletableResultCode export(Collection collection) { - Map> projectToTimeSeries; - + Preconditions.checkArgument( + collection.stream() + .flatMap(metricData -> metricData.getData().getPoints().stream()) + .allMatch(pd -> BigtableExporterUtils.getProjectId(pd).equals(projectId)), + "Some metric data has unexpected projectId"); for (MetricData metricData : collection) { - projectToTimeSeries = + List timeSeries = metricData.getData().getPoints().stream() - .collect( - Collectors.groupingBy( - BigtableExporterUtils::getProjectId, - Collectors.mapping( - pointData -> - BigtableExporterUtils.convertPointToTimeSeries( - metricData, pointData, taskId, monitoredResource), - Collectors.toList()))); + .map( + pointData -> + BigtableExporterUtils.convertPointToTimeSeries( + metricData, pointData, taskId, monitoredResource)) + .collect(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(); + ProjectName projectName = ProjectName.of(projectId); + CreateTimeSeriesRequest request = + CreateTimeSeriesRequest.newBuilder() + .setName(projectName.toString()) + .addAllTimeSeries(timeSeries) + .build(); - try { - this.client.createServiceTimeSeries(request); - } catch (Throwable e) { - logger.log( - Level.WARNING, - "Exception thrown when exporting TimeSeries for projectName=" - + projectName.getProject(), - e); - } - } + this.client.createServiceTimeSeries(request); } + return CompletableResultCode.ofSuccess(); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index 94f926eb29..7a6ade43ef 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -41,8 +41,9 @@ import com.google.monitoring.v3.TimeInterval; import com.google.monitoring.v3.TimeSeries; import com.google.monitoring.v3.TypedValue; -import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Timestamps; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.DoublePointData; import io.opentelemetry.sdk.metrics.data.HistogramData; @@ -56,16 +57,15 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.security.SecureRandom; -import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +/** Utils to convert OpenTelemetry types to Cloud Monitoring API types. */ class BigtableExporterUtils { private static final Logger logger = Logger.getLogger(BigtableExporterUtils.class.getName()); - private static final long NANO_PER_SECOND = (long) 1e9; static String getDefaultTaskValue() { // Something like '@' @@ -92,19 +92,23 @@ static TimeSeries convertPointToTimeSeries( PointData pointData, String taskId, MonitoredResource monitoredResource) { - Map, Object> attributes = pointData.getAttributes().asMap(); + Attributes attributes = pointData.getAttributes(); MonitoredResource.Builder monitoredResourceBuilder = monitoredResource.toBuilder(); ImmutableMap.Builder metricLabels = new ImmutableMap.Builder<>(); - for (AttributeKey attributeKey : attributes.keySet()) { - if (PROMOTED_RESOURCE_LABELS.contains(attributeKey)) { - monitoredResourceBuilder.putLabels( - attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); - } else { - if (attributes.get(attributeKey) != null) { - metricLabels.put(attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); - } - } + + // Populated monitored resource schema + for (AttributeKey attributeKey : PROMOTED_RESOURCE_LABELS) { + monitoredResourceBuilder.putLabels( + attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); } + + // Populate the rest of the metric labels + attributes.forEach( + (key, value) -> { + if (!PROMOTED_RESOURCE_LABELS.contains(key) && value != null) { + metricLabels.put(key.getKey(), String.valueOf(value)); + } + }); metricLabels.put(CLIENT_UID.getKey(), taskId); TimeSeries.Builder builder = @@ -120,8 +124,8 @@ static TimeSeries convertPointToTimeSeries( TimeInterval timeInterval = TimeInterval.newBuilder() - .setStartTime(convertTimestamp(pointData.getStartEpochNanos())) - .setEndTime(convertTimestamp(pointData.getEpochNanos())) + .setStartTime(Timestamps.fromNanos(pointData.getStartEpochNanos())) + .setEndTime(Timestamps.fromNanos(pointData.getEpochNanos())) .build(); builder.addPoints(createPoint(metricData.getType(), pointData, timeInterval)); @@ -137,7 +141,7 @@ private static MetricKind convertMetricKind(MetricData metricData) { switch (metricData.getType()) { case HISTOGRAM: case EXPONENTIAL_HISTOGRAM: - return convertHistogramDataType(metricData.getHistogramData()); + return convertHistogramType(metricData.getHistogramData()); case LONG_GAUGE: case DOUBLE_GAUGE: return GAUGE; @@ -150,7 +154,7 @@ private static MetricKind convertMetricKind(MetricData metricData) { } } - private static MetricKind convertHistogramDataType(HistogramData histogramData) { + private static MetricKind convertHistogramType(HistogramData histogramData) { if (histogramData.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) { return CUMULATIVE; } @@ -183,13 +187,6 @@ private static ValueType convertValueType(MetricDataType metricDataType) { } } - private static Timestamp convertTimestamp(long epochNanos) { - return Timestamp.newBuilder() - .setSeconds(epochNanos / NANO_PER_SECOND) - .setNanos((int) (epochNanos % NANO_PER_SECOND)) - .build(); - } - private static Point createPoint( MetricDataType type, PointData pointData, TimeInterval timeInterval) { Point.Builder builder = Point.newBuilder().setInterval(timeInterval); diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java index 16a4e64778..6219562670 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java @@ -84,6 +84,7 @@ public void setUp() { exporter = new BigtableCloudMonitoringExporter( + projectId, fakeMetricServiceClient, MonitoredResource.newBuilder().setType("bigtable-table").build(), taskId); From 91fcd80c692057ed9deced7f24fe0225687c641f Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 28 Jun 2023 11:52:49 -0400 Subject: [PATCH 03/18] filter out only bigtable metrics --- .../data/v2/stub/metrics/BigtableCloudMonitoringExporter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 07bbe005a7..22080a6009 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -84,6 +84,9 @@ public CompletableResultCode export(Collection collection) { .allMatch(pd -> BigtableExporterUtils.getProjectId(pd).equals(projectId)), "Some metric data has unexpected projectId"); for (MetricData metricData : collection) { + if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { + continue; + } List timeSeries = metricData.getData().getPoints().stream() .map( From 32ac2bee9d71c8721a99536c3c168d40b082081b Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 28 Jun 2023 11:58:28 -0400 Subject: [PATCH 04/18] fix test --- .../v2/stub/metrics/BigtableCloudMonitoringExporterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java index 6219562670..685d80b296 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java @@ -101,7 +101,7 @@ public void setUp() { resource = Resource.create(Attributes.empty()); - scope = InstrumentationScopeInfo.create("bigtable"); + scope = InstrumentationScopeInfo.create("bigtable.googleapis.com"); } @After From af6cbb28dac102f1fa11fc720d4a2401dfdfc5f9 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 28 Jun 2023 14:59:09 -0400 Subject: [PATCH 05/18] use the bom --- google-cloud-bigtable-deps-bom/pom.xml | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml index fdca70aef5..83e5c30a05 100644 --- a/google-cloud-bigtable-deps-bom/pom.xml +++ b/google-cloud-bigtable-deps-bom/pom.xml @@ -86,23 +86,10 @@ io.opentelemetry - opentelemetry-api - 1.26.0 - - - io.opentelemetry - opentelemetry-sdk-metrics - 1.26.0 - - - io.opentelemetry - opentelemetry-sdk-testing - 1.26.0 - - - io.opentelemetry - opentelemetry-sdk-common - 1.26.0 + opentelemetry-bom + 1.27.0 + pom + import From e87cf914463b9e48aae5d536ae718aa6d187e27e Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Fri, 30 Jun 2023 16:34:13 -0400 Subject: [PATCH 06/18] update --- .../v2/stub/metrics/BigtableCloudMonitoringExporter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 22080a6009..7e7912ed82 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -47,6 +47,7 @@ final class BigtableCloudMonitoringExporter implements MetricExporter { private final String projectId; private final String taskId; private final MonitoredResource monitoredResource; + private boolean isShutdown = false; private static final String RESOURCE_TYPE = "bigtable_client_raw"; @@ -78,6 +79,9 @@ static BigtableCloudMonitoringExporter create(String projectId, Credentials cred @Override public CompletableResultCode export(Collection collection) { + if (isShutdown) { + return CompletableResultCode.ofFailure(); + } Preconditions.checkArgument( collection.stream() .flatMap(metricData -> metricData.getData().getPoints().stream()) @@ -102,7 +106,7 @@ public CompletableResultCode export(Collection collection) { .addAllTimeSeries(timeSeries) .build(); - this.client.createServiceTimeSeries(request); + this.client.createServiceTimeSeriesCallable().futureCall(request); } return CompletableResultCode.ofSuccess(); @@ -116,6 +120,7 @@ public CompletableResultCode flush() { @Override public CompletableResultCode shutdown() { client.shutdown(); + isShutdown = true; return CompletableResultCode.ofSuccess(); } From e2b7e5859a664b290ef605b45c28ad8d585d3fbd Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 5 Jul 2023 13:30:12 -0400 Subject: [PATCH 07/18] update --- .../stub/metrics/BigtableCloudMonitoringExporterTest.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/BigtableCloudMonitoringExporterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java index 685d80b296..26b8dc8cd7 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java @@ -28,6 +28,8 @@ import com.google.api.Distribution; import com.google.api.MonitoredResource; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.monitoring.v3.MetricServiceClient; import com.google.cloud.monitoring.v3.stub.MetricServiceStub; @@ -114,7 +116,8 @@ public void testExportingSumData() { UnaryCallable mockCallable = mock(UnaryCallable.class); when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable); - when(mockCallable.call(argumentCaptor.capture())).thenReturn(Empty.getDefaultInstance()); + ApiFuture future = ApiFutures.immediateFuture(Empty.getDefaultInstance()); + when(mockCallable.futureCall(argumentCaptor.capture())).thenReturn(future); long fakeValue = 11L; @@ -160,7 +163,8 @@ public void testExportingHistogramData() { UnaryCallable mockCallable = mock(UnaryCallable.class); when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable); - when(mockCallable.call(argumentCaptor.capture())).thenReturn(Empty.getDefaultInstance()); + ApiFuture future = ApiFutures.immediateFuture(Empty.getDefaultInstance()); + when(mockCallable.futureCall(argumentCaptor.capture())).thenReturn(future); HistogramPointData histogramPointData = ImmutableHistogramPointData.create( From e8be9c2fe252156c50a38fe2b53adcccfef62b10 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 12 Jul 2023 13:06:14 -0400 Subject: [PATCH 08/18] update completeResultCode --- .../BigtableCloudMonitoringExporter.java | 77 ++++++++++++++----- .../stub/metrics/BigtableExporterUtils.java | 3 +- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 7e7912ed82..39c182adad 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -16,23 +16,30 @@ package com.google.cloud.bigtable.data.v2.stub.metrics; import com.google.api.MonitoredResource; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutureCallback; +import com.google.api.core.ApiFutures; import com.google.api.gax.core.FixedCredentialsProvider; 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 com.google.common.util.concurrent.MoreExecutors; import com.google.monitoring.v3.CreateTimeSeriesRequest; import com.google.monitoring.v3.ProjectName; import com.google.monitoring.v3.TimeSeries; +import com.google.protobuf.Empty; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.threeten.bp.Duration; @@ -47,10 +54,12 @@ final class BigtableCloudMonitoringExporter implements MetricExporter { private final String projectId; private final String taskId; private final MonitoredResource monitoredResource; - private boolean isShutdown = false; + private AtomicBoolean isShutdown = new AtomicBoolean(false); private static final String RESOURCE_TYPE = "bigtable_client_raw"; + private CompletableResultCode lastCode; + static BigtableCloudMonitoringExporter create(String projectId, Credentials credentials) throws IOException { MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder(); @@ -79,18 +88,26 @@ static BigtableCloudMonitoringExporter create(String projectId, Credentials cred @Override public CompletableResultCode export(Collection collection) { - if (isShutdown) { + if (isShutdown.get()) { + return CompletableResultCode.ofFailure(); + } + if (!collection.stream() + .flatMap(metricData -> metricData.getData().getPoints().stream()) + .allMatch(pd -> BigtableExporterUtils.getProjectId(pd).equals(projectId))) { + logger.log(Level.WARNING, "Metric data has different a projectId. Skip exporting."); return CompletableResultCode.ofFailure(); } - Preconditions.checkArgument( - collection.stream() - .flatMap(metricData -> metricData.getData().getPoints().stream()) - .allMatch(pd -> BigtableExporterUtils.getProjectId(pd).equals(projectId)), - "Some metric data has unexpected projectId"); + + lastCode = new CompletableResultCode(); + + List allTimeSeries = new ArrayList<>(); for (MetricData metricData : collection) { if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { continue; } + + CompletableResultCode code = new CompletableResultCode(); + List timeSeries = metricData.getData().getPoints().stream() .map( @@ -98,30 +115,52 @@ public CompletableResultCode export(Collection collection) { BigtableExporterUtils.convertPointToTimeSeries( metricData, pointData, taskId, monitoredResource)) .collect(Collectors.toList()); + allTimeSeries.addAll(timeSeries); + } - ProjectName projectName = ProjectName.of(projectId); - CreateTimeSeriesRequest request = - CreateTimeSeriesRequest.newBuilder() - .setName(projectName.toString()) - .addAllTimeSeries(timeSeries) - .build(); + ProjectName projectName = ProjectName.of(projectId); + CreateTimeSeriesRequest request = + CreateTimeSeriesRequest.newBuilder() + .setName(projectName.toString()) + .addAllTimeSeries(allTimeSeries) + .build(); - this.client.createServiceTimeSeriesCallable().futureCall(request); - } + ApiFuture future = this.client.createServiceTimeSeriesCallable().futureCall(request); - return CompletableResultCode.ofSuccess(); + ApiFutures.addCallback( + future, + new ApiFutureCallback() { + @Override + public void onFailure(Throwable throwable) { + lastCode.fail(); + } + + @Override + public void onSuccess(Empty empty) { + lastCode.succeed(); + } + }, + MoreExecutors.directExecutor()); + + return lastCode; } @Override public CompletableResultCode flush() { + if (lastCode != null) { + return lastCode; + } return CompletableResultCode.ofSuccess(); } @Override public CompletableResultCode shutdown() { + if (!isShutdown.compareAndSet(false, true)) { + logger.log(Level.INFO, "shutdown is called multiple times"); + return CompletableResultCode.ofSuccess(); + } client.shutdown(); - isShutdown = true; - return CompletableResultCode.ofSuccess(); + return flush(); } @Override diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index 7a6ade43ef..fff2cc52d1 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -56,7 +56,6 @@ import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.UnknownHostException; -import java.security.SecureRandom; import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -79,7 +78,7 @@ static String getDefaultTaskValue() { 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-" + UUID.randomUUID() + "@" + hostname; } return "java-" + UUID.randomUUID() + jvmName; } From cd042528d7c2dff5a53bc938717e9e0a69ac99d1 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Wed, 12 Jul 2023 15:30:48 -0400 Subject: [PATCH 09/18] add a comment --- .../data/v2/stub/metrics/BigtableCloudMonitoringExporter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 39c182adad..bd32f51d3b 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -66,6 +66,8 @@ static BigtableCloudMonitoringExporter create(String projectId, Credentials cred settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); org.threeten.bp.Duration timeout = Duration.ofMinutes(1); + // TODO: createServiceTimeSeries needs special handling if the request failed. Leaving + // it as not retried for now. settingsBuilder.createServiceTimeSeriesSettings().setSimpleTimeoutNoRetries(timeout); return new BigtableCloudMonitoringExporter( projectId, From ee2fe9950f76ea63f0969bf47633c438872153fe Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Tue, 16 Jan 2024 13:47:29 -0500 Subject: [PATCH 10/18] address comments --- .../BigtableCloudMonitoringExporter.java | 41 ++++++------------- .../stub/metrics/BigtableExporterUtils.java | 27 +++++++++++- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index bd32f51d3b..37a426a001 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -35,13 +35,11 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import org.threeten.bp.Duration; /** Bigtable Cloud Monitoring OpenTelemetry Exporter. */ @@ -54,11 +52,11 @@ final class BigtableCloudMonitoringExporter implements MetricExporter { private final String projectId; private final String taskId; private final MonitoredResource monitoredResource; - private AtomicBoolean isShutdown = new AtomicBoolean(false); + private final AtomicBoolean isShutdown = new AtomicBoolean(false); private static final String RESOURCE_TYPE = "bigtable_client_raw"; - private CompletableResultCode lastCode; + private CompletableResultCode lastExportCode; static BigtableCloudMonitoringExporter create(String projectId, Credentials credentials) throws IOException { @@ -91,6 +89,7 @@ static BigtableCloudMonitoringExporter create(String projectId, Credentials cred @Override public CompletableResultCode export(Collection collection) { if (isShutdown.get()) { + logger.log(Level.WARNING, "Exporter is shutting down"); return CompletableResultCode.ofFailure(); } if (!collection.stream() @@ -100,25 +99,9 @@ public CompletableResultCode export(Collection collection) { return CompletableResultCode.ofFailure(); } - lastCode = new CompletableResultCode(); - - List allTimeSeries = new ArrayList<>(); - for (MetricData metricData : collection) { - if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { - continue; - } - - CompletableResultCode code = new CompletableResultCode(); - - List timeSeries = - metricData.getData().getPoints().stream() - .map( - pointData -> - BigtableExporterUtils.convertPointToTimeSeries( - metricData, pointData, taskId, monitoredResource)) - .collect(Collectors.toList()); - allTimeSeries.addAll(timeSeries); - } + List allTimeSeries = + BigtableExporterUtils.convertCollectionToListOfTimeSeries( + collection, taskId, monitoredResource); ProjectName projectName = ProjectName.of(projectId); CreateTimeSeriesRequest request = @@ -129,28 +112,30 @@ public CompletableResultCode export(Collection collection) { ApiFuture future = this.client.createServiceTimeSeriesCallable().futureCall(request); + lastExportCode = new CompletableResultCode(); + ApiFutures.addCallback( future, new ApiFutureCallback() { @Override public void onFailure(Throwable throwable) { - lastCode.fail(); + lastExportCode.fail(); } @Override public void onSuccess(Empty empty) { - lastCode.succeed(); + lastExportCode.succeed(); } }, MoreExecutors.directExecutor()); - return lastCode; + return lastExportCode; } @Override public CompletableResultCode flush() { - if (lastCode != null) { - return lastCode; + if (lastExportCode != null) { + return lastExportCode; } return CompletableResultCode.ofSuccess(); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index fff2cc52d1..7a3eadc35c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -56,10 +56,14 @@ import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; /** Utils to convert OpenTelemetry types to Cloud Monitoring API types. */ class BigtableExporterUtils { @@ -86,7 +90,28 @@ static String getDefaultTaskValue() { private static final Set> PROMOTED_RESOURCE_LABELS = ImmutableSet.of(PROJECT_ID, INSTANCE_ID, TABLE_ID, CLUSTER_ID, ZONE_ID); - static TimeSeries convertPointToTimeSeries( + static List convertCollectionToListOfTimeSeries( + Collection collection, String taskId, MonitoredResource monitoredResource) { + List allTimeSeries = new ArrayList<>(); + + for (MetricData metricData : collection) { + if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { + continue; + } + + List timeSeries = + metricData.getData().getPoints().stream() + .map( + pointData -> + convertPointToTimeSeries(metricData, pointData, taskId, monitoredResource)) + .collect(Collectors.toList()); + allTimeSeries.addAll(timeSeries); + } + + return allTimeSeries; + } + + private static TimeSeries convertPointToTimeSeries( MetricData metricData, PointData pointData, String taskId, From da57c98235fe786286b058ea23abd526cce88b3e Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 11:54:44 -0500 Subject: [PATCH 11/18] address comments --- .../BigtableCloudMonitoringExporter.java | 28 +++++++-- .../stub/metrics/BigtableExporterUtils.java | 59 ++++++++----------- .../metrics/BuiltinMetricsAttributes.java | 10 ++-- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 37a426a001..39e8639f4d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -94,14 +94,20 @@ public CompletableResultCode export(Collection collection) { } if (!collection.stream() .flatMap(metricData -> metricData.getData().getPoints().stream()) - .allMatch(pd -> BigtableExporterUtils.getProjectId(pd).equals(projectId))) { + .allMatch(pd -> projectId.equals(BigtableExporterUtils.getProjectId(pd)))) { logger.log(Level.WARNING, "Metric data has different a projectId. Skip exporting."); return CompletableResultCode.ofFailure(); } - List allTimeSeries = - BigtableExporterUtils.convertCollectionToListOfTimeSeries( - collection, taskId, monitoredResource); + List allTimeSeries; + try { + allTimeSeries = + BigtableExporterUtils.convertCollectionToListOfTimeSeries( + collection, taskId, monitoredResource); + } catch (Throwable e) { + logger.log(Level.WARNING, "Failed to convert metric data to cloud monitoring timeseries.", e); + return CompletableResultCode.ofFailure(); + } ProjectName projectName = ProjectName.of(projectId); CreateTimeSeriesRequest request = @@ -146,10 +152,20 @@ public CompletableResultCode shutdown() { logger.log(Level.INFO, "shutdown is called multiple times"); return CompletableResultCode.ofSuccess(); } - client.shutdown(); - return flush(); + CompletableResultCode resultCode = flush(); + try { + client.shutdown(); + } catch (Throwable e) { + logger.log(Level.WARNING, "failed to shutdown the client", e); + return CompletableResultCode.ofFailure(); + } + return resultCode; } + /** + * For cloud monarch always return CUMULATIVE to keep track of the cumulative value of a metric + * over time. + */ @Override public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { return AggregationTemporality.CUMULATIVE; diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index 7a3eadc35c..ac4a78c2dd 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -35,7 +35,6 @@ import com.google.api.Distribution; import com.google.api.Metric; import com.google.api.MonitoredResource; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.monitoring.v3.Point; import com.google.monitoring.v3.TimeInterval; @@ -63,17 +62,26 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** Utils to convert OpenTelemetry types to Cloud Monitoring API types. */ class BigtableExporterUtils { private static final Logger logger = Logger.getLogger(BigtableExporterUtils.class.getName()); + // These metric labels will be promoted to the bigtable_table monitored resource fields + private static final Set> PROMOTED_RESOURCE_LABELS = + ImmutableSet.of(PROJECT_ID, INSTANCE_ID, TABLE_ID, CLUSTER_ID, ZONE_ID); + + private BigtableExporterUtils() {} + + /** + * In most cases this should look like java-${UUID}@${hostname}. The hostname will be retrieved + * from the jvm name but fallback on the local hostname. + */ static String getDefaultTaskValue() { // Something like '@' final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); - // If not the expected format then generate a random number. + // If not the expected format then fallback to localhost if (jvmName.indexOf('@') < 1) { String hostname = "localhost"; try { @@ -87,9 +95,6 @@ static String getDefaultTaskValue() { return "java-" + UUID.randomUUID() + jvmName; } - private static final Set> PROMOTED_RESOURCE_LABELS = - ImmutableSet.of(PROJECT_ID, INSTANCE_ID, TABLE_ID, CLUSTER_ID, ZONE_ID); - static List convertCollectionToListOfTimeSeries( Collection collection, String taskId, MonitoredResource monitoredResource) { List allTimeSeries = new ArrayList<>(); @@ -98,14 +103,11 @@ static List convertCollectionToListOfTimeSeries( if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { continue; } - - List timeSeries = - metricData.getData().getPoints().stream() - .map( - pointData -> - convertPointToTimeSeries(metricData, pointData, taskId, monitoredResource)) - .collect(Collectors.toList()); - allTimeSeries.addAll(timeSeries); + metricData.getData().getPoints().stream() + .map( + pointData -> + convertPointToTimeSeries(metricData, pointData, taskId, monitoredResource)) + .forEach(allTimeSeries::add); } return allTimeSeries; @@ -118,32 +120,23 @@ private static TimeSeries convertPointToTimeSeries( MonitoredResource monitoredResource) { Attributes attributes = pointData.getAttributes(); MonitoredResource.Builder monitoredResourceBuilder = monitoredResource.toBuilder(); - ImmutableMap.Builder metricLabels = new ImmutableMap.Builder<>(); - // Populated monitored resource schema - for (AttributeKey attributeKey : PROMOTED_RESOURCE_LABELS) { - monitoredResourceBuilder.putLabels( - attributeKey.getKey(), String.valueOf(attributes.get(attributeKey))); - } + Metric.Builder metricBuilder = Metric.newBuilder().setType(metricData.getName()); - // Populate the rest of the metric labels - attributes.forEach( - (key, value) -> { - if (!PROMOTED_RESOURCE_LABELS.contains(key) && value != null) { - metricLabels.put(key.getKey(), String.valueOf(value)); - } - }); - metricLabels.put(CLIENT_UID.getKey(), taskId); + for (AttributeKey key : attributes.asMap().keySet()) { + if (PROMOTED_RESOURCE_LABELS.contains(key)) { + monitoredResourceBuilder.putLabels(key.getKey(), String.valueOf(attributes.get(key))); + } else { + metricBuilder.putLabels(key.getKey(), String.valueOf(attributes.get(key))); + } + } + metricBuilder.putLabels(CLIENT_UID.getKey(), taskId); TimeSeries.Builder builder = TimeSeries.newBuilder() .setResource(monitoredResourceBuilder.build()) .setMetricKind(convertMetricKind(metricData)) - .setMetric( - Metric.newBuilder() - .setType(metricData.getName()) - .putAllLabels(metricLabels.build()) - .build()) + .setMetric(metricBuilder.build()) .setValueType(convertValueType(metricData.getType())); TimeInterval timeInterval = diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java index 070544cbd8..8c6eb4cebe 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java @@ -19,11 +19,11 @@ class BuiltinMetricsAttributes { - public static final AttributeKey PROJECT_ID = AttributeKey.stringKey("project_id"); - public static final AttributeKey INSTANCE_ID = AttributeKey.stringKey("instance"); - public static final AttributeKey TABLE_ID = AttributeKey.stringKey("table"); - public static final AttributeKey CLUSTER_ID = AttributeKey.stringKey("cluster"); - public static final AttributeKey ZONE_ID = AttributeKey.stringKey("zone"); + static final AttributeKey PROJECT_ID = AttributeKey.stringKey("project_id"); + static final AttributeKey INSTANCE_ID = AttributeKey.stringKey("instance"); + static final AttributeKey TABLE_ID = AttributeKey.stringKey("table"); + static final AttributeKey CLUSTER_ID = AttributeKey.stringKey("cluster"); + static final AttributeKey ZONE_ID = AttributeKey.stringKey("zone"); static final AttributeKey CLIENT_UID = AttributeKey.stringKey("client_uid"); static final AttributeKey APP_PROFILE = AttributeKey.stringKey("app_profile"); From dfb44a658d2aef4929b5a6808ecf78aae074c5e7 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 11:55:56 -0500 Subject: [PATCH 12/18] update pom --- 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 5d8a2ea789..9b968931d7 100644 --- a/google-cloud-bigtable-deps-bom/pom.xml +++ b/google-cloud-bigtable-deps-bom/pom.xml @@ -80,7 +80,7 @@ io.opentelemetry opentelemetry-bom - 1.27.0 + 1.34.1 pom import From c3f36edf2673bc28c100e6ca9fb310d71896f4fe Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 12:36:42 -0500 Subject: [PATCH 13/18] small fix --- .../metrics/BigtableCloudMonitoringExporter.java | 5 +++-- .../data/v2/stub/metrics/BigtableExporterUtils.java | 13 ++++++------- .../v2/stub/metrics/BuiltinMetricsAttributes.java | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 39e8639f4d..516211ee16 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -125,6 +125,7 @@ public CompletableResultCode export(Collection collection) { new ApiFutureCallback() { @Override public void onFailure(Throwable throwable) { + logger.log(Level.WARNING, "createServiceTimeSeries request failed. ", throwable); lastExportCode.fail(); } @@ -149,14 +150,14 @@ public CompletableResultCode flush() { @Override public CompletableResultCode shutdown() { if (!isShutdown.compareAndSet(false, true)) { - logger.log(Level.INFO, "shutdown is called multiple times"); + logger.log(Level.WARNING, "shutdown is called multiple times"); return CompletableResultCode.ofSuccess(); } CompletableResultCode resultCode = flush(); try { client.shutdown(); } catch (Throwable e) { - logger.log(Level.WARNING, "failed to shutdown the client", e); + logger.log(Level.WARNING, "failed to shutdown the monitoring client", e); return CompletableResultCode.ofFailure(); } return resultCode; diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index ac4a78c2dd..72b949c42f 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -76,12 +76,12 @@ private BigtableExporterUtils() {} /** * In most cases this should look like java-${UUID}@${hostname}. The hostname will be retrieved - * from the jvm name but fallback on the local hostname. + * from the jvm name and fallback to the local hostname. */ static String getDefaultTaskValue() { // Something like '@' final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); - // If not the expected format then fallback to localhost + // If jvm doesn't have the expected format, fallback to the local hostname if (jvmName.indexOf('@') < 1) { String hostname = "localhost"; try { @@ -95,6 +95,10 @@ static String getDefaultTaskValue() { return "java-" + UUID.randomUUID() + jvmName; } + static String getProjectId(PointData pointData) { + return pointData.getAttributes().get(PROJECT_ID); + } + static List convertCollectionToListOfTimeSeries( Collection collection, String taskId, MonitoredResource monitoredResource) { List allTimeSeries = new ArrayList<>(); @@ -120,7 +124,6 @@ private static TimeSeries convertPointToTimeSeries( MonitoredResource monitoredResource) { Attributes attributes = pointData.getAttributes(); MonitoredResource.Builder monitoredResourceBuilder = monitoredResource.toBuilder(); - Metric.Builder metricBuilder = Metric.newBuilder().setType(metricData.getName()); for (AttributeKey key : attributes.asMap().keySet()) { @@ -150,10 +153,6 @@ private static TimeSeries convertPointToTimeSeries( return builder.build(); } - static String getProjectId(PointData pointData) { - return pointData.getAttributes().get(PROJECT_ID); - } - private static MetricKind convertMetricKind(MetricData metricData) { switch (metricData.getType()) { case HISTOGRAM: diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java index 8c6eb4cebe..e34659444b 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsAttributes.java @@ -24,11 +24,11 @@ class BuiltinMetricsAttributes { static final AttributeKey TABLE_ID = AttributeKey.stringKey("table"); static final AttributeKey CLUSTER_ID = AttributeKey.stringKey("cluster"); static final AttributeKey ZONE_ID = AttributeKey.stringKey("zone"); - static final AttributeKey CLIENT_UID = AttributeKey.stringKey("client_uid"); static final AttributeKey APP_PROFILE = AttributeKey.stringKey("app_profile"); static final AttributeKey STREAMING = AttributeKey.booleanKey("streaming"); static final AttributeKey METHOD = AttributeKey.stringKey("method"); static final AttributeKey STATUS = AttributeKey.stringKey("status"); static final AttributeKey CLIENT_NAME = AttributeKey.stringKey("client_name"); + static final AttributeKey CLIENT_UID = AttributeKey.stringKey("client_uid"); } From 1fef2a16d45e69ad50345625ea7b5c3910527a4a Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 12:48:20 -0500 Subject: [PATCH 14/18] also check timestamp --- .../BigtableCloudMonitoringExporterTest.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java index 26b8dc8cd7..0f3bed1d90 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java @@ -121,7 +121,10 @@ public void testExportingSumData() { long fakeValue = 11L; - LongPointData longPointData = ImmutableLongPointData.create(0, 1, attributes, fakeValue); + long startEpoch = 10; + long endEpoch = 15; + LongPointData longPointData = + ImmutableLongPointData.create(startEpoch, endEpoch, attributes, fakeValue); MetricData longData = ImmutableMetricData.createLongSum( @@ -154,6 +157,9 @@ public void testExportingSumData() { .containsAtLeast(APP_PROFILE.getKey(), appProfileId); assertThat(timeSeries.getMetric().getLabelsMap()).containsAtLeast(CLIENT_UID.getKey(), taskId); assertThat(timeSeries.getPoints(0).getValue().getInt64Value()).isEqualTo(fakeValue); + assertThat(timeSeries.getPoints(0).getInterval().getStartTime().getNanos()) + .isEqualTo(startEpoch); + assertThat(timeSeries.getPoints(0).getInterval().getEndTime().getNanos()).isEqualTo(endEpoch); } @Test @@ -166,10 +172,12 @@ public void testExportingHistogramData() { ApiFuture future = ApiFutures.immediateFuture(Empty.getDefaultInstance()); when(mockCallable.futureCall(argumentCaptor.capture())).thenReturn(future); + long startEpoch = 10; + long endEpoch = 15; HistogramPointData histogramPointData = ImmutableHistogramPointData.create( - 0, - 1, + startEpoch, + endEpoch, attributes, 3d, true, @@ -211,6 +219,9 @@ public void testExportingHistogramData() { assertThat(timeSeries.getMetric().getLabelsMap()).containsAtLeast(CLIENT_UID.getKey(), taskId); Distribution distribution = timeSeries.getPoints(0).getValue().getDistributionValue(); assertThat(distribution.getCount()).isEqualTo(3); + assertThat(timeSeries.getPoints(0).getInterval().getStartTime().getNanos()) + .isEqualTo(startEpoch); + assertThat(timeSeries.getPoints(0).getInterval().getEndTime().getNanos()).isEqualTo(endEpoch); } private static class FakeMetricServiceClient extends MetricServiceClient { From faadf773ffdb93ecee7419025cab13e1bae42882 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 16:03:30 -0500 Subject: [PATCH 15/18] address comment --- .../stub/metrics/BigtableCloudMonitoringExporter.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 516211ee16..0b130bb391 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.util.Collection; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -154,6 +155,11 @@ public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } CompletableResultCode resultCode = flush(); + // wait 1 minute for flush + resultCode.join(1, TimeUnit.MINUTES); + if (!resultCode.isSuccess()) { + logger.log(Level.WARNING, "Timed out waiting for exporter flush."); + } try { client.shutdown(); } catch (Throwable e) { @@ -164,8 +170,8 @@ public CompletableResultCode shutdown() { } /** - * For cloud monarch always return CUMULATIVE to keep track of the cumulative value of a metric - * over time. + * For Google Cloud Monitoring always return CUMULATIVE to keep track of the cumulative value of a + * metric over time. */ @Override public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { From 5a255e8b79e8839f654b19668a76b08d20604780 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 16:13:07 -0500 Subject: [PATCH 16/18] updates --- .../v2/stub/metrics/BigtableCloudMonitoringExporter.java | 7 ++++++- .../data/v2/stub/metrics/BigtableExporterUtils.java | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 0b130bb391..0c00610b64 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -43,7 +43,12 @@ import java.util.logging.Logger; import org.threeten.bp.Duration; -/** Bigtable Cloud Monitoring OpenTelemetry Exporter. */ +/** + * Bigtable Cloud Monitoring OpenTelemetry Exporter. + * + *

The exporter will look for all bigtable owned metrics under bigtable.googleapis.com + * instrumentation scope and upload it via the Google Cloud Monitoring api. + */ final class BigtableCloudMonitoringExporter implements MetricExporter { private static final Logger logger = diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index 72b949c42f..a225033296 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -63,7 +63,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -/** Utils to convert OpenTelemetry types to Cloud Monitoring API types. */ +/** Utils to convert OpenTelemetry types to Google Cloud Monitoring API types. */ class BigtableExporterUtils { private static final Logger logger = Logger.getLogger(BigtableExporterUtils.class.getName()); @@ -104,6 +104,8 @@ static List convertCollectionToListOfTimeSeries( List allTimeSeries = new ArrayList<>(); for (MetricData metricData : collection) { + // TODO: scope will be defined in BuiltinMetricsConstants. Update this field in the following + // PR. if (!metricData.getInstrumentationScopeInfo().getName().equals("bigtable.googleapis.com")) { continue; } From 3d0d72f64f8d0b9583319186a0bb1b27996c7f4e Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 16:15:57 -0500 Subject: [PATCH 17/18] update --- .../data/v2/stub/metrics/BigtableCloudMonitoringExporter.java | 2 +- .../bigtable/data/v2/stub/metrics/BigtableExporterUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 0c00610b64..4fa9c7ba52 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -47,7 +47,7 @@ * Bigtable Cloud Monitoring OpenTelemetry Exporter. * *

The exporter will look for all bigtable owned metrics under bigtable.googleapis.com - * instrumentation scope and upload it via the Google Cloud Monitoring api. + * instrumentation scope and upload it via the Google Cloud Monitoring API. */ final class BigtableCloudMonitoringExporter implements MetricExporter { diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java index a225033296..7c3dc09fc4 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableExporterUtils.java @@ -63,7 +63,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -/** Utils to convert OpenTelemetry types to Google Cloud Monitoring API types. */ +/** Utils to convert OpenTelemetry types to Google Cloud Monitoring types. */ class BigtableExporterUtils { private static final Logger logger = Logger.getLogger(BigtableExporterUtils.class.getName()); From efda056c1fef0f39722d215283eac43cb5e258f4 Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 22 Jan 2024 21:48:29 -0500 Subject: [PATCH 18/18] do not block on shutdown --- .../BigtableCloudMonitoringExporter.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 4fa9c7ba52..5ca8271791 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -35,9 +35,9 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -159,19 +159,24 @@ public CompletableResultCode shutdown() { logger.log(Level.WARNING, "shutdown is called multiple times"); return CompletableResultCode.ofSuccess(); } - CompletableResultCode resultCode = flush(); - // wait 1 minute for flush - resultCode.join(1, TimeUnit.MINUTES); - if (!resultCode.isSuccess()) { - logger.log(Level.WARNING, "Timed out waiting for exporter flush."); - } - try { - client.shutdown(); - } catch (Throwable e) { - logger.log(Level.WARNING, "failed to shutdown the monitoring client", e); - return CompletableResultCode.ofFailure(); - } - return resultCode; + CompletableResultCode flushResult = flush(); + CompletableResultCode shutdownResult = new CompletableResultCode(); + flushResult.whenComplete( + () -> { + Throwable throwable = null; + try { + client.shutdown(); + } catch (Throwable e) { + logger.log(Level.WARNING, "failed to shutdown the monitoring client", e); + throwable = e; + } + if (throwable != null) { + shutdownResult.fail(); + } else { + shutdownResult.succeed(); + } + }); + return CompletableResultCode.ofAll(Arrays.asList(flushResult, shutdownResult)); } /**