Skip to content

Commit

Permalink
feat:client metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
surbhigarg92 committed Jul 3, 2024
1 parent c3d3f23 commit 7bc15e5
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 7 deletions.
7 changes: 3 additions & 4 deletions google-cloud-spanner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<site.installationModule>google-cloud-spanner</site.installationModule>
<opencensus.version>0.31.1</opencensus.version>
<spanner.testenv.config.class>com.google.cloud.spanner.GceTestEnvConfig</spanner.testenv.config.class>
<spanner.testenv.instance>projects/gcloud-devel/instances/spanner-testing-east1</spanner.testenv.instance>
<spanner.gce.config.project_id>gcloud-devel</spanner.gce.config.project_id>
<spanner.testenv.kms_key.name>projects/gcloud-devel/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key</spanner.testenv.kms_key.name>
<spanner.testenv.instance>projects/span-cloud-testing/instances/surbhi-testing</spanner.testenv.instance>
<spanner.gce.config.project_id>span-cloud-testing</spanner.gce.config.project_id>
<spanner.testenv.kms_key.name>projects/span-cloud-testing/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key</spanner.testenv.kms_key.name>
</properties>


Expand Down Expand Up @@ -431,7 +431,6 @@
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.google.cloud.spanner;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.metrics.Aggregation;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -79,4 +81,12 @@ public class BuiltInMetricsConstant {
CLIENT_NAME_KEY,
DIRECT_PATH_ENABLED_KEY,
DIRECT_PATH_USED_KEY);

public static Aggregation AGGREGATION_WITH_MILLIS_HISTOGRAM =
Aggregation.explicitBucketHistogram(
ImmutableList.of(
0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0, 16.0, 20.0, 25.0, 30.0, 40.0,
50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0, 300.0, 400.0, 500.0, 650.0,
800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0, 100000.0, 200000.0,
400000.0, 800000.0, 1600000.0, 3200000.0));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner;

import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_NAME_KEY;
import static com.google.cloud.spanner.BuiltInMetricsConstant.CLIENT_UID_KEY;
import static com.google.cloud.spanner.BuiltInMetricsConstant.DIRECT_PATH_ENABLED_KEY;
import static com.google.cloud.spanner.BuiltInMetricsConstant.INSTANCE_CONFIG_ID_KEY;
import static com.google.cloud.spanner.BuiltInMetricsConstant.LOCATION_ID_KEY;
import static com.google.cloud.spanner.BuiltInMetricsConstant.PROJECT_ID_KEY;

import com.google.api.gax.core.GaxProperties;
import com.google.auth.Credentials;
import com.google.cloud.opentelemetry.detection.DetectedPlatform;
import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
import com.google.common.collect.ImmutableSet;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.View;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

class BuiltInOpenTelemetryMetricsProvider {

private static final Logger logger =
Logger.getLogger(BuiltInOpenTelemetryMetricsProvider.class.getName());

private OpenTelemetry openTelemetry;

public OpenTelemetry getOpenTelemetry(String projectId, @Nullable Credentials credentials) {
if (this.openTelemetry == null) {

// Use custom exporter
MetricExporter metricExporter = null;
try {
metricExporter = SpannerCloudMonitoringExporter.create(projectId, credentials);

SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder();
registerView(
sdkMeterProviderBuilder,
BuiltInMetricsConstant.OPERATION_LATENCY_NAME,
BuiltInMetricsConstant.OPERATION_LATENCIES_NAME,
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
InstrumentType.HISTOGRAM,
"ms");
registerView(
sdkMeterProviderBuilder,
BuiltInMetricsConstant.ATTEMPT_LATENCY_NAME,
BuiltInMetricsConstant.ATTEMPT_LATENCIES_NAME,
BuiltInMetricsConstant.AGGREGATION_WITH_MILLIS_HISTOGRAM,
InstrumentType.HISTOGRAM,
"ms");
registerView(
sdkMeterProviderBuilder,
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
BuiltInMetricsConstant.OPERATION_COUNT_NAME,
Aggregation.sum(),
InstrumentType.COUNTER,
"1");
registerView(
sdkMeterProviderBuilder,
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
BuiltInMetricsConstant.ATTEMPT_COUNT_NAME,
Aggregation.sum(),
InstrumentType.COUNTER,
"1");

SdkMeterProvider sdkMeterProvider =
sdkMeterProviderBuilder
.registerMetricReader(PeriodicMetricReader.create(metricExporter))
.build();

this.openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
} catch (IOException e) {
logger.log(
Level.WARNING,
"Unable to get OpenTelemetry object for client side metrics, will skip exporting client side metrics",
e);
}
}
return this.openTelemetry;
}

public Map<String, String> getClientAttributes(String projectId) {
Map<String, String> clientAttributes = new HashMap<>();
clientAttributes.put(LOCATION_ID_KEY.getKey(), detectClientLocation());
clientAttributes.put(PROJECT_ID_KEY.getKey(), projectId);
clientAttributes.put(INSTANCE_CONFIG_ID_KEY.getKey(), "us-central1");
clientAttributes.put(DIRECT_PATH_ENABLED_KEY.getKey(), "true");
clientAttributes.put(
CLIENT_NAME_KEY.getKey(),
"spanner-java/"
+ GaxProperties.getLibraryVersion(SpannerCloudMonitoringExporterUtils.class));
clientAttributes.put(CLIENT_UID_KEY.getKey(), getDefaultTaskValue());
return clientAttributes;
}

private void registerView(
SdkMeterProviderBuilder sdkMeterProviderBuilder, String metricName, String metricViewName,
Aggregation aggregation,
InstrumentType type,
String unit) {
InstrumentSelector selector =
InstrumentSelector.builder()
.setName(BuiltInMetricsConstant.METER_NAME + '/' + metricName)
.setMeterName(BuiltInMetricsConstant.GAX_METER_NAME)
.setType(type)
.setUnit(unit)
.build();
Set<String> attributesFilter =
ImmutableSet.<String>builder()
.addAll(
BuiltInMetricsConstant.COMMON_ATTRIBUTES.stream()
.map(AttributeKey::getKey)
.collect(Collectors.toSet()))
.build();
View view =
View.builder()
.setName(BuiltInMetricsConstant.METER_NAME + '/' + metricViewName)
.setAggregation(aggregation)
.setAttributeFilter(attributesFilter)
.build();
sdkMeterProviderBuilder.registerView(selector, view);
}

private String detectClientLocation() {
GCPPlatformDetector detector = GCPPlatformDetector.DEFAULT_INSTANCE;
DetectedPlatform detectedPlatform = detector.detectPlatform();
String region = detectedPlatform.getAttributes().get("cloud.region");
return region;
}

/**
* In most cases this should look like ${UUID}@${hostname}. The hostname will be retrieved from
* the jvm name and fallback to the local hostname.
*/
private String getDefaultTaskValue() {
// Something like '<pid>@<hostname>'
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
// If jvm doesn't have the expected format, fallback to the local hostname
if (jvmName.indexOf('@') < 1) {
String hostname = "localhost";
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
logger.log(Level.INFO, "Unable to get the hostname.", e);
}
// Generate a random number and use the same format "random_number@hostname".
return UUID.randomUUID() + "@" + hostname;
}
return UUID.randomUUID() + jvmName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.api.gax.tracing.ApiTracerFactory;
import com.google.api.gax.tracing.BaseApiTracerFactory;
import com.google.api.gax.tracing.MetricsTracerFactory;
import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder;
import com.google.api.gax.tracing.OpencensusTracerFactory;
import com.google.cloud.NoCredentials;
import com.google.cloud.ServiceDefaults;
Expand Down Expand Up @@ -157,7 +159,9 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
private final boolean useVirtualThreads;
private final OpenTelemetry openTelemetry;
private final boolean enableApiTracing;
private final boolean enableBuiltInMetrics;
private final boolean enableExtendedTracing;
private OpenTelemetry builtInOpenTelemetry;

enum TracingFramework {
OPEN_CENSUS,
Expand Down Expand Up @@ -664,6 +668,7 @@ protected SpannerOptions(Builder builder) {
openTelemetry = builder.openTelemetry;
enableApiTracing = builder.enableApiTracing;
enableExtendedTracing = builder.enableExtendedTracing;
enableBuiltInMetrics = builder.enableBuiltInMetrics;
}

/**
Expand Down Expand Up @@ -696,6 +701,10 @@ default boolean isEnableExtendedTracing() {
default boolean isEnableApiTracing() {
return false;
}

default boolean isEnableBuiltInMetrics() {
return true;
}
}

/**
Expand All @@ -709,6 +718,7 @@ private static class SpannerEnvironmentImpl implements SpannerEnvironment {
"SPANNER_OPTIMIZER_STATISTICS_PACKAGE";
private static final String SPANNER_ENABLE_EXTENDED_TRACING = "SPANNER_ENABLE_EXTENDED_TRACING";
private static final String SPANNER_ENABLE_API_TRACING = "SPANNER_ENABLE_API_TRACING";
private static final String SPANNER_ENABLE_BUILTIN_METRICS = "SPANNER_ENABLE_BUILTIN_METRICS";

private SpannerEnvironmentImpl() {}

Expand All @@ -734,6 +744,13 @@ public boolean isEnableExtendedTracing() {
public boolean isEnableApiTracing() {
return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_API_TRACING));
}

@Override
public boolean isEnableBuiltInMetrics() {
if (System.getenv(SPANNER_ENABLE_BUILTIN_METRICS) != null)
return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_BUILTIN_METRICS));
return true;
}
}

/** Builder for {@link SpannerOptions} instances. */
Expand Down Expand Up @@ -799,6 +816,7 @@ public static class Builder
private OpenTelemetry openTelemetry;
private boolean enableApiTracing = SpannerOptions.environment.isEnableApiTracing();
private boolean enableExtendedTracing = SpannerOptions.environment.isEnableExtendedTracing();
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();

private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
Expand Down Expand Up @@ -864,6 +882,7 @@ protected Builder() {
this.useVirtualThreads = options.useVirtualThreads;
this.enableApiTracing = options.enableApiTracing;
this.enableExtendedTracing = options.enableExtendedTracing;
this.enableBuiltInMetrics = options.enableBuiltInMetrics;
}

@Override
Expand Down Expand Up @@ -1391,6 +1410,12 @@ public Builder setEnableExtendedTracing(boolean enableExtendedTracing) {
return this;
}

/** Disables client built in metrics. */
public Builder disableBuiltInMetrics() {
this.enableBuiltInMetrics = false;
return this;
}

@SuppressWarnings("rawtypes")
@Override
public SpannerOptions build() {
Expand Down Expand Up @@ -1623,6 +1648,7 @@ public boolean isAttemptDirectPath() {
public OpenTelemetry getOpenTelemetry() {
if (this.openTelemetry != null) {
return this.openTelemetry;

} else {
return GlobalOpenTelemetry.get();
}
Expand All @@ -1634,13 +1660,19 @@ public ApiTracerFactory getApiTracerFactory() {
// Prefer any direct ApiTracerFactory that might have been set on the builder.
apiTracerFactories.add(
MoreObjects.firstNonNull(super.getApiTracerFactory(), getDefaultApiTracerFactory()));

// Add Metrics Tracer factory
if (isEnableBuiltInMetrics()) {
ApiTracerFactory metricsTracerFactory = getMetricsApiTracerFactory();
if (metricsTracerFactory != null) {
apiTracerFactories.add(metricsTracerFactory);
}
}
return new CompositeTracerFactory(apiTracerFactories);
}

private ApiTracerFactory getDefaultApiTracerFactory() {
if (isEnableApiTracing()) {
if (activeTracingFramework == TracingFramework.OPEN_TELEMETRY) {
if (!isEnableApiTracing()) {
if (activeTracingFramework != TracingFramework.OPEN_TELEMETRY) {
return new OpenTelemetryApiTracerFactory(
getOpenTelemetry()
.getTracer(
Expand All @@ -1654,6 +1686,17 @@ private ApiTracerFactory getDefaultApiTracerFactory() {
return BaseApiTracerFactory.getInstance();
}

private ApiTracerFactory getMetricsApiTracerFactory() {
OpenTelemetry openTelemetry =
new BuiltInOpenTelemetryMetricsProvider()
.getOpenTelemetry(getDefaultProjectId(), getCredentials());

return openTelemetry != null
? new MetricsTracerFactory(
new OpenTelemetryMetricsRecorder(openTelemetry, BuiltInMetricsConstant.METER_NAME))
: null;
}

/**
* Returns true if an {@link com.google.api.gax.tracing.ApiTracer} should be created and set on
* the Spanner client. Enabling this only has effect if an OpenTelemetry or OpenCensus trace
Expand All @@ -1663,6 +1706,15 @@ public boolean isEnableApiTracing() {
return enableApiTracing;
}

/**
* Returns true if an {@link com.google.api.gax.tracing.ApiTracer} should be created and set on
* the Spanner client. Enabling this only has effect if an OpenTelemetry or OpenCensus trace
* exporter has been configured.
*/
public boolean isEnableBuiltInMetrics() {
return enableBuiltInMetrics;
}

@BetaApi
public boolean isUseVirtualThreads() {
return useVirtualThreads;
Expand Down
Loading

0 comments on commit 7bc15e5

Please sign in to comment.