1919import com .google .auth .Credentials ;
2020import com .google .cloud .NoCredentials ;
2121import com .google .common .base .Preconditions ;
22+ import com .google .common .hash .HashFunction ;
23+ import com .google .common .hash .Hashing ;
2224import io .opentelemetry .api .OpenTelemetry ;
2325import io .opentelemetry .api .common .Attributes ;
2426import io .opentelemetry .api .common .AttributesBuilder ;
2527import io .opentelemetry .sdk .OpenTelemetrySdk ;
2628import io .opentelemetry .sdk .metrics .SdkMeterProvider ;
2729import io .opentelemetry .sdk .metrics .SdkMeterProviderBuilder ;
2830import io .opentelemetry .sdk .resources .Resource ;
29- import java .lang .management .ManagementFactory ;
30- import java .lang .reflect .Method ;
31- import java .net .InetAddress ;
32- import java .net .UnknownHostException ;
3331import java .util .HashMap ;
3432import java .util .Map ;
3533import java .util .UUID ;
@@ -61,18 +59,44 @@ class BuiltInDatastoreMetricsProvider {
6159 private static volatile String location ;
6260 private static final String DEFAULT_LOCATION = "global" ;
6361
64- // Pre-computed once per JVM; hostname lookup can block, so we pay the cost at class-init time.
65- private static final String PID_AND_HOSTNAME = getProcessId () + "@" + getHostnameSafely ();
66-
6762 private BuiltInDatastoreMetricsProvider () {}
6863
6964 static Map <String , String > buildClientAttributes () {
7065 Map <String , String > attrs = new HashMap <>();
71- attrs .put (TelemetryConstants .CLIENT_UID_KEY .getKey (), getDefaultTaskValue ());
66+ attrs .put (
67+ TelemetryConstants .CLIENT_UID_KEY .getKey (), hashClientUId (UUID .randomUUID ().toString ()));
7268 attrs .put (TelemetryConstants .SERVICE_KEY .getKey (), TelemetryConstants .SERVICE_VALUE );
7369 return attrs ;
7470 }
7571
72+ /**
73+ * Generates a 6-digit zero-padded all lower case hexadecimal representation of hash of the
74+ * accounting group. The hash utilizes the 10 most significant bits of the value returned by
75+ * `Hashing.goodFastHash(64).hashBytes()`, so effectively the returned values are uniformly
76+ * distributed in the range [000000, 0003ff].
77+ *
78+ * <p>The primary purpose of this function is to generate a hash value for the `client_uid` metric
79+ * field. The range of values is chosen to be small enough to keep the cardinality under control.
80+ *
81+ * <p>Note: If at later time the range needs to be increased, it can be done by increasing the
82+ * value of `kPrefixLength` to up to 24 bits without changing the format of the returned value.
83+ *
84+ * @return Returns a 6-digit zero-padded all lower case hexadecimal representation of hash of the
85+ * accounting group.
86+ */
87+ private static String hashClientUId (String uuid ) {
88+ if (uuid == null ) {
89+ return "000000" ;
90+ }
91+
92+ HashFunction hashFunction = Hashing .goodFastHash (64 );
93+ long hash = hashFunction .hashBytes (uuid .getBytes ()).asLong ();
94+ // Don't change this value without reading above comment
95+ int kPrefixLength = 10 ;
96+ long shiftedValue = hash >>> (64 - kPrefixLength );
97+ return String .format ("%06x" , shiftedValue );
98+ }
99+
76100 /**
77101 * Creates a new {@link OpenTelemetry} instance for a single Datastore client's built-in metrics.
78102 *
@@ -133,7 +157,7 @@ public OpenTelemetry createOpenTelemetry(
133157 */
134158 String detectClientLocation () {
135159 if (location == null ) {
136- location = "global" ;
160+ location = DEFAULT_LOCATION ;
137161 }
138162 return location ;
139163 }
@@ -157,46 +181,4 @@ Attributes createResourceAttributes(String projectId, String databaseId) {
157181 .put (TelemetryConstants .LOCATION_ID_KEY , detectClientLocation ());
158182 return attributesBuilder .build ();
159183 }
160-
161- /**
162- * Generates a unique identifier for the {@code client_uid} metric field.
163- *
164- * <p>Combines a random UUID with the pre-computed {@code PID_AND_HOSTNAME} (typically {@code
165- * pid@hostname}). The UUID prefix ensures uniqueness across process restarts that reuse the same
166- * PID, preventing Cloud Monitoring from conflating time series from different process lifecycles.
167- *
168- * @return a unique identifier string.
169- */
170- private static String getDefaultTaskValue () {
171- return UUID .randomUUID ().toString () + "@" + PID_AND_HOSTNAME ;
172- }
173-
174- private static String getHostnameSafely () {
175- try {
176- return InetAddress .getLocalHost ().getHostName ();
177- } catch (UnknownHostException e ) {
178- logger .log (Level .CONFIG , "Unable to get the hostname." , e );
179- return "localhost" ;
180- }
181- }
182-
183- private static String getProcessId () {
184- try {
185- // Check if Java 9+ and ProcessHandle class is available
186- Class <?> processHandleClass = Class .forName ("java.lang.ProcessHandle" );
187- Method currentMethod = processHandleClass .getMethod ("current" );
188- Object processHandleInstance = currentMethod .invoke (null );
189- Method pidMethod = processHandleClass .getMethod ("pid" );
190- long pid = (long ) pidMethod .invoke (processHandleInstance );
191- return Long .toString (pid );
192- } catch (Exception e ) {
193- // Fallback to Java 8 method
194- final String jvmName = ManagementFactory .getRuntimeMXBean ().getName ();
195- if (jvmName != null && jvmName .contains ("@" )) {
196- return jvmName .split ("@" )[0 ];
197- } else {
198- return "unknown" ;
199- }
200- }
201- }
202184}
0 commit comments