2020import static com .google .common .truth .Truth .assertWithMessage ;
2121import 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 ;
2326import com .google .cloud .datastore .telemetry .TelemetryConstants ;
2427import io .opentelemetry .api .common .AttributeKey ;
2528import io .opentelemetry .sdk .OpenTelemetrySdk ;
3336import com .google .cloud .monitoring .v3 .MetricServiceClient ;
3437import com .google .monitoring .v3 .ListTimeSeriesRequest ;
3538import com .google .monitoring .v3 .TimeInterval ;
39+ import com .google .api .gax .rpc .ApiException ;
3640import com .google .protobuf .Timestamp ;
3741import java .util .Collection ;
3842import 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