Skip to content

Commit 705b627

Browse files
fix: always add instance-id for built-in metrics (#3612)
* fix: always add instance-id for built-in metrics The instance-id was only added for built-in metrics when the headers of the request was sent. This meant that any request that would NOT get sent would not include an instance-id, which again would cause the export of ALL built-in metrics to stop. This would happen because the metric without an instance-id would remain in the collected metrics. A request would get into this state if the client ran into a network issue that would prevent the client from making a network connection to Spanner. Fixes #3611 * chore: generate libraries at Sun Jan 26 12:30:46 UTC 2025 --------- Co-authored-by: cloud-java-bot <cloud-java-bot@google.com>
1 parent 1be250f commit 705b627

File tree

4 files changed

+187
-56
lines changed

4 files changed

+187
-56
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java

+49-22
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.opentelemetry.sdk.metrics.InstrumentType;
4040
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
4141
import io.opentelemetry.sdk.metrics.data.MetricData;
42+
import io.opentelemetry.sdk.metrics.data.PointData;
4243
import io.opentelemetry.sdk.metrics.export.MetricExporter;
4344
import java.io.IOException;
4445
import java.time.Duration;
@@ -49,6 +50,7 @@
4950
import java.util.logging.Level;
5051
import java.util.logging.Logger;
5152
import java.util.stream.Collectors;
53+
import javax.annotation.Nonnull;
5254
import javax.annotation.Nullable;
5355

5456
/**
@@ -66,7 +68,7 @@ class SpannerCloudMonitoringExporter implements MetricExporter {
6668
// https://cloud.google.com/monitoring/quotas#custom_metrics_quotas.
6769
private static final int EXPORT_BATCH_SIZE_LIMIT = 200;
6870
private final AtomicBoolean spannerExportFailureLogged = new AtomicBoolean(false);
69-
private CompletableResultCode lastExportCode;
71+
private final AtomicBoolean lastExportSkippedData = new AtomicBoolean(false);
7072
private final MetricServiceClient client;
7173
private final String spannerProjectId;
7274

@@ -101,44 +103,49 @@ static SpannerCloudMonitoringExporter create(
101103
}
102104

103105
@Override
104-
public CompletableResultCode export(Collection<MetricData> collection) {
106+
public CompletableResultCode export(@Nonnull Collection<MetricData> collection) {
105107
if (client.isShutdown()) {
106108
logger.log(Level.WARNING, "Exporter is shut down");
107109
return CompletableResultCode.ofFailure();
108110
}
109111

110-
this.lastExportCode = exportSpannerClientMetrics(collection);
111-
return lastExportCode;
112+
return exportSpannerClientMetrics(collection);
112113
}
113114

114115
/** Export client built in metrics */
115116
private CompletableResultCode exportSpannerClientMetrics(Collection<MetricData> collection) {
116-
// Filter spanner metrics
117+
// Filter spanner metrics. Only include metrics that contain a project and instance ID.
117118
List<MetricData> spannerMetricData =
118119
collection.stream()
119120
.filter(md -> SPANNER_METRICS.contains(md.getName()))
120121
.collect(Collectors.toList());
121122

122-
// Skips exporting if there's none
123-
if (spannerMetricData.isEmpty()) {
124-
return CompletableResultCode.ofSuccess();
125-
}
126-
127-
// Verifies metrics project id is the same as the spanner project id set on this client
128-
if (!spannerMetricData.stream()
123+
// Log warnings for metrics that will be skipped.
124+
boolean mustFilter = false;
125+
if (spannerMetricData.stream()
129126
.flatMap(metricData -> metricData.getData().getPoints().stream())
130-
.allMatch(
131-
pd -> spannerProjectId.equals(SpannerCloudMonitoringExporterUtils.getProjectId(pd)))) {
132-
logger.log(Level.WARNING, "Metric data has a different projectId. Skipping export.");
133-
return CompletableResultCode.ofFailure();
127+
.anyMatch(this::shouldSkipPointDataDueToProjectId)) {
128+
logger.log(
129+
Level.WARNING, "Some metric data contain a different projectId. These will be skipped.");
130+
mustFilter = true;
134131
}
135-
136-
// Verifies if metrics data has missing instance id.
137132
if (spannerMetricData.stream()
138133
.flatMap(metricData -> metricData.getData().getPoints().stream())
139-
.anyMatch(pd -> SpannerCloudMonitoringExporterUtils.getInstanceId(pd) == null)) {
140-
logger.log(Level.WARNING, "Metric data has missing instanceId. Skipping export.");
141-
return CompletableResultCode.ofFailure();
134+
.anyMatch(this::shouldSkipPointDataDueToMissingInstanceId)) {
135+
logger.log(Level.WARNING, "Some metric data miss instanceId. These will be skipped.");
136+
mustFilter = true;
137+
}
138+
if (mustFilter) {
139+
spannerMetricData =
140+
spannerMetricData.stream()
141+
.filter(this::shouldSkipMetricData)
142+
.collect(Collectors.toList());
143+
}
144+
lastExportSkippedData.set(mustFilter);
145+
146+
// Skips exporting if there's none
147+
if (spannerMetricData.isEmpty()) {
148+
return CompletableResultCode.ofSuccess();
142149
}
143150

144151
List<TimeSeries> spannerTimeSeries;
@@ -190,6 +197,26 @@ public void onSuccess(List<Empty> empty) {
190197
return spannerExportCode;
191198
}
192199

200+
private boolean shouldSkipMetricData(MetricData metricData) {
201+
return metricData.getData().getPoints().stream()
202+
.anyMatch(
203+
pd ->
204+
shouldSkipPointDataDueToProjectId(pd)
205+
|| shouldSkipPointDataDueToMissingInstanceId(pd));
206+
}
207+
208+
private boolean shouldSkipPointDataDueToProjectId(PointData pointData) {
209+
return !spannerProjectId.equals(SpannerCloudMonitoringExporterUtils.getProjectId(pointData));
210+
}
211+
212+
private boolean shouldSkipPointDataDueToMissingInstanceId(PointData pointData) {
213+
return SpannerCloudMonitoringExporterUtils.getInstanceId(pointData) == null;
214+
}
215+
216+
boolean lastExportSkippedData() {
217+
return this.lastExportSkippedData.get();
218+
}
219+
193220
private ApiFuture<List<Empty>> exportTimeSeriesInBatch(
194221
ProjectName projectName, List<TimeSeries> timeSeries) {
195222
List<ApiFuture<Empty>> batchResults = new ArrayList<>();
@@ -233,7 +260,7 @@ public CompletableResultCode shutdown() {
233260
* metric over time.
234261
*/
235262
@Override
236-
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
263+
public AggregationTemporality getAggregationTemporality(@Nonnull InstrumentType instrumentType) {
237264
return AggregationTemporality.CUMULATIVE;
238265
}
239266
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java

+12-11
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.google.cloud.spanner.CompositeTracer;
2929
import com.google.cloud.spanner.SpannerExceptionFactory;
3030
import com.google.cloud.spanner.SpannerRpcMetrics;
31-
import com.google.common.base.Supplier;
3231
import com.google.common.cache.Cache;
3332
import com.google.common.cache.CacheBuilder;
3433
import com.google.spanner.admin.database.v1.DatabaseName;
@@ -57,6 +56,7 @@
5756
import java.util.HashMap;
5857
import java.util.Map;
5958
import java.util.concurrent.ExecutionException;
59+
import java.util.function.Supplier;
6060
import java.util.logging.Level;
6161
import java.util.logging.Logger;
6262
import java.util.regex.Matcher;
@@ -120,14 +120,14 @@ public void start(Listener<RespT> responseListener, Metadata headers) {
120120
getMetricAttributes(key, method.getFullMethodName(), databaseName);
121121
Map<String, String> builtInMetricsAttributes =
122122
getBuiltInMetricAttributes(key, databaseName);
123+
addBuiltInMetricAttributes(compositeTracer, builtInMetricsAttributes);
123124
super.start(
124125
new SimpleForwardingClientCallListener<RespT>(responseListener) {
125126
@Override
126127
public void onHeaders(Metadata metadata) {
127128
Boolean isDirectPathUsed =
128129
isDirectPathUsed(getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
129-
addBuiltInMetricAttributes(
130-
compositeTracer, builtInMetricsAttributes, isDirectPathUsed);
130+
addDirectPathUsedAttribute(compositeTracer, isDirectPathUsed);
131131
processHeader(metadata, tagContext, attributes, span);
132132
super.onHeaders(metadata);
133133
}
@@ -241,16 +241,17 @@ private Map<String, String> getBuiltInMetricAttributes(String key, DatabaseName
241241
}
242242

243243
private void addBuiltInMetricAttributes(
244-
CompositeTracer compositeTracer,
245-
Map<String, String> builtInMetricsAttributes,
246-
Boolean isDirectPathUsed) {
244+
CompositeTracer compositeTracer, Map<String, String> builtInMetricsAttributes) {
247245
if (compositeTracer != null) {
248-
// Direct Path used attribute
249-
Map<String, String> attributes = new HashMap<>(builtInMetricsAttributes);
250-
attributes.put(
251-
BuiltInMetricsConstant.DIRECT_PATH_USED_KEY.getKey(), Boolean.toString(isDirectPathUsed));
246+
compositeTracer.addAttributes(builtInMetricsAttributes);
247+
}
248+
}
252249

253-
compositeTracer.addAttributes(attributes);
250+
private void addDirectPathUsedAttribute(
251+
CompositeTracer compositeTracer, Boolean isDirectPathUsed) {
252+
if (compositeTracer != null) {
253+
compositeTracer.addAttributes(
254+
BuiltInMetricsConstant.DIRECT_PATH_USED_KEY.getKey(), Boolean.toString(isDirectPathUsed));
254255
}
255256
}
256257

0 commit comments

Comments
 (0)