Skip to content

Commit e06f42a

Browse files
committed
test(datastore): improve metrics IT robustness and handle NOT_FOUND errors
1 parent f90a43e commit e06f42a

1 file changed

Lines changed: 73 additions & 32 deletions

File tree

java-datastore/google-cloud-datastore/src/test/java/com/google/cloud/datastore/ITDatastoreBuiltInAndCustomMetrics.java

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import static com.google.common.truth.Truth.assertWithMessage;
2121
import static org.junit.Assume.assumeNotNull;
2222

23+
import com.google.cloud.TransportOptions;
24+
import com.google.cloud.grpc.GrpcTransportOptions;
25+
import com.google.cloud.http.HttpTransportOptions;
2326
import com.google.cloud.datastore.telemetry.TelemetryConstants;
2427
import io.opentelemetry.api.common.AttributeKey;
2528
import io.opentelemetry.sdk.OpenTelemetrySdk;
@@ -33,6 +36,7 @@
3336
import com.google.cloud.monitoring.v3.MetricServiceClient;
3437
import com.google.monitoring.v3.ListTimeSeriesRequest;
3538
import com.google.monitoring.v3.TimeInterval;
39+
import com.google.api.gax.rpc.ApiException;
3640
import com.google.protobuf.Timestamp;
3741
import java.util.Collection;
3842
import java.util.Optional;
@@ -94,16 +98,21 @@ public class ITDatastoreBuiltInAndCustomMetrics {
9498
private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT");
9599
private static final String DATABASE_ID =
96100
System.getenv().getOrDefault("DATASTORE_DATABASE_ID", "");
101+
private boolean isDatastoreClosed = false;
97102

98-
private final boolean useGrpc;
103+
private final TransportOptions transportOptions;
99104

100-
public ITDatastoreBuiltInAndCustomMetrics(boolean useGrpc) {
101-
this.useGrpc = useGrpc;
105+
public ITDatastoreBuiltInAndCustomMetrics(TransportOptions transportOptions) {
106+
this.transportOptions = transportOptions;
102107
}
103108

104-
@Parameterized.Parameters(name = "useGrpc: {0}")
109+
@Parameterized.Parameters(name = "transport: {0}")
105110
public static Iterable<Object[]> data() {
106-
return Arrays.asList(new Object[][] {{true}, {false}});
111+
return Arrays.asList(
112+
new Object[][] {
113+
{DatastoreOptions.getDefaultGrpcTransportOptions()},
114+
{DatastoreOptions.getDefaultHttpTransportOptions()}
115+
});
107116
}
108117

109118
/**
@@ -149,10 +158,10 @@ public void setUp() {
149158
.setExportBuiltinMetricsToGoogleCloudMonitoring(true)
150159
.build());
151160

152-
if (useGrpc) {
153-
builder.setTransportOptions(DatastoreOptions.getDefaultGrpcTransportOptions());
161+
if (transportOptions instanceof GrpcTransportOptions) {
162+
builder.setTransportOptions((GrpcTransportOptions) transportOptions);
154163
} else {
155-
builder.setTransportOptions(DatastoreOptions.getDefaultHttpTransportOptions());
164+
builder.setTransportOptions((HttpTransportOptions) transportOptions);
156165
}
157166

158167
datastore = builder.build().getService();
@@ -164,22 +173,20 @@ public void setUp() {
164173

165174
@After
166175
public void tearDown() throws Exception {
167-
if (datastore != null) {
176+
if (datastore != null && !isDatastoreClosed) {
168177
Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-it-entity");
169-
datastore.delete(key);
170-
// Closing the client flushes the built-in SDK and shuts down the PeriodicMetricReader,
171-
// ensuring any buffered metrics are exported before the test process exits.
178+
try {
179+
datastore.delete(key);
180+
} catch (Exception e) {
181+
// ignore if fails, we are cleaning up
182+
}
172183
datastore.close();
173184
}
174185
if (customMeterProvider != null) {
175186
customMeterProvider.close();
176187
}
177188
}
178189

179-
180-
181-
182-
183190
/**
184191
* Verifies that a transaction operation records {@code transaction_latency} and {@code
185192
* transaction_attempt_count} metrics in the custom (in-memory) OTel backend.
@@ -395,27 +402,53 @@ public void metricsExportedToCloudMonitoring() throws Exception {
395402
tx.put(Entity.newBuilder(current).set("value", current.getLong("value") + 1).build());
396403
return null;
397404
});
405+
406+
// Perform a lookup to generate GAX metrics
407+
datastore.get(key);
398408

399-
// Wait for metrics to be flushed and ingested.
400-
// The default interval is 60 seconds, so we need to wait at least that long, plus ingestion delay.
401-
// Let's use a polling loop with a timeout of 150 seconds.
402-
409+
// Clean up entity before closing client
410+
datastore.delete(key);
411+
412+
// Close client to force flush metrics
413+
datastore.close();
414+
isDatastoreClosed = true;
415+
416+
// List of metrics to verify in Cloud Monitoring
417+
java.util.List<String> metricNames = Arrays.asList(
418+
TelemetryConstants.METRIC_NAME_TRANSACTION_LATENCY,
419+
TelemetryConstants.METRIC_NAME_TRANSACTION_ATTEMPT_COUNT,
420+
TelemetryConstants.METRIC_PREFIX + "/operation_latencies",
421+
TelemetryConstants.METRIC_PREFIX + "/attempt_latencies",
422+
TelemetryConstants.METRIC_PREFIX + "/operation_count",
423+
TelemetryConstants.METRIC_PREFIX + "/attempt_count"
424+
);
425+
426+
for (String metricName : metricNames) {
427+
String filter = "metric.type = \"" + metricName + "\"";
428+
boolean found = verifyWithPolling(filter);
429+
assertWithMessage("Metric " + metricName + " should be present in Cloud Monitoring").that(found).isTrue();
430+
}
431+
}
432+
433+
private boolean verifyWithPolling(String filter) throws Exception {
434+
// Try to read immediately first
435+
if (isMetricPresent(filter)) {
436+
System.out.println("Metric found immediately!");
437+
return true;
438+
}
439+
440+
// Fallback to short polling loop (30 seconds total)
403441
long startTimeMillis = System.currentTimeMillis();
404-
String filter = "metric.type = \"custom.googleapis.com/internal/client/transaction_latency\"";
405-
406-
boolean found = false;
407442
int attempts = 0;
408-
while (System.currentTimeMillis() - startTimeMillis < 150000) {
443+
while (System.currentTimeMillis() - startTimeMillis < 30000) {
409444
attempts++;
410-
System.out.println("Checking Cloud Monitoring for metrics (attempt " + attempts + ")...");
445+
System.out.println("Polling Cloud Monitoring for metric (attempt " + attempts + ")...");
411446
if (isMetricPresent(filter)) {
412-
found = true;
413-
break;
447+
return true;
414448
}
415-
Thread.sleep(10000); // Wait 10 seconds between attempts
449+
Thread.sleep(5000); // Wait 5 seconds between attempts
416450
}
417-
418-
assertWithMessage("Metrics should be present in Cloud Monitoring").that(found).isTrue();
451+
return false;
419452
}
420453

421454
private boolean isMetricPresent(String filter) throws Exception {
@@ -439,8 +472,16 @@ private boolean isMetricPresent(String filter) throws Exception {
439472
.setView(ListTimeSeriesRequest.TimeSeriesView.FULL)
440473
.build();
441474

442-
MetricServiceClient.ListTimeSeriesPagedResponse response = client.listTimeSeries(request);
443-
return response.iterateAll().iterator().hasNext();
475+
try {
476+
MetricServiceClient.ListTimeSeriesPagedResponse response = client.listTimeSeries(request);
477+
return response.iterateAll().iterator().hasNext();
478+
} catch (ApiException e) {
479+
if (e.getStatusCode().getCode() == com.google.api.gax.rpc.StatusCode.Code.NOT_FOUND) {
480+
System.out.println("Metric not found yet (NOT_FOUND status).");
481+
return false;
482+
}
483+
throw e;
484+
}
444485
}
445486
}
446487
}

0 commit comments

Comments
 (0)