Skip to content

Commit c982e17

Browse files
committed
chore: Address PR feedback
- Pre-compute PID_AND_HOSTNAME statically to avoid per-client hostname lookup - Remove cachedClientAttributes/getClientAttributes() from singleton; use buildClientAttributes() directly - Include credentials hash in MetricServiceClient cache key to isolate clients with different credentials - Remove EXPONENTIAL_HISTOGRAM cases (exporter will never receive them) - Remove unused CLIENT_NAME_KEY constant
1 parent 33ce2f7 commit c982e17

6 files changed

Lines changed: 35 additions & 56 deletions

File tree

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

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.lang.reflect.Method;
3030
import java.net.InetAddress;
3131
import java.net.UnknownHostException;
32-
import java.util.Collections;
3332
import java.util.HashMap;
3433
import java.util.Map;
3534
import java.util.UUID;
@@ -59,13 +58,12 @@ public final class BuiltInDatastoreMetricsProvider {
5958
private static volatile String location;
6059
private static final String DEFAULT_LOCATION = "global";
6160

62-
private final Map<String, String> cachedClientAttributes;
61+
// Pre-computed once per JVM; hostname lookup can block, so we pay the cost at class-init time.
62+
private static final String PID_AND_HOSTNAME = getProcessId() + "@" + getHostnameSafely();
6363

64-
private BuiltInDatastoreMetricsProvider() {
65-
cachedClientAttributes = Collections.unmodifiableMap(buildClientAttributes());
66-
}
64+
private BuiltInDatastoreMetricsProvider() {}
6765

68-
private Map<String, String> buildClientAttributes() {
66+
static Map<String, String> buildClientAttributes() {
6967
Map<String, String> attrs = new HashMap<>();
7068
attrs.put(TelemetryConstants.CLIENT_UID_KEY.getKey(), getDefaultTaskValue());
7169
attrs.put(TelemetryConstants.SERVICE_KEY.getKey(), TelemetryConstants.SERVICE_VALUE);
@@ -96,8 +94,6 @@ public OpenTelemetry createOpenTelemetry(
9694
@Nonnull String projectId, @Nonnull String databaseId, @Nonnull Credentials credentials) {
9795
SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder();
9896

99-
// Generate unique client attributes (including unique taskId) for this specific client
100-
// instance.
10197
Map<String, String> clientAttributes = buildClientAttributes();
10298

10399
if (credentials instanceof NoCredentials) {
@@ -158,45 +154,25 @@ Attributes createResourceAttributes(String projectId, String databaseId) {
158154
return attributesBuilder.build();
159155
}
160156

161-
/**
162-
* Returns common client attributes added to every exported metric data point.
163-
*
164-
* <p>The returned map is pre-computed at construction time and shared across all export calls,
165-
* since {@code client_name}, {@code client_uid}, and {@code service} are stable for the lifetime
166-
* of the process.
167-
*
168-
* @return an unmodifiable map of client attributes.
169-
*/
170-
Map<String, String> getClientAttributes() {
171-
return cachedClientAttributes;
172-
}
173-
174157
/**
175158
* Generates a unique identifier for the {@code client_uid} metric field.
176159
*
177-
* <p>Combines a random UUID with {@code RuntimeMXBean.getName()} (typically {@code
160+
* <p>Combines a random UUID with the pre-computed {@code PID_AND_HOSTNAME} (typically {@code
178161
* pid@hostname}). The UUID prefix ensures uniqueness across process restarts that reuse the same
179162
* PID, preventing Cloud Monitoring from conflating time series from different process lifecycles.
180163
*
181-
* <p>For Java 9 and later, the PID is obtained using the ProcessHandle API. For Java 8, the PID
182-
* is extracted from ManagementFactory.getRuntimeMXBean().getName().
183-
*
184-
* <p><b>Note</b>: This method generates a new value every time it is called to ensure that each
185-
* client instance gets a unique ID. It should be called sparingly (e.g., once per client
186-
* creation) to avoid performance overhead from UUID generation and hostname lookup.
187-
*
188164
* @return a unique identifier string.
189165
*/
190166
private static String getDefaultTaskValue() {
191-
String identifier = UUID.randomUUID().toString();
192-
String pid = getProcessId();
167+
return UUID.randomUUID().toString() + "@" + PID_AND_HOSTNAME;
168+
}
193169

170+
private static String getHostnameSafely() {
194171
try {
195-
String hostname = InetAddress.getLocalHost().getHostName();
196-
return identifier + "@" + pid + "@" + hostname;
172+
return InetAddress.getLocalHost().getHostName();
197173
} catch (UnknownHostException e) {
198174
logger.log(Level.CONFIG, "Unable to get the hostname.", e);
199-
return identifier + "@" + pid + "@localhost";
175+
return "localhost";
200176
}
201177
}
202178

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,19 @@ static class CachedMetricsClient {
8585
}
8686

8787
/**
88-
* Shared cache for {@link MetricServiceClient} instances, keyed by "projectId:databaseId". This
89-
* prevents creating a new gRPC client for every exporter instance, reducing resource usage.
90-
* Reference counting is used to safely shut down the client when no longer needed.
88+
* Shared cache for {@link MetricServiceClient} instances, keyed by
89+
* "projectId:databaseId:credentialsHashCode". Sharing a single gRPC channel across exporter
90+
* instances that target the same project, database, and credentials avoids per-client channel
91+
* overhead (threads, connections, memory). The credentials hash ensures that clients using
92+
* different credentials get their own isolated channel. Reference counting is used to safely shut
93+
* down the client when no longer needed.
9194
*/
9295
static final ConcurrentHashMap<String, CachedMetricsClient> METRICS_CLIENT_CACHE =
9396
new ConcurrentHashMap<>();
9497

9598
private final MetricServiceClient client;
9699
private final Map<String, String> clientAttributes;
100+
private final String cacheKey;
97101

98102
// This is the quota limit from Cloud Monitoring. More details in
99103
// https://cloud.google.com/monitoring/quotas#custom_metrics_quotas.
@@ -132,7 +136,8 @@ static DatastoreCloudMonitoringExporter create(
132136
String databaseId,
133137
Credentials credentials,
134138
Map<String, String> clientAttributes) {
135-
String key = projectId + ":" + databaseId;
139+
int credHash = credentials != null ? credentials.hashCode() : 0;
140+
String key = projectId + ":" + databaseId + ":" + credHash;
136141

137142
// Use compute to acquire or create the client atomically with reference counting.
138143
// If creation fails, we log the error and return null so it's not added to the map.
@@ -161,7 +166,7 @@ static DatastoreCloudMonitoringExporter create(
161166
}
162167

163168
return new DatastoreCloudMonitoringExporter(
164-
projectId, databaseId, cachedMetricsClient.client, clientAttributes);
169+
key, projectId, databaseId, cachedMetricsClient.client, clientAttributes);
165170
}
166171

167172
private static MetricServiceClient createMetricServiceClient(Credentials credentials)
@@ -185,10 +190,12 @@ private static MetricServiceClient createMetricServiceClient(Credentials credent
185190

186191
@VisibleForTesting
187192
DatastoreCloudMonitoringExporter(
193+
String cacheKey,
188194
String projectId,
189195
String databaseId,
190196
MetricServiceClient client,
191197
Map<String, String> clientAttributes) {
198+
this.cacheKey = cacheKey;
192199
this.client = client;
193200
this.projectId = projectId;
194201
this.databaseId = databaseId;
@@ -307,10 +314,9 @@ public CompletableResultCode shutdown() {
307314
}
308315
CompletableResultCode shutdownResult = new CompletableResultCode();
309316
try {
310-
String key = projectId + ":" + databaseId;
311317
// Atomically decrement reference count and cleanup if zero.
312318
METRICS_CLIENT_CACHE.compute(
313-
key,
319+
cacheKey,
314320
(k, v) -> {
315321
if (v != null && v.refCount.decrementAndGet() == 0) {
316322
v.client.shutdown();

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ static List<TimeSeries> convertToDatastoreTimeSeries(
132132
* <p>{@code clientAttributes} (e.g. {@code client_name}, {@code client_uid}) are injected here
133133
* rather than being looked up from a singleton so that this method is testable in isolation. The
134134
* caller ({@link DatastoreCloudMonitoringExporter}) is responsible for supplying them from {@link
135-
* BuiltInDatastoreMetricsProvider#getClientAttributes()}.
135+
* BuiltInDatastoreMetricsProvider#buildClientAttributes()}.
136136
*/
137137
private static TimeSeries convertPointToDatastoreTimeSeries(
138138
MetricData metricData,
@@ -179,7 +179,6 @@ private static TimeSeries convertPointToDatastoreTimeSeries(
179179
private static MetricKind convertMetricKind(MetricData metricData) {
180180
switch (metricData.getType()) {
181181
case HISTOGRAM:
182-
case EXPONENTIAL_HISTOGRAM:
183182
return convertHistogramType(metricData.getHistogramData());
184183
case LONG_GAUGE:
185184
case DOUBLE_GAUGE:
@@ -235,7 +234,6 @@ private static ValueType convertValueType(MetricDataType metricDataType) {
235234
case DOUBLE_SUM:
236235
return DOUBLE;
237236
case HISTOGRAM:
238-
case EXPONENTIAL_HISTOGRAM:
239237
return DISTRIBUTION;
240238
default:
241239
return ValueType.UNRECOGNIZED;
@@ -248,7 +246,6 @@ private static Point createPoint(
248246
Point.Builder builder = Point.newBuilder().setInterval(timeInterval);
249247
switch (type) {
250248
case HISTOGRAM:
251-
case EXPONENTIAL_HISTOGRAM:
252249
return builder
253250
.setValue(
254251
TypedValue.newBuilder()

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ public class TelemetryConstants {
105105

106106
// Metric attribute keys (used on metric data points)
107107
public static final AttributeKey<String> CLIENT_UID_KEY = AttributeKey.stringKey("client_uid");
108-
public static final AttributeKey<String> CLIENT_NAME_KEY = AttributeKey.stringKey("client_name");
109108
public static final AttributeKey<String> METHOD_KEY = AttributeKey.stringKey("method");
110109
public static final AttributeKey<String> STATUS_KEY = AttributeKey.stringKey("status");
111110
public static final AttributeKey<String> SERVICE_KEY = AttributeKey.stringKey("service");

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public class BuiltInDatastoreMetricsProviderTest {
3030
private static final String PROJECT_ID = "project-id";
3131

3232
@Test
33-
public void testGetClientAttributes() {
34-
Map<String, String> attributes = BuiltInDatastoreMetricsProvider.INSTANCE.getClientAttributes();
33+
public void testBuildClientAttributes() {
34+
Map<String, String> attributes = BuiltInDatastoreMetricsProvider.buildClientAttributes();
3535
assertThat(attributes).containsKey(TelemetryConstants.CLIENT_UID_KEY.getKey());
3636
assertThat(attributes.get(TelemetryConstants.CLIENT_UID_KEY.getKey())).contains("@");
3737
assertThat(attributes).containsKey(TelemetryConstants.SERVICE_KEY.getKey());

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,16 @@ public void setUp() {
7878
mockMetricServiceStub = createMock(MetricServiceStub.class);
7979
fakeMetricServiceClient = new FakeMetricServiceClient(mockMetricServiceStub);
8080

81-
Map<String, String> clientAttributes =
82-
BuiltInDatastoreMetricsProvider.INSTANCE.getClientAttributes();
81+
Map<String, String> clientAttributes = BuiltInDatastoreMetricsProvider.buildClientAttributes();
8382
this.clientUid = clientAttributes.get(CLIENT_UID_KEY.getKey());
8483

8584
exporter =
8685
new DatastoreCloudMonitoringExporter(
87-
PROJECT_ID, DATABASE_ID, fakeMetricServiceClient, clientAttributes);
86+
PROJECT_ID + ":" + DATABASE_ID + ":0",
87+
PROJECT_ID,
88+
DATABASE_ID,
89+
fakeMetricServiceClient,
90+
clientAttributes);
8891

8992
attributes =
9093
Attributes.builder()
@@ -163,24 +166,22 @@ public void testClientCacheReferenceCounting() {
163166
EasyMock.expectLastCall(); // Expect shutdown when refCount reaches 0
164167
replay(mockClient);
165168

166-
String key = PROJECT_ID + ":" + DATABASE_ID;
169+
String key = PROJECT_ID + ":" + DATABASE_ID + ":0";
167170
DatastoreCloudMonitoringExporter.CachedMetricsClient cachedMetricsClient =
168171
new DatastoreCloudMonitoringExporter.CachedMetricsClient(mockClient);
169172
cachedMetricsClient.refCount.set(2); // Simulate 2 references
170173
DatastoreCloudMonitoringExporter.METRICS_CLIENT_CACHE.put(key, cachedMetricsClient);
171174

172175
DatastoreCloudMonitoringExporter exporter1 =
173-
new DatastoreCloudMonitoringExporter(
174-
PROJECT_ID, DATABASE_ID, mockClient, Collections.emptyMap());
176+
new DatastoreCloudMonitoringExporter(key, PROJECT_ID, DATABASE_ID, mockClient, Collections.emptyMap());
175177

176178
// First shutdown should decrement refCount to 1, but not close client
177179
exporter1.shutdown();
178180
assertThat(cachedMetricsClient.refCount.get()).isEqualTo(1);
179181
assertThat(DatastoreCloudMonitoringExporter.METRICS_CLIENT_CACHE.containsKey(key)).isTrue();
180182

181183
DatastoreCloudMonitoringExporter exporter2 =
182-
new DatastoreCloudMonitoringExporter(
183-
PROJECT_ID, DATABASE_ID, mockClient, Collections.emptyMap());
184+
new DatastoreCloudMonitoringExporter(key, PROJECT_ID, DATABASE_ID, mockClient, Collections.emptyMap());
184185

185186
// Second shutdown should decrement refCount to 0, close client, and remove from cache
186187
exporter2.shutdown();

0 commit comments

Comments
 (0)