Skip to content

Commit da5fdd5

Browse files
committed
chore: Update Datastore implementation
1 parent 67be025 commit da5fdd5

File tree

8 files changed

+323
-117
lines changed

8 files changed

+323
-117
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public Builder setDatabaseId(String databaseId) {
193193
}
194194

195195
/**
196-
* Sets the {@link DatastoreOpenTelemetryOptions} to be used for this Firestore instance.
196+
* Sets the {@link DatastoreOpenTelemetryOptions} to be used for this Datastore instance.
197197
*
198198
* @param openTelemetryOptions The `DatastoreOpenTelemetryOptions` to use.
199199
*/

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/DatastoreBuiltInMetricsProvider.java

Lines changed: 53 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,20 @@
1818

1919
import com.google.api.gax.core.GaxProperties;
2020
import com.google.auth.Credentials;
21-
import com.google.common.base.Strings;
2221
import io.opentelemetry.api.OpenTelemetry;
2322
import io.opentelemetry.api.common.Attributes;
2423
import io.opentelemetry.api.common.AttributesBuilder;
2524
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
2625
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
2726
import io.opentelemetry.sdk.resources.Resource;
28-
import java.io.BufferedReader;
2927
import java.io.IOException;
30-
import java.io.InputStream;
31-
import java.io.InputStreamReader;
3228
import java.lang.management.ManagementFactory;
33-
import java.lang.reflect.Method;
34-
import java.net.HttpURLConnection;
35-
import java.net.URL;
36-
import java.nio.charset.StandardCharsets;
29+
import java.util.Collections;
3730
import java.util.HashMap;
3831
import java.util.Map;
3932
import java.util.logging.Level;
4033
import java.util.logging.Logger;
34+
import javax.annotation.Nonnull;
4135
import javax.annotation.Nullable;
4236

4337
/**
@@ -62,86 +56,71 @@ public final class DatastoreBuiltInMetricsProvider {
6256
private static String location;
6357
private static final String DEFAULT_LOCATION = "global";
6458

65-
private SdkMeterProvider sdkMeterProvider;
66-
private OpenTelemetry openTelemetry;
59+
private final Map<String, String> cachedClientAttributes;
6760

68-
private DatastoreBuiltInMetricsProvider() {}
61+
private DatastoreBuiltInMetricsProvider() {
62+
cachedClientAttributes =
63+
Collections.unmodifiableMap(buildClientAttributes());
64+
}
65+
66+
private Map<String, String> buildClientAttributes() {
67+
Map<String, String> attrs = new HashMap<>();
68+
attrs.put(
69+
TelemetryConstants.CLIENT_NAME_KEY.getKey(),
70+
"datastore-java/" + GaxProperties.getLibraryVersion(getClass()));
71+
attrs.put(TelemetryConstants.CLIENT_UID_KEY.getKey(), getDefaultTaskValue());
72+
attrs.put(TelemetryConstants.SERVICE_KEY.getKey(), TelemetryConstants.SERVICE_VALUE);
73+
return attrs;
74+
}
6975

7076
/**
71-
* Returns a singleton {@link OpenTelemetry} instance for built-in metrics.
77+
* Creates a new {@link OpenTelemetry} instance for a single Datastore client's built-in metrics.
78+
*
79+
* <p>Each call returns a dedicated {@link SdkMeterProvider} configured with the provided
80+
* project's monitored resource attributes and a {@link DatastoreCloudMonitoringExporter}. A
81+
* shutdown hook is registered to flush and close the provider when the JVM exits.
7282
*
73-
* <p>This method initializes an {@link SdkMeterProvider} with a {@link
74-
* DatastoreCloudMonitoringExporter} and the appropriate resource attributes. It also registers a
75-
* shutdown hook to ensure metrics are flushed when the JVM exits.
83+
* <p>Callers are responsible for holding the returned instance for the lifetime of their
84+
* Datastore client; no caching is performed here.
7685
*
7786
* @param projectId the GCP project ID.
7887
* @param databaseId the Datastore database ID.
7988
* @param credentials the credentials to use for exporting metrics.
8089
* @param monitoringHost optional monitoring host override.
8190
* @param universeDomain the universe domain to use for monitoring.
82-
* @return the {@link OpenTelemetry} instance, or {@code null} if it could not be created.
91+
* @return a new {@link OpenTelemetry} instance, or {@code null} if it could not be created.
8392
*/
84-
public OpenTelemetry getOrCreateOpenTelemetry(
85-
String projectId,
86-
String databaseId,
93+
@Nullable
94+
public OpenTelemetry createOpenTelemetry(
95+
@Nonnull String projectId,
96+
@Nonnull String databaseId,
8797
@Nullable Credentials credentials,
8898
@Nullable String monitoringHost,
89-
String universeDomain) {
99+
@Nonnull String universeDomain) {
90100
try {
91-
if (this.openTelemetry == null) {
92-
SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder();
93-
// Register Datastore-specific views and the PeriodicMetricReader.
94-
DatastoreBuiltInMetricsView.registerBuiltinMetrics(
95-
DatastoreCloudMonitoringExporter.create(
96-
projectId, credentials, monitoringHost, universeDomain),
97-
sdkMeterProviderBuilder);
98-
// Configure the monitored resource attributes.
99-
sdkMeterProviderBuilder.setResource(
100-
Resource.create(createResourceAttributes(projectId, databaseId)));
101-
this.sdkMeterProvider = sdkMeterProviderBuilder.build();
102-
this.openTelemetry = new DatastoreOpenTelemetry(this.sdkMeterProvider);
103-
// Ensure cleanup on shutdown.
104-
Runtime.getRuntime().addShutdownHook(new Thread(this.sdkMeterProvider::close));
105-
}
106-
return this.openTelemetry;
101+
SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder();
102+
// Register Datastore-specific views and the PeriodicMetricReader.
103+
DatastoreBuiltInMetricsView.registerBuiltinMetrics(
104+
DatastoreCloudMonitoringExporter.create(
105+
projectId, credentials, monitoringHost, universeDomain),
106+
sdkMeterProviderBuilder);
107+
// Configure the monitored resource attributes for this specific client.
108+
sdkMeterProviderBuilder.setResource(
109+
Resource.create(createResourceAttributes(projectId, databaseId)));
110+
SdkMeterProvider sdkMeterProvider = sdkMeterProviderBuilder.build();
111+
// Ensure cleanup on shutdown.
112+
Runtime.getRuntime().addShutdownHook(new Thread(sdkMeterProvider::close));
113+
return new DatastoreOpenTelemetry(sdkMeterProvider);
107114
} catch (IOException ex) {
108115
logger.log(
109116
Level.WARNING,
110-
"Unable to get OpenTelemetry object for client side metrics, will skip exporting client"
111-
+ " side metrics",
117+
"Unable to create OpenTelemetry instance for client side metrics, will skip exporting"
118+
+ " built-in metrics",
112119
ex);
113120
return null;
114121
}
115122
}
116123

117-
/**
118-
* Quick check to see if the application is running on GCP by querying the metadata server.
119-
*
120-
* <p>This is used to determine if we should enable built-in metrics by default.
121-
*
122-
* @return {@code true} if running on GCP, {@code false} otherwise.
123-
*/
124-
public static boolean quickCheckIsRunningOnGcp() {
125-
int timeout = 5000;
126-
try {
127-
URL url = new URL("http://metadata.google.internal/computeMetadata/v1/project/project-id");
128-
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
129-
connection.setConnectTimeout(timeout);
130-
connection.setRequestProperty("Metadata-Flavor", "Google");
131-
if (connection.getResponseCode() == 200
132-
&& ("Google").equals(connection.getHeaderField("Metadata-Flavor"))) {
133-
InputStream input = connection.getInputStream();
134-
try (BufferedReader reader =
135-
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
136-
return !Strings.isNullOrEmpty(reader.readLine());
137-
}
138-
}
139-
} catch (IOException ignore) {
140-
// ignore
141-
}
142-
return false;
143-
}
144-
145124
/**
146125
* Detects the client's GCP location (region).
147126
*
@@ -179,30 +158,24 @@ Attributes createResourceAttributes(String projectId, String databaseId) {
179158
}
180159

181160
/**
182-
* Creates common client attributes for each metric data point.
161+
* Returns common client attributes added to every exported metric data point.
183162
*
184-
* <p>These attributes (client_name, client_uid) are added to every exported metric to identify
185-
* the specific client instance.
163+
* <p>The returned map is pre-computed at construction time and shared across all export calls,
164+
* since {@code client_name}, {@code client_uid}, and {@code service} are stable for the lifetime
165+
* of the process.
186166
*
187-
* @return a map of client attributes.
167+
* @return an unmodifiable map of client attributes.
188168
*/
189169
public Map<String, String> createClientAttributes() {
190-
Map<String, String> clientAttributes = new HashMap<>();
191-
clientAttributes.put(
192-
TelemetryConstants.CLIENT_NAME_KEY.getKey(),
193-
"datastore-java/" + GaxProperties.getLibraryVersion(getClass()));
194-
clientAttributes.put(TelemetryConstants.CLIENT_UID_KEY.getKey(), getDefaultTaskValue());
195-
clientAttributes.put(TelemetryConstants.SERVICE_KEY.getKey(), TelemetryConstants.SERVICE_VALUE);
196-
return clientAttributes;
170+
return cachedClientAttributes;
197171
}
198172

199173
/**
200174
* Generates a unique identifier for the {@code client_uid} metric field.
201175
*
202-
* <p>The identifier is composed of a UUID, the process ID (PID), and the hostname to ensure
203-
* uniqueness across different instances and restarts.
176+
* <p>Uses {@code RuntimeMXBean.getName()} which typically returns {@code pid@hostname}.
204177
*
205-
* @return a unique identifier string in the format UUID@PID@hostname.
178+
* @return a unique identifier string.
206179
*/
207180
private static String getDefaultTaskValue() {
208181
if (taskId == null) {
@@ -211,34 +184,6 @@ private static String getDefaultTaskValue() {
211184
return taskId;
212185
}
213186

214-
/**
215-
* Returns the process ID.
216-
*
217-
* <p>Attempts to use {@code ProcessHandle} (Java 9+) and falls back to {@code RuntimeMXBean} for
218-
* Java 8 compatibility.
219-
*
220-
* @return the PID as a string.
221-
*/
222-
private static String getProcessId() {
223-
try {
224-
// Check if Java 9+ and ProcessHandle class is available
225-
Class<?> processHandleClass = Class.forName("java.lang.ProcessHandle");
226-
Method currentMethod = processHandleClass.getMethod("current");
227-
Object processHandleInstance = currentMethod.invoke(null);
228-
Method pidMethod = processHandleClass.getMethod("pid");
229-
long pid = (long) pidMethod.invoke(processHandleInstance);
230-
return Long.toString(pid);
231-
} catch (Exception e) {
232-
// Fallback to Java 8 method
233-
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
234-
if (jvmName != null && jvmName.contains("@")) {
235-
return jvmName.split("@")[0];
236-
} else {
237-
return "unknown";
238-
}
239-
}
240-
}
241-
242187
/**
243188
* A simple {@link OpenTelemetry} implementation that only provides a {@link
244189
* io.opentelemetry.api.metrics.MeterProvider}.

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/MetricsRecorder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ static MetricsRecorder getInstance(@Nonnull DatastoreOptions datastoreOptions) {
9292
&& !metricsDisabledViaEnv) {
9393
try {
9494
OpenTelemetry builtInOtel =
95-
DatastoreBuiltInMetricsProvider.INSTANCE.getOrCreateOpenTelemetry(
95+
DatastoreBuiltInMetricsProvider.INSTANCE.createOpenTelemetry(
9696
datastoreOptions.getProjectId(),
9797
datastoreOptions.getDatabaseId(),
9898
datastoreOptions.getCredentials(),

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/TelemetryConstants.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@
3434
@InternalApi
3535
public class TelemetryConstants {
3636

37-
// TODO(lawrenceqiu): For now, use `custom.googleapis.com` until metrics can be written to
38-
// datastore domain
37+
// Datastore metrics are exported under the Firestore service domain. The `service` metric
38+
// attribute is set to "datastore.googleapis.com" to distinguish Datastore traffic from Firestore.
3939
public static final String SERVICE_NAME = "firestore.googleapis.com";
40-
static final String METER_NAME = "com.google.cloud.datastore";
4140

4241
// Built-in metrics constants for Cloud Monitoring export
4342
public static final String METRIC_PREFIX = "firestore.googleapis.com/internal/client";

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/telemetry/TelemetryUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.datastore.telemetry;
1818

1919
import com.google.api.core.InternalApi;
20+
import com.google.api.gax.core.GaxProperties;
2021
import com.google.api.gax.rpc.StatusCode;
2122
import com.google.cloud.datastore.DatastoreException;
2223
import com.google.cloud.datastore.DatastoreOptions;
@@ -54,6 +55,9 @@ public static Map<String, String> buildMetricAttributes(
5455
attributes.put(
5556
TelemetryConstants.ATTRIBUTES_KEY_TRANSPORT,
5657
TelemetryConstants.getTransportName(datastoreOptions.getTransportOptions()));
58+
attributes.put(
59+
TelemetryConstants.ATTRIBUTES_KEY_LIBRARY_VERSION,
60+
GaxProperties.getLibraryVersion(DatastoreOptions.class));
5761
return attributes;
5862
}
5963

java-datastore/google-cloud-datastore/src/test/java/com/google/cloud/datastore/telemetry/DatastoreBuiltInMetricsProviderTest.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,24 @@ public void testCreateClientAttributes() {
4444
}
4545

4646
@Test
47-
public void testGetOrCreateOpenTelemetry() {
47+
public void testCreateOpenTelemetry_returnsNonNull() {
4848
OpenTelemetry otel =
49-
DatastoreBuiltInMetricsProvider.INSTANCE.getOrCreateOpenTelemetry(
49+
DatastoreBuiltInMetricsProvider.INSTANCE.createOpenTelemetry(
5050
PROJECT_ID, "test-db", null, null, "googleapis.com");
5151
assertThat(otel).isNotNull();
52+
}
5253

54+
@Test
55+
public void testCreateOpenTelemetry_eachCallReturnsDistinctInstance() {
56+
OpenTelemetry otel1 =
57+
DatastoreBuiltInMetricsProvider.INSTANCE.createOpenTelemetry(
58+
PROJECT_ID, "test-db", null, null, "googleapis.com");
5359
OpenTelemetry otel2 =
54-
DatastoreBuiltInMetricsProvider.INSTANCE.getOrCreateOpenTelemetry(
60+
DatastoreBuiltInMetricsProvider.INSTANCE.createOpenTelemetry(
5561
PROJECT_ID, "test-db", null, null, "googleapis.com");
56-
assertThat(otel2).isSameInstanceAs(otel);
62+
assertThat(otel1).isNotNull();
63+
assertThat(otel2).isNotNull();
64+
assertThat(otel1).isNotSameInstanceAs(otel2);
5765
}
5866

5967
@Test

0 commit comments

Comments
 (0)