diff --git a/google-cloud-bigtable/clirr-ignored-differences.xml b/google-cloud-bigtable/clirr-ignored-differences.xml index c9a6f3762c..383f60a7c1 100644 --- a/google-cloud-bigtable/clirr-ignored-differences.xml +++ b/google-cloud-bigtable/clirr-ignored-differences.xml @@ -499,4 +499,16 @@ com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPoolSettings$Builder com.google.cloud.bigtable.gaxx.grpc.BigtableChannelPoolSettings$Builder setLoadBalancingStrategy(com.google.cloud.bigtable.gaxx.grpc.BigtableChannelPoolSettings$LoadBalancingStrategy) + + + 7004 + com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings$InternalMetricsProvider + * + + + + 7004 + com/google/cloud/bigtable/data/v2/stub/metrics/Util + * + diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java index c26f16b305..f366190eb6 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java @@ -19,6 +19,7 @@ import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.core.FixedCredentialsProvider; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.rpc.ClientContext; @@ -41,6 +42,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -58,6 +60,9 @@ public class BigtableClientContext { @Nullable private final OpenTelemetrySdk internalOpenTelemetry; private final MetricsProvider metricsProvider; private final ClientContext clientContext; + // the background executor shared for OTEL instances and monitoring client and all other + // background tasks + private final ExecutorProvider backgroundExecutorProvider; public static BigtableClientContext create(EnhancedBigtableStubSettings settings) throws IOException { @@ -75,6 +80,14 @@ public static BigtableClientContext create(EnhancedBigtableStubSettings settings String universeDomain = settings.getUniverseDomain(); + boolean shouldAutoClose = settings.getBackgroundExecutorProvider().shouldAutoClose(); + ScheduledExecutorService backgroundExecutor = + settings.getBackgroundExecutorProvider().getExecutor(); + // TODO: after gax change is merged, migrate to use gax's FixedExecutorProvider + BigtableExecutorProvider executorProvider = + BigtableExecutorProvider.create(backgroundExecutor, shouldAutoClose); + builder.setBackgroundExecutorProvider(executorProvider); + // Set up OpenTelemetry OpenTelemetry openTelemetry = null; try { @@ -85,7 +98,8 @@ public static BigtableClientContext create(EnhancedBigtableStubSettings settings settings.getMetricsProvider(), credentials, settings.getMetricsEndpoint(), - universeDomain); + universeDomain, + backgroundExecutor); } catch (Throwable t) { logger.log(Level.WARNING, "Failed to get OTEL, will skip exporting client side metrics", t); } @@ -103,7 +117,9 @@ public static BigtableClientContext create(EnhancedBigtableStubSettings settings // no reason to build the internal OtelProvider if (transportProvider != null) { internalOtel = - settings.getInternalMetricsProvider().createOtelProvider(settings, credentials); + settings + .getInternalMetricsProvider() + .createOtelProvider(settings, credentials, backgroundExecutor); if (internalOtel != null) { channelPoolMetricsTracer = new ChannelPoolMetricsTracer( @@ -148,7 +164,11 @@ public static BigtableClientContext create(EnhancedBigtableStubSettings settings } return new BigtableClientContext( - clientContext, openTelemetry, internalOtel, settings.getMetricsProvider()); + clientContext, + openTelemetry, + internalOtel, + settings.getMetricsProvider(), + executorProvider); } private static void configureGrpcOtel( @@ -182,11 +202,13 @@ private BigtableClientContext( ClientContext clientContext, @Nullable OpenTelemetry openTelemetry, @Nullable OpenTelemetrySdk internalOtel, - MetricsProvider metricsProvider) { + MetricsProvider metricsProvider, + ExecutorProvider backgroundExecutorProvider) { this.clientContext = clientContext; this.openTelemetry = openTelemetry; this.internalOpenTelemetry = internalOtel; this.metricsProvider = metricsProvider; + this.backgroundExecutorProvider = backgroundExecutorProvider; } public OpenTelemetry getOpenTelemetry() { @@ -199,7 +221,11 @@ public ClientContext getClientContext() { public BigtableClientContext withClientContext(ClientContext clientContext) { return new BigtableClientContext( - clientContext, openTelemetry, internalOpenTelemetry, metricsProvider); + clientContext, + openTelemetry, + internalOpenTelemetry, + metricsProvider, + backgroundExecutorProvider); } public void close() throws Exception { @@ -212,13 +238,17 @@ public void close() throws Exception { if (metricsProvider instanceof DefaultMetricsProvider && openTelemetry != null) { ((OpenTelemetrySdk) openTelemetry).close(); } + if (backgroundExecutorProvider.shouldAutoClose()) { + backgroundExecutorProvider.getExecutor().shutdown(); + } } private static OpenTelemetry getOpenTelemetryFromMetricsProvider( MetricsProvider metricsProvider, @Nullable Credentials defaultCredentials, @Nullable String metricsEndpoint, - String universeDomain) + String universeDomain, + ScheduledExecutorService executor) throws IOException { if (metricsProvider instanceof CustomOpenTelemetryMetricsProvider) { CustomOpenTelemetryMetricsProvider customMetricsProvider = @@ -230,7 +260,8 @@ private static OpenTelemetry getOpenTelemetryFromMetricsProvider( ? BigtableDataSettings.getMetricsCredentials() : defaultCredentials; DefaultMetricsProvider defaultMetricsProvider = (DefaultMetricsProvider) metricsProvider; - return defaultMetricsProvider.getOpenTelemetry(metricsEndpoint, universeDomain, credentials); + return defaultMetricsProvider.getOpenTelemetry( + metricsEndpoint, universeDomain, credentials, executor); } else if (metricsProvider instanceof NoopMetricsProvider) { return null; } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableExecutorProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableExecutorProvider.java new file mode 100644 index 0000000000..6b38b92909 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableExecutorProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 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; + +import com.google.api.gax.core.ExecutorProvider; +import java.util.concurrent.ScheduledExecutorService; + +// TODO: migrate to gax's FixedExecutorProvider once the change is merged +class BigtableExecutorProvider implements ExecutorProvider { + + private final ScheduledExecutorService executorService; + private final boolean shouldAutoClose; + + @Override + public boolean shouldAutoClose() { + return shouldAutoClose; + } + + @Override + public ScheduledExecutorService getExecutor() { + return executorService; + } + + static BigtableExecutorProvider create( + ScheduledExecutorService executor, boolean shouldAutoClose) { + return new BigtableExecutorProvider(executor, shouldAutoClose); + } + + private BigtableExecutorProvider( + ScheduledExecutorService executorService, boolean shouldAutoClose) { + this.shouldAutoClose = shouldAutoClose; + this.executorService = executorService; + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index f4572333c9..acd3323957 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -69,6 +69,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -1388,11 +1389,14 @@ public String toString() { public interface InternalMetricsProvider { @Nullable OpenTelemetrySdk createOtelProvider( - EnhancedBigtableStubSettings userSettings, Credentials creds) throws IOException; + EnhancedBigtableStubSettings userSettings, + Credentials creds, + ScheduledExecutorService executor) + throws IOException; } private static final InternalMetricsProvider DEFAULT_INTERNAL_OTEL_PROVIDER = Util::newInternalOpentelemetry; private static final InternalMetricsProvider DISABLED_INTERNAL_OTEL_PROVIDER = - (ignored1, ignored2) -> null; + (ignored1, ignored2, ignored3) -> null; } 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 1244ee5fdc..375ab17142 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,6 +35,7 @@ import com.google.api.core.InternalApi; import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.core.FixedExecutorProvider; import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.rpc.PermissionDeniedException; import com.google.auth.Credentials; @@ -65,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -115,7 +117,8 @@ static BigtableCloudMonitoringExporter create( @Nullable Credentials credentials, @Nullable String endpoint, String universeDomain, - TimeSeriesConverter converter) + TimeSeriesConverter converter, + @Nullable ScheduledExecutorService executorService) throws IOException { Preconditions.checkNotNull(universeDomain); MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder(); @@ -127,6 +130,15 @@ static BigtableCloudMonitoringExporter create( settingsBuilder.setUniverseDomain(universeDomain); + // If background executor is not null, use it for the monitoring client. This allows us to + // share the same background executor with the data client. When it's null, the monitoring + // client will create a new executor service from InstantiatingExecutorProvider. It could be + // null if someone uses a CustomOpenTelemetryMetricsProvider#setupSdkMeterProvider without + // the executor. + if (executorService != null) { + settingsBuilder.setBackgroundExecutorProvider(FixedExecutorProvider.create(executorService)); + } + if (MONITORING_ENDPOINT_OVERRIDE_SYS_PROP != null) { logger.warning( "Setting the monitoring endpoint through system variable will be removed in future" diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java index f6df7fe6cd..24e38c3a2c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java @@ -22,8 +22,10 @@ import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder; import java.io.IOException; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; /** @@ -100,14 +102,29 @@ public static void registerBuiltinMetrics( @Nullable Credentials credentials, SdkMeterProviderBuilder builder, @Nullable String endpoint) throws IOException { registerBuiltinMetricsWithUniverseDomain( - credentials, builder, endpoint, Credentials.GOOGLE_DEFAULT_UNIVERSE); + credentials, builder, endpoint, Credentials.GOOGLE_DEFAULT_UNIVERSE, null); + } + + /** + * Register built-in metrics on the {@link SdkMeterProviderBuilder} with custom credentials, + * endpoint and executor service. + */ + public static void registerBuiltinMetrics( + @Nullable Credentials credentials, + SdkMeterProviderBuilder builder, + @Nullable String endpoint, + @Nullable ScheduledExecutorService executorService) + throws IOException { + registerBuiltinMetricsWithUniverseDomain( + credentials, builder, endpoint, Credentials.GOOGLE_DEFAULT_UNIVERSE, executorService); } static void registerBuiltinMetricsWithUniverseDomain( @Nullable Credentials credentials, SdkMeterProviderBuilder builder, @Nullable String endpoint, - String universeDomain) + String universeDomain, + @Nullable ScheduledExecutorService executorService) throws IOException { MetricExporter publicExporter = BigtableCloudMonitoringExporter.create( @@ -115,12 +132,17 @@ static void registerBuiltinMetricsWithUniverseDomain( credentials, endpoint, universeDomain, - new BigtableCloudMonitoringExporter.PublicTimeSeriesConverter()); + new BigtableCloudMonitoringExporter.PublicTimeSeriesConverter(), + executorService); for (Map.Entry entry : BuiltinMetricsConstants.getAllViews().entrySet()) { builder.registerView(entry.getKey(), entry.getValue()); } - builder.registerMetricReader(PeriodicMetricReader.create(publicExporter)); + PeriodicMetricReaderBuilder readerBuilder = PeriodicMetricReader.builder(publicExporter); + if (executorService != null) { + readerBuilder.setExecutor(executorService); + } + builder.registerMetricReader(readerBuilder.build()); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java index efcec28ffa..c0a8ed7f36 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java @@ -20,6 +20,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; /** * Set a custom OpenTelemetry instance. @@ -70,26 +71,39 @@ public OpenTelemetry getOpenTelemetry() { * Convenient method to set up SdkMeterProviderBuilder with the default credential and endpoint. */ public static void setupSdkMeterProvider(SdkMeterProviderBuilder builder) throws IOException { - setupSdkMeterProvider(builder, null, null); + setupSdkMeterProvider(builder, null, null, null); } /** Convenient method to set up SdkMeterProviderBuilder with a custom credential. */ public static void setupSdkMeterProvider(SdkMeterProviderBuilder builder, Credentials credentials) throws IOException { - setupSdkMeterProvider(builder, credentials, null); + setupSdkMeterProvider(builder, credentials, null, null); } /** Convenient method to set up SdkMeterProviderBuilder with a custom endpoint. */ public static void setupSdkMeterProvider(SdkMeterProviderBuilder builder, String endpoint) throws IOException { - setupSdkMeterProvider(builder, null, endpoint); + setupSdkMeterProvider(builder, null, endpoint, null); } - /** Convenient method to set up SdkMeterProviderBuilder with a custom credentials and endpoint. */ + /** Convenient method to set up SdkMeterProviderBuilder with custom credentials and endpoint. */ public static void setupSdkMeterProvider( SdkMeterProviderBuilder builder, Credentials credentials, String endpoint) throws IOException { - BuiltinMetricsView.registerBuiltinMetrics(credentials, builder, endpoint); + setupSdkMeterProvider(builder, credentials, endpoint, null); + } + + /** + * Convenient method to set up SdkMeterProviderBuilder with custom credentials, endpoint and a + * shared executor service. + */ + public static void setupSdkMeterProvider( + SdkMeterProviderBuilder builder, + Credentials credentials, + String endpoint, + ScheduledExecutorService executor) + throws IOException { + BuiltinMetricsView.registerBuiltinMetrics(credentials, builder, endpoint, executor); } @Override diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java index 7b18125b95..4a226d25d9 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java @@ -22,6 +22,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; /** @@ -39,11 +40,14 @@ private DefaultMetricsProvider() {} @InternalApi public OpenTelemetry getOpenTelemetry( - @Nullable String metricsEndpoint, String universeDomain, @Nullable Credentials credentials) + @Nullable String metricsEndpoint, + String universeDomain, + @Nullable Credentials credentials, + ScheduledExecutorService executor) throws IOException { SdkMeterProviderBuilder meterProvider = SdkMeterProvider.builder(); BuiltinMetricsView.registerBuiltinMetricsWithUniverseDomain( - credentials, meterProvider, metricsEndpoint, universeDomain); + credentials, meterProvider, metricsEndpoint, universeDomain, executor); return OpenTelemetrySdk.builder().setMeterProvider(meterProvider.build()).build(); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java index 012aae024d..9ba2d39c49 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/Util.java @@ -52,6 +52,7 @@ import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import java.io.IOException; +import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; @@ -60,6 +61,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -256,7 +258,10 @@ static GrpcCallContext injectBigtableStreamTracer( } public static OpenTelemetrySdk newInternalOpentelemetry( - EnhancedBigtableStubSettings settings, Credentials credentials) throws IOException { + EnhancedBigtableStubSettings settings, + Credentials credentials, + ScheduledExecutorService executor) + throws IOException { SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); for (Map.Entry e : @@ -265,15 +270,19 @@ public static OpenTelemetrySdk newInternalOpentelemetry( } meterProviderBuilder.registerMetricReader( - PeriodicMetricReader.create( - BigtableCloudMonitoringExporter.create( - "application metrics", - credentials, - settings.getMetricsEndpoint(), - settings.getUniverseDomain(), - new BigtableCloudMonitoringExporter.InternalTimeSeriesConverter( - Suppliers.memoize( - () -> BigtableExporterUtils.createInternalMonitoredResource(settings)))))); + PeriodicMetricReader.builder( + BigtableCloudMonitoringExporter.create( + "application metrics", + credentials, + settings.getMetricsEndpoint(), + settings.getUniverseDomain(), + new BigtableCloudMonitoringExporter.InternalTimeSeriesConverter( + Suppliers.memoize( + () -> BigtableExporterUtils.createInternalMonitoredResource(settings))), + executor)) + .setExecutor(settings.getBackgroundExecutorProvider().getExecutor()) + .setInterval(Duration.ofMinutes(1)) + .build()); return OpenTelemetrySdk.builder().setMeterProvider(meterProviderBuilder.build()).build(); } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java index c3d326fbef..b8c187a8ac 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java @@ -167,7 +167,7 @@ public void transportTerminated(Attributes transportAttrs) { .stubSettings() .setTransportChannelProvider(transportChannelProvider) .setCredentialsProvider(credentialsProvider) - .setExecutorProvider(executorProvider) + .setBackgroundExecutorProvider(executorProvider) .setStreamWatchdogProvider(watchdogProvider) .setClock(apiClock);