blob: addcdb82003751f0d9dab5b769c02660ce6543c4 [file] [log] [blame]
/*
* 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 static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
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.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 {
/**
* 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.
*
* Configurable by the "oak.sessionStats.initStackTraceThreshold" system property. Set to "0" to record stack trace
* information on each session creation.
*
*/
static final int INIT_STACK_TRACE_THRESHOLD = Integer.getInteger("oak.sessionStats.initStackTraceThreshold", 1000);
private final Exception initStackTrace;
private final AtomicReference<RepositoryException> lastFailedSave =
new AtomicReference<RepositoryException>();
private final Counters counters;
private final String sessionId;
private final AuthInfo authInfo;
private final Clock clock;
private final RefreshStrategy refreshStrategy;
private volatile SessionDelegate sessionDelegate;
private Map<String, Object> attributes = Collections.emptyMap();
public SessionStats(String sessionId, AuthInfo authInfo, Clock clock,
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.getStatsCounter(Type.SESSION_COUNT).getCount();
initStackTrace = (activeSessionCount > INIT_STACK_TRACE_THRESHOLD) ?
new Exception("The session was opened here:") : null;
}
public void close() {
sessionDelegate = null;
}
public static class Counters {
private final Clock clock;
private final long loginTime;
public long accessTime;
public long readTime = 0;
public long writeTime = 0;
public long refreshTime = 0;
public long saveTime = 0;
public long readCount = 0;
public long writeCount = 0;
public long refreshCount = 0;
public long saveCount = 0;
public Counters(Clock clock) {
long time = clock.getTime();
this.clock = clock;
this.loginTime = time;
this.accessTime = time;
}
public Date getLoginTime() {
return new Date(loginTime);
}
public Date getReadTime() {
return getTime(readTime);
}
public long getReadCount() {
return readCount;
}
public Date getWriteTime() {
return getTime(writeTime);
}
public long getWriteCount() {
return writeCount;
}
public Date getRefreshTime() {
return getTime(refreshTime);
}
public long getRefreshCount() {
return refreshCount;
}
public Date getSaveTime() {
return getTime(saveTime);
}
public long getSaveCount() {
return saveCount;
}
public long getSecondsSinceLogin() {
return SECONDS.convert(clock.getTime() - loginTime, MILLISECONDS);
}
private static Date getTime(long timestamp) {
if (timestamp != 0) {
return new Date(timestamp);
} else {
return null;
}
}
}
public Counters getCounters() {
return counters;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
public void failedSave(RepositoryException repositoryException) {
lastFailedSave.set(repositoryException);
}
@Override
public String toString() {
return getAuthInfo().getUserID() + '@' + sessionId + '@' + getLoginTimeStamp();
}
//------------------------------------------------------------< SessionMBean >---
@Override
public String getInitStackTrace() {
return format(initStackTrace);
}
@Override
public AuthInfo getAuthInfo() {
return authInfo;
}
@Override
public String getLoginTimeStamp() {
return formatDate(counters.getLoginTime());
}
@Override
public String getLastReadAccess() {
return formatDate(counters.getReadTime());
}
@Override
public long getReadCount() {
return counters.getReadCount();
}
@Override
public double getReadRate() {
return calculateRate(getReadCount());
}
@Override
public String getLastWriteAccess() {
return formatDate(counters.getWriteTime());
}
@Override
public long getWriteCount() {
return counters.getWriteCount();
}
@Override
public double getWriteRate() {
return calculateRate(getWriteCount());
}
@Override
public String getLastRefresh() {
return formatDate(counters.getRefreshTime());
}
@Override
public String getRefreshStrategy() {
return refreshStrategy.toString();
}
@Override
public boolean getRefreshPending() {
return refreshStrategy.needsRefresh(
SECONDS.convert(clock.getTime() - counters.accessTime, MILLISECONDS));
}
@Override
public long getRefreshCount() {
return counters.getRefreshCount();
}
@Override
public double getRefreshRate() {
return calculateRate(getRefreshCount());
}
@Override
public String getLastSave() {
return formatDate(counters.getSaveTime());
}
@Override
public long getSaveCount() {
return counters.getSaveCount();
}
@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());
}
@Override
public void refresh() {
final SessionDelegate sd = sessionDelegate;
if (sd != null) {
sd.safePerform(new SessionOperation<Void>("MBean initiated refresh", true) {
@Override
public Void perform() {
sd.refresh(true);
return null;
}
@Override
public void checkPreconditions() throws RepositoryException {
sd.checkAlive();
}
});
sd.refresh(true);
}
}
//------------------------------------------------------------< 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 = counters.getSecondsSinceLogin();
if (dt > 0) {
return count / dt;
} else {
return Double.NaN;
}
}
}