1818
1919import com .google .api .gax .core .GaxProperties ;
2020import com .google .auth .Credentials ;
21- import com .google .common .base .Strings ;
2221import io .opentelemetry .api .OpenTelemetry ;
2322import io .opentelemetry .api .common .Attributes ;
2423import io .opentelemetry .api .common .AttributesBuilder ;
2524import io .opentelemetry .sdk .metrics .SdkMeterProvider ;
2625import io .opentelemetry .sdk .metrics .SdkMeterProviderBuilder ;
2726import io .opentelemetry .sdk .resources .Resource ;
28- import java .io .BufferedReader ;
2927import java .io .IOException ;
30- import java .io .InputStream ;
31- import java .io .InputStreamReader ;
3228import 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 ;
3730import java .util .HashMap ;
3831import java .util .Map ;
3932import java .util .logging .Level ;
4033import java .util .logging .Logger ;
34+ import javax .annotation .Nonnull ;
4135import 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}.
0 commit comments