From 56d3c970ff1e73ea4cb57863448c3b04eb57afb6 Mon Sep 17 00:00:00 2001 From: Joel Richard Date: Mon, 10 Aug 2015 16:27:26 +0200 Subject: [PATCH] OAK-3153 - Make it possible to disable recording of stack trace in SessionStats * Introduce threshold before the init stack trace is not recorded * Implement some basic tests for it --- .../oak/jcr/delegate/SessionDelegate.java | 5 +- .../jackrabbit/oak/jcr/session/SessionStats.java | 21 ++++++- .../oak/jcr/session/SessionStatsTest.java | 72 ++++++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java index c0fa3f7..26ac8b6 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java @@ -43,6 +43,7 @@ import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import com.google.common.collect.ImmutableMap; +import org.apache.jackrabbit.api.stats.RepositoryStatistics; import org.apache.jackrabbit.oak.api.AuthInfo; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.ContentSession; @@ -140,10 +141,10 @@ public class SessionDelegate { this.root = contentSession.getLatestRoot(); this.idManager = new IdentifierManager(root); this.clock = checkNotNull(clock); + checkNotNull(statisticManager); this.sessionStats = new SessionStats(contentSession.toString(), - contentSession.getAuthInfo(), clock, refreshStrategy, this); + contentSession.getAuthInfo(), clock, refreshStrategy, this, statisticManager); this.sessionCounters = sessionStats.getCounters(); - checkNotNull(statisticManager); readCounter = statisticManager.getCounter(SESSION_READ_COUNTER); readDuration = statisticManager.getCounter(SESSION_READ_DURATION); writeCounter = statisticManager.getCounter(SESSION_WRITE_COUNTER); diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java index 82dff76..b2c30f0 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java @@ -32,14 +32,27 @@ import java.util.concurrent.atomic.AtomicReference; import javax.jcr.RepositoryException; +import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type; import org.apache.jackrabbit.oak.api.AuthInfo; import org.apache.jackrabbit.oak.api.jmx.SessionMBean; import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation; import org.apache.jackrabbit.oak.stats.Clock; +import org.apache.jackrabbit.oak.stats.StatisticManager; public class SessionStats implements SessionMBean { - private final Exception initStackTrace = new Exception("The session was opened here:"); + + /** + * The threshold of active sessions from where on it should record the stack trace for new sessions. The reason why + * this is not enabled by default is because recording stack traces is rather expensive and can significantly + * slow down the code if sessions are created and thrown away in a loop. + * + * Once this threshold is exceeded, we assume that there is a session leak which should be fixed and start recording + * the stack traces to make it easier to find the cause of it. + */ + static final int INIT_STACK_TRACE_THRESHOLD = Integer.getInteger("oak.sessionStats.initStackTraceThreshold", 1000); + + private final Exception initStackTrace; private final AtomicReference lastFailedSave = new AtomicReference(); @@ -55,13 +68,17 @@ public class SessionStats implements SessionMBean { private Map attributes = Collections.emptyMap(); public SessionStats(String sessionId, AuthInfo authInfo, Clock clock, - RefreshStrategy refreshStrategy, SessionDelegate sessionDelegate) { + RefreshStrategy refreshStrategy, SessionDelegate sessionDelegate, StatisticManager statisticManager) { this.counters = new Counters(clock); this.sessionId = sessionId; this.authInfo = authInfo; this.clock = clock; this.refreshStrategy = refreshStrategy; this.sessionDelegate = sessionDelegate; + + long activeSessionCount = statisticManager.getCounter(Type.SESSION_COUNT).get(); + initStackTrace = (activeSessionCount > INIT_STACK_TRACE_THRESHOLD) ? + new Exception("The session was opened here:") : null; } public void close() { diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java new file mode 100644 index 0000000..245fcc5 --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.jcr.session; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; +import org.apache.jackrabbit.test.AbstractJCRTest; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import java.util.ArrayList; +import java.util.List; + +public class SessionStatsTest extends AbstractJCRTest { + + /** + * Tests if the init stack trace is not recorded by default. + */ + public void testInitStackTraceDisabledByDefault() throws IllegalAccessException { + assertTrue("initStackTrace is not empty", getInitStackTrace(superuser).isEmpty()); + } + + /** + * Tests if the init stack trace is recorded after opening a lot of sessions. + */ + public void testInitStackTraceEnabledAfterOpeningManySessions() + throws IllegalAccessException, RepositoryException { + final int sessionCount = SessionStats.INIT_STACK_TRACE_THRESHOLD + 1; + final List sessions = new ArrayList(sessionCount); + for (int i = 0; i < sessionCount; i++) { + sessions.add(createSession()); + } + + // Stack trace should be recorded by now + Session lastSession = sessions.get(sessionCount - 1); + assertFalse("initStackTrace is empty", getInitStackTrace(lastSession).isEmpty()); + + for (Session session : sessions) { + session.logout(); + } + + // Stack trace should not be recorded anymore + Session afterSession = createSession(); + assertTrue("initStackTrace is not empty", getInitStackTrace(afterSession).isEmpty()); + afterSession.logout(); + } + + private Session createSession() throws RepositoryException { + return getHelper().getReadWriteSession(); + } + + private String getInitStackTrace(Session session) throws IllegalAccessException { + SessionDelegate sessionDelegate = (SessionDelegate) FieldUtils.readDeclaredField(session, "sd", true); + SessionStats sessionStats = sessionDelegate.getSessionStats(); + return sessionStats.getInitStackTrace(); + } + +} -- 2.3.6