|
30 | 30 | import io.opentelemetry.sdk.metrics.data.PointData; |
31 | 31 | import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; |
32 | 32 | import java.util.Arrays; |
| 33 | +import com.google.cloud.monitoring.v3.MetricServiceClient; |
| 34 | +import com.google.monitoring.v3.ListTimeSeriesRequest; |
| 35 | +import com.google.monitoring.v3.TimeInterval; |
| 36 | +import com.google.protobuf.Timestamp; |
33 | 37 | import java.util.Collection; |
34 | 38 | import java.util.Optional; |
35 | 39 | import org.junit.After; |
@@ -172,42 +176,9 @@ public void tearDown() throws Exception { |
172 | 176 | } |
173 | 177 | } |
174 | 178 |
|
175 | | - /** |
176 | | - * Verifies that the Datastore client is configured with a composite recorder so that both the |
177 | | - * built-in Cloud Monitoring backend and the user-provided custom OTel backend are active. |
178 | | - * |
179 | | - * <p>The composite recorder is the internal mechanism that fans out every {@code record*()} call |
180 | | - * to all registered backends. Its presence guarantees that: |
181 | | - * |
182 | | - * <ul> |
183 | | - * <li>the built-in SDK was successfully created and its exporter was wired up, AND |
184 | | - * <li>the user-provided custom OTel instance is also receiving metric data. |
185 | | - * </ul> |
186 | | - */ |
187 | | - @Test |
188 | | - public void bothBackendsActive_recorderIsComposite() { |
189 | | - // DatastoreOptions.getMetricsRecorder() is package-private; accessible because this test |
190 | | - // lives in the same package (com.google.cloud.datastore). |
191 | | - String recorderClassName = |
192 | | - datastore.getOptions().getMetricsRecorder().getClass().getSimpleName(); |
193 | | - assertThat(recorderClassName).isEqualTo("CompositeDatastoreMetricsRecorder"); |
194 | | - } |
195 | 179 |
|
196 | | - /** |
197 | | - * Verifies that the built-in metrics export flag is off by default. The flag is disabled until |
198 | | - * the Datastore namespace in Cloud Monitoring is deployed; it must be explicitly opted in via |
199 | | - * {@link DatastoreOpenTelemetryOptions.Builder#setExportBuiltinMetricsToGoogleCloudMonitoring}. |
200 | | - */ |
201 | | - @Test |
202 | | - public void builtInMetricsExport_isDisabledByDefault() { |
203 | | - DatastoreOptions defaultOptions = |
204 | | - DatastoreOptions.newBuilder().setProjectId(PROJECT_ID).build(); |
205 | | - assertThat( |
206 | | - defaultOptions |
207 | | - .getOpenTelemetryOptions() |
208 | | - .isExportBuiltinMetricsToGoogleCloudMonitoring()) |
209 | | - .isFalse(); |
210 | | - } |
| 180 | + |
| 181 | + |
211 | 182 |
|
212 | 183 | /** |
213 | 184 | * Verifies that a transaction operation records {@code transaction_latency} and {@code |
@@ -411,4 +382,65 @@ private static boolean dataContainsStringAttribute( |
411 | 382 | String actual = point.getAttributes().get(AttributeKey.stringKey(attributeKey)); |
412 | 383 | return expectedValue.equals(actual); |
413 | 384 | } |
| 385 | + @Test |
| 386 | + public void metricsExportedToCloudMonitoring() throws Exception { |
| 387 | + // Perform an operation to generate metrics |
| 388 | + Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-it-entity"); |
| 389 | + Entity initial = Entity.newBuilder(key).set("value", 0L).build(); |
| 390 | + datastore.put(initial); |
| 391 | + |
| 392 | + datastore.runInTransaction( |
| 393 | + tx -> { |
| 394 | + Entity current = tx.get(key); |
| 395 | + tx.put(Entity.newBuilder(current).set("value", current.getLong("value") + 1).build()); |
| 396 | + return null; |
| 397 | + }); |
| 398 | + |
| 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 | + |
| 403 | + long startTimeMillis = System.currentTimeMillis(); |
| 404 | + String filter = "metric.type = \"custom.googleapis.com/internal/client/transaction_latency\""; |
| 405 | + |
| 406 | + boolean found = false; |
| 407 | + int attempts = 0; |
| 408 | + while (System.currentTimeMillis() - startTimeMillis < 150000) { |
| 409 | + attempts++; |
| 410 | + System.out.println("Checking Cloud Monitoring for metrics (attempt " + attempts + ")..."); |
| 411 | + if (isMetricPresent(filter)) { |
| 412 | + found = true; |
| 413 | + break; |
| 414 | + } |
| 415 | + Thread.sleep(10000); // Wait 10 seconds between attempts |
| 416 | + } |
| 417 | + |
| 418 | + assertWithMessage("Metrics should be present in Cloud Monitoring").that(found).isTrue(); |
| 419 | + } |
| 420 | + |
| 421 | + private boolean isMetricPresent(String filter) throws Exception { |
| 422 | + try (MetricServiceClient client = MetricServiceClient.create()) { |
| 423 | + String name = "projects/" + PROJECT_ID; |
| 424 | + |
| 425 | + // Use a time interval covering the last 5 minutes |
| 426 | + long now = System.currentTimeMillis(); |
| 427 | + Timestamp endTime = Timestamp.newBuilder().setSeconds(now / 1000).build(); |
| 428 | + Timestamp startTime = Timestamp.newBuilder().setSeconds((now - 300000) / 1000).build(); |
| 429 | + |
| 430 | + TimeInterval interval = TimeInterval.newBuilder() |
| 431 | + .setStartTime(startTime) |
| 432 | + .setEndTime(endTime) |
| 433 | + .build(); |
| 434 | + |
| 435 | + ListTimeSeriesRequest request = ListTimeSeriesRequest.newBuilder() |
| 436 | + .setName(name) |
| 437 | + .setFilter(filter) |
| 438 | + .setInterval(interval) |
| 439 | + .setView(ListTimeSeriesRequest.TimeSeriesView.FULL) |
| 440 | + .build(); |
| 441 | + |
| 442 | + MetricServiceClient.ListTimeSeriesPagedResponse response = client.listTimeSeries(request); |
| 443 | + return response.iterateAll().iterator().hasNext(); |
| 444 | + } |
| 445 | + } |
414 | 446 | } |
0 commit comments