Skip to content
Permalink
Browse files
feat: instrument Spanner client with OpenCensus metrics (#54)
* feat: add session metrics
Add active_sessions (The number of active sessions in use) and max_sessions (The number of max sessions configured the user) metrics

* Fix code reviews

Use maxSessionsInUse instead of numSessionsInUse and update the description.

* Change active sessions description

* add numSessionsInUse metric

* Fix package structure

* rename metric name and description

* fix nits

* createMockSession for metrics validations
  • Loading branch information
mayurkale22 committed Feb 6, 2020
1 parent ce93f87 commit d9a00a81c454ae793f9687d0e2de2bcc58d96502
@@ -0,0 +1,53 @@
/*
* Copyright 2020 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
*
* https://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 com.google.common.collect.ImmutableList;
import io.opencensus.metrics.LabelKey;
import io.opencensus.metrics.LabelValue;

/** A helper class that holds OpenCensus's related constants. */
class MetricRegistryConstants {

// The label keys are used to uniquely identify timeseries.
private static final LabelKey DATABASE = LabelKey.create("database", "Target database");
private static final LabelKey INSTANCE_ID =
LabelKey.create("instance_id", "Name of the instance");
private static final LabelKey LIBRARY_VERSION =
LabelKey.create("library_version", "Library version");

/** The label value is used to represent missing value. */
private static final LabelValue UNSET_LABEL = LabelValue.create(null);

static final ImmutableList<LabelKey> SPANNER_LABEL_KEYS =
ImmutableList.of(DATABASE, INSTANCE_ID, LIBRARY_VERSION);

static final ImmutableList<LabelValue> SPANNER_DEFAULT_LABEL_VALUES =
ImmutableList.of(UNSET_LABEL, UNSET_LABEL, UNSET_LABEL);

/** Unit to represent counts. */
static final String COUNT = "1";

// The Metric name and description
static final String MAX_IN_USE_SESSIONS = "cloud.google.com/java/spanner/max_in_use_session";
static final String MAX_ALLOWED_SESSIONS = "cloud.google.com/java/spanner/max_allowed_sessions";
static final String IN_USE_SESSIONS = "cloud.google.com/java/spanner/in_use_sessions";
static final String MAX_IN_USE_SESSIONS_DESCRIPTION =
"The maximum number of sessions in use during the last 10 minute interval.";
static final String MAX_ALLOWED_SESSIONS_DESCRIPTION =
"The maximum number of sessions allowed. Configurable by the user.";
static final String IN_USE_SESSIONS_DESCRIPTION = "The number of sessions currently in use.";
}
@@ -16,6 +16,15 @@

package com.google.cloud.spanner;

import static com.google.cloud.spanner.MetricRegistryConstants.COUNT;
import static com.google.cloud.spanner.MetricRegistryConstants.IN_USE_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.IN_USE_SESSIONS_DESCRIPTION;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_ALLOWED_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_ALLOWED_SESSIONS_DESCRIPTION;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS_DESCRIPTION;
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_DEFAULT_LABEL_VALUES;
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS;
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;

import com.google.api.core.ApiFuture;
@@ -40,6 +49,12 @@
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.protobuf.Empty;
import io.opencensus.common.Scope;
import io.opencensus.common.ToLongFunction;
import io.opencensus.metrics.DerivedLongGauge;
import io.opencensus.metrics.LabelValue;
import io.opencensus.metrics.MetricOptions;
import io.opencensus.metrics.MetricRegistry;
import io.opencensus.metrics.Metrics;
import io.opencensus.trace.Annotation;
import io.opencensus.trace.AttributeValue;
import io.opencensus.trace.Span;
@@ -49,6 +64,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
@@ -1116,11 +1132,15 @@ private static enum Position {
* Return pool is immediately ready for use, though getting a session might block for sessions to
* be created.
*/
static SessionPool createPool(SpannerOptions spannerOptions, SessionClient sessionClient) {
static SessionPool createPool(
SpannerOptions spannerOptions, SessionClient sessionClient, List<LabelValue> labelValues) {
return createPool(
spannerOptions.getSessionPoolOptions(),
((GrpcTransportOptions) spannerOptions.getTransportOptions()).getExecutorFactory(),
sessionClient);
sessionClient,
new Clock(),
Metrics.getMetricRegistry(),
labelValues);
}

static SessionPool createPool(
@@ -1135,8 +1155,31 @@ static SessionPool createPool(
ExecutorFactory<ScheduledExecutorService> executorFactory,
SessionClient sessionClient,
Clock clock) {
return createPool(
poolOptions,
executorFactory,
sessionClient,
clock,
Metrics.getMetricRegistry(),
SPANNER_DEFAULT_LABEL_VALUES);
}

static SessionPool createPool(
SessionPoolOptions poolOptions,
ExecutorFactory<ScheduledExecutorService> executorFactory,
SessionClient sessionClient,
Clock clock,
MetricRegistry metricRegistry,
List<LabelValue> labelValues) {
SessionPool pool =
new SessionPool(poolOptions, executorFactory, executorFactory.get(), sessionClient, clock);
new SessionPool(
poolOptions,
executorFactory,
executorFactory.get(),
sessionClient,
clock,
metricRegistry,
labelValues);
pool.initPool();
return pool;
}
@@ -1146,13 +1189,16 @@ private SessionPool(
ExecutorFactory<ScheduledExecutorService> executorFactory,
ScheduledExecutorService executor,
SessionClient sessionClient,
Clock clock) {
Clock clock,
MetricRegistry metricRegistry,
List<LabelValue> labelValues) {
this.options = options;
this.executorFactory = executorFactory;
this.executor = executor;
this.sessionClient = sessionClient;
this.clock = clock;
this.poolMaintainer = new PoolMaintainer();
this.initMetricsCollection(metricRegistry, labelValues);
}

@VisibleForTesting
@@ -1766,4 +1812,73 @@ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount
}
}
}

/**
* Initializes and creates Spanner session relevant metrics. When coupled with an exporter, it
* allows users to monitor client behavior.
*/
private void initMetricsCollection(MetricRegistry metricRegistry, List<LabelValue> labelValues) {
DerivedLongGauge maxInUseSessionsMetric =
metricRegistry.addDerivedLongGauge(
MAX_IN_USE_SESSIONS,
MetricOptions.builder()
.setDescription(MAX_IN_USE_SESSIONS_DESCRIPTION)
.setUnit(COUNT)
.setLabelKeys(SPANNER_LABEL_KEYS)
.build());

DerivedLongGauge maxAllowedSessionsMetric =
metricRegistry.addDerivedLongGauge(
MAX_ALLOWED_SESSIONS,
MetricOptions.builder()
.setDescription(MAX_ALLOWED_SESSIONS_DESCRIPTION)
.setUnit(COUNT)
.setLabelKeys(SPANNER_LABEL_KEYS)
.build());

DerivedLongGauge numInUseSessionsMetric =
metricRegistry.addDerivedLongGauge(
IN_USE_SESSIONS,
MetricOptions.builder()
.setDescription(IN_USE_SESSIONS_DESCRIPTION)
.setUnit(COUNT)
.setLabelKeys(SPANNER_LABEL_KEYS)
.build());

// The value of a maxSessionsInUse is observed from a callback function. This function is
// invoked whenever metrics are collected.
maxInUseSessionsMetric.createTimeSeries(
labelValues,
this,
new ToLongFunction<SessionPool>() {
@Override
public long applyAsLong(SessionPool sessionPool) {
return sessionPool.maxSessionsInUse;
}
});

// The value of a maxSessions is observed from a callback function. This function is invoked
// whenever metrics are collected.
maxAllowedSessionsMetric.createTimeSeries(
labelValues,
options,
new ToLongFunction<SessionPoolOptions>() {
@Override
public long applyAsLong(SessionPoolOptions options) {
return options.getMaxSessions();
}
});

// The value of a numSessionsInUse is observed from a callback function. This function is
// invoked whenever metrics are collected.
numInUseSessionsMetric.createTimeSeries(
labelValues,
this,
new ToLongFunction<SessionPool>() {
@Override
public long applyAsLong(SessionPool sessionPool) {
return sessionPool.numSessionsInUse;
}
});
}
}
@@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.core.GaxProperties;
import com.google.api.gax.paging.Page;
import com.google.cloud.BaseService;
import com.google.cloud.PageImpl;
@@ -27,8 +28,10 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.opencensus.metrics.LabelValue;
import io.opencensus.trace.Tracer;
import io.opencensus.trace.Tracing;
import java.util.ArrayList;
@@ -142,8 +145,14 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
if (dbClients.containsKey(db)) {
return dbClients.get(db);
} else {
List<LabelValue> labelValues =
ImmutableList.of(
LabelValue.create(db.getDatabase()),
LabelValue.create(db.getInstanceId().getName()),
LabelValue.create(GaxProperties.getLibraryVersion(getOptions().getClass())));
SessionPool pool =
SessionPool.createPool(getOptions(), SpannerImpl.this.getSessionClient(db));
SessionPool.createPool(
getOptions(), SpannerImpl.this.getSessionClient(db), labelValues);
DatabaseClientImpl dbClient = createDatabaseClient(pool);
dbClients.put(db, dbClient);
return dbClient;

0 comments on commit d9a00a8

Please sign in to comment.