Skip to content

Commit 1f9f169

Browse files
fix: batch time series data when exporting client-side metric (#2222)
* Batch time series data when exporting client-side metric to fix issue with too many distinct resources. * Apply cleanups based on the comments. * Revert export code changes to diagnose integration test failures. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 26d5437 commit 1f9f169

File tree

3 files changed

+107
-20
lines changed

3 files changed

+107
-20
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ If you are using Maven without the BOM, add this to your dependencies:
5050
If you are using Gradle 5.x or later, add this to your dependencies:
5151

5252
```Groovy
53-
implementation platform('com.google.cloud:libraries-bom:26.37.0')
53+
implementation platform('com.google.cloud:libraries-bom:26.38.0')
5454
5555
implementation 'com.google.cloud:google-cloud-bigtable'
5656
```

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.google.common.base.MoreObjects;
4343
import com.google.common.collect.ImmutableList;
4444
import com.google.common.collect.ImmutableSet;
45+
import com.google.common.collect.Iterables;
4546
import com.google.common.util.concurrent.MoreExecutors;
4647
import com.google.monitoring.v3.CreateTimeSeriesRequest;
4748
import com.google.monitoring.v3.ProjectName;
@@ -53,6 +54,7 @@
5354
import io.opentelemetry.sdk.metrics.data.MetricData;
5455
import io.opentelemetry.sdk.metrics.export.MetricExporter;
5556
import java.io.IOException;
57+
import java.util.ArrayList;
5658
import java.util.Arrays;
5759
import java.util.Collection;
5860
import java.util.List;
@@ -85,6 +87,10 @@ public final class BigtableCloudMonitoringExporter implements MetricExporter {
8587

8688
private static final String APPLICATION_RESOURCE_PROJECT_ID = "project_id";
8789

90+
// This the quota limit from Cloud Monitoring. More details in
91+
// https://cloud.google.com/monitoring/quotas#custom_metrics_quotas.
92+
private static final int EXPORT_BATCH_SIZE_LIMIT = 200;
93+
8894
private final MetricServiceClient client;
8995

9096
private final String bigtableProjectId;
@@ -216,19 +222,12 @@ private CompletableResultCode exportBigtableResourceMetrics(Collection<MetricDat
216222
}
217223

218224
ProjectName projectName = ProjectName.of(bigtableProjectId);
219-
CreateTimeSeriesRequest bigtableRequest =
220-
CreateTimeSeriesRequest.newBuilder()
221-
.setName(projectName.toString())
222-
.addAllTimeSeries(bigtableTimeSeries)
223-
.build();
224-
225-
ApiFuture<Empty> future =
226-
this.client.createServiceTimeSeriesCallable().futureCall(bigtableRequest);
225+
ApiFuture<List<Empty>> future = exportTimeSeries(projectName, bigtableTimeSeries);
227226

228227
CompletableResultCode bigtableExportCode = new CompletableResultCode();
229228
ApiFutures.addCallback(
230229
future,
231-
new ApiFutureCallback<Empty>() {
230+
new ApiFutureCallback<List<Empty>>() {
232231
@Override
233232
public void onFailure(Throwable throwable) {
234233
if (bigtableExportFailureLogged.compareAndSet(false, true)) {
@@ -245,7 +244,7 @@ public void onFailure(Throwable throwable) {
245244
}
246245

247246
@Override
248-
public void onSuccess(Empty empty) {
247+
public void onSuccess(List<Empty> emptyList) {
249248
// When an export succeeded reset the export failure flag to false so if there's a
250249
// transient failure it'll be logged.
251250
bigtableExportFailureLogged.set(false);
@@ -290,22 +289,17 @@ private CompletableResultCode exportApplicationResourceMetrics(
290289

291290
// Construct the request. The project id will be the project id of the detected monitored
292291
// resource.
293-
ApiFuture<Empty> gceOrGkeFuture;
292+
ApiFuture<List<Empty>> gceOrGkeFuture;
294293
CompletableResultCode exportCode = new CompletableResultCode();
295294
try {
296295
ProjectName projectName =
297296
ProjectName.of(applicationResource.getLabelsOrThrow(APPLICATION_RESOURCE_PROJECT_ID));
298-
CreateTimeSeriesRequest request =
299-
CreateTimeSeriesRequest.newBuilder()
300-
.setName(projectName.toString())
301-
.addAllTimeSeries(timeSeries)
302-
.build();
303297

304-
gceOrGkeFuture = this.client.createServiceTimeSeriesCallable().futureCall(request);
298+
gceOrGkeFuture = exportTimeSeries(projectName, timeSeries);
305299

306300
ApiFutures.addCallback(
307301
gceOrGkeFuture,
308-
new ApiFutureCallback<Empty>() {
302+
new ApiFutureCallback<List<Empty>>() {
309303
@Override
310304
public void onFailure(Throwable throwable) {
311305
if (applicationExportFailureLogged.compareAndSet(false, true)) {
@@ -322,7 +316,7 @@ public void onFailure(Throwable throwable) {
322316
}
323317

324318
@Override
325-
public void onSuccess(Empty empty) {
319+
public void onSuccess(List<Empty> emptyList) {
326320
// When an export succeeded reset the export failure flag to false so if there's a
327321
// transient failure it'll be logged.
328322
applicationExportFailureLogged.set(false);
@@ -341,6 +335,23 @@ public void onSuccess(Empty empty) {
341335
return exportCode;
342336
}
343337

338+
private ApiFuture<List<Empty>> exportTimeSeries(
339+
ProjectName projectName, List<TimeSeries> timeSeries) {
340+
List<ApiFuture<Empty>> batchResults = new ArrayList<>();
341+
342+
for (List<TimeSeries> batch : Iterables.partition(timeSeries, EXPORT_BATCH_SIZE_LIMIT)) {
343+
CreateTimeSeriesRequest req =
344+
CreateTimeSeriesRequest.newBuilder()
345+
.setName(projectName.toString())
346+
.addAllTimeSeries(batch)
347+
.build();
348+
ApiFuture<Empty> f = this.client.createServiceTimeSeriesCallable().futureCall(req);
349+
batchResults.add(f);
350+
}
351+
352+
return ApiFutures.allAsList(batchResults);
353+
}
354+
344355
@Override
345356
public CompletableResultCode flush() {
346357
if (lastExportCode != null) {

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporterTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData;
5151
import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData;
5252
import io.opentelemetry.sdk.resources.Resource;
53+
import java.util.ArrayList;
5354
import java.util.Arrays;
55+
import java.util.Collection;
5456
import org.junit.After;
5557
import org.junit.Before;
5658
import org.junit.Rule;
@@ -220,6 +222,80 @@ public void testExportingHistogramData() {
220222
assertThat(timeSeries.getPoints(0).getInterval().getEndTime().getNanos()).isEqualTo(endEpoch);
221223
}
222224

225+
@Test
226+
public void testExportingSumDataInBatches() {
227+
ArgumentCaptor<CreateTimeSeriesRequest> argumentCaptor =
228+
ArgumentCaptor.forClass(CreateTimeSeriesRequest.class);
229+
230+
UnaryCallable<CreateTimeSeriesRequest, Empty> mockCallable = mock(UnaryCallable.class);
231+
when(mockMetricServiceStub.createServiceTimeSeriesCallable()).thenReturn(mockCallable);
232+
ApiFuture<Empty> future = ApiFutures.immediateFuture(Empty.getDefaultInstance());
233+
when(mockCallable.futureCall(argumentCaptor.capture())).thenReturn(future);
234+
235+
long startEpoch = 10;
236+
long endEpoch = 15;
237+
238+
Collection<MetricData> toExport = new ArrayList<>();
239+
for (int i = 0; i < 250; i++) {
240+
Attributes testAttributes =
241+
Attributes.builder()
242+
.put(BIGTABLE_PROJECT_ID_KEY, projectId)
243+
.put(INSTANCE_ID_KEY, instanceId)
244+
.put(TABLE_ID_KEY, tableId + i)
245+
.put(CLUSTER_ID_KEY, cluster)
246+
.put(ZONE_ID_KEY, zone)
247+
.put(APP_PROFILE_KEY, appProfileId)
248+
.build();
249+
LongPointData longPointData =
250+
ImmutableLongPointData.create(startEpoch, endEpoch, testAttributes, i);
251+
252+
MetricData longData =
253+
ImmutableMetricData.createLongSum(
254+
resource,
255+
scope,
256+
"bigtable.googleapis.com/internal/client/retry_count",
257+
"description",
258+
"1",
259+
ImmutableSumData.create(
260+
true, AggregationTemporality.CUMULATIVE, ImmutableList.of(longPointData)));
261+
toExport.add(longData);
262+
}
263+
264+
exporter.export(toExport);
265+
266+
assertThat(argumentCaptor.getAllValues()).hasSize(2);
267+
CreateTimeSeriesRequest firstRequest = argumentCaptor.getAllValues().get(0);
268+
CreateTimeSeriesRequest secondRequest = argumentCaptor.getAllValues().get(1);
269+
270+
assertThat(firstRequest.getTimeSeriesList()).hasSize(200);
271+
assertThat(secondRequest.getTimeSeriesList()).hasSize(50);
272+
273+
for (int i = 0; i < 250; i++) {
274+
TimeSeries timeSeries;
275+
if (i < 200) {
276+
timeSeries = firstRequest.getTimeSeriesList().get(i);
277+
} else {
278+
timeSeries = secondRequest.getTimeSeriesList().get(i - 200);
279+
}
280+
281+
assertThat(timeSeries.getResource().getLabelsMap())
282+
.containsExactly(
283+
BIGTABLE_PROJECT_ID_KEY.getKey(), projectId,
284+
INSTANCE_ID_KEY.getKey(), instanceId,
285+
TABLE_ID_KEY.getKey(), tableId + i,
286+
CLUSTER_ID_KEY.getKey(), cluster,
287+
ZONE_ID_KEY.getKey(), zone);
288+
289+
assertThat(timeSeries.getMetric().getLabelsMap()).hasSize(2);
290+
assertThat(timeSeries.getMetric().getLabelsMap())
291+
.containsAtLeast(APP_PROFILE_KEY.getKey(), appProfileId, CLIENT_UID_KEY.getKey(), taskId);
292+
assertThat(timeSeries.getPoints(0).getValue().getInt64Value()).isEqualTo(i);
293+
assertThat(timeSeries.getPoints(0).getInterval().getStartTime().getNanos())
294+
.isEqualTo(startEpoch);
295+
assertThat(timeSeries.getPoints(0).getInterval().getEndTime().getNanos()).isEqualTo(endEpoch);
296+
}
297+
}
298+
223299
@Test
224300
public void testTimeSeriesForMetricWithGceOrGkeResource() {
225301
String gceProjectId = "fake-gce-project";

0 commit comments

Comments
 (0)