Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
03aabb2
chore(datastore): Update generated stubs and native image config
lqiu96 Apr 1, 2026
25af60d
chore(datastore): Rename internal metrics recorder hierarchy and fix …
lqiu96 Apr 1, 2026
2093485
Merge branch 'main' of github.com:googleapis/google-cloud-java into d…
lqiu96 Apr 1, 2026
f2f1e9b
chore: Fix variable names
lqiu96 Apr 1, 2026
ae438e8
chore: Fix variable names
lqiu96 Apr 1, 2026
6c8def8
chore: Resolve merge conflicts
lqiu96 Apr 1, 2026
29f7ce1
chore: Remove metrics sample
lqiu96 Apr 1, 2026
0bf197b
chore: Update to remove the extra attributes
lqiu96 Apr 2, 2026
842d262
feat(datastore): Add built-in Cloud Monitoring metrics export
lqiu96 Apr 1, 2026
20be0a9
chore(datastore): Restore constants needed by built-in metrics pipeli…
lqiu96 Apr 2, 2026
eeb3653
samples(datastore): Add client-side metrics sample
lqiu96 Apr 1, 2026
5dc702f
chore: Remove duplicate declaration of attempt and operation metrics
lqiu96 Apr 2, 2026
c82e1d8
test: update DatastoreImplMetricsTest to expect singular GAX metric n…
lqiu96 Apr 2, 2026
b36e61c
chore: Remove unnecessary transaction null check
lqiu96 Apr 2, 2026
c47a6eb
chore: Resolve merge conflicts
lqiu96 Apr 2, 2026
2acc3a3
chore: Ensure that view properally remaps singular to plural metrics
lqiu96 Apr 2, 2026
5f41919
chore: Update logic to determine the PID
lqiu96 Apr 2, 2026
a7ae636
chore: Remove the unncessary filtering logic for the private Otel ins…
lqiu96 Apr 2, 2026
ddd06f8
chore: Remove the unused clientName attribute
lqiu96 Apr 2, 2026
abd527e
samples(datastore): Add client-side metrics sample
lqiu96 Apr 1, 2026
ad5efe0
chore: Resolve merge conflicts
lqiu96 Apr 2, 2026
0707021
merge main into datastore-csm-impl-2
lqiu96 Apr 8, 2026
02ddff0
chore(datastore): Address PR feedback on OTel metrics
lqiu96 Apr 10, 2026
972e48c
chore: Update the google-cloud-monitoring version
lqiu96 Apr 13, 2026
31451ce
Merge branch 'main' of github.com:googleapis/google-cloud-java into d…
lqiu96 Apr 13, 2026
74ca437
chore: Update monitoring to the latest version
lqiu96 Apr 13, 2026
f5fb153
Merge branch 'main' into datastore-csm-impl-2
lqiu96 Apr 13, 2026
4dbc83a
chore: Remove the env var for enable datastore metrics
lqiu96 Apr 13, 2026
51399b5
Merge branch 'datastore-csm-impl-2' of github.com:googleapis/google-c…
lqiu96 Apr 13, 2026
33ce2f7
chore: Address PR feedback
lqiu96 Apr 13, 2026
c982e17
chore: Address PR feedback
lqiu96 Apr 17, 2026
981f6c4
chore: address PR feedback
lqiu96 Apr 17, 2026
7418242
chore: Remove credential hashing from MetricServiceClient cache key
lqiu96 Apr 17, 2026
6e0b04d
Merge branch 'main' into datastore-csm-impl-2
lqiu96 Apr 17, 2026
b37ca37
chore: Address PR feedback
lqiu96 Apr 22, 2026
9023c68
Merge branch 'main' into datastore-csm-impl-2
lqiu96 Apr 22, 2026
e25db58
chore: Refactor code and add comments
lqiu96 Apr 22, 2026
40ec408
Merge branch 'datastore-csm-impl-2' of github.com:googleapis/google-c…
lqiu96 Apr 22, 2026
9035de1
chore: refactor logic for Otel views
lqiu96 Apr 23, 2026
ec30b98
chore: address code review feedback for built-in metrics
lqiu96 Apr 23, 2026
0a0d897
Merge branch 'main' of github.com:googleapis/google-cloud-java into d…
lqiu96 Apr 23, 2026
da3f901
chore: Clean up PR
lqiu96 Apr 23, 2026
a804ca4
Merge branch 'main' into datastore-csm-impl-2
lqiu96 Apr 24, 2026
da326d5
chore: Resolve merge conflicts
lqiu96 Apr 24, 2026
8729826
Merge branch 'datastore-csm-impl-2' into datastore-csm-impl-3
lqiu96 Apr 24, 2026
701b10c
chore(datastore): use local version in snippets to fix build
lqiu96 Apr 24, 2026
ac56599
chore: Rename the IT class to use IT as the prefix
lqiu96 Apr 24, 2026
316cdbe
chore: rename the metrics ITs test
lqiu96 Apr 24, 2026
b9f269f
Merge branch 'datastore-csm-impl-3' into datastore-csm-impl-2
lqiu96 Apr 24, 2026
8fbc1c6
feat(datastore): add database_id to metric attributes and fix IT tests
lqiu96 Apr 24, 2026
c9f5e00
chore: fix lint issues
lqiu96 Apr 24, 2026
6920311
test(datastore): parameterize metrics IT to use both gRPC and HTTP tr…
lqiu96 Apr 24, 2026
f90a43e
test(datastore): add Cloud Monitoring verification to IT and move con…
lqiu96 Apr 24, 2026
e06f42a
test(datastore): improve metrics IT robustness and handle NOT_FOUND e…
lqiu96 Apr 24, 2026
04e9a28
test(datastore): remove Cloud Monitoring verification from IT and add…
lqiu96 Apr 24, 2026
481e613
chore(datastore): format code in test files
lqiu96 Apr 24, 2026
05d20aa
chore: Clean up the code
lqiu96 Apr 24, 2026
10a15c1
chore: Use release please to manage datastore snippet version
lqiu96 Apr 27, 2026
bcc0f76
chore: Use UUID with hashing
lqiu96 Apr 27, 2026
b60105f
chore: Add protobuf-util-java dep
lqiu96 Apr 27, 2026
dc073fb
Merge branch 'main' into datastore-csm-impl-2
lqiu96 Apr 27, 2026
6d749ad
chore: Do not send metrics if there is an emulator
lqiu96 Apr 27, 2026
adbe8b0
chore: Always return CompositeMetricsRecorder
lqiu96 Apr 27, 2026
0deff42
chore(datastore): address PR feedback, update test cases to match new…
lqiu96 Apr 28, 2026
a977960
test(datastore): ensure all created OpenTelemetry instances are close…
lqiu96 Apr 28, 2026
515092a
chore: Clean up code based on PR feedback
lqiu96 Apr 28, 2026
b2f1459
test(datastore): explicitly disable built-in metrics export in IT test
lqiu96 Apr 28, 2026
2c13e0d
test(datastore): clean up Javadoc in IT test to remove obsolete refer…
lqiu96 Apr 28, 2026
d05b612
chore: Update comments in the ITs
lqiu96 Apr 28, 2026
bfd9357
test(datastore): update test case to expect 1 NoOp recorder
lqiu96 Apr 29, 2026
4cf7f7c
chore: Address PR feedback
lqiu96 Apr 29, 2026
ae44891
test(datastore): fix NPE in emulator test case by passing NoCredentials
lqiu96 Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions java-datastore/google-cloud-datastore/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax</artifactId>
Expand Down Expand Up @@ -196,12 +200,20 @@
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-monitoring</artifactId>
<version>3.91.0</version><!-- {x-version-update:google-cloud-monitoring:current} -->
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-cloud-monitoring-v3</artifactId>
<version>3.91.0</version><!-- {x-version-update:google-cloud-monitoring:current} -->
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
Expand All @@ -216,7 +228,6 @@
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-metrics</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.google.cloud.ServiceOptions;
import com.google.cloud.datastore.execution.AggregationQueryExecutor;
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
import com.google.cloud.datastore.telemetry.BuiltInDatastoreMetricsProvider;
import com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder;
import com.google.cloud.datastore.telemetry.TelemetryConstants;
import com.google.cloud.datastore.telemetry.TelemetryUtils;
Expand All @@ -69,7 +70,9 @@
import com.google.datastore.v1.TransactionOptions;
import com.google.protobuf.ByteString;
import io.grpc.Status;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -98,7 +101,9 @@ final class DatastoreImpl extends BaseService<DatastoreOptions> implements Datas

private final com.google.cloud.datastore.telemetry.TraceUtil otelTraceUtil =
getOptions().getTraceUtil();
private final DatastoreMetricsRecorder metricsRecorder = getOptions().getMetricsRecorder();
private final DatastoreMetricsRecorder metricsRecorder;
private final OpenTelemetry builtInOpenTelemetry;

private final ReadOptionProtoPreparer readOptionProtoPreparer;
private final AggregationQueryExecutor aggregationQueryExecutor;

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

readOptionProtoPreparer = new ReadOptionProtoPreparer();
aggregationQueryExecutor =
Expand Down Expand Up @@ -162,13 +169,31 @@ public T call() throws DatastoreException {
}
}

/**
* Closes the Datastore client and releases all resources.
*
* <p>This method closes the underlying RPC channel and then closes the {@link
* com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder}. For clients using the built-in
* Cloud Monitoring exporter, closing the recorder flushes any buffered metrics and shuts down the
* private {@link io.opentelemetry.sdk.OpenTelemetrySdk} instance. For clients using a
* user-provided {@link io.opentelemetry.api.OpenTelemetry} instance, the recorder close is a
* no-op since the user owns that instance's lifecycle.
*/
@Override
public void close() throws Exception {
try {
datastoreRpc.close();
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to close channels", e);
}
// Shut down the built-in OTel SDK as we manage its lifecycle
if (builtInOpenTelemetry instanceof OpenTelemetrySdk) {
Comment thread
blakeli0 marked this conversation as resolved.
try {
((OpenTelemetrySdk) builtInOpenTelemetry).close();
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to close built-in OpenTelemetry SDK instance.", e);
}
}
}

@Override
Expand Down Expand Up @@ -242,7 +267,9 @@ public T call() throws DatastoreException {
private void recordAttempt(String status) {
Map<String, String> attributes =
TelemetryUtils.buildMetricAttributes(
TelemetryConstants.METHOD_TRANSACTION_COMMIT, status);
TelemetryConstants.METHOD_TRANSACTION_COMMIT,
status,
datastore.getOptions().getDatabaseId());
metricsRecorder.recordTransactionAttemptCount(1, attributes);
}
}
Expand Down Expand Up @@ -280,7 +307,8 @@ public <T> T runInTransaction(
} finally {
long latencyMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);
Map<String, String> attributes =
TelemetryUtils.buildMetricAttributes(TelemetryConstants.METHOD_TRANSACTION_RUN, status);
TelemetryUtils.buildMetricAttributes(
TelemetryConstants.METHOD_TRANSACTION_RUN, status, getOptions().getDatabaseId());
metricsRecorder.recordTransactionLatency(latencyMs, attributes);
span.end();
}
Expand Down Expand Up @@ -789,7 +817,8 @@ private <T> T runWithObservability(

DatastoreOptions options = getOptions();
Callable<T> attemptCallable =
TelemetryUtils.attemptMetricsCallable(callable, metricsRecorder, methodName);
TelemetryUtils.attemptMetricsCallable(
callable, metricsRecorder, methodName, options.getDatabaseId());
try (TraceUtil.Scope ignored = span.makeCurrent()) {
return RetryHelper.runWithRetries(
attemptCallable, retrySettings, exceptionHandler, options.getClock());
Expand All @@ -799,7 +828,11 @@ private <T> T runWithObservability(
throw DatastoreException.translateAndThrow(e);
} finally {
TelemetryUtils.recordOperationMetrics(
metricsRecorder, operationStopwatch, methodName, operationStatus);
metricsRecorder,
operationStopwatch,
methodName,
operationStatus,
options.getDatabaseId());
span.end();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,39 @@

package com.google.cloud.datastore;

import com.google.api.core.BetaApi;
import io.opentelemetry.api.OpenTelemetry;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* Represents the options that are used to configure the use of OpenTelemetry for telemetry
* collection in the Datastore SDK.
*/
public class DatastoreOpenTelemetryOptions {
private final boolean tracingEnabled;
private final boolean metricsEnabled;
private final boolean exportBuiltinMetricsToGoogleCloudMonitoring;
private final @Nullable OpenTelemetry openTelemetry;

DatastoreOpenTelemetryOptions(Builder builder) {
this.tracingEnabled = builder.tracingEnabled;
this.metricsEnabled = builder.metricsEnabled;
this.exportBuiltinMetricsToGoogleCloudMonitoring =
builder.exportBuiltinMetricsToGoogleCloudMonitoring;
this.openTelemetry = builder.openTelemetry;
}

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

/**
* Returns whether metrics are enabled.
* Returns whether metrics are enabled for the custom (user-provided) OpenTelemetry backend.
*
* @return {@code true} if metrics are enabled, {@code false} otherwise.
*/
public boolean isMetricsEnabled() {
return metricsEnabled;
}

/**
* Returns whether built-in metrics should be exported to Google Cloud Monitoring.
*
* <p>When enabled, client-side metrics are automatically exported to Google Cloud Monitoring
* using the Cloud Monitoring API. This is independent of the custom OpenTelemetry backend
* configured via {@link #getOpenTelemetry()}.
*
* @return {@code true} if built-in metrics export to Cloud Monitoring is enabled, {@code false}
* otherwise.
*/
@BetaApi
public boolean isExportBuiltinMetricsToGoogleCloudMonitoring() {
return exportBuiltinMetricsToGoogleCloudMonitoring;
}

/**
* Returns the custom {@link OpenTelemetry} instance, if one was provided.
*
* @return the custom {@link OpenTelemetry} instance, or {@code null} if none was provided.
*/
@Nullable
public OpenTelemetry getOpenTelemetry() {
return openTelemetry;
}

/**
* Returns a new {@link Builder} initialized with the values from this options instance.
*
* @return a new {@link Builder}.
*/
@Nonnull
public DatastoreOpenTelemetryOptions.Builder toBuilder() {
return new DatastoreOpenTelemetryOptions.Builder(this);
}

/**
* Returns a new default {@link Builder}.
*
* @return a new {@link Builder}.
*/
@Nonnull
public static DatastoreOpenTelemetryOptions.Builder newBuilder() {
return new DatastoreOpenTelemetryOptions.Builder();
Expand All @@ -77,25 +121,31 @@ public static class Builder {

private boolean tracingEnabled;
private boolean metricsEnabled;
private boolean exportBuiltinMetricsToGoogleCloudMonitoring;

@Nullable private OpenTelemetry openTelemetry;

private Builder() {
tracingEnabled = false;
metricsEnabled = false;
// TODO(b/405457573): This is disabled by default until the Firestore namespace is deployed
exportBuiltinMetricsToGoogleCloudMonitoring = false;
openTelemetry = null;
}

private Builder(DatastoreOpenTelemetryOptions options) {
this.tracingEnabled = options.tracingEnabled;
this.metricsEnabled = options.metricsEnabled;
this.exportBuiltinMetricsToGoogleCloudMonitoring =
options.exportBuiltinMetricsToGoogleCloudMonitoring;
this.openTelemetry = options.openTelemetry;
}

/**
* Sets whether tracing should be enabled.
*
* @param enabled Whether tracing should be enabled.
* @return this builder instance.
*/
@Nonnull
public DatastoreOpenTelemetryOptions.Builder setTracingEnabled(boolean enabled) {
Expand All @@ -104,23 +154,41 @@ public DatastoreOpenTelemetryOptions.Builder setTracingEnabled(boolean enabled)
}

/**
* Sets whether metrics should be enabled.
* Sets whether metrics should be enabled for the custom (user-provided) OpenTelemetry backend.
*
* @param enabled Whether metrics should be enabled.
* @return the builder instance.
* @return this builder instance.
*/
@Nonnull
DatastoreOpenTelemetryOptions.Builder setMetricsEnabled(boolean enabled) {
this.metricsEnabled = enabled;
return this;
}

/**
* Sets whether built-in metrics should be exported to Google Cloud Monitoring.
*
* <p>When enabled, client-side metrics are automatically exported to Google Cloud Monitoring
* using the Cloud Monitoring API. This can be disabled to prevent metrics from being sent to
* Cloud Monitoring while still allowing metrics to flow to a custom OpenTelemetry backend.
*
* @param exportBuiltinMetrics Whether built-in metrics should be exported to Cloud Monitoring.
* @return this builder instance.
*/
@BetaApi
public DatastoreOpenTelemetryOptions.Builder setExportBuiltinMetricsToGoogleCloudMonitoring(
boolean exportBuiltinMetrics) {
this.exportBuiltinMetricsToGoogleCloudMonitoring = exportBuiltinMetrics;
return this;
}

/**
* Sets the {@link OpenTelemetry} to use with this Datastore instance. If telemetry collection
* is enabled, but an `OpenTelemetry` is not provided, the Datastore SDK will attempt to use the
* `GlobalOpenTelemetry`.
* is enabled, but an {@code OpenTelemetry} is not provided, the Datastore SDK will attempt to
* use the {@code GlobalOpenTelemetry}.
*
* @param openTelemetry The OpenTelemetry that should be used by this Datastore instance.
* @return this builder instance.
*/
@Nonnull
public DatastoreOpenTelemetryOptions.Builder setOpenTelemetry(
Expand All @@ -129,6 +197,11 @@ public DatastoreOpenTelemetryOptions.Builder setOpenTelemetry(
return this;
}

/**
* Builds a new {@link DatastoreOpenTelemetryOptions} instance from this builder.
*
* @return a new {@link DatastoreOpenTelemetryOptions}.
*/
@Nonnull
public DatastoreOpenTelemetryOptions build() {
return new DatastoreOpenTelemetryOptions(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
import com.google.cloud.datastore.spi.v1.GrpcDatastoreRpc;
import com.google.cloud.datastore.spi.v1.HttpDatastoreRpc;
import com.google.cloud.datastore.telemetry.DatastoreMetricsRecorder;
import com.google.cloud.datastore.v1.DatastoreSettings;
import com.google.cloud.grpc.GrpcTransportOptions;
import com.google.cloud.http.HttpTransportOptions;
Expand Down Expand Up @@ -82,7 +81,6 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions

private final transient @Nonnull DatastoreOpenTelemetryOptions openTelemetryOptions;
private final transient @Nonnull com.google.cloud.datastore.telemetry.TraceUtil traceUtil;
private final transient @Nonnull DatastoreMetricsRecorder metricsRecorder;

public static class DefaultDatastoreFactory implements DatastoreFactory {

Expand Down Expand Up @@ -123,11 +121,6 @@ public DatastoreOpenTelemetryOptions getOpenTelemetryOptions() {
return openTelemetryOptions;
}

@Nonnull
DatastoreMetricsRecorder getMetricsRecorder() {
return metricsRecorder;
}

public static class Builder extends ServiceOptions.Builder<Datastore, DatastoreOptions, Builder> {

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

namespace = MoreObjects.firstNonNull(builder.namespace, defaultNamespace());
databaseId = MoreObjects.firstNonNull(builder.databaseId, DEFAULT_DATABASE_ID);
Expand Down
Loading
Loading