Skip to content

Commit facbb3f

Browse files
authored
chore: client metrics (#3125)
* feat: client metrics * Review comments * Few issues and code optimisations * review comments * directpath_used attribute * removed directpath enabled attribute * removed directpath enabled attribute * lint * bucket boundaries
1 parent b5a4ebd commit facbb3f

File tree

11 files changed

+844
-22
lines changed

11 files changed

+844
-22
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,13 @@
695695
<method>boolean isEnableApiTracing()</method>
696696
</difference>
697697

698+
<!-- Added Built In Metrics option -->
699+
<difference>
700+
<differenceType>7012</differenceType>
701+
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
702+
<method>boolean isEnableBuiltInMetrics()</method>
703+
</difference>
704+
698705
<!-- Added ExcludeTxnFromChangeStreams -->
699706
<difference>
700707
<differenceType>7012</differenceType>
@@ -725,7 +732,7 @@
725732
<className>com/google/cloud/spanner/SessionPoolOptions$Builder</className>
726733
<method>com.google.cloud.spanner.SessionPoolOptions$Builder setUseMultiplexedSession(boolean)</method>
727734
</difference>
728-
735+
729736
<!-- Added reset() method -->
730737
<difference>
731738
<differenceType>7012</differenceType>

google-cloud-spanner/pom.xml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@
246246
<groupId>io.opentelemetry</groupId>
247247
<artifactId>opentelemetry-context</artifactId>
248248
</dependency>
249+
<dependency>
250+
<groupId>io.opentelemetry</groupId>
251+
<artifactId>opentelemetry-sdk</artifactId>
252+
</dependency>
249253
<dependency>
250254
<groupId>io.opentelemetry</groupId>
251255
<artifactId>opentelemetry-sdk-common</artifactId>
@@ -254,6 +258,10 @@
254258
<groupId>io.opentelemetry</groupId>
255259
<artifactId>opentelemetry-sdk-metrics</artifactId>
256260
</dependency>
261+
<dependency>
262+
<groupId>com.google.cloud.opentelemetry</groupId>
263+
<artifactId>detector-resources-support</artifactId>
264+
</dependency>
257265
<dependency>
258266
<groupId>com.google.cloud</groupId>
259267
<artifactId>google-cloud-monitoring</artifactId>
@@ -437,11 +445,6 @@
437445
<scope>test</scope>
438446
</dependency>
439447
<!-- OpenTelemetry test dependencies -->
440-
<dependency>
441-
<groupId>io.opentelemetry</groupId>
442-
<artifactId>opentelemetry-sdk</artifactId>
443-
<scope>test</scope>
444-
</dependency>
445448
<dependency>
446449
<groupId>io.opentelemetry</groupId>
447450
<artifactId>opentelemetry-sdk-trace</artifactId>
@@ -610,4 +613,4 @@
610613
</dependencies>
611614
</profile>
612615
</profiles>
613-
</project>
616+
</project>

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

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,26 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.core.InternalApi;
20+
import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder;
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableMap;
1923
import com.google.common.collect.ImmutableSet;
2024
import io.opentelemetry.api.common.AttributeKey;
25+
import io.opentelemetry.sdk.metrics.Aggregation;
26+
import io.opentelemetry.sdk.metrics.InstrumentSelector;
27+
import io.opentelemetry.sdk.metrics.InstrumentType;
28+
import io.opentelemetry.sdk.metrics.View;
29+
import java.util.Map;
2130
import java.util.Set;
2231
import java.util.stream.Collectors;
2332

33+
@InternalApi
2434
public class BuiltInMetricsConstant {
2535

2636
public static final String METER_NAME = "spanner.googleapis.com/internal/client";
2737

28-
public static final String GAX_METER_NAME = "gax-java";
38+
public static final String GAX_METER_NAME = OpenTelemetryMetricsRecorder.GAX_METER_NAME;
2939

3040
static final String OPERATION_LATENCIES_NAME = "operation_latencies";
3141
static final String ATTEMPT_LATENCIES_NAME = "attempt_latencies";
@@ -66,6 +76,10 @@ public class BuiltInMetricsConstant {
6676
public static final AttributeKey<String> DIRECT_PATH_USED_KEY =
6777
AttributeKey.stringKey("directpath_used");
6878

79+
// IP address prefixes allocated for DirectPath backends.
80+
public static final String DP_IPV6_PREFIX = "2001:4860:8040";
81+
public static final String DP_IPV4_PREFIX = "34.126";
82+
6983
public static final Set<AttributeKey> COMMON_ATTRIBUTES =
7084
ImmutableSet.of(
7185
PROJECT_ID_KEY,
@@ -79,4 +93,73 @@ public class BuiltInMetricsConstant {
7993
CLIENT_NAME_KEY,
8094
DIRECT_PATH_ENABLED_KEY,
8195
DIRECT_PATH_USED_KEY);
96+
97+
static Aggregation AGGREGATION_WITH_MILLIS_HISTOGRAM =
98+
Aggregation.explicitBucketHistogram(
99+
ImmutableList.of(
100+
0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0,
101+
15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0,
102+
160.0, 200.0, 250.0, 300.0, 400.0, 500.0, 650.0, 800.0, 1000.0, 2000.0, 5000.0,
103+
10000.0, 20000.0, 50000.0, 100000.0, 200000.0, 400000.0, 800000.0, 1600000.0,
104+
3200000.0));
105+
106+
static Map<InstrumentSelector, View> getAllViews() {
107+
ImmutableMap.Builder<InstrumentSelector, View> views = ImmutableMap.builder();
108+
defineView(
109+
views,
110+
BuiltInMetricsConstant.OPERATION_LATENCY_NAME,
111+
BuiltInMetricsConstant.OPERATION_LATENCIES_NAME,
112+
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
113+
InstrumentType.HISTOGRAM,
114+
"ms");
115+
defineView(
116+
views,
117+
BuiltInMetricsConstant.ATTEMPT_LATENCY_NAME,
118+
BuiltInMetricsConstant.ATTEMPT_LATENCIES_NAME,
119+
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
120+
InstrumentType.HISTOGRAM,
121+
"ms");
122+
defineView(
123+
views,
124+
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
125+
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
126+
Aggregation.sum(),
127+
InstrumentType.COUNTER,
128+
"1");
129+
defineView(
130+
views,
131+
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
132+
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
133+
Aggregation.sum(),
134+
InstrumentType.COUNTER,
135+
"1");
136+
return views.build();
137+
}
138+
139+
private static void defineView(
140+
ImmutableMap.Builder<InstrumentSelector, View> viewMap,
141+
String metricName,
142+
String metricViewName,
143+
Aggregation aggregation,
144+
InstrumentType type,
145+
String unit) {
146+
InstrumentSelector selector =
147+
InstrumentSelector.builder()
148+
.setName(BuiltInMetricsConstant.METER_NAME + '/' + metricName)
149+
.setMeterName(BuiltInMetricsConstant.GAX_METER_NAME)
150+
.setType(type)
151+
.setUnit(unit)
152+
.build();
153+
Set<String> attributesFilter =
154+
BuiltInMetricsConstant.COMMON_ATTRIBUTES.stream()
155+
.map(AttributeKey::getKey)
156+
.collect(Collectors.toSet());
157+
View view =
158+
View.builder()
159+
.setName(BuiltInMetricsConstant.METER_NAME + '/' + metricViewName)
160+
.setAggregation(aggregation)
161+
.setAttributeFilter(attributesFilter)
162+
.build();
163+
viewMap.put(selector, view);
164+
}
82165
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 static com.google.cloud.opentelemetry.detection.GCPPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE;
20+
import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_NAME_KEY;
21+
import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_UID_KEY;
22+
import static com.google.cloud.spanner.BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY;
23+
import static com.google.cloud.spanner.BuiltInMetricsConstant.LOCATION_ID_KEY;
24+
import static com.google.cloud.spanner.BuiltInMetricsConstant.PROJECT_ID_KEY;
25+
26+
import com.google.auth.Credentials;
27+
import com.google.cloud.opentelemetry.detection.AttributeKeys;
28+
import com.google.cloud.opentelemetry.detection.DetectedPlatform;
29+
import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
30+
import io.opentelemetry.api.OpenTelemetry;
31+
import io.opentelemetry.sdk.OpenTelemetrySdk;
32+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
33+
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
34+
import java.io.IOException;
35+
import java.lang.management.ManagementFactory;
36+
import java.lang.reflect.Method;
37+
import java.net.InetAddress;
38+
import java.net.UnknownHostException;
39+
import java.util.HashMap;
40+
import java.util.Map;
41+
import java.util.UUID;
42+
import java.util.logging.Level;
43+
import java.util.logging.Logger;
44+
import javax.annotation.Nullable;
45+
46+
final class BuiltInOpenTelemetryMetricsProvider {
47+
48+
static BuiltInOpenTelemetryMetricsProvider INSTANCE = new BuiltInOpenTelemetryMetricsProvider();
49+
50+
private static final Logger logger =
51+
Logger.getLogger(BuiltInOpenTelemetryMetricsProvider.class.getName());
52+
53+
private static String taskId;
54+
55+
private OpenTelemetry openTelemetry;
56+
57+
private BuiltInOpenTelemetryMetricsProvider() {}
58+
59+
OpenTelemetry getOrCreateOpenTelemetry(String projectId, @Nullable Credentials credentials) {
60+
try {
61+
if (this.openTelemetry == null) {
62+
SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder();
63+
BuiltInOpenTelemetryMetricsView.registerBuiltinMetrics(
64+
SpannerCloudMonitoringExporter.create(projectId, credentials), sdkMeterProviderBuilder);
65+
this.openTelemetry =
66+
OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProviderBuilder.build()).build();
67+
}
68+
return this.openTelemetry;
69+
} catch (IOException ex) {
70+
logger.log(
71+
Level.WARNING,
72+
"Unable to get OpenTelemetry object for client side metrics, will skip exporting client side metrics",
73+
ex);
74+
return null;
75+
}
76+
}
77+
78+
Map<String, String> createClientAttributes(String projectId, String client_name) {
79+
Map<String, String> clientAttributes = new HashMap<>();
80+
clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation());
81+
clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId);
82+
// TODO: Replace this with real value.
83+
clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "unknown");
84+
clientAttributes.put(CLIENT_NAME_KEY.getKey(), client_name);
85+
clientAttributes.put(CLIENT_UID_KEY.getKey(), getDefaultTaskValue());
86+
return clientAttributes;
87+
}
88+
89+
static String detectClientLocation() {
90+
GCPPlatformDetector detector = GCPPlatformDetector.DEFAULT_INSTANCE;
91+
DetectedPlatform detectedPlatform = detector.detectPlatform();
92+
// All platform except GKE uses "cloud_region" for region attribute.
93+
String region = detectedPlatform.getAttributes().get("cloud_region");
94+
if (detectedPlatform.getSupportedPlatform() == GOOGLE_KUBERNETES_ENGINE) {
95+
region = detectedPlatform.getAttributes().get(AttributeKeys.GKE_LOCATION_TYPE_REGION);
96+
}
97+
return region == null ? "global" : region;
98+
}
99+
100+
/**
101+
* Generates a unique identifier for the Client_uid metric field. The identifier is composed of a
102+
* UUID, the process ID (PID), and the hostname.
103+
*
104+
* <p>For Java 9 and later, the PID is obtained using the ProcessHandle API. For Java 8, the PID
105+
* is extracted from ManagementFactory.getRuntimeMXBean().getName().
106+
*
107+
* @return A unique identifier string in the format UUID@PID@hostname
108+
*/
109+
private static String getDefaultTaskValue() {
110+
if (taskId == null) {
111+
String identifier = UUID.randomUUID().toString();
112+
String pid = getProcessId();
113+
114+
try {
115+
String hostname = InetAddress.getLocalHost().getHostName();
116+
taskId = identifier + "@" + pid + "@" + hostname;
117+
} catch (UnknownHostException e) {
118+
logger.log(Level.INFO, "Unable to get the hostname.", e);
119+
taskId = identifier + "@" + pid + "@localhost";
120+
}
121+
}
122+
return taskId;
123+
}
124+
125+
private static String getProcessId() {
126+
try {
127+
// Check if Java 9+ and ProcessHandle class is available
128+
Class<?> processHandleClass = Class.forName("java.lang.ProcessHandle");
129+
Method currentMethod = processHandleClass.getMethod("current");
130+
Object processHandleInstance = currentMethod.invoke(null);
131+
Method pidMethod = processHandleClass.getMethod("pid");
132+
long pid = (long) pidMethod.invoke(processHandleInstance);
133+
return Long.toString(pid);
134+
} catch (Exception e) {
135+
// Fallback to Java 8 method
136+
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
137+
if (jvmName != null && jvmName.contains("@")) {
138+
return jvmName.split("@")[0];
139+
} else {
140+
return "unknown";
141+
}
142+
}
143+
}
144+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
20+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
21+
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
22+
23+
class BuiltInOpenTelemetryMetricsView {
24+
25+
private BuiltInOpenTelemetryMetricsView() {}
26+
27+
/** Register built-in metrics on the {@link SdkMeterProviderBuilder} with credentials. */
28+
static void registerBuiltinMetrics(
29+
MetricExporter metricExporter, SdkMeterProviderBuilder builder) {
30+
BuiltInMetricsConstant.getAllViews().forEach(builder::registerView);
31+
builder.registerMetricReader(PeriodicMetricReader.create(metricExporter));
32+
}
33+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.common.collect.ImmutableList;
2424
import java.util.ArrayList;
2525
import java.util.List;
26+
import java.util.Map;
2627
import org.threeten.bp.Duration;
2728

2829
@InternalApi
@@ -177,5 +178,14 @@ public void addAttributes(String key, String value) {
177178
metricsTracer.addAttributes(key, value);
178179
}
179180
}
180-
};
181+
}
182+
183+
public void addAttributes(Map<String, String> attributes) {
184+
for (ApiTracer child : children) {
185+
if (child instanceof MetricsTracer) {
186+
MetricsTracer metricsTracer = (MetricsTracer) child;
187+
attributes.forEach((key, value) -> metricsTracer.addAttributes(key, value));
188+
}
189+
}
190+
}
181191
}

0 commit comments

Comments
 (0)