From: markt Date: Thu, 18 Nov 2010 19:59:11 +0000 (+0000) Subject: Fix expiration statistics broken by r1036281 X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=43f76888ff115a4243c01901118a112db17eb57f;p=tomcat7.0 Fix expiration statistics broken by r1036281 Add session creation and expiration rate statistics based on the 100 most recently created/expired sessions Modify average session alive time to also use 100 most recently expired sessions Update benchmarks - new statistics add overhead but not significant in overall processing chain git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1036595 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/java/org/apache/catalina/Manager.java b/java/org/apache/catalina/Manager.java index 8a7a897da..e49034970 100644 --- a/java/org/apache/catalina/Manager.java +++ b/java/org/apache/catalina/Manager.java @@ -213,24 +213,30 @@ public interface Manager { /** * Gets the average time (in seconds) that expired sessions had been - * alive. - * + * alive. This may be based on sample data. + * * @return Average time (in seconds) that expired sessions had been * alive. */ public int getSessionAverageAliveTime(); - + /** - * Sets the average time (in seconds) that expired sessions had been - * alive. - * - * @param sessionAverageAliveTime Average time (in seconds) that expired - * sessions had been alive. + * Gets the current rate of session creation (in session per minute). This + * may be based on sample data. + * + * @return The current rate (in sessions per minute) of session creation */ - public void setSessionAverageAliveTime(int sessionAverageAliveTime); - + public int getSessionCreateRate(); + + /** + * Gets the current rate of session expiration (in session per minute). This + * may be based on sample data + * + * @return The current rate (in sessions per minute) of session expiration + */ + public int getSessionExpireRate(); // --------------------------------------------------------- Public Methods @@ -326,6 +332,15 @@ public interface Manager { /** + * Remove this Session from the active Sessions for this Manager. + * + * @param session Session to be removed + * @param update Should the expiration statistics be updated + */ + public void remove(Session session, boolean update); + + + /** * Remove a property change listener from this component. * * @param listener The listener to remove diff --git a/java/org/apache/catalina/ha/session/DeltaManager.java b/java/org/apache/catalina/ha/session/DeltaManager.java index 0beefa1db..de987a33a 100644 --- a/java/org/apache/catalina/ha/session/DeltaManager.java +++ b/java/org/apache/catalina/ha/session/DeltaManager.java @@ -40,6 +40,7 @@ import org.apache.catalina.ha.CatalinaCluster; import org.apache.catalina.ha.ClusterManager; import org.apache.catalina.ha.ClusterMessage; import org.apache.catalina.ha.tcp.ReplicationValve; +import org.apache.catalina.session.ManagerBase; import org.apache.catalina.tribes.Member; import org.apache.catalina.tribes.io.ReplicationStream; import org.apache.tomcat.util.ExceptionUtils; @@ -1117,7 +1118,17 @@ public CatalinaCluster getCluster() { */ public synchronized void resetStatistics() { processingTime = 0 ; - expiredSessions = 0 ; + expiredSessions.set(0); + sessionCreationTiming.clear(); + while (sessionCreationTiming.size() < + ManagerBase.TIMING_STATS_CACHE_SIZE) { + sessionCreationTiming.add(null); + } + sessionExpirationTiming.clear(); + while (sessionExpirationTiming.size() < + ManagerBase.TIMING_STATS_CACHE_SIZE) { + sessionExpirationTiming.add(null); + } rejectedSessions = 0 ; sessionReplaceCounter = 0 ; counterNoStateTransfered = 0 ; diff --git a/java/org/apache/catalina/session/ManagerBase.java b/java/org/apache/catalina/session/ManagerBase.java index 0c914ec4b..b523c43a0 100644 --- a/java/org/apache/catalina/session/ManagerBase.java +++ b/java/org/apache/catalina/session/ManagerBase.java @@ -34,15 +34,20 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivilegedAction; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Date; +import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -183,16 +188,18 @@ public abstract class ManagerBase extends LifecycleMBeanBase private final Object sessionMaxAliveTimeUpdateLock = new Object(); - /** - * Average time (in seconds) that expired sessions had been alive. - */ - protected int sessionAverageAliveTime; + protected static final int TIMING_STATS_CACHE_SIZE = 100; + protected Deque sessionCreationTiming = + new LinkedList(); + + protected Deque sessionExpirationTiming = + new LinkedList(); /** * Number of sessions that have expired. */ - protected long expiredSessions = 0; + protected AtomicLong expiredSessions = new AtomicLong(0); /** @@ -760,7 +767,7 @@ public abstract class ManagerBase extends LifecycleMBeanBase */ @Override public long getExpiredSessions() { - return expiredSessions; + return expiredSessions.get(); } @@ -771,7 +778,7 @@ public abstract class ManagerBase extends LifecycleMBeanBase */ @Override public void setExpiredSessions(long expiredSessions) { - this.expiredSessions = expiredSessions; + this.expiredSessions.set(expiredSessions); } public long getProcessingTime() { @@ -863,6 +870,15 @@ public abstract class ManagerBase extends LifecycleMBeanBase randomInputStreams.add(is); } + // Ensure caches for timing stats are the right size by filling with + // nulls. + while (sessionCreationTiming.size() < TIMING_STATS_CACHE_SIZE) { + sessionCreationTiming.add(null); + } + while (sessionExpirationTiming.size() < TIMING_STATS_CACHE_SIZE) { + sessionExpirationTiming.add(null); + } + // Force initialization of the random number generator if (log.isDebugEnabled()) log.debug("Force random number initialization starting"); @@ -948,6 +964,11 @@ public abstract class ManagerBase extends LifecycleMBeanBase session.setId(id); sessionCounter++; + SessionTiming timing = new SessionTiming(session.getCreationTime(), 0); + synchronized (sessionCreationTiming) { + sessionCreationTiming.add(timing); + sessionCreationTiming.poll(); + } return (session); } @@ -1004,20 +1025,29 @@ public abstract class ManagerBase extends LifecycleMBeanBase */ @Override public void remove(Session session) { + remove(session, false); + } + + /** + * Remove this Session from the active Sessions for this Manager. + * + * @param session Session to be removed + * @param update Should the expiration statistics be updated + */ + @Override + public void remove(Session session, boolean update) { // If the session has expired - as opposed to just being removed from // the manager because it is being persisted - update the expired stats - if (!session.isValid()) { + if (update) { long timeNow = System.currentTimeMillis(); int timeAlive = (int) ((timeNow - session.getCreationTime())/1000); updateSessionMaxAliveTime(timeAlive); - synchronized (this) { - long numExpired = getExpiredSessions(); - numExpired++; - setExpiredSessions(numExpired); - int average = getSessionAverageAliveTime(); - average = (int) (((average * (numExpired-1)) + timeAlive)/numExpired); - setSessionAverageAliveTime(average); + expiredSessions.incrementAndGet(); + SessionTiming timing = new SessionTiming(timeNow, timeAlive); + synchronized (sessionExpirationTiming) { + sessionExpirationTiming.add(timing); + sessionExpirationTiming.poll(); } } @@ -1322,27 +1352,124 @@ public abstract class ManagerBase extends LifecycleMBeanBase /** * Gets the average time (in seconds) that expired sessions had been - * alive. - * + * alive based on the last 100 sessions to expire. If less than + * 100 sessions have expired then all available data is used. + * * @return Average time (in seconds) that expired sessions had been * alive. */ @Override public int getSessionAverageAliveTime() { - return sessionAverageAliveTime; + // Copy current stats + List copy = new ArrayList(); + synchronized (sessionExpirationTiming) { + copy.addAll(sessionExpirationTiming); + } + + // Init + int counter = 0; + int result = 0; + Iterator iter = copy.iterator(); + + // Calculate average + while (iter.hasNext()) { + SessionTiming timing = iter.next(); + if (timing != null) { + int timeAlive = timing.getDuration(); + counter++; + // Very careful not to overflow - probably not necessary + result = + (result * ((counter - 1)/counter)) + (timeAlive/counter); + } + } + return result; } + + /** + * Gets the current rate of session creation (in session per minute) based + * on the creation time of the previous 100 sessions created. If less than + * 100 sessions have been created then all available data is used. + * + * @return The current rate (in sessions per minute) of session creation + */ + @Override + public int getSessionCreateRate() { + long now = System.currentTimeMillis(); + // Copy current stats + List copy = new ArrayList(); + synchronized (sessionCreationTiming) { + copy.addAll(sessionCreationTiming); + } + + // Init + long oldest = now; + int counter = 0; + int result = 0; + Iterator iter = copy.iterator(); + + // Calculate rate + while (iter.hasNext()) { + SessionTiming timing = iter.next(); + if (timing != null) { + counter++; + if (timing.getTimestamp() < oldest) { + oldest = timing.getTimestamp(); + } + } + } + if (counter > 0) { + if (oldest < now) { + result = (int) ((1000*60*counter)/(now - oldest)); + } else { + result = Integer.MAX_VALUE; + } + } + return result; + } + /** - * Sets the average time (in seconds) that expired sessions had been - * alive. - * - * @param sessionAverageAliveTime Average time (in seconds) that expired - * sessions had been alive. + * Gets the current rate of session expiration (in session per minute) based + * on the expiry time of the previous 100 sessions expired. If less than + * 100 sessions have expired then all available data is used. + * + * @return The current rate (in sessions per minute) of session expiration */ @Override - public void setSessionAverageAliveTime(int sessionAverageAliveTime) { - this.sessionAverageAliveTime = sessionAverageAliveTime; + public int getSessionExpireRate() { + long now = System.currentTimeMillis(); + // Copy current stats + List copy = new ArrayList(); + synchronized (sessionExpirationTiming) { + copy.addAll(sessionExpirationTiming); + } + + // Init + long oldest = now; + int counter = 0; + int result = 0; + Iterator iter = copy.iterator(); + + // Calculate rate + while (iter.hasNext()) { + SessionTiming timing = iter.next(); + if (timing != null) { + counter++; + if (timing.getTimestamp() < oldest) { + oldest = timing.getTimestamp(); + } + } + } + if (counter > 0) { + if (oldest < now) { + result = (int) ((1000*60*counter)/(now - oldest)); + } else { + // Better than reporting zero + result = Integer.MAX_VALUE; + } + } + return result; } @@ -1552,4 +1679,31 @@ public abstract class ManagerBase extends LifecycleMBeanBase } } } + + // ----------------------------------------------------------- Inner classes + + protected static final class SessionTiming { + private long timestamp; + private int duration; + + public SessionTiming(long timestamp, int duration) { + this.timestamp = timestamp; + this.duration = duration; + } + + /** + * Time stamp associated with this piece of timing information in + * milliseconds. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Duration associated with this piece of timing information in seconds. + */ + public int getDuration() { + return duration; + } + } } diff --git a/java/org/apache/catalina/session/PersistentManagerBase.java b/java/org/apache/catalina/session/PersistentManagerBase.java index bfa686ccc..2622071f7 100644 --- a/java/org/apache/catalina/session/PersistentManagerBase.java +++ b/java/org/apache/catalina/session/PersistentManagerBase.java @@ -438,7 +438,7 @@ public abstract class PersistentManagerBase extends ManagerBase { log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length); for (int i = 0; i < sessions.length; i++) { if (!sessions[i].isValid()) { - expiredSessions++; + expiredSessions.incrementAndGet(); expireHere++; } } diff --git a/java/org/apache/catalina/session/StandardSession.java b/java/org/apache/catalina/session/StandardSession.java index 4be7050df..9f96c61b8 100644 --- a/java/org/apache/catalina/session/StandardSession.java +++ b/java/org/apache/catalina/session/StandardSession.java @@ -836,7 +836,7 @@ public class StandardSession setValid(false); // Remove this session from our manager's active sessions - manager.remove(this); + manager.remove(this, true); // Notify interested session event listeners if (notify) { diff --git a/java/org/apache/catalina/session/mbeans-descriptors.xml b/java/org/apache/catalina/session/mbeans-descriptors.xml index 677b16bfc..8d30a712d 100644 --- a/java/org/apache/catalina/session/mbeans-descriptors.xml +++ b/java/org/apache/catalina/session/mbeans-descriptors.xml @@ -96,12 +96,23 @@ + type="int" + writeable="false" /> + + + + + type="int" + writeable="false" /> + + + +