Skip to content

Commit 552a34d

Browse files
authored
feat(Datastore): Introduce Client Side Metrics (#12718)
1 parent 797de91 commit 552a34d

25 files changed

Lines changed: 2521 additions & 169 deletions

java-datastore/google-cloud-datastore/pom.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@
7979
<groupId>com.google.protobuf</groupId>
8080
<artifactId>protobuf-java</artifactId>
8181
</dependency>
82+
<dependency>
83+
<groupId>com.google.protobuf</groupId>
84+
<artifactId>protobuf-java-util</artifactId>
85+
</dependency>
8286
<dependency>
8387
<groupId>com.google.api</groupId>
8488
<artifactId>gax</artifactId>
@@ -196,12 +200,20 @@
196200
<dependency>
197201
<groupId>io.opentelemetry</groupId>
198202
<artifactId>opentelemetry-sdk</artifactId>
199-
<scope>test</scope>
200203
</dependency>
201204
<dependency>
202205
<groupId>io.opentelemetry</groupId>
203206
<artifactId>opentelemetry-sdk-common</artifactId>
204-
<scope>test</scope>
207+
</dependency>
208+
<dependency>
209+
<groupId>com.google.cloud</groupId>
210+
<artifactId>google-cloud-monitoring</artifactId>
211+
<version>3.91.0</version><!-- {x-version-update:google-cloud-monitoring:current} -->
212+
</dependency>
213+
<dependency>
214+
<groupId>com.google.api.grpc</groupId>
215+
<artifactId>proto-google-cloud-monitoring-v3</artifactId>
216+
<version>3.91.0</version><!-- {x-version-update:google-cloud-monitoring:current} -->
205217
</dependency>
206218
<dependency>
207219
<groupId>io.opentelemetry</groupId>
@@ -216,7 +228,6 @@
216228
<dependency>
217229
<groupId>io.opentelemetry</groupId>
218230
<artifactId>opentelemetry-sdk-metrics</artifactId>
219-
<scope>test</scope>
220231
</dependency>
221232
<dependency>
222233
<groupId>io.opentelemetry</groupId>

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreImpl.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.cloud.ServiceOptions;
4848
import com.google.cloud.datastore.execution.AggregationQueryExecutor;
4949
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
50+
import com.google.cloud.datastore.telemetry.BuiltInDatastoreMetricsProvider;
5051
import com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder;
5152
import com.google.cloud.datastore.telemetry.TelemetryConstants;
5253
import com.google.cloud.datastore.telemetry.TelemetryUtils;
@@ -69,7 +70,9 @@
6970
import com.google.datastore.v1.TransactionOptions;
7071
import com.google.protobuf.ByteString;
7172
import io.grpc.Status;
73+
import io.opentelemetry.api.OpenTelemetry;
7274
import io.opentelemetry.context.Context;
75+
import io.opentelemetry.sdk.OpenTelemetrySdk;
7376
import java.util.ArrayList;
7477
import java.util.Arrays;
7578
import java.util.Collections;
@@ -98,7 +101,9 @@ final class DatastoreImpl extends BaseService<DatastoreOptions> implements Datas
98101

99102
private final com.google.cloud.datastore.telemetry.TraceUtil otelTraceUtil =
100103
getOptions().getTraceUtil();
101-
private final DatastoreMetricsRecorder metricsRecorder = getOptions().getMetricsRecorder();
104+
private final DatastoreMetricsRecorder metricsRecorder;
105+
private final OpenTelemetry builtInOpenTelemetry;
106+
102107
private final ReadOptionProtoPreparer readOptionProtoPreparer;
103108
private final AggregationQueryExecutor aggregationQueryExecutor;
104109

@@ -107,6 +112,8 @@ final class DatastoreImpl extends BaseService<DatastoreOptions> implements Datas
107112
this.datastoreRpc = options.getDatastoreRpcV1();
108113
retrySettings =
109114
MoreObjects.firstNonNull(options.getRetrySettings(), ServiceOptions.getNoRetrySettings());
115+
builtInOpenTelemetry = BuiltInDatastoreMetricsProvider.INSTANCE.createOpenTelemetry(options);
116+
metricsRecorder = DatastoreMetricsRecorder.getInstance(options, builtInOpenTelemetry);
110117

111118
readOptionProtoPreparer = new ReadOptionProtoPreparer();
112119
aggregationQueryExecutor =
@@ -162,13 +169,31 @@ public T call() throws DatastoreException {
162169
}
163170
}
164171

172+
/**
173+
* Closes the Datastore client and releases all resources.
174+
*
175+
* <p>This method closes the underlying RPC channel and then closes the {@link
176+
* com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder}. For clients using the built-in
177+
* Cloud Monitoring exporter, closing the recorder flushes any buffered metrics and shuts down the
178+
* private {@link io.opentelemetry.sdk.OpenTelemetrySdk} instance. For clients using a
179+
* user-provided {@link io.opentelemetry.api.OpenTelemetry} instance, the recorder close is a
180+
* no-op since the user owns that instance's lifecycle.
181+
*/
165182
@Override
166183
public void close() throws Exception {
167184
try {
168185
datastoreRpc.close();
169186
} catch (Exception e) {
170187
logger.log(Level.WARNING, "Failed to close channels", e);
171188
}
189+
// Shut down the built-in OTel SDK as we manage its lifecycle
190+
if (builtInOpenTelemetry instanceof OpenTelemetrySdk) {
191+
try {
192+
((OpenTelemetrySdk) builtInOpenTelemetry).close();
193+
} catch (Exception e) {
194+
logger.log(Level.WARNING, "Failed to close built-in OpenTelemetry SDK instance.", e);
195+
}
196+
}
172197
}
173198

174199
@Override
@@ -242,7 +267,9 @@ public T call() throws DatastoreException {
242267
private void recordAttempt(String status) {
243268
Map<String, String> attributes =
244269
TelemetryUtils.buildMetricAttributes(
245-
TelemetryConstants.METHOD_TRANSACTION_COMMIT, status);
270+
TelemetryConstants.METHOD_TRANSACTION_COMMIT,
271+
status,
272+
datastore.getOptions().getDatabaseId());
246273
metricsRecorder.recordTransactionAttemptCount(1, attributes);
247274
}
248275
}
@@ -280,7 +307,8 @@ public <T> T runInTransaction(
280307
} finally {
281308
long latencyMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);
282309
Map<String, String> attributes =
283-
TelemetryUtils.buildMetricAttributes(TelemetryConstants.METHOD_TRANSACTION_RUN, status);
310+
TelemetryUtils.buildMetricAttributes(
311+
TelemetryConstants.METHOD_TRANSACTION_RUN, status, getOptions().getDatabaseId());
284312
metricsRecorder.recordTransactionLatency(latencyMs, attributes);
285313
span.end();
286314
}
@@ -789,7 +817,8 @@ private <T> T runWithObservability(
789817

790818
DatastoreOptions options = getOptions();
791819
Callable<T> attemptCallable =
792-
TelemetryUtils.attemptMetricsCallable(callable, metricsRecorder, methodName);
820+
TelemetryUtils.attemptMetricsCallable(
821+
callable, metricsRecorder, methodName, options.getDatabaseId());
793822
try (TraceUtil.Scope ignored = span.makeCurrent()) {
794823
return RetryHelper.runWithRetries(
795824
attemptCallable, retrySettings, exceptionHandler, options.getClock());
@@ -799,7 +828,11 @@ private <T> T runWithObservability(
799828
throw DatastoreException.translateAndThrow(e);
800829
} finally {
801830
TelemetryUtils.recordOperationMetrics(
802-
metricsRecorder, operationStopwatch, methodName, operationStatus);
831+
metricsRecorder,
832+
operationStopwatch,
833+
methodName,
834+
operationStatus,
835+
options.getDatabaseId());
803836
span.end();
804837
}
805838
}

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOpenTelemetryOptions.java

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,39 @@
1616

1717
package com.google.cloud.datastore;
1818

19+
import com.google.api.core.BetaApi;
1920
import io.opentelemetry.api.OpenTelemetry;
2021
import javax.annotation.Nonnull;
2122
import javax.annotation.Nullable;
2223

24+
/**
25+
* Represents the options that are used to configure the use of OpenTelemetry for telemetry
26+
* collection in the Datastore SDK.
27+
*/
2328
public class DatastoreOpenTelemetryOptions {
2429
private final boolean tracingEnabled;
2530
private final boolean metricsEnabled;
31+
private final boolean exportBuiltinMetricsToGoogleCloudMonitoring;
2632
private final @Nullable OpenTelemetry openTelemetry;
2733

2834
DatastoreOpenTelemetryOptions(Builder builder) {
2935
this.tracingEnabled = builder.tracingEnabled;
3036
this.metricsEnabled = builder.metricsEnabled;
37+
this.exportBuiltinMetricsToGoogleCloudMonitoring =
38+
builder.exportBuiltinMetricsToGoogleCloudMonitoring;
3139
this.openTelemetry = builder.openTelemetry;
3240
}
3341

3442
/**
35-
* Returns whether either tracing or metrics are enabled. Telemetry is disabled by default.
43+
* Returns whether either tracing or custom metrics (via a user-provided {@link OpenTelemetry}
44+
* instance) are enabled.
45+
*
46+
* <p><b>Note:</b> This method does <em>not</em> reflect the state of built-in metrics export to
47+
* Google Cloud Monitoring, which is controlled separately by {@link
48+
* #isExportBuiltinMetricsToGoogleCloudMonitoring()} and is {@code false} by default. To check
49+
* whether any telemetry is active, also consult that flag.
3650
*
37-
* @return {@code true} if either tracing or metrics are enabled, {@code false} otherwise.
51+
* @return {@code true} if tracing or custom OTel metrics are enabled, {@code false} otherwise.
3852
*/
3953
public boolean isEnabled() {
4054
return tracingEnabled || metricsEnabled;
@@ -50,24 +64,54 @@ public boolean isTracingEnabled() {
5064
}
5165

5266
/**
53-
* Returns whether metrics are enabled.
67+
* Returns whether metrics are enabled for the custom (user-provided) OpenTelemetry backend.
5468
*
5569
* @return {@code true} if metrics are enabled, {@code false} otherwise.
5670
*/
5771
public boolean isMetricsEnabled() {
5872
return metricsEnabled;
5973
}
6074

75+
/**
76+
* Returns whether built-in metrics should be exported to Google Cloud Monitoring.
77+
*
78+
* <p>When enabled, client-side metrics are automatically exported to Google Cloud Monitoring
79+
* using the Cloud Monitoring API. This is independent of the custom OpenTelemetry backend
80+
* configured via {@link #getOpenTelemetry()}.
81+
*
82+
* @return {@code true} if built-in metrics export to Cloud Monitoring is enabled, {@code false}
83+
* otherwise.
84+
*/
85+
@BetaApi
86+
public boolean isExportBuiltinMetricsToGoogleCloudMonitoring() {
87+
return exportBuiltinMetricsToGoogleCloudMonitoring;
88+
}
89+
90+
/**
91+
* Returns the custom {@link OpenTelemetry} instance, if one was provided.
92+
*
93+
* @return the custom {@link OpenTelemetry} instance, or {@code null} if none was provided.
94+
*/
6195
@Nullable
6296
public OpenTelemetry getOpenTelemetry() {
6397
return openTelemetry;
6498
}
6599

100+
/**
101+
* Returns a new {@link Builder} initialized with the values from this options instance.
102+
*
103+
* @return a new {@link Builder}.
104+
*/
66105
@Nonnull
67106
public DatastoreOpenTelemetryOptions.Builder toBuilder() {
68107
return new DatastoreOpenTelemetryOptions.Builder(this);
69108
}
70109

110+
/**
111+
* Returns a new default {@link Builder}.
112+
*
113+
* @return a new {@link Builder}.
114+
*/
71115
@Nonnull
72116
public static DatastoreOpenTelemetryOptions.Builder newBuilder() {
73117
return new DatastoreOpenTelemetryOptions.Builder();
@@ -77,25 +121,31 @@ public static class Builder {
77121

78122
private boolean tracingEnabled;
79123
private boolean metricsEnabled;
124+
private boolean exportBuiltinMetricsToGoogleCloudMonitoring;
80125

81126
@Nullable private OpenTelemetry openTelemetry;
82127

83128
private Builder() {
84129
tracingEnabled = false;
85130
metricsEnabled = false;
131+
// TODO(b/405457573): This is disabled by default until the Firestore namespace is deployed
132+
exportBuiltinMetricsToGoogleCloudMonitoring = false;
86133
openTelemetry = null;
87134
}
88135

89136
private Builder(DatastoreOpenTelemetryOptions options) {
90137
this.tracingEnabled = options.tracingEnabled;
91138
this.metricsEnabled = options.metricsEnabled;
139+
this.exportBuiltinMetricsToGoogleCloudMonitoring =
140+
options.exportBuiltinMetricsToGoogleCloudMonitoring;
92141
this.openTelemetry = options.openTelemetry;
93142
}
94143

95144
/**
96145
* Sets whether tracing should be enabled.
97146
*
98147
* @param enabled Whether tracing should be enabled.
148+
* @return this builder instance.
99149
*/
100150
@Nonnull
101151
public DatastoreOpenTelemetryOptions.Builder setTracingEnabled(boolean enabled) {
@@ -104,23 +154,41 @@ public DatastoreOpenTelemetryOptions.Builder setTracingEnabled(boolean enabled)
104154
}
105155

106156
/**
107-
* Sets whether metrics should be enabled.
157+
* Sets whether metrics should be enabled for the custom (user-provided) OpenTelemetry backend.
108158
*
109159
* @param enabled Whether metrics should be enabled.
110-
* @return the builder instance.
160+
* @return this builder instance.
111161
*/
112162
@Nonnull
113163
DatastoreOpenTelemetryOptions.Builder setMetricsEnabled(boolean enabled) {
114164
this.metricsEnabled = enabled;
115165
return this;
116166
}
117167

168+
/**
169+
* Sets whether built-in metrics should be exported to Google Cloud Monitoring.
170+
*
171+
* <p>When enabled, client-side metrics are automatically exported to Google Cloud Monitoring
172+
* using the Cloud Monitoring API. This can be disabled to prevent metrics from being sent to
173+
* Cloud Monitoring while still allowing metrics to flow to a custom OpenTelemetry backend.
174+
*
175+
* @param exportBuiltinMetrics Whether built-in metrics should be exported to Cloud Monitoring.
176+
* @return this builder instance.
177+
*/
178+
@BetaApi
179+
public DatastoreOpenTelemetryOptions.Builder setExportBuiltinMetricsToGoogleCloudMonitoring(
180+
boolean exportBuiltinMetrics) {
181+
this.exportBuiltinMetricsToGoogleCloudMonitoring = exportBuiltinMetrics;
182+
return this;
183+
}
184+
118185
/**
119186
* Sets the {@link OpenTelemetry} to use with this Datastore instance. If telemetry collection
120-
* is enabled, but an `OpenTelemetry` is not provided, the Datastore SDK will attempt to use the
121-
* `GlobalOpenTelemetry`.
187+
* is enabled, but an {@code OpenTelemetry} is not provided, the Datastore SDK will attempt to
188+
* use the {@code GlobalOpenTelemetry}.
122189
*
123190
* @param openTelemetry The OpenTelemetry that should be used by this Datastore instance.
191+
* @return this builder instance.
124192
*/
125193
@Nonnull
126194
public DatastoreOpenTelemetryOptions.Builder setOpenTelemetry(
@@ -129,6 +197,11 @@ public DatastoreOpenTelemetryOptions.Builder setOpenTelemetry(
129197
return this;
130198
}
131199

200+
/**
201+
* Builds a new {@link DatastoreOpenTelemetryOptions} instance from this builder.
202+
*
203+
* @return a new {@link DatastoreOpenTelemetryOptions}.
204+
*/
132205
@Nonnull
133206
public DatastoreOpenTelemetryOptions build() {
134207
return new DatastoreOpenTelemetryOptions(this);

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOptions.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
3232
import com.google.cloud.datastore.spi.v1.GrpcDatastoreRpc;
3333
import com.google.cloud.datastore.spi.v1.HttpDatastoreRpc;
34-
import com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder;
3534
import com.google.cloud.datastore.v1.DatastoreSettings;
3635
import com.google.cloud.grpc.GrpcTransportOptions;
3736
import com.google.cloud.http.HttpTransportOptions;
@@ -82,7 +81,6 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
8281

8382
private final transient @Nonnull DatastoreOpenTelemetryOptions openTelemetryOptions;
8483
private final transient @Nonnull com.google.cloud.datastore.telemetry.TraceUtil traceUtil;
85-
private final transient @Nonnull DatastoreMetricsRecorder metricsRecorder;
8684

8785
public static class DefaultDatastoreFactory implements DatastoreFactory {
8886

@@ -123,11 +121,6 @@ public DatastoreOpenTelemetryOptions getOpenTelemetryOptions() {
123121
return openTelemetryOptions;
124122
}
125123

126-
@Nonnull
127-
DatastoreMetricsRecorder getMetricsRecorder() {
128-
return metricsRecorder;
129-
}
130-
131124
public static class Builder extends ServiceOptions.Builder<Datastore, DatastoreOptions, Builder> {
132125

133126
private String namespace;
@@ -240,7 +233,6 @@ private DatastoreOptions(Builder builder) {
240233
? builder.openTelemetryOptions
241234
: DatastoreOpenTelemetryOptions.newBuilder().build();
242235
this.traceUtil = com.google.cloud.datastore.telemetry.TraceUtil.getInstance(this);
243-
this.metricsRecorder = DatastoreMetricsRecorder.getInstance(this);
244236

245237
namespace = MoreObjects.firstNonNull(builder.namespace, defaultNamespace());
246238
databaseId = MoreObjects.firstNonNull(builder.databaseId, DEFAULT_DATABASE_ID);

0 commit comments

Comments
 (0)