Skip to content

Commit 518a1f4

Browse files
committed
chore: add support for OpenTelemetry metrics to Connection API
1 parent e99b78c commit 518a1f4

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import com.google.common.base.Strings;
4040
import com.google.common.collect.Sets;
4141
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
42+
import io.opentelemetry.api.GlobalOpenTelemetry;
43+
import io.opentelemetry.api.OpenTelemetry;
4244
import java.io.IOException;
4345
import java.lang.reflect.Constructor;
4446
import java.lang.reflect.InvocationTargetException;
@@ -54,6 +56,7 @@
5456
import java.util.regex.Matcher;
5557
import java.util.regex.Pattern;
5658
import java.util.stream.Stream;
59+
import javax.annotation.Nonnull;
5760
import javax.annotation.Nullable;
5861

5962
/**
@@ -169,6 +172,7 @@ public String[] getValidValues() {
169172
static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true;
170173
static final boolean DEFAULT_USE_VIRTUAL_THREADS = false;
171174
static final boolean DEFAULT_USE_VIRTUAL_GRPC_TRANSPORT_THREADS = false;
175+
static final boolean DEFAULT_ENABLE_OPENTELEMETRY_METRICS = false;
172176
private static final String DEFAULT_CREDENTIALS = null;
173177
private static final String DEFAULT_OAUTH_TOKEN = null;
174178
private static final String DEFAULT_MIN_SESSIONS = null;
@@ -211,6 +215,9 @@ public String[] getValidValues() {
211215
/** Name of the property to enable/disable virtual threads for gRPC transport. */
212216
public static final String USE_VIRTUAL_GRPC_TRANSPORT_THREADS_PROPERTY_NAME =
213217
"useVirtualGrpcTransportThreads";
218+
/** Name of the property to enable/disable OpenTelemtry metrics. */
219+
public static final String ENABLE_OPENTELEMETRY_METRICS_PROPERTY_NAME =
220+
"enableOpenTelemetryMetrics";
214221
/** Name of the 'credentials' connection property. */
215222
public static final String CREDENTIALS_PROPERTY_NAME = "credentials";
216223
/** Name of the 'encodedCredentials' connection property. */
@@ -310,6 +317,10 @@ private static String generateGuardedConnectionPropertyError(
310317
"Use a virtual thread instead of a platform thread for the gRPC executor (true/false). "
311318
+ "This option only has any effect if the application is running on Java 21 or higher. In all other cases, the option is ignored.",
312319
DEFAULT_USE_VIRTUAL_GRPC_TRANSPORT_THREADS),
320+
ConnectionProperty.createBooleanProperty(
321+
ENABLE_OPENTELEMETRY_METRICS_PROPERTY_NAME,
322+
"Enable OpenTelemetry metrics in the client library (true/false). ",
323+
DEFAULT_ENABLE_OPENTELEMETRY_METRICS),
313324
ConnectionProperty.createStringProperty(
314325
CREDENTIALS_PROPERTY_NAME,
315326
"The location of the credentials file to use for this connection. If neither this property or encoded credentials are set, the connection will use the default Google Cloud credentials for the runtime environment."),
@@ -484,6 +495,7 @@ public static class Builder {
484495
private List<StatementExecutionInterceptor> statementExecutionInterceptors =
485496
Collections.emptyList();
486497
private SpannerOptionsConfigurator configurator;
498+
private OpenTelemetry openTelemetry;
487499

488500
private Builder() {}
489501

@@ -633,6 +645,11 @@ Builder setCredentials(Credentials credentials) {
633645
return this;
634646
}
635647

648+
public Builder setOpenTelemetry(OpenTelemetry openTelemetry) {
649+
this.openTelemetry = openTelemetry;
650+
return this;
651+
}
652+
636653
/** @return the {@link ConnectionOptions} */
637654
public ConnectionOptions build() {
638655
Preconditions.checkState(this.uri != null, "Connection URI is required");
@@ -691,6 +708,8 @@ public static Builder newBuilder() {
691708
private final boolean retryAbortsInternally;
692709
private final boolean useVirtualThreads;
693710
private final boolean useVirtualGrpcTransportThreads;
711+
private final boolean enableOpenTelemetryMetrics;
712+
private final OpenTelemetry openTelemetry;
694713
private final List<StatementExecutionInterceptor> statementExecutionInterceptors;
695714
private final SpannerOptionsConfigurator configurator;
696715

@@ -792,6 +811,8 @@ private ConnectionOptions(Builder builder) {
792811
this.retryAbortsInternally = parseRetryAbortsInternally(this.uri);
793812
this.useVirtualThreads = parseUseVirtualThreads(this.uri);
794813
this.useVirtualGrpcTransportThreads = parseUseVirtualGrpcTransportThreads(this.uri);
814+
this.enableOpenTelemetryMetrics = parseEnableOpenTelemetryMetrics(this.uri);
815+
this.openTelemetry = builder.openTelemetry;
795816
this.statementExecutionInterceptors =
796817
Collections.unmodifiableList(builder.statementExecutionInterceptors);
797818
this.configurator = builder.configurator;
@@ -856,6 +877,19 @@ private static Integer parseIntegerProperty(String propertyName, String value) {
856877
return null;
857878
}
858879

880+
/**
881+
* Returns an instance of OpenTelemetry. If OpenTelemetry object is not set then
882+
* GlobalOpenTelemetry will be used as fallback.
883+
*/
884+
@Nonnull
885+
OpenTelemetry getOpenTelemetry() {
886+
if (this.openTelemetry != null) {
887+
return this.openTelemetry;
888+
} else {
889+
return GlobalOpenTelemetry.get();
890+
}
891+
}
892+
859893
SpannerOptionsConfigurator getConfigurator() {
860894
return configurator;
861895
}
@@ -906,6 +940,12 @@ static boolean parseUseVirtualGrpcTransportThreads(String uri) {
906940
return value != null ? Boolean.parseBoolean(value) : DEFAULT_USE_VIRTUAL_GRPC_TRANSPORT_THREADS;
907941
}
908942

943+
@VisibleForTesting
944+
static boolean parseEnableOpenTelemetryMetrics(String uri) {
945+
String value = parseUriProperty(uri, ENABLE_OPENTELEMETRY_METRICS_PROPERTY_NAME);
946+
return value != null ? Boolean.parseBoolean(value) : DEFAULT_ENABLE_OPENTELEMETRY_METRICS;
947+
}
948+
909949
@VisibleForTesting
910950
static @Nullable String parseCredentials(String uri) {
911951
String value = parseUriProperty(uri, CREDENTIALS_PROPERTY_NAME);
@@ -1336,6 +1376,11 @@ public boolean isUseVirtualGrpcTransportThreads() {
13361376
return useVirtualGrpcTransportThreads;
13371377
}
13381378

1379+
/** Whether connections should enable OpenTelemetry metrics. */
1380+
public boolean isEnabledOpenTelemetryMetrics() {
1381+
return enableOpenTelemetryMetrics;
1382+
}
1383+
13391384
/** Any warnings that were generated while creating the {@link ConnectionOptions} instance. */
13401385
@Nullable
13411386
public String getWarnings() {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ static class SpannerPoolKey {
156156
private final String databaseRole;
157157
private final boolean routeToLeader;
158158
private final boolean useVirtualGrpcTransportThreads;
159+
private final boolean enableOpenTelemetryMetrics;
159160

160161
@VisibleForTesting
161162
static SpannerPoolKey of(ConnectionOptions options) {
@@ -183,6 +184,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException {
183184
this.userAgent = options.getUserAgent();
184185
this.routeToLeader = options.isRouteToLeader();
185186
this.useVirtualGrpcTransportThreads = options.isUseVirtualGrpcTransportThreads();
187+
this.enableOpenTelemetryMetrics = options.isEnabledOpenTelemetryMetrics();
186188
}
187189

188190
@Override
@@ -201,7 +203,8 @@ public boolean equals(Object o) {
201203
&& Objects.equals(this.userAgent, other.userAgent)
202204
&& Objects.equals(this.routeToLeader, other.routeToLeader)
203205
&& Objects.equals(
204-
this.useVirtualGrpcTransportThreads, other.useVirtualGrpcTransportThreads);
206+
this.useVirtualGrpcTransportThreads, other.useVirtualGrpcTransportThreads)
207+
&& Objects.equals(this.enableOpenTelemetryMetrics, other.enableOpenTelemetryMetrics);
205208
}
206209

207210
@Override
@@ -216,7 +219,8 @@ public int hashCode() {
216219
this.databaseRole,
217220
this.userAgent,
218221
this.routeToLeader,
219-
this.useVirtualGrpcTransportThreads);
222+
this.useVirtualGrpcTransportThreads,
223+
this.enableOpenTelemetryMetrics);
220224
}
221225
}
222226

@@ -337,6 +341,10 @@ private void initialize() {
337341

338342
@VisibleForTesting
339343
Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
344+
if (key.enableOpenTelemetryMetrics
345+
&& !ConnectionSpannerOptions.isEnabledOpenTelemetryMetrics()) {
346+
ConnectionSpannerOptions.enableOpenTelemetryMetrics();
347+
}
340348
ConnectionSpannerOptions.Builder builder = ConnectionSpannerOptions.newBuilder();
341349
builder
342350
.setUseVirtualThreads(key.useVirtualGrpcTransportThreads)
@@ -349,6 +357,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
349357
.setDatabaseRole(options.getDatabaseRole())
350358
.setCredentials(options.getCredentials());
351359
builder.setSessionPoolOption(key.sessionPoolOptions);
360+
if (key.enableOpenTelemetryMetrics) {
361+
builder.setOpenTelemetry(options.getOpenTelemetry());
362+
}
352363
if (key.numChannels != null) {
353364
builder.setNumChannels(key.numChannels);
354365
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,4 +1087,30 @@ public void testUseVirtualGrpcTransportThreads() {
10871087
.build()
10881088
.isUseVirtualThreads());
10891089
}
1090+
1091+
@Test
1092+
public void testEnableOpenTelemetryMetrics() {
1093+
assertTrue(
1094+
ConnectionOptions.newBuilder()
1095+
.setUri(
1096+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?enableOpenTelemetryMetrics=true")
1097+
.setCredentials(NoCredentials.getInstance())
1098+
.build()
1099+
.isEnabledOpenTelemetryMetrics());
1100+
assertFalse(
1101+
ConnectionOptions.newBuilder()
1102+
.setUri(
1103+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?enableOpenTelemetryMetrics=false")
1104+
.setCredentials(NoCredentials.getInstance())
1105+
.build()
1106+
.isEnabledOpenTelemetryMetrics());
1107+
assertEquals(
1108+
ConnectionOptions.DEFAULT_ENABLE_OPENTELEMETRY_METRICS,
1109+
ConnectionOptions.newBuilder()
1110+
.setUri(
1111+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database")
1112+
.setCredentials(NoCredentials.getInstance())
1113+
.build()
1114+
.isEnabledOpenTelemetryMetrics());
1115+
}
10901116
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ public class SpannerPoolTest {
6969
private ConnectionOptions options7 = mock(ConnectionOptions.class);
7070
private ConnectionOptions options8 = mock(ConnectionOptions.class);
7171

72+
private ConnectionOptions optionsDisabledMetrics = mock(ConnectionOptions.class);
73+
private ConnectionOptions optionsEnabledMetrics = mock(ConnectionOptions.class);
74+
7275
private SpannerPool createSubjectAndMocks() {
7376
return createSubjectAndMocks(0L, Ticker.systemTicker());
7477
}
@@ -101,6 +104,12 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
101104
when(options8.getProjectId()).thenReturn("test-project-3");
102105
when(options8.isRouteToLeader()).thenReturn(false);
103106

107+
// ConnectionOptions disabling/enabling OpenTelemetry metrics.
108+
when(optionsDisabledMetrics.getProjectId()).thenReturn("test-project-3");
109+
when(optionsDisabledMetrics.isEnabledOpenTelemetryMetrics()).thenReturn(false);
110+
when(optionsEnabledMetrics.getProjectId()).thenReturn("test-project-3");
111+
when(optionsEnabledMetrics.isEnabledOpenTelemetryMetrics()).thenReturn(true);
112+
104113
return pool;
105114
}
106115

@@ -498,4 +507,29 @@ public void testSpannerPoolKeyEquality() {
498507
assertEquals(key3, key4);
499508
assertNotEquals(key4, key5);
500509
}
510+
511+
@Test
512+
public void testGetSpannerForEnablingOpenTelemetryMetrics() {
513+
SpannerPool pool = createSubjectAndMocks();
514+
Spanner spanner1;
515+
Spanner spanner2;
516+
517+
// assert equal
518+
spanner1 = pool.getSpanner(optionsEnabledMetrics, connection1);
519+
spanner2 = pool.getSpanner(optionsEnabledMetrics, connection2);
520+
assertEquals(spanner1, spanner2);
521+
522+
spanner1 = pool.getSpanner(optionsDisabledMetrics, connection1);
523+
spanner2 = pool.getSpanner(optionsDisabledMetrics, connection2);
524+
assertEquals(spanner1, spanner2);
525+
526+
// assert not equal
527+
spanner1 = pool.getSpanner(optionsDisabledMetrics, connection1);
528+
spanner2 = pool.getSpanner(optionsEnabledMetrics, connection2);
529+
assertNotEquals(spanner1, spanner2);
530+
531+
spanner1 = pool.getSpanner(optionsEnabledMetrics, connection1);
532+
spanner2 = pool.getSpanner(optionsDisabledMetrics, connection2);
533+
assertNotEquals(spanner1, spanner2);
534+
}
501535
}

0 commit comments

Comments
 (0)