You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2013/12/18 15:02:31 UTC
svn commit: r1551947 - in /jackrabbit/oak/trunk: oak-core/
oak-core/src/main/java/org/apache/jackrabbit/oak/api/
oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/
oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/
oak...
Author: mduerig
Date: Wed Dec 18 14:02:30 2013
New Revision: 1551947
URL: http://svn.apache.org/r1551947
Log:
OAK-941 MBean to track sessions
SessionMBean for basic session related information
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/SessionMBean.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java
Modified:
jackrabbit/oak/trunk/oak-core/pom.xml
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/AuthInfo.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthInfoImpl.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
Modified: jackrabbit/oak/trunk/oak-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/pom.xml?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-core/pom.xml Wed Dec 18 14:02:30 2013
@@ -44,6 +44,7 @@
<Export-Package>
org.apache.jackrabbit.oak,
org.apache.jackrabbit.oak.api,
+ org.apache.jackrabbit.oak.api.jmx,
org.apache.jackrabbit.oak.kernel,
org.apache.jackrabbit.oak.util,
org.apache.jackrabbit.oak.namepath,
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/AuthInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/AuthInfo.java?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/AuthInfo.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/AuthInfo.java Wed Dec 18 14:02:30 2013
@@ -50,6 +50,11 @@ public interface AuthInfo {
public Set<Principal> getPrincipals() {
return Collections.emptySet();
}
+
+ @Override
+ public String toString() {
+ return "empty";
+ }
};
/**
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/SessionMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/SessionMBean.java?rev=1551947&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/SessionMBean.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/SessionMBean.java Wed Dec 18 14:02:30 2013
@@ -0,0 +1,114 @@
+/*
+ * 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.api.jmx;
+
+import org.apache.jackrabbit.oak.api.AuthInfo;
+
+/**
+ * MBean providing basic {@code Session} information and statistics.
+ */
+public interface SessionMBean {
+ String TYPE = "SessionStatistics";
+
+ /**
+ * @return stack trace from where the session was acquired.
+ */
+ String getInitStackTrace();
+
+ /**
+ * @return {@code AuthInfo} for the user associated with the session.
+ */
+ AuthInfo getAuthInfo();
+
+ /**
+ * @return time stamp from when the session was acquired.
+ */
+ String getLoginTimeStamp();
+
+ /**
+ * @return time stamp from the last read access
+ */
+ String getLastReadAccess();
+
+ /**
+ * @return number of read accesses
+ */
+ long getReadCount();
+
+ /**
+ * @return read operations per time
+ */
+ double getReadRate();
+
+ /**
+ * @return time stamp from the last write access
+ */
+ String getLastWriteAccess();
+
+ /**
+ * @return number of write accesses
+ */
+ long getWriteCount();
+
+ /**
+ * @return write operations per time
+ */
+ double getWriteRate();
+
+ /**
+ * @return time stamp from the last refresh
+ */
+ String getLastRefresh();
+
+ /**
+ * @return number of refresh operations
+ */
+ long getRefreshCount();
+
+ /**
+ * @return refresh operations per time
+ */
+ double getRefreshRate();
+
+ /**
+ * @return time stamp from the last save
+ */
+ String getLastSave();
+
+ /**
+ * @return number of save operations
+ */
+ long getSaveCount();
+
+ /**
+ * @return save operations per time
+ */
+ double getSaveRate();
+
+ /**
+ * @return attributes associated with the session
+ */
+ String[] getSessionAttributes();
+
+ /**
+ * @return stack trace of the last exception that occurred during a save operation
+ */
+ String getLastFailedSave();
+}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthInfoImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthInfoImpl.java?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthInfoImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthInfoImpl.java Wed Dec 18 14:02:30 2013
@@ -20,9 +20,11 @@ import java.security.Principal;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import com.google.common.base.Objects;
import org.apache.jackrabbit.oak.api.AuthInfo;
/**
@@ -41,6 +43,14 @@ public final class AuthInfoImpl implemen
this.principals = (principals == null) ? Collections.<Principal>emptySet() : Collections.unmodifiableSet(principals);
}
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("userID", userID)
+ .add("attributes", attributes)
+ .add("principals", principals).toString();
+ }
+
//-----------------------------------------------------------< AuthInfo >---
@Override
public String getUserID() {
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java Wed Dec 18 14:02:30 2013
@@ -36,6 +36,7 @@ import org.apache.jackrabbit.oak.api.Roo
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
+import org.apache.jackrabbit.oak.jcr.session.SessionStats;
import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
import org.slf4j.Logger;
@@ -55,6 +56,7 @@ public class SessionDelegate {
private final Root root;
private final IdentifierManager idManager;
+ private final SessionStats sessionStats;
private boolean isAlive = true;
private int sessionOpCount;
@@ -78,6 +80,12 @@ public class SessionDelegate {
this.refreshStrategy = checkNotNull(refreshStrategy);
this.root = contentSession.getLatestRoot();
this.idManager = new IdentifierManager(root);
+ this.sessionStats = new SessionStats(this);
+ }
+
+ @Nonnull
+ public SessionStats getSessionStats() {
+ return sessionStats;
}
public void refreshAtNextAccess() {
@@ -116,7 +124,10 @@ public class SessionDelegate {
} finally {
sessionOpCount--;
if (sessionOperation.isUpdate()) {
+ sessionStats.write();
updateCount++;
+ } else {
+ sessionStats.read();
}
if (sessionOperation.isSave()) {
refreshStrategy.saved();
@@ -308,10 +319,13 @@ public class SessionDelegate {
}
public void save() throws RepositoryException {
+ sessionStats.save();
try {
commit();
} catch (CommitFailedException e) {
- throw newRepositoryException(e);
+ RepositoryException repositoryException = newRepositoryException(e);
+ sessionStats.failedSave(repositoryException);
+ throw repositoryException;
}
}
@@ -326,18 +340,22 @@ public class SessionDelegate {
* @throws RepositoryException
*/
public void save(final String path) throws RepositoryException {
+ sessionStats.save();
if (denotesRoot(path)) {
save();
} else {
try {
root.commit(null, path);
} catch (CommitFailedException e) {
- throw newRepositoryException(e);
+ RepositoryException repositoryException = newRepositoryException(e);
+ sessionStats.failedSave(repositoryException);
+ throw repositoryException;
}
}
}
public void refresh(boolean keepChanges) {
+ sessionStats.refresh();
if (keepChanges && hasPendingChanges()) {
root.rebase();
} else {
@@ -389,6 +407,7 @@ public class SessionDelegate {
throw new RepositoryException("Cannot move node at " + srcPath + " to " + destPath);
}
if (!transientOp) {
+ sessionStats.save();
commit(moveRoot);
refresh(true);
}
@@ -444,5 +463,4 @@ public class SessionDelegate {
private static RepositoryException newRepositoryException(CommitFailedException exception) {
return exception.asRepositoryException();
}
-
}
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java Wed Dec 18 14:02:30 2013
@@ -16,11 +16,16 @@
*/
package org.apache.jackrabbit.oak.jcr.session;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Sets.newHashSet;
+import static com.google.common.collect.Sets.newTreeSet;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.jcr.PathNotFoundException;
@@ -37,6 +42,7 @@ import org.apache.jackrabbit.api.securit
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.jmx.SessionMBean;
import org.apache.jackrabbit.oak.jcr.delegate.AccessControlManagerDelegator;
import org.apache.jackrabbit.oak.jcr.delegate.JackrabbitAccessControlManagerDelegator;
import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
@@ -59,15 +65,13 @@ import org.apache.jackrabbit.oak.spi.sec
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConfiguration;
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Sets.newHashSet;
-import static com.google.common.collect.Sets.newTreeSet;
-
/**
* Instances of this class are passed to all JCR implementation classes
* (e.g. {@code SessionImpl}, {@code NodeImpl}, etc.) and provide access to
@@ -98,6 +102,7 @@ public class SessionContext implements N
private UserManager userManager;
private PrivilegeManager privilegeManager;
private ObservationManagerImpl observationManager;
+ private final Registration sessionMBeanRegistration;
/** Paths (tokens) of all open scoped locks held by this session. */
private final Set<String> openScopedLocks = newTreeSet();
@@ -114,6 +119,10 @@ public class SessionContext implements N
this.whiteboard = checkNotNull(whiteboard);
this.attributes = checkNotNull(attributes);
this.delegate = checkNotNull(delegate);
+ SessionStats sessionStats = delegate.getSessionStats();
+ sessionStats.setAttributes(attributes);
+ this.sessionMBeanRegistration = WhiteboardUtils.registerMBean(whiteboard, SessionMBean.class,
+ sessionStats, SessionMBean.TYPE, sessionStats.toString());
this.namespaces = new SessionNamespaces(this);
LocalNameMapper nameMapper = new LocalNameMapper(delegate.getRoot().getTree("/")) {
@@ -362,6 +371,7 @@ public class SessionContext implements N
//-----------------------------------------------------------< internal >---
void dispose() {
+ sessionMBeanRegistration.unregister();
try {
unlockAllSessionScopedLocks();
} catch (RepositoryException e) {
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java?rev=1551947&r1=1551946&r2=1551947&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java Wed Dec 18 14:02:30 2013
@@ -16,12 +16,16 @@
*/
package org.apache.jackrabbit.oak.jcr.session;
+import static com.google.common.collect.Sets.newTreeSet;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.Collections;
import java.util.Set;
+
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.jcr.AccessDeniedException;
@@ -66,9 +70,6 @@ import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
-import static com.google.common.collect.Sets.newTreeSet;
-import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
-
/**
* TODO document
*/
Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java?rev=1551947&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionStats.java Wed Dec 18 14:02:30 2013
@@ -0,0 +1,219 @@
+/*
+ * 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 java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.api.jmx.SessionMBean;
+import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
+
+public class SessionStats implements SessionMBean {
+ private static final long EPS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
+
+ private final Exception initStackTrace = new Exception("The session was opened here:");
+ private final Date loginTimeStamp = new Date();
+ private final AtomicReference<Date> lastReadAccess = new AtomicReference<Date>();
+ private final AtomicLong readCount = new AtomicLong();
+ private final AtomicReference<Date> lastWriteAccess = new AtomicReference<Date>();
+ private final AtomicLong writeCount = new AtomicLong();
+ private final AtomicReference<Date> lastRefresh = new AtomicReference<Date>();
+ private final AtomicLong refreshCount = new AtomicLong();
+ private final AtomicReference<Date> lastSave = new AtomicReference<Date>();
+ private final AtomicLong saveCount = new AtomicLong();
+ private final AtomicReference<RepositoryException> lastFailedSave =
+ new AtomicReference<RepositoryException>();
+
+ private final SessionDelegate sessionDelegate;
+
+ private Map<String, Object> attributes = Collections.emptyMap();
+
+ public SessionStats(SessionDelegate sessionDelegate) {
+ this.sessionDelegate = sessionDelegate;
+ }
+
+ public void setAttributes(Map<String, Object> attributes) {
+ this.attributes = attributes;
+ }
+
+ public void read() {
+ // There is a small race here and in the following methods, which might lead to
+ // a small window of inconsistency between the date and the count. Since the
+ // effect is small and the provided information is for information purposes
+ // this is preferable to synchronizing the method.
+ lastReadAccess.set(new Date());
+ readCount.incrementAndGet();
+ }
+
+ public void write() {
+ lastWriteAccess.set(new Date());
+ writeCount.incrementAndGet();
+ }
+
+ public void refresh() {
+ lastRefresh.set(new Date());
+ refreshCount.incrementAndGet();
+ }
+
+ public void save() {
+ lastSave.set(new Date());
+ saveCount.incrementAndGet();
+ }
+
+ public void failedSave(RepositoryException repositoryException) {
+ lastFailedSave.set(repositoryException);
+ }
+
+ @Override
+ public String toString() {
+ return getAuthInfo().getUserID() + '@' + sessionDelegate.toString() + '@' + getLoginTimeStamp();
+ }
+
+ //------------------------------------------------------------< SessionMBean >---
+
+ @Override
+ public String getInitStackTrace() {
+ return format(initStackTrace);
+ }
+
+ @Override
+ public AuthInfo getAuthInfo() {
+ return sessionDelegate.getAuthInfo();
+ }
+
+ @Override
+ public String getLoginTimeStamp() {
+ return formatDate(loginTimeStamp);
+ }
+
+ @Override
+ public String getLastReadAccess() {
+ return formatDate(lastReadAccess.get());
+ }
+
+ @Override
+ public long getReadCount() {
+ return readCount.get();
+ }
+
+ @Override
+ public double getReadRate() {
+ return calculateRate(getReadCount());
+ }
+
+ @Override
+ public String getLastWriteAccess() {
+ return formatDate(lastWriteAccess.get());
+ }
+
+ @Override
+ public long getWriteCount() {
+ return writeCount.get();
+ }
+
+ @Override
+ public double getWriteRate() {
+ return calculateRate(getWriteCount());
+ }
+
+ @Override
+ public String getLastRefresh() {
+ return formatDate(lastRefresh.get());
+ }
+
+ @Override
+ public long getRefreshCount() {
+ return refreshCount.get();
+ }
+
+ @Override
+ public double getRefreshRate() {
+ return calculateRate(getRefreshCount());
+ }
+
+ @Override
+ public String getLastSave() {
+ return formatDate(lastSave.get());
+ }
+
+ @Override
+ public long getSaveCount() {
+ return saveCount.get();
+ }
+
+ @Override
+ public double getSaveRate() {
+ return calculateRate(getSaveCount());
+ }
+
+ @Override
+ public String[] getSessionAttributes() {
+ String[] atts = new String[attributes.size()];
+ int k = 0;
+ for (Entry<String, Object> attribute : attributes.entrySet()) {
+ atts[k] = attribute.getKey() + '=' + attribute.getValue();
+ }
+ return atts;
+ }
+
+ @Override
+ public String getLastFailedSave() {
+ return format(lastFailedSave.get());
+ }
+
+ //------------------------------------------------------------< internal >---
+
+ private static String formatDate(Date date) {
+ return date == null
+ ? ""
+ : DateFormat.getDateTimeInstance().format(date);
+ }
+
+ private static String format(Exception exception) {
+ if (exception == null) {
+ return "";
+ } else {
+ StringWriter writer = new StringWriter();
+ exception.printStackTrace(new PrintWriter(writer));
+ return writer.toString();
+ }
+ }
+
+ private double calculateRate(long count) {
+ double dt = System.currentTimeMillis() - loginTimeStamp.getTime();
+ if (dt > EPS) {
+ return count / dt;
+ } else {
+ return Double.NaN;
+ }
+ }
+
+}