diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java index ef1d70eec2..9367c45b63 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInOpenTelemetryMetricsProvider.java @@ -20,7 +20,6 @@ import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_HASH_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_NAME_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_UID_KEY; -import static com.google.cloud.spanner.BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.LOCATION_ID_KEY; import static com.google.cloud.spanner.BuiltInMetricsConstant.PROJECT_ID_KEY; @@ -83,8 +82,6 @@ Map createClientAttributes(String projectId, String client_name) Map clientAttributes = new HashMap<>(); clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation()); clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId); - // TODO: Replace this with real value. - clientAttributes.put(DIRECT_PATH_ENABLED_KEY.getKey(), "false"); clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "unknown"); clientAttributes.put(CLIENT_NAME_KEY.getKey(), client_name); String clientUid = getDefaultTaskValue(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 2360b5d517..fe23b09798 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -29,6 +29,7 @@ import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.grpc.GrpcTransportChannel; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.retrying.ResultRetryAlgorithm; @@ -78,13 +79,14 @@ import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStub; import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStubSettings; import com.google.cloud.spanner.encryption.EncryptionConfigProtoMapper; -import com.google.cloud.spanner.v1.stub.GrpcSpannerStub; import com.google.cloud.spanner.v1.stub.SpannerStub; import com.google.cloud.spanner.v1.stub.SpannerStubSettings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; @@ -276,6 +278,8 @@ public class GapicSpannerRpc implements SpannerRpc { private final int numChannels; private final boolean isGrpcGcpExtensionEnabled; + private Supplier directPathEnabledSupplier = () -> false; + public static GapicSpannerRpc create(SpannerOptions options) { return new GapicSpannerRpc(options); } @@ -351,7 +355,9 @@ public GapicSpannerRpc(final SpannerOptions options) { SpannerInterceptorProvider.create( MoreObjects.firstNonNull( options.getInterceptorProvider(), - SpannerInterceptorProvider.createDefault(options.getOpenTelemetry()))) + SpannerInterceptorProvider.createDefault( + options.getOpenTelemetry(), + (() -> directPathEnabledSupplier.get())))) // This sets the trace context headers. .withTraceContext(endToEndTracingEnabled, options.getOpenTelemetry()) // This sets the response compressor (Server -> Client). @@ -396,18 +402,27 @@ public GapicSpannerRpc(final SpannerOptions options) { final String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST"); try { + SpannerStubSettings spannerStubSettings = + options + .getSpannerStubSettings() + .toBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(credentialsProvider) + .setStreamWatchdogProvider(watchdogProvider) + .setTracerFactory( + options.getApiTracerFactory( + /* isAdminClient = */ false, isEmulatorEnabled(options, emulatorHost))) + .build(); + ClientContext clientContext = ClientContext.create(spannerStubSettings); this.spannerStub = - GrpcSpannerStub.create( - options - .getSpannerStubSettings() - .toBuilder() - .setTransportChannelProvider(channelProvider) - .setCredentialsProvider(credentialsProvider) - .setStreamWatchdogProvider(watchdogProvider) - .setTracerFactory( - options.getApiTracerFactory( - /* isAdminClient = */ false, isEmulatorEnabled(options, emulatorHost))) - .build()); + GrpcSpannerStubWithStubSettingsAndClientContext.create( + spannerStubSettings, clientContext); + this.directPathEnabledSupplier = + Suppliers.memoize( + () -> { + return ((GrpcTransportChannel) clientContext.getTransportChannel()).isDirectPath() + && isAttemptDirectPathXds; + }); this.readRetrySettings = options.getSpannerStubSettings().streamingReadSettings().getRetrySettings(); this.readRetryableCodes = @@ -455,7 +470,8 @@ public GapicSpannerRpc(final SpannerOptions options) { .getStreamWatchdogProvider() .withCheckInterval(pdmlSettings.getStreamWatchdogCheckInterval())); } - this.partitionedDmlStub = GrpcSpannerStub.create(pdmlSettings.build()); + this.partitionedDmlStub = + GrpcSpannerStubWithStubSettingsAndClientContext.create(pdmlSettings.build()); this.instanceAdminStubSettings = options .getInstanceAdminStubSettings() diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcSpannerStubWithStubSettingsAndClientContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcSpannerStubWithStubSettingsAndClientContext.java new file mode 100644 index 0000000000..6c575e1074 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcSpannerStubWithStubSettingsAndClientContext.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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 + * + * http://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.spanner.spi.v1; + +import com.google.api.gax.rpc.ClientContext; +import com.google.cloud.spanner.v1.stub.GrpcSpannerStub; +import com.google.cloud.spanner.v1.stub.SpannerStubSettings; +import java.io.IOException; + +/** + * Wrapper around {@link GrpcSpannerStub} to make the constructor available inside this package. + * This makes it possible to create a {@link GrpcSpannerStub} with a {@link SpannerStubSettings} and + * a {@link ClientContext}. + */ +class GrpcSpannerStubWithStubSettingsAndClientContext extends GrpcSpannerStub { + + static final GrpcSpannerStubWithStubSettingsAndClientContext create( + SpannerStubSettings settings, ClientContext clientContext) throws IOException { + return new GrpcSpannerStubWithStubSettingsAndClientContext(settings, clientContext); + } + + protected GrpcSpannerStubWithStubSettingsAndClientContext( + SpannerStubSettings settings, ClientContext clientContext) throws IOException { + super(settings, clientContext); + } +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java index dd414bed39..026f9b4ca9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java @@ -28,6 +28,7 @@ import com.google.cloud.spanner.CompositeTracer; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerRpcMetrics; +import com.google.common.base.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.spanner.admin.database.v1.DatabaseName; @@ -93,8 +94,12 @@ class HeaderInterceptor implements ClientInterceptor { private static final Level LEVEL = Level.INFO; private final SpannerRpcMetrics spannerRpcMetrics; - HeaderInterceptor(SpannerRpcMetrics spannerRpcMetrics) { + private final Supplier directPathEnabledSupplier; + + HeaderInterceptor( + SpannerRpcMetrics spannerRpcMetrics, Supplier directPathEnabledSupplier) { this.spannerRpcMetrics = spannerRpcMetrics; + this.directPathEnabledSupplier = directPathEnabledSupplier; } @Override @@ -228,6 +233,9 @@ private Map getBuiltInMetricAttributes(String key, DatabaseName attributes.put(BuiltInMetricsConstant.DATABASE_KEY.getKey(), databaseName.getDatabase()); attributes.put( BuiltInMetricsConstant.INSTANCE_ID_KEY.getKey(), databaseName.getInstance()); + attributes.put( + BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY.getKey(), + String.valueOf(this.directPathEnabledSupplier.get())); return attributes; }); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java index b4d28ef078..c3c05b8af1 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java @@ -19,6 +19,8 @@ import com.google.api.core.ObsoleteApi; import com.google.api.gax.grpc.GrpcInterceptorProvider; import com.google.cloud.spanner.SpannerRpcMetrics; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import io.grpc.ClientInterceptor; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -46,11 +48,22 @@ public static SpannerInterceptorProvider createDefault() { } public static SpannerInterceptorProvider createDefault(OpenTelemetry openTelemetry) { + return createDefault( + openTelemetry, + Suppliers.memoize( + () -> { + return false; + })); + } + + public static SpannerInterceptorProvider createDefault( + OpenTelemetry openTelemetry, Supplier directPathEnabledSupplier) { List defaultInterceptorList = new ArrayList<>(); defaultInterceptorList.add(new SpannerErrorInterceptor()); defaultInterceptorList.add( new LoggingInterceptor(Logger.getLogger(GapicSpannerRpc.class.getName()), Level.FINER)); - defaultInterceptorList.add(new HeaderInterceptor(new SpannerRpcMetrics(openTelemetry))); + defaultInterceptorList.add( + new HeaderInterceptor(new SpannerRpcMetrics(openTelemetry), directPathEnabledSupplier)); return new SpannerInterceptorProvider(ImmutableList.copyOf(defaultInterceptorList)); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java index b493e36c96..9f65402c31 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetryBuiltInMetricsTracerTest.java @@ -94,7 +94,6 @@ public static void setup() { Attributes.builder() .put(BuiltInMetricsConstant.PROJECT_ID_KEY, "test-project") .put(BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY, "unknown") - .put(BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY, "false") .put( BuiltInMetricsConstant.LOCATION_ID_KEY, BuiltInOpenTelemetryMetricsProvider.detectClientLocation())