blob: 9ce4b9311503045de4687a499446e96a479aa333 [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.mina.filter.statistic;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoEventType;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.write.WriteRequest;
/**
* This class will measure the time it takes for a method in the {@link IoFilterAdapter} class to execute. The basic
* premise of the logic in this class is to get the current time at the beginning of the method, call method on
* nextFilter, and then get the current time again. An example of how to use the filter is:
*
* <pre>
* ProfilerTimerFilter profiler = new ProfilerTimerFilter(TimeUnit.MILLISECOND, IoEventType.MESSAGE_RECEIVED);
* chain.addFirst("Profiler", profiler);
* </pre>
*
* The profiled {@link IoEventType} are :
* <ul>
* <li>IoEventType.MESSAGE_RECEIVED</li>
* <li>IoEventType.MESSAGE_SENT</li>
* <li>IoEventType.SESSION_CREATED</li>
* <li>IoEventType.SESSION_OPENED</li>
* <li>IoEventType.SESSION_IDLE</li>
* <li>IoEventType.SESSION_CLOSED</li>
* </ul>
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
* @org.apache.xbean.XBean
*/
public class ProfilerTimerFilter extends IoFilterAdapter {
/** TRhe selected time unit */
private volatile TimeUnit timeUnit;
/** A TimerWorker for the MessageReceived events */
private TimerWorker messageReceivedTimerWorker;
/** A flag to tell the filter that the MessageReceived must be profiled */
private boolean profileMessageReceived = false;
/** A TimerWorker for the MessageSent events */
private TimerWorker messageSentTimerWorker;
/** A flag to tell the filter that the MessageSent must be profiled */
private boolean profileMessageSent = false;
/** A TimerWorker for the SessionCreated events */
private TimerWorker sessionCreatedTimerWorker;
/** A flag to tell the filter that the SessionCreated must be profiled */
private boolean profileSessionCreated = false;
/** A TimerWorker for the SessionOpened events */
private TimerWorker sessionOpenedTimerWorker;
/** A flag to tell the filter that the SessionOpened must be profiled */
private boolean profileSessionOpened = false;
/** A TimerWorker for the SessionIdle events */
private TimerWorker sessionIdleTimerWorker;
/** A flag to tell the filter that the SessionIdle must be profiled */
private boolean profileSessionIdle = false;
/** A TimerWorker for the SessionClosed events */
private TimerWorker sessionClosedTimerWorker;
/** A flag to tell the filter that the SessionClosed must be profiled */
private boolean profileSessionClosed = false;
/**
* Creates a new instance of ProfilerFilter. This is the default constructor and will print out timings for
* messageReceived and messageSent and the time increment will be in milliseconds.
*/
public ProfilerTimerFilter() {
this(TimeUnit.MILLISECONDS, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
}
/**
* Creates a new instance of ProfilerFilter. This is the default constructor and will print out timings for
* messageReceived and messageSent.
*
* @param timeUnit
* the time increment to set
*/
public ProfilerTimerFilter(TimeUnit timeUnit) {
this(timeUnit, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
}
/**
* Creates a new instance of ProfilerFilter. An example of this call would be:
*
* <pre>
* new ProfilerTimerFilter(TimeUnit.MILLISECONDS, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
* </pre>
*
* Note : you can add as many {@link IoEventType} as you want. The method accepts a variable number of arguments.
*
* @param timeUnit
* Used to determine the level of precision you need in your timing.
* @param eventTypes
* A list of {@link IoEventType} representation of the methods to profile
*/
public ProfilerTimerFilter(TimeUnit timeUnit, IoEventType... eventTypes) {
this.timeUnit = timeUnit;
setProfilers(eventTypes);
}
/**
* Create the profilers for a list of {@link IoEventType}.
*
* @param eventTypes
* the list of {@link IoEventType} to profile
*/
private void setProfilers(IoEventType... eventTypes) {
for (IoEventType type : eventTypes) {
switch (type) {
case MESSAGE_RECEIVED:
messageReceivedTimerWorker = new TimerWorker();
profileMessageReceived = true;
break;
case MESSAGE_SENT:
messageSentTimerWorker = new TimerWorker();
profileMessageSent = true;
break;
case SESSION_CREATED:
sessionCreatedTimerWorker = new TimerWorker();
profileSessionCreated = true;
break;
case SESSION_OPENED:
sessionOpenedTimerWorker = new TimerWorker();
profileSessionOpened = true;
break;
case SESSION_IDLE:
sessionIdleTimerWorker = new TimerWorker();
profileSessionIdle = true;
break;
case SESSION_CLOSED:
sessionClosedTimerWorker = new TimerWorker();
profileSessionClosed = true;
break;
default:
break;
}
}
}
/**
* Sets the {@link TimeUnit} being used.
*
* @param timeUnit
* the new {@link TimeUnit} to be used.
*/
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
/**
* Set the {@link IoEventType} to be profiled
*
* @param type
* The {@link IoEventType} to profile
*/
public void profile(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
profileMessageReceived = true;
if (messageReceivedTimerWorker == null) {
messageReceivedTimerWorker = new TimerWorker();
}
return;
case MESSAGE_SENT:
profileMessageSent = true;
if (messageSentTimerWorker == null) {
messageSentTimerWorker = new TimerWorker();
}
return;
case SESSION_CREATED:
profileSessionCreated = true;
if (sessionCreatedTimerWorker == null) {
sessionCreatedTimerWorker = new TimerWorker();
}
return;
case SESSION_OPENED:
profileSessionOpened = true;
if (sessionOpenedTimerWorker == null) {
sessionOpenedTimerWorker = new TimerWorker();
}
return;
case SESSION_IDLE:
profileSessionIdle = true;
if (sessionIdleTimerWorker == null) {
sessionIdleTimerWorker = new TimerWorker();
}
return;
case SESSION_CLOSED:
profileSessionClosed = true;
if (sessionClosedTimerWorker == null) {
sessionClosedTimerWorker = new TimerWorker();
}
return;
default:
break;
}
}
/**
* Stop profiling an {@link IoEventType}
*
* @param type
* The {@link IoEventType} to stop profiling
*/
public void stopProfile(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
profileMessageReceived = false;
return;
case MESSAGE_SENT:
profileMessageSent = false;
return;
case SESSION_CREATED:
profileSessionCreated = false;
return;
case SESSION_OPENED:
profileSessionOpened = false;
return;
case SESSION_IDLE:
profileSessionIdle = false;
return;
case SESSION_CLOSED:
profileSessionClosed = false;
return;
default:
return;
}
}
/**
* Return the set of {@link IoEventType} which are profiled.
*
* @return a Set containing all the profiled {@link IoEventType}
*/
public Set<IoEventType> getEventsToProfile() {
Set<IoEventType> set = new HashSet<>();
if (profileMessageReceived) {
set.add(IoEventType.MESSAGE_RECEIVED);
}
if (profileMessageSent) {
set.add(IoEventType.MESSAGE_SENT);
}
if (profileSessionCreated) {
set.add(IoEventType.SESSION_CREATED);
}
if (profileSessionOpened) {
set.add(IoEventType.SESSION_OPENED);
}
if (profileSessionIdle) {
set.add(IoEventType.SESSION_IDLE);
}
if (profileSessionClosed) {
set.add(IoEventType.SESSION_CLOSED);
}
return set;
}
/**
* Set the profilers for a list of {@link IoEventType}
*
* @param eventTypes
* the list of {@link IoEventType} to profile
*/
public void setEventsToProfile(IoEventType... eventTypes) {
setProfilers(eventTypes);
}
/**
* Profile a MessageReceived event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
* @param message
* the received message
*/
@Override
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
if (profileMessageReceived) {
long start = timeNow();
nextFilter.messageReceived(session, message);
long end = timeNow();
messageReceivedTimerWorker.addNewDuration(end - start);
} else {
nextFilter.messageReceived(session, message);
}
}
/**
* Profile a MessageSent event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
* @param writeRequest
* the sent message
*/
@Override
public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
if (profileMessageSent) {
long start = timeNow();
nextFilter.messageSent(session, writeRequest);
long end = timeNow();
messageSentTimerWorker.addNewDuration(end - start);
} else {
nextFilter.messageSent(session, writeRequest);
}
}
/**
* Profile a SessionCreated event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
*/
@Override
public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
if (profileSessionCreated) {
long start = timeNow();
nextFilter.sessionCreated(session);
long end = timeNow();
sessionCreatedTimerWorker.addNewDuration(end - start);
} else {
nextFilter.sessionCreated(session);
}
}
/**
* Profile a SessionOpened event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
*/
@Override
public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
if (profileSessionOpened) {
long start = timeNow();
nextFilter.sessionOpened(session);
long end = timeNow();
sessionOpenedTimerWorker.addNewDuration(end - start);
} else {
nextFilter.sessionOpened(session);
}
}
/**
* Profile a SessionIdle event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
* @param status
* The session's status
*/
@Override
public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
if (profileSessionIdle) {
long start = timeNow();
nextFilter.sessionIdle(session, status);
long end = timeNow();
sessionIdleTimerWorker.addNewDuration(end - start);
} else {
nextFilter.sessionIdle(session, status);
}
}
/**
* Profile a SessionClosed event. This method will gather the following informations : - the method duration - the
* shortest execution time - the slowest execution time - the average execution time - the global number of calls
*
* @param nextFilter
* The filter to call next
* @param session
* The associated session
*/
@Override
public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
if (profileSessionClosed) {
long start = timeNow();
nextFilter.sessionClosed(session);
long end = timeNow();
sessionClosedTimerWorker.addNewDuration(end - start);
} else {
nextFilter.sessionClosed(session);
}
}
/**
* Get the average time for the specified method represented by the {@link IoEventType}
*
* @param type
* The {@link IoEventType} that the user wants to get the average method call time
* @return The average time it took to execute the method represented by the {@link IoEventType}
*/
public double getAverageTime(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
if (profileMessageReceived) {
return messageReceivedTimerWorker.getAverage();
}
break;
case MESSAGE_SENT:
if (profileMessageSent) {
return messageSentTimerWorker.getAverage();
}
break;
case SESSION_CREATED:
if (profileSessionCreated) {
return sessionCreatedTimerWorker.getAverage();
}
break;
case SESSION_OPENED:
if (profileSessionOpened) {
return sessionOpenedTimerWorker.getAverage();
}
break;
case SESSION_IDLE:
if (profileSessionIdle) {
return sessionIdleTimerWorker.getAverage();
}
break;
case SESSION_CLOSED:
if (profileSessionClosed) {
return sessionClosedTimerWorker.getAverage();
}
break;
default:
break;
}
throw new IllegalArgumentException("You are not monitoring this event. Please add this event first.");
}
/**
* Gets the total number of times the method has been called that is represented by the {@link IoEventType}
*
* @param type
* The {@link IoEventType} that the user wants to get the total number of method calls
* @return The total number of method calls for the method represented by the {@link IoEventType}
*/
public long getTotalCalls(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
if (profileMessageReceived) {
return messageReceivedTimerWorker.getCallsNumber();
}
break;
case MESSAGE_SENT:
if (profileMessageSent) {
return messageSentTimerWorker.getCallsNumber();
}
break;
case SESSION_CREATED:
if (profileSessionCreated) {
return sessionCreatedTimerWorker.getCallsNumber();
}
break;
case SESSION_OPENED:
if (profileSessionOpened) {
return sessionOpenedTimerWorker.getCallsNumber();
}
break;
case SESSION_IDLE:
if (profileSessionIdle) {
return sessionIdleTimerWorker.getCallsNumber();
}
break;
case SESSION_CLOSED:
if (profileSessionClosed) {
return sessionClosedTimerWorker.getCallsNumber();
}
break;
default:
break;
}
throw new IllegalArgumentException("You are not monitoring this event. Please add this event first.");
}
/**
* The total time this method has been executing
*
* @param type
* The {@link IoEventType} that the user wants to get the total time this method has been executing
* @return The total time for the method represented by the {@link IoEventType}
*/
public long getTotalTime(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
if (profileMessageReceived) {
return messageReceivedTimerWorker.getTotal();
}
break;
case MESSAGE_SENT:
if (profileMessageSent) {
return messageSentTimerWorker.getTotal();
}
break;
case SESSION_CREATED:
if (profileSessionCreated) {
return sessionCreatedTimerWorker.getTotal();
}
break;
case SESSION_OPENED:
if (profileSessionOpened) {
return sessionOpenedTimerWorker.getTotal();
}
break;
case SESSION_IDLE:
if (profileSessionIdle) {
return sessionIdleTimerWorker.getTotal();
}
break;
case SESSION_CLOSED:
if (profileSessionClosed) {
return sessionClosedTimerWorker.getTotal();
}
break;
default:
break;
}
throw new IllegalArgumentException("You are not monitoring this event. Please add this event first.");
}
/**
* The minimum time the method represented by {@link IoEventType} has executed
*
* @param type
* The {@link IoEventType} that the user wants to get the minimum time this method has executed
* @return The minimum time this method has executed represented by the {@link IoEventType}
*/
public long getMinimumTime(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
if (profileMessageReceived) {
return messageReceivedTimerWorker.getMinimum();
}
break;
case MESSAGE_SENT:
if (profileMessageSent) {
return messageSentTimerWorker.getMinimum();
}
break;
case SESSION_CREATED:
if (profileSessionCreated) {
return sessionCreatedTimerWorker.getMinimum();
}
break;
case SESSION_OPENED:
if (profileSessionOpened) {
return sessionOpenedTimerWorker.getMinimum();
}
break;
case SESSION_IDLE:
if (profileSessionIdle) {
return sessionIdleTimerWorker.getMinimum();
}
break;
case SESSION_CLOSED:
if (profileSessionClosed) {
return sessionClosedTimerWorker.getMinimum();
}
break;
default:
break;
}
throw new IllegalArgumentException("You are not monitoring this event. Please add this event first.");
}
/**
* The maximum time the method represented by {@link IoEventType} has executed
*
* @param type
* The {@link IoEventType} that the user wants to get the maximum time this method has executed
* @return The maximum time this method has executed represented by the {@link IoEventType}
*/
public long getMaximumTime(IoEventType type) {
switch (type) {
case MESSAGE_RECEIVED:
if (profileMessageReceived) {
return messageReceivedTimerWorker.getMaximum();
}
break;
case MESSAGE_SENT:
if (profileMessageSent) {
return messageSentTimerWorker.getMaximum();
}
break;
case SESSION_CREATED:
if (profileSessionCreated) {
return sessionCreatedTimerWorker.getMaximum();
}
break;
case SESSION_OPENED:
if (profileSessionOpened) {
return sessionOpenedTimerWorker.getMaximum();
}
break;
case SESSION_IDLE:
if (profileSessionIdle) {
return sessionIdleTimerWorker.getMaximum();
}
break;
case SESSION_CLOSED:
if (profileSessionClosed) {
return sessionClosedTimerWorker.getMaximum();
}
break;
default:
break;
}
throw new IllegalArgumentException("You are not monitoring this event. Please add this event first.");
}
/**
* Class that will track the time each method takes and be able to provide information for each method.
*
*/
private class TimerWorker {
/** The sum of all operation durations */
private final AtomicLong total;
/** The number of calls */
private final AtomicLong callsNumber;
/** The fastest operation */
private final AtomicLong minimum;
/** The slowest operation */
private final AtomicLong maximum;
/** A lock for synchinized blocks */
private final Object lock = new Object();
/**
* Creates a new instance of TimerWorker.
*
*/
public TimerWorker() {
total = new AtomicLong();
callsNumber = new AtomicLong();
minimum = new AtomicLong();
maximum = new AtomicLong();
}
/**
* Add a new operation duration to this class. Total is updated and calls is incremented
*
* @param duration
* The new operation duration
*/
public void addNewDuration(long duration) {
callsNumber.incrementAndGet();
total.addAndGet(duration);
synchronized (lock) {
// this is not entirely thread-safe, must lock
if (duration < minimum.longValue()) {
minimum.set(duration);
}
// this is not entirely thread-safe, must lock
if (duration > maximum.longValue()) {
maximum.set(duration);
}
}
}
/**
* Gets the average reading for this event
*
* @return the average reading for this event
*/
public double getAverage() {
synchronized (lock) {
// There are two operations, we need to synchronize the block
return callsNumber.longValue() != 0 ? total.longValue() / callsNumber.longValue() : 0;
}
}
/**
* @return The total number of profiled operation
*/
public long getCallsNumber() {
return callsNumber.longValue();
}
/**
* @return the total time
*/
public long getTotal() {
return total.longValue();
}
/**
* @return the lowest execution time
*/
public long getMinimum() {
return minimum.longValue();
}
/**
* @return the longest execution time
*/
public long getMaximum() {
return maximum.longValue();
}
}
/**
* @return the current time, expressed using the fixed TimeUnit.
*/
private long timeNow() {
switch (timeUnit) {
case SECONDS:
return System.currentTimeMillis() / 1000;
case MICROSECONDS:
return System.nanoTime() / 1000;
case NANOSECONDS:
return System.nanoTime();
default:
return System.currentTimeMillis();
}
}
}