diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java index 93d9fe1258..8da8ee1506 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java @@ -30,12 +30,21 @@ class MetricRegistryConstants { LabelKey.create("instance_id", "Name of the instance"); private static final LabelKey LIBRARY_VERSION = LabelKey.create("library_version", "Library version"); + private static final LabelKey SESSION_TYPE = LabelKey.create("Type", "Type of the Sessions"); /** The label value is used to represent missing value. */ private static final LabelValue UNSET_LABEL = LabelValue.create(null); + static final LabelValue NUM_IN_USE_SESSIONS = LabelValue.create("num_in_use_sessions"); + static final LabelValue NUM_SESSIONS_BEING_PREPARED = + LabelValue.create("num_sessions_being_prepared"); + static final LabelValue NUM_READ_SESSIONS = LabelValue.create("num_read_sessions"); + static final LabelValue NUM_WRITE_SESSIONS = LabelValue.create("num_write_prepared_sessions"); + static final ImmutableList SPANNER_LABEL_KEYS = ImmutableList.of(CLIENT_ID, DATABASE, INSTANCE_ID, LIBRARY_VERSION); + static final ImmutableList SPANNER_LABEL_KEYS_WITH_TYPE = + ImmutableList.of(CLIENT_ID, DATABASE, INSTANCE_ID, LIBRARY_VERSION, SESSION_TYPE); static final ImmutableList SPANNER_DEFAULT_LABEL_VALUES = ImmutableList.of(UNSET_LABEL, UNSET_LABEL, UNSET_LABEL, UNSET_LABEL); @@ -46,20 +55,20 @@ class MetricRegistryConstants { // The Metric name and description static final String MAX_IN_USE_SESSIONS = "cloud.google.com/java/spanner/max_in_use_sessions"; 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 GET_SESSION_TIMEOUTS = "cloud.google.com/java/spanner/get_session_timeouts"; static final String NUM_ACQUIRED_SESSIONS = "cloud.google.com/java/spanner/num_acquired_sessions"; static final String NUM_RELEASED_SESSIONS = "cloud.google.com/java/spanner/num_released_sessions"; + static final String NUM_SESSIONS_IN_POOL = "cloud.google.com/java/spanner/num_sessions_in_pool"; 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."; static final String SESSIONS_TIMEOUTS_DESCRIPTION = "The number of get sessions timeouts due to pool exhaustion"; static final String NUM_ACQUIRED_SESSIONS_DESCRIPTION = "The number of sessions acquired from the session pool."; static final String NUM_RELEASED_SESSIONS_DESCRIPTION = "The number of sessions released by the user and pool maintainer."; + static final String NUM_SESSIONS_IN_POOL_DESCRIPTION = "The number of sessions in the pool."; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java index 0db55bcccb..776c3df1c9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java @@ -18,19 +18,24 @@ import static com.google.cloud.spanner.MetricRegistryConstants.COUNT; import static com.google.cloud.spanner.MetricRegistryConstants.GET_SESSION_TIMEOUTS; -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.NUM_ACQUIRED_SESSIONS; import static com.google.cloud.spanner.MetricRegistryConstants.NUM_ACQUIRED_SESSIONS_DESCRIPTION; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_IN_USE_SESSIONS; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_READ_SESSIONS; import static com.google.cloud.spanner.MetricRegistryConstants.NUM_RELEASED_SESSIONS; import static com.google.cloud.spanner.MetricRegistryConstants.NUM_RELEASED_SESSIONS_DESCRIPTION; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_BEING_PREPARED; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_IN_POOL; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_IN_POOL_DESCRIPTION; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_WRITE_SESSIONS; import static com.google.cloud.spanner.MetricRegistryConstants.SESSIONS_TIMEOUTS_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.MetricRegistryConstants.SPANNER_LABEL_KEYS_WITH_TYPE; import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException; import com.google.api.core.ApiFuture; @@ -71,6 +76,7 @@ import io.opencensus.trace.Status; import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; @@ -2030,15 +2036,6 @@ private void initMetricsCollection(MetricRegistry metricRegistry, List() { - @Override - public long applyAsLong(SessionPool sessionPool) { - return sessionPool.numSessionsInUse; - } - }); - // The value of a numWaiterTimeouts is observed from a callback function. This function is // invoked whenever metrics are collected. sessionsTimeouts.createTimeSeries( @@ -2133,5 +2127,53 @@ public long applyAsLong(SessionPool sessionPool) { return sessionPool.numSessionsReleased; } }); + + List labelValuesWithBeingPreparedType = new ArrayList<>(labelValues); + labelValuesWithBeingPreparedType.add(NUM_SESSIONS_BEING_PREPARED); + numSessionsInPoolMetric.createTimeSeries( + labelValuesWithBeingPreparedType, + this, + new ToLongFunction() { + @Override + public long applyAsLong(SessionPool sessionPool) { + return sessionPool.numSessionsBeingPrepared; + } + }); + + List labelValuesWithInUseType = new ArrayList<>(labelValues); + labelValuesWithInUseType.add(NUM_IN_USE_SESSIONS); + numSessionsInPoolMetric.createTimeSeries( + labelValuesWithInUseType, + this, + new ToLongFunction() { + @Override + public long applyAsLong(SessionPool sessionPool) { + return sessionPool.numSessionsInUse; + } + }); + + List labelValuesWithReadType = new ArrayList<>(labelValues); + labelValuesWithReadType.add(NUM_READ_SESSIONS); + numSessionsInPoolMetric.createTimeSeries( + labelValuesWithReadType, + this, + new ToLongFunction() { + @Override + public long applyAsLong(SessionPool sessionPool) { + return sessionPool.readSessions.size(); + } + }); + + List labelValuesWithWriteType = new ArrayList<>(labelValues); + labelValuesWithWriteType.add(NUM_WRITE_SESSIONS); + numSessionsInPoolMetric.createTimeSeries( + labelValuesWithWriteType, + this, + new ToLongFunction() { + @Override + public long applyAsLong(SessionPool sessionPool) { + return sessionPool.writePreparedSessions.size(); + } + }); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MetricRegistryTestUtils.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MetricRegistryTestUtils.java index a3444baac7..da3b145271 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MetricRegistryTestUtils.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MetricRegistryTestUtils.java @@ -29,6 +29,7 @@ import io.opencensus.metrics.LongGauge; import io.opencensus.metrics.MetricOptions; import io.opencensus.metrics.MetricRegistry; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -37,36 +38,39 @@ class MetricRegistryTestUtils { static class PointWithFunction { private final T ref; private final ToLongFunction function; + private final List key; + private final List values; - PointWithFunction(T obj, ToLongFunction function) { + PointWithFunction( + T obj, ToLongFunction function, List keys, List values) { this.ref = obj; this.function = function; + this.key = keys; + this.values = values; } - long get() { + long value() { return function.applyAsLong(ref); } + + List keys() { + return key; + } + + List values() { + return values; + } } static class MetricsRecord { - private final Map metrics; - private final Map, List> labels; + private final Map> metrics; private MetricsRecord() { this.metrics = Maps.newHashMap(); - this.labels = Maps.newHashMap(); } - Map getMetrics() { - Map copy = Maps.newHashMap(); - for (Map.Entry entry : metrics.entrySet()) { - copy.put(entry.getKey(), entry.getValue().get()); - } - return copy; - } - - Map, List> getLabels() { - return this.labels; + Map> getMetrics() { + return metrics; } } @@ -85,8 +89,13 @@ private FakeDerivedLongGauge( @Override public void createTimeSeries( List labelValues, T t, ToLongFunction toLongFunction) { - this.record.metrics.put(this.name, new PointWithFunction(t, toLongFunction)); - this.record.labels.put(this.labelKeys, labelValues); + if (!this.record.metrics.containsKey(this.name)) { + this.record.metrics.put(this.name, new ArrayList()); + } + this.record + .metrics + .get(this.name) + .add(new PointWithFunction(t, toLongFunction, labelKeys, labelValues)); } @Override @@ -111,8 +120,13 @@ private FakeDerivedLongCumulative( @Override public void createTimeSeries( List labelValues, T t, ToLongFunction toLongFunction) { - this.record.metrics.put(this.name, new PointWithFunction(t, toLongFunction)); - this.record.labels.put(this.labelKeys, labelValues); + if (!this.record.metrics.containsKey(this.name)) { + this.record.metrics.put(this.name, new ArrayList()); + } + this.record + .metrics + .get(this.name) + .add(new PointWithFunction(t, toLongFunction, labelKeys, labelValues)); } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java index a12e31e90b..395a60ade3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java @@ -16,7 +16,12 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_IN_USE_SESSIONS; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_READ_SESSIONS; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_BEING_PREPARED; +import static com.google.cloud.spanner.MetricRegistryConstants.NUM_WRITE_SESSIONS; import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS; +import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS_WITH_TYPE; import static com.google.cloud.spanner.SpannerMatchers.isSpannerException; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -37,6 +42,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.spanner.MetricRegistryTestUtils.FakeMetricRegistry; import com.google.cloud.spanner.MetricRegistryTestUtils.MetricsRecord; +import com.google.cloud.spanner.MetricRegistryTestUtils.PointWithFunction; import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode; import com.google.cloud.spanner.SessionClient.SessionConsumer; import com.google.cloud.spanner.SessionPool.Clock; @@ -1710,17 +1716,69 @@ public void testSessionMetrics() throws Exception { MetricsRecord record = metricRegistry.pollRecord(); assertThat(record.getMetrics().size()).isEqualTo(6); - assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.IN_USE_SESSIONS, 2L); - assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.MAX_IN_USE_SESSIONS, 2L); - assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.GET_SESSION_TIMEOUTS, 0L); - assertThat(record.getMetrics()) - .containsEntry(MetricRegistryConstants.NUM_ACQUIRED_SESSIONS, 2L); - assertThat(record.getMetrics()) - .containsEntry(MetricRegistryConstants.NUM_RELEASED_SESSIONS, 0L); - assertThat(record.getMetrics()) - .containsEntry( - MetricRegistryConstants.MAX_ALLOWED_SESSIONS, (long) options.getMaxSessions()); - assertThat(record.getLabels()).containsEntry(SPANNER_LABEL_KEYS, labelValues); + + List maxInUseSessions = + record.getMetrics().get(MetricRegistryConstants.MAX_IN_USE_SESSIONS); + assertThat(maxInUseSessions.size()).isEqualTo(1); + assertThat(maxInUseSessions.get(0).value()).isEqualTo(2L); + assertThat(maxInUseSessions.get(0).keys()).isEqualTo(SPANNER_LABEL_KEYS); + assertThat(maxInUseSessions.get(0).values()).isEqualTo(labelValues); + + List getSessionsTimeouts = + record.getMetrics().get(MetricRegistryConstants.GET_SESSION_TIMEOUTS); + assertThat(getSessionsTimeouts.size()).isEqualTo(1); + assertThat(getSessionsTimeouts.get(0).value()).isAtMost(1L); + assertThat(getSessionsTimeouts.get(0).keys()).isEqualTo(SPANNER_LABEL_KEYS); + assertThat(getSessionsTimeouts.get(0).values()).isEqualTo(labelValues); + + List numAcquiredSessions = + record.getMetrics().get(MetricRegistryConstants.NUM_ACQUIRED_SESSIONS); + assertThat(numAcquiredSessions.size()).isEqualTo(1); + assertThat(numAcquiredSessions.get(0).value()).isEqualTo(2L); + assertThat(numAcquiredSessions.get(0).keys()).isEqualTo(SPANNER_LABEL_KEYS); + assertThat(numAcquiredSessions.get(0).values()).isEqualTo(labelValues); + + List numReleasedSessions = + record.getMetrics().get(MetricRegistryConstants.NUM_RELEASED_SESSIONS); + assertThat(numReleasedSessions.size()).isEqualTo(1); + assertThat(numReleasedSessions.get(0).value()).isEqualTo(0); + assertThat(numReleasedSessions.get(0).keys()).isEqualTo(SPANNER_LABEL_KEYS); + assertThat(numReleasedSessions.get(0).values()).isEqualTo(labelValues); + + List maxAllowedSessions = + record.getMetrics().get(MetricRegistryConstants.MAX_ALLOWED_SESSIONS); + assertThat(maxAllowedSessions.size()).isEqualTo(1); + assertThat(maxAllowedSessions.get(0).value()).isEqualTo(options.getMaxSessions()); + assertThat(maxAllowedSessions.get(0).keys()).isEqualTo(SPANNER_LABEL_KEYS); + assertThat(maxAllowedSessions.get(0).values()).isEqualTo(labelValues); + + List numSessionsInPool = + record.getMetrics().get(MetricRegistryConstants.NUM_SESSIONS_IN_POOL); + assertThat(numSessionsInPool.size()).isEqualTo(4); + PointWithFunction beingPrepared = numSessionsInPool.get(0); + List labelValuesWithBeingPreparedType = new ArrayList<>(labelValues); + labelValuesWithBeingPreparedType.add(NUM_SESSIONS_BEING_PREPARED); + assertThat(beingPrepared.value()).isEqualTo(0L); + assertThat(beingPrepared.keys()).isEqualTo(SPANNER_LABEL_KEYS_WITH_TYPE); + assertThat(beingPrepared.values()).isEqualTo(labelValuesWithBeingPreparedType); + PointWithFunction numSessionsInUse = numSessionsInPool.get(1); + List labelValuesWithInUseType = new ArrayList<>(labelValues); + labelValuesWithInUseType.add(NUM_IN_USE_SESSIONS); + assertThat(numSessionsInUse.value()).isEqualTo(2L); + assertThat(numSessionsInUse.keys()).isEqualTo(SPANNER_LABEL_KEYS_WITH_TYPE); + assertThat(numSessionsInUse.values()).isEqualTo(labelValuesWithInUseType); + PointWithFunction readSessions = numSessionsInPool.get(2); + List labelValuesWithReadType = new ArrayList<>(labelValues); + labelValuesWithReadType.add(NUM_READ_SESSIONS); + assertThat(readSessions.value()).isEqualTo(0L); + assertThat(readSessions.keys()).isEqualTo(SPANNER_LABEL_KEYS_WITH_TYPE); + assertThat(readSessions.values()).isEqualTo(labelValuesWithReadType); + PointWithFunction writePreparedSessions = numSessionsInPool.get(3); + List labelValuesWithWriteType = new ArrayList<>(labelValues); + labelValuesWithWriteType.add(NUM_WRITE_SESSIONS); + assertThat(writePreparedSessions.value()).isEqualTo(0L); + assertThat(writePreparedSessions.keys()).isEqualTo(SPANNER_LABEL_KEYS_WITH_TYPE); + assertThat(writePreparedSessions.values()).isEqualTo(labelValuesWithWriteType); final CountDownLatch latch = new CountDownLatch(1); // Try asynchronously to take another session. This attempt should time out. @@ -1751,14 +1809,28 @@ public Void call() { executor.shutdown(); session1.close(); - assertThat(record.getMetrics().get(MetricRegistryConstants.GET_SESSION_TIMEOUTS).longValue()) - .isAtLeast(1L); - assertThat(record.getMetrics()) - .containsEntry(MetricRegistryConstants.NUM_ACQUIRED_SESSIONS, 3L); - assertThat(record.getMetrics()) - .containsEntry(MetricRegistryConstants.NUM_RELEASED_SESSIONS, 3L); - assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.IN_USE_SESSIONS, 0L); - assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.MAX_IN_USE_SESSIONS, 2L); + numAcquiredSessions = record.getMetrics().get(MetricRegistryConstants.NUM_ACQUIRED_SESSIONS); + assertThat(numAcquiredSessions.size()).isEqualTo(1); + assertThat(numAcquiredSessions.get(0).value()).isEqualTo(3L); + + numReleasedSessions = record.getMetrics().get(MetricRegistryConstants.NUM_RELEASED_SESSIONS); + assertThat(numReleasedSessions.size()).isEqualTo(1); + assertThat(numReleasedSessions.get(0).value()).isEqualTo(3L); + + maxInUseSessions = record.getMetrics().get(MetricRegistryConstants.MAX_IN_USE_SESSIONS); + assertThat(maxInUseSessions.size()).isEqualTo(1); + assertThat(maxInUseSessions.get(0).value()).isEqualTo(2L); + + numSessionsInPool = record.getMetrics().get(MetricRegistryConstants.NUM_SESSIONS_IN_POOL); + assertThat(numSessionsInPool.size()).isEqualTo(4); + beingPrepared = numSessionsInPool.get(0); + assertThat(beingPrepared.value()).isEqualTo(0L); + numSessionsInUse = numSessionsInPool.get(1); + assertThat(numSessionsInUse.value()).isEqualTo(0L); + readSessions = numSessionsInPool.get(2); + assertThat(readSessions.value()).isEqualTo(2L); + writePreparedSessions = numSessionsInPool.get(3); + assertThat(writePreparedSessions.value()).isEqualTo(0L); } private void mockKeepAlive(Session session) {