Skip to content

Commit 8d84b53

Browse files
authored
feat: add session timeout metric (#65)
* feat: add session timeout metric * minor patch * rename metric name and description * fix potential flaky test * fix code format * update metric name * minor nit * fix review comment * rename get_sessions_timeouts to get_session_timeouts
1 parent 1178958 commit 8d84b53

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,16 @@ class MetricRegistryConstants {
4242
static final String COUNT = "1";
4343

4444
// The Metric name and description
45-
static final String MAX_IN_USE_SESSIONS = "cloud.google.com/java/spanner/max_in_use_session";
45+
static final String MAX_IN_USE_SESSIONS = "cloud.google.com/java/spanner/max_in_use_sessions";
4646
static final String MAX_ALLOWED_SESSIONS = "cloud.google.com/java/spanner/max_allowed_sessions";
4747
static final String IN_USE_SESSIONS = "cloud.google.com/java/spanner/in_use_sessions";
48+
static final String GET_SESSION_TIMEOUTS = "cloud.google.com/java/spanner/get_session_timeouts";
49+
4850
static final String MAX_IN_USE_SESSIONS_DESCRIPTION =
4951
"The maximum number of sessions in use during the last 10 minute interval.";
5052
static final String MAX_ALLOWED_SESSIONS_DESCRIPTION =
5153
"The maximum number of sessions allowed. Configurable by the user.";
5254
static final String IN_USE_SESSIONS_DESCRIPTION = "The number of sessions currently in use.";
55+
static final String SESSIONS_TIMEOUTS_DESCRIPTION =
56+
"The number of get sessions timeouts due to pool exhaustion";
5357
}

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

+24
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package com.google.cloud.spanner;
1818

1919
import static com.google.cloud.spanner.MetricRegistryConstants.COUNT;
20+
import static com.google.cloud.spanner.MetricRegistryConstants.GET_SESSION_TIMEOUTS;
2021
import static com.google.cloud.spanner.MetricRegistryConstants.IN_USE_SESSIONS;
2122
import static com.google.cloud.spanner.MetricRegistryConstants.IN_USE_SESSIONS_DESCRIPTION;
2223
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_ALLOWED_SESSIONS;
2324
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_ALLOWED_SESSIONS_DESCRIPTION;
2425
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS;
2526
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS_DESCRIPTION;
27+
import static com.google.cloud.spanner.MetricRegistryConstants.SESSIONS_TIMEOUTS_DESCRIPTION;
2628
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_DEFAULT_LABEL_VALUES;
2729
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS;
2830
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;
@@ -50,6 +52,7 @@
5052
import com.google.protobuf.Empty;
5153
import io.opencensus.common.Scope;
5254
import io.opencensus.common.ToLongFunction;
55+
import io.opencensus.metrics.DerivedLongCumulative;
5356
import io.opencensus.metrics.DerivedLongGauge;
5457
import io.opencensus.metrics.LabelValue;
5558
import io.opencensus.metrics.MetricOptions;
@@ -1845,6 +1848,15 @@ private void initMetricsCollection(MetricRegistry metricRegistry, List<LabelValu
18451848
.setLabelKeys(SPANNER_LABEL_KEYS)
18461849
.build());
18471850

1851+
DerivedLongCumulative sessionsTimeouts =
1852+
metricRegistry.addDerivedLongCumulative(
1853+
GET_SESSION_TIMEOUTS,
1854+
MetricOptions.builder()
1855+
.setDescription(SESSIONS_TIMEOUTS_DESCRIPTION)
1856+
.setUnit(COUNT)
1857+
.setLabelKeys(SPANNER_LABEL_KEYS)
1858+
.build());
1859+
18481860
// The value of a maxSessionsInUse is observed from a callback function. This function is
18491861
// invoked whenever metrics are collected.
18501862
maxInUseSessionsMetric.createTimeSeries(
@@ -1880,5 +1892,17 @@ public long applyAsLong(SessionPool sessionPool) {
18801892
return sessionPool.numSessionsInUse;
18811893
}
18821894
});
1895+
1896+
// The value of a numWaiterTimeouts is observed from a callback function. This function is
1897+
// invoked whenever metrics are collected.
1898+
sessionsTimeouts.createTimeSeries(
1899+
labelValues,
1900+
this,
1901+
new ToLongFunction<SessionPool>() {
1902+
@Override
1903+
public long applyAsLong(SessionPool sessionPool) {
1904+
return sessionPool.getNumWaiterTimeouts();
1905+
}
1906+
});
18831907
}
18841908
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/MetricRegistryTestUtils.java

+27-1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,32 @@ public void removeTimeSeries(List<LabelValue> list) {}
9696
public void clear() {}
9797
}
9898

99+
public static final class FakeDerivedLongCumulative extends DerivedLongCumulative {
100+
private final MetricsRecord record;
101+
private final String name;
102+
private final List<LabelKey> labelKeys;
103+
104+
private FakeDerivedLongCumulative(
105+
FakeMetricRegistry metricRegistry, String name, List<LabelKey> labelKeys) {
106+
this.record = metricRegistry.record;
107+
this.labelKeys = labelKeys;
108+
this.name = name;
109+
}
110+
111+
@Override
112+
public <T> void createTimeSeries(
113+
List<LabelValue> labelValues, T t, ToLongFunction<T> toLongFunction) {
114+
this.record.metrics.put(this.name, new PointWithFunction(t, toLongFunction));
115+
this.record.labels.put(this.labelKeys, labelValues);
116+
}
117+
118+
@Override
119+
public void removeTimeSeries(List<LabelValue> list) {}
120+
121+
@Override
122+
public void clear() {}
123+
}
124+
99125
/**
100126
* A {@link MetricRegistry} implementation that saves metrics records to be accessible from {@link
101127
* #pollRecord()}.
@@ -144,7 +170,7 @@ public DoubleCumulative addDoubleCumulative(String s, MetricOptions metricOption
144170

145171
@Override
146172
public DerivedLongCumulative addDerivedLongCumulative(String s, MetricOptions metricOptions) {
147-
throw new UnsupportedOperationException();
173+
return new FakeDerivedLongCumulative(this, s, metricOptions.getLabelKeys());
148174
}
149175

150176
@Override

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java

+35-3
Original file line numberDiff line numberDiff line change
@@ -1561,12 +1561,14 @@ public void run() {
15611561
}
15621562

15631563
@Test
1564-
public void testSessionMetrics() {
1564+
public void testSessionMetrics() throws Exception {
1565+
// Create a session pool with max 2 session and a low timeout for waiting for a session.
15651566
options =
15661567
SessionPoolOptions.newBuilder()
15671568
.setMinSessions(1)
1568-
.setMaxSessions(3)
1569+
.setMaxSessions(2)
15691570
.setMaxIdleSessions(0)
1571+
.setInitialWaitForSessionTimeoutMillis(20L)
15701572
.build();
15711573
FakeClock clock = new FakeClock();
15721574
clock.currentTimeMillis = System.currentTimeMillis();
@@ -1583,16 +1585,46 @@ public void testSessionMetrics() {
15831585
Session session2 = pool.getReadSession();
15841586

15851587
MetricsRecord record = metricRegistry.pollRecord();
1588+
assertThat(record.getMetrics().size()).isEqualTo(4);
15861589
assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.IN_USE_SESSIONS, 2L);
15871590
assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.MAX_IN_USE_SESSIONS, 2L);
1591+
assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.GET_SESSION_TIMEOUTS, 0L);
15881592
assertThat(record.getMetrics())
15891593
.containsEntry(
15901594
MetricRegistryConstants.MAX_ALLOWED_SESSIONS, (long) options.getMaxSessions());
15911595
assertThat(record.getLabels()).containsEntry(SPANNER_LABEL_KEYS, labelValues);
15921596

1597+
final CountDownLatch latch = new CountDownLatch(1);
1598+
// Try asynchronously to take another session. This attempt should time out.
1599+
Future<Void> fut =
1600+
executor.submit(
1601+
new Callable<Void>() {
1602+
@Override
1603+
public Void call() {
1604+
latch.countDown();
1605+
Session session = pool.getReadSession();
1606+
session.close();
1607+
return null;
1608+
}
1609+
});
1610+
// Wait until the background thread is actually waiting for a session.
1611+
latch.await();
1612+
// Wait until the request has timed out.
1613+
int waitCount = 0;
1614+
while (pool.getNumWaiterTimeouts() == 0L && waitCount < 1000) {
1615+
Thread.sleep(5L);
1616+
waitCount++;
1617+
}
1618+
// Return the checked out session to the pool so the async request will get a session and
1619+
// finish.
15931620
session2.close();
1594-
session1.close();
1621+
// Verify that the async request also succeeds.
1622+
fut.get(10L, TimeUnit.SECONDS);
1623+
executor.shutdown();
15951624

1625+
session1.close();
1626+
assertThat(record.getMetrics().get(MetricRegistryConstants.GET_SESSION_TIMEOUTS).longValue())
1627+
.isAtLeast(1L);
15961628
assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.IN_USE_SESSIONS, 0L);
15971629
assertThat(record.getMetrics()).containsEntry(MetricRegistryConstants.MAX_IN_USE_SESSIONS, 2L);
15981630
}

0 commit comments

Comments
 (0)