Skip to content

Commit cca5a81

Browse files
committed
Adding gfe_latencies metric to built-in metrics
1 parent 705b627 commit cca5a81

File tree

8 files changed

+191
-24
lines changed

8 files changed

+191
-24
lines changed

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
public class BuiltInMetricsConstant {
3535

3636
public static final String METER_NAME = "spanner.googleapis.com/internal/client";
37-
3837
public static final String GAX_METER_NAME = OpenTelemetryMetricsRecorder.GAX_METER_NAME;
39-
38+
static final String SPANNER_METER_NAME = "spanner-java";
39+
static final String GFE_LATENCIES_NAME = "gfe_latencies";
4040
static final String OPERATION_LATENCIES_NAME = "operation_latencies";
4141
static final String ATTEMPT_LATENCIES_NAME = "attempt_latencies";
4242
static final String OPERATION_LATENCY_NAME = "operation_latency";
@@ -114,27 +114,39 @@ static Map<InstrumentSelector, View> getAllViews() {
114114
ImmutableMap.Builder<InstrumentSelector, View> views = ImmutableMap.builder();
115115
defineView(
116116
views,
117+
BuiltInMetricsConstant.GAX_METER_NAME,
117118
BuiltInMetricsConstant.OPERATION_LATENCY_NAME,
118119
BuiltInMetricsConstant.OPERATION_LATENCIES_NAME,
119120
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
120121
InstrumentType.HISTOGRAM,
121122
"ms");
122123
defineView(
123124
views,
125+
BuiltInMetricsConstant.GAX_METER_NAME,
124126
BuiltInMetricsConstant.ATTEMPT_LATENCY_NAME,
125127
BuiltInMetricsConstant.ATTEMPT_LATENCIES_NAME,
126128
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
127129
InstrumentType.HISTOGRAM,
128130
"ms");
129131
defineView(
130132
views,
133+
BuiltInMetricsConstant.SPANNER_METER_NAME,
134+
BuiltInMetricsConstant.GFE_LATENCIES_NAME,
135+
BuiltInMetricsConstant.GFE_LATENCIES_NAME,
136+
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
137+
InstrumentType.HISTOGRAM,
138+
"ms");
139+
defineView(
140+
views,
141+
BuiltInMetricsConstant.GAX_METER_NAME,
131142
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
132143
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
133144
Aggregation.sum(),
134145
InstrumentType.COUNTER,
135146
"1");
136147
defineView(
137148
views,
149+
BuiltInMetricsConstant.GAX_METER_NAME,
138150
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
139151
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
140152
Aggregation.sum(),
@@ -145,6 +157,7 @@ static Map<InstrumentSelector, View> getAllViews() {
145157

146158
private static void defineView(
147159
ImmutableMap.Builder<InstrumentSelector, View> viewMap,
160+
String meterName,
148161
String metricName,
149162
String metricViewName,
150163
Aggregation aggregation,
@@ -153,7 +166,7 @@ private static void defineView(
153166
InstrumentSelector selector =
154167
InstrumentSelector.builder()
155168
.setName(BuiltInMetricsConstant.METER_NAME + '/' + metricName)
156-
.setMeterName(BuiltInMetricsConstant.GAX_METER_NAME)
169+
.setMeterName(meterName)
157170
.setType(type)
158171
.setUnit(unit)
159172
.build();

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.google.cloud.opentelemetry.detection.AttributeKeys;
2929
import com.google.cloud.opentelemetry.detection.DetectedPlatform;
3030
import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
31+
import com.google.common.cache.Cache;
32+
import com.google.common.cache.CacheBuilder;
3133
import com.google.common.hash.HashFunction;
3234
import com.google.common.hash.Hashing;
3335
import io.opentelemetry.api.OpenTelemetry;
@@ -42,6 +44,7 @@
4244
import java.util.HashMap;
4345
import java.util.Map;
4446
import java.util.UUID;
47+
import java.util.concurrent.ExecutionException;
4548
import java.util.logging.Level;
4649
import java.util.logging.Logger;
4750
import javax.annotation.Nullable;
@@ -57,6 +60,9 @@ final class BuiltInOpenTelemetryMetricsProvider {
5760

5861
private OpenTelemetry openTelemetry;
5962

63+
private final Cache<String, Map<String, String>> clientAttributesCache =
64+
CacheBuilder.newBuilder().maximumSize(1000).build();
65+
6066
private BuiltInOpenTelemetryMetricsProvider() {}
6167

6268
OpenTelemetry getOrCreateOpenTelemetry(
@@ -81,16 +87,29 @@ OpenTelemetry getOrCreateOpenTelemetry(
8187
}
8288
}
8389

84-
Map<String, String> createClientAttributes(String projectId, String client_name) {
85-
Map<String, String> clientAttributes = new HashMap<>();
86-
clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation());
87-
clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId);
88-
clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "unknown");
89-
clientAttributes.put(CLIENT_NAME_KEY.getKey(), client_name);
90-
String clientUid = getDefaultTaskValue();
91-
clientAttributes.put(CLIENT_UID_KEY.getKey(), clientUid);
92-
clientAttributes.put(CLIENT_HASH_KEY.getKey(), generateClientHash(clientUid));
93-
return clientAttributes;
90+
Map<String, String> createOrGetClientAttributes(String projectId, String client_name) {
91+
try {
92+
String key = projectId + client_name;
93+
return clientAttributesCache.get(
94+
key,
95+
() -> {
96+
Map<String, String> clientAttributes = new HashMap<>();
97+
clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation());
98+
clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId);
99+
clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "unknown");
100+
clientAttributes.put(CLIENT_NAME_KEY.getKey(), client_name);
101+
String clientUid = getDefaultTaskValue();
102+
clientAttributes.put(CLIENT_UID_KEY.getKey(), clientUid);
103+
clientAttributes.put(CLIENT_HASH_KEY.getKey(), generateClientHash(clientUid));
104+
return clientAttributes;
105+
});
106+
} catch (ExecutionException executionException) {
107+
logger.log(
108+
Level.WARNING,
109+
"Unable to get Client Attributes for client side metrics, will skip exporting client side metrics",
110+
executionException);
111+
return null;
112+
}
94113
}
95114

96115
/**
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import com.google.api.gax.core.GaxProperties;
20+
import com.google.common.annotations.VisibleForTesting;
21+
import com.google.common.base.Preconditions;
22+
import io.opentelemetry.api.OpenTelemetry;
23+
import io.opentelemetry.api.common.Attributes;
24+
import io.opentelemetry.api.common.AttributesBuilder;
25+
import io.opentelemetry.api.metrics.DoubleHistogram;
26+
import io.opentelemetry.api.metrics.Meter;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
/** OpenTelemetry implementation of recording built in metrics. */
31+
public class BuiltInOpenTelemetryMetricsRecorder {
32+
33+
private final DoubleHistogram gfeLatencyRecorder;
34+
private final Map<String, String> attributes = new HashMap<>();
35+
36+
/**
37+
* Creates the following instruments for the following metrics:
38+
*
39+
* <ul>
40+
* <li>GFE Latency: Histogram
41+
* </ul>
42+
*
43+
* @param openTelemetry OpenTelemetry instance
44+
*/
45+
public BuiltInOpenTelemetryMetricsRecorder(
46+
OpenTelemetry openTelemetry, Map<String, String> clientAttributes) {
47+
if (openTelemetry != null && clientAttributes != null) {
48+
gfeLatencyRecorder = null;
49+
return;
50+
}
51+
Meter meter =
52+
openTelemetry
53+
.meterBuilder(BuiltInMetricsConstant.SPANNER_METER_NAME)
54+
.setInstrumentationVersion(GaxProperties.getLibraryVersion(getClass()))
55+
.build();
56+
this.gfeLatencyRecorder =
57+
meter
58+
.histogramBuilder(
59+
BuiltInMetricsConstant.METER_NAME + '/' + BuiltInMetricsConstant.GFE_LATENCIES_NAME)
60+
.setDescription(
61+
"Latency between Google's network receiving an RPC and reading back the first byte of the response")
62+
.setUnit("ms")
63+
.build();
64+
this.attributes.putAll(clientAttributes);
65+
}
66+
67+
/**
68+
* Record the latency between Google's network receiving an RPC and reading back the first byte of
69+
* the response. Data is stored in a Histogram.
70+
*
71+
* @param gfeLatency Attempt Latency in ms
72+
* @param attributes Map of the attributes to store
73+
*/
74+
public void recordGFELatency(double gfeLatency, Map<String, String> attributes) {
75+
if (gfeLatencyRecorder != null) {
76+
this.attributes.putAll(attributes);
77+
gfeLatencyRecorder.record(gfeLatency, toOtelAttributes(this.attributes));
78+
}
79+
}
80+
81+
@VisibleForTesting
82+
Attributes toOtelAttributes(Map<String, String> attributes) {
83+
Preconditions.checkNotNull(attributes, "Attributes map cannot be null");
84+
AttributesBuilder attributesBuilder = Attributes.builder();
85+
attributes.forEach(attributesBuilder::put);
86+
return attributesBuilder.build();
87+
}
88+
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,18 @@ public OpenTelemetry getOpenTelemetry() {
18641864
}
18651865
}
18661866

1867+
/** Returns an instance of OpenTelemetry object for Built-in Client metrics. */
1868+
public OpenTelemetry getBuiltInMetricsOpenTelemetry() {
1869+
return this.builtInOpenTelemetryMetricsProvider.getOrCreateOpenTelemetry(
1870+
this.getProjectId(), getCredentials());
1871+
}
1872+
1873+
/** Returns attributes for an instance of Built-in Client metrics. */
1874+
public Map<String, String> getBuiltInMetricsClientAttributes() {
1875+
return builtInOpenTelemetryMetricsProvider.createOrGetClientAttributes(
1876+
this.getProjectId(), "spanner-java/" + GaxProperties.getLibraryVersion(getClass()));
1877+
}
1878+
18671879
@Override
18681880
public ApiTracerFactory getApiTracerFactory() {
18691881
return createApiTracerFactory(false, false);
@@ -1913,11 +1925,13 @@ private ApiTracerFactory createMetricsApiTracerFactory() {
19131925
this.builtInOpenTelemetryMetricsProvider.getOrCreateOpenTelemetry(
19141926
this.getProjectId(), getCredentials(), this.monitoringHost);
19151927

1916-
return openTelemetry != null
1928+
Map<String, String> clientAttributes =
1929+
builtInOpenTelemetryMetricsProvider.createOrGetClientAttributes(
1930+
this.getProjectId(), "spanner-java/" + GaxProperties.getLibraryVersion(getClass()));
1931+
return openTelemetry != null && clientAttributes != null
19171932
? new MetricsTracerFactory(
19181933
new OpenTelemetryMetricsRecorder(openTelemetry, BuiltInMetricsConstant.METER_NAME),
1919-
builtInOpenTelemetryMetricsProvider.createClientAttributes(
1920-
this.getProjectId(), "spanner-java/" + GaxProperties.getLibraryVersion(getClass())))
1934+
clientAttributes)
19211935
: null;
19221936
}
19231937

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ public GapicSpannerRpc(final SpannerOptions options) {
357357
options.getInterceptorProvider(),
358358
SpannerInterceptorProvider.createDefault(
359359
options.getOpenTelemetry(),
360+
options.getBuiltInMetricsOpenTelemetry(),
361+
options.getBuiltInMetricsClientAttributes(),
360362
(() -> directPathEnabledSupplier.get()))))
361363
// This sets the trace context headers.
362364
.withTraceContext(endToEndTracingEnabled, options.getOpenTelemetry())

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.google.api.gax.tracing.ApiTracer;
2727
import com.google.cloud.spanner.BuiltInMetricsConstant;
28+
import com.google.cloud.spanner.BuiltInOpenTelemetryMetricsRecorder;
2829
import com.google.cloud.spanner.CompositeTracer;
2930
import com.google.cloud.spanner.SpannerExceptionFactory;
3031
import com.google.cloud.spanner.SpannerRpcMetrics;
@@ -94,12 +95,17 @@ class HeaderInterceptor implements ClientInterceptor {
9495
private static final Level LEVEL = Level.INFO;
9596
private final SpannerRpcMetrics spannerRpcMetrics;
9697

98+
private final BuiltInOpenTelemetryMetricsRecorder builtInOpenTelemetryMetricsRecorder;
99+
97100
private final Supplier<Boolean> directPathEnabledSupplier;
98101

99102
HeaderInterceptor(
100-
SpannerRpcMetrics spannerRpcMetrics, Supplier<Boolean> directPathEnabledSupplier) {
103+
SpannerRpcMetrics spannerRpcMetrics,
104+
BuiltInOpenTelemetryMetricsRecorder builtInOpenTelemetryMetricsRecorder,
105+
Supplier<Boolean> directPathEnabledSupplier) {
101106
this.spannerRpcMetrics = spannerRpcMetrics;
102107
this.directPathEnabledSupplier = directPathEnabledSupplier;
108+
this.builtInOpenTelemetryMetricsRecorder = builtInOpenTelemetryMetricsRecorder;
103109
}
104110

105111
@Override
@@ -128,7 +134,12 @@ public void onHeaders(Metadata metadata) {
128134
Boolean isDirectPathUsed =
129135
isDirectPathUsed(getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
130136
addDirectPathUsedAttribute(compositeTracer, isDirectPathUsed);
131-
processHeader(metadata, tagContext, attributes, span);
137+
processHeader(
138+
metadata,
139+
tagContext,
140+
attributes,
141+
span,
142+
builtInMetricsAttributes , isDirectPathUsed);
132143
super.onHeaders(metadata);
133144
}
134145
},
@@ -142,7 +153,12 @@ public void onHeaders(Metadata metadata) {
142153
}
143154

144155
private void processHeader(
145-
Metadata metadata, TagContext tagContext, Attributes attributes, Span span) {
156+
Metadata metadata,
157+
TagContext tagContext,
158+
Attributes attributes,
159+
Span span,
160+
Map<String, String> builtInMetricsAttributes,
161+
Boolean isDirectPathUsed) {
146162
MeasureMap measureMap = STATS_RECORDER.newMeasureMap();
147163
String serverTiming = metadata.get(SERVER_TIMING_HEADER_KEY);
148164
if (serverTiming != null && serverTiming.startsWith(SERVER_TIMING_HEADER_PREFIX)) {
@@ -154,6 +170,8 @@ private void processHeader(
154170

155171
spannerRpcMetrics.recordGfeLatency(latency, attributes);
156172
spannerRpcMetrics.recordGfeHeaderMissingCount(0L, attributes);
173+
// TODO: Also pass directpath used
174+
builtInOpenTelemetryMetricsRecorder.recordGFELatency(latency, builtInMetricsAttributes);
157175

158176
if (span != null) {
159177
span.setAttribute("gfe_latency", String.valueOf(latency));

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.google.api.core.InternalApi;
1919
import com.google.api.core.ObsoleteApi;
2020
import com.google.api.gax.grpc.GrpcInterceptorProvider;
21+
import com.google.cloud.spanner.BuiltInOpenTelemetryMetricsRecorder;
2122
import com.google.cloud.spanner.SpannerRpcMetrics;
2223
import com.google.common.base.Supplier;
2324
import com.google.common.base.Suppliers;
@@ -26,7 +27,9 @@
2627
import io.opentelemetry.api.GlobalOpenTelemetry;
2728
import io.opentelemetry.api.OpenTelemetry;
2829
import java.util.ArrayList;
30+
import java.util.HashMap;
2931
import java.util.List;
32+
import java.util.Map;
3033
import java.util.logging.Level;
3134
import java.util.logging.Logger;
3235

@@ -44,26 +47,36 @@ private SpannerInterceptorProvider(List<ClientInterceptor> clientInterceptors) {
4447

4548
@ObsoleteApi("This method always uses Global OpenTelemetry")
4649
public static SpannerInterceptorProvider createDefault() {
47-
return createDefault(GlobalOpenTelemetry.get());
50+
return createDefault(GlobalOpenTelemetry.get(), GlobalOpenTelemetry.get());
4851
}
4952

50-
public static SpannerInterceptorProvider createDefault(OpenTelemetry openTelemetry) {
53+
public static SpannerInterceptorProvider createDefault(
54+
OpenTelemetry openTelemetry, OpenTelemetry builtInMetricsopenTelemetry) {
5155
return createDefault(
5256
openTelemetry,
57+
builtInMetricsopenTelemetry,
58+
new HashMap<>(),
5359
Suppliers.memoize(
5460
() -> {
5561
return false;
5662
}));
5763
}
5864

5965
public static SpannerInterceptorProvider createDefault(
60-
OpenTelemetry openTelemetry, Supplier<Boolean> directPathEnabledSupplier) {
66+
OpenTelemetry openTelemetry,
67+
OpenTelemetry builtInMetricsopenTelemetry,
68+
Map<String, String> builtInMetricsClientAttributes,
69+
Supplier<Boolean> directPathEnabledSupplier) {
6170
List<ClientInterceptor> defaultInterceptorList = new ArrayList<>();
6271
defaultInterceptorList.add(new SpannerErrorInterceptor());
6372
defaultInterceptorList.add(
6473
new LoggingInterceptor(Logger.getLogger(GapicSpannerRpc.class.getName()), Level.FINER));
6574
defaultInterceptorList.add(
66-
new HeaderInterceptor(new SpannerRpcMetrics(openTelemetry), directPathEnabledSupplier));
75+
new HeaderInterceptor(
76+
new SpannerRpcMetrics(openTelemetry),
77+
new BuiltInOpenTelemetryMetricsRecorder(
78+
builtInMetricsopenTelemetry, builtInMetricsClientAttributes),
79+
directPathEnabledSupplier));
6780
return new SpannerInterceptorProvider(ImmutableList.copyOf(defaultInterceptorList));
6881
}
6982

0 commit comments

Comments
 (0)