| FunctionStatistics.java |
/*
* $Id: FunctionStatistics.java,v 1.31 2013/01/14 11:14:30 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.server;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import org.apache.log4j.NDC;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xins.common.text.DateConverter;
import org.xins.common.xml.ElementFormatter;
/**
* Statistics of a function.
*
* <p>The implementation of this class is thread-safe.
*
* @version $Revision: 1.31 $ $Date: 2013/01/14 11:14:30 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
*
* @since XINS 1.0.0
*/
class FunctionStatistics {
/**
* String to insert instead of a figure when the figure is unavailable.
*/
private static final String NOT_AVAILABLE = "N/A";
/**
* The time zone used when generating dates for output.
*/
private static final TimeZone TIME_ZONE = TimeZone.getDefault();
/**
* Constructs a new <code>FunctionStatistics</code> instance.
*/
FunctionStatistics() {
_successful = new Statistic();
_unsuccessful = new Statistic();
_notModified = new Statistic();
_errorCodeStatistics = new TreeMap();
}
/**
* Statistics for the successful calls. Never <code>null</code>.
*/
private final Statistic _successful;
/**
* Statistic over the unsuccessful calls. Never <code>null</code>.
*/
private final Statistic _unsuccessful;
/**
* Statistic over the not modified results. Never <code>null</code>.
*/
private final Statistic _notModified;
/**
* Statistics over the unsuccessful calls sorted by error code.
* The key of the map is the error code and the Statistic object
* corresponding to the error code. Never <code>null</code>.
*/
private final Map<String, Statistic> _errorCodeStatistics;
/**
* Callback method that may be called after a call to this function. This
* method will store statistics-related information.
*
* <p />This method does not <em>have</em> to be called. If statistics
* gathering is disabled, then this method should not be called.
*
* @param start
* the start time, in milliseconds since the UNIX Epoch.
*
* @param xinsResult
* the result of the function call, cannot be <code>null</code>.
*/
final synchronized void recordCall(long start, FunctionResult xinsResult) {
long duration = System.currentTimeMillis() - start;
// Call resulted in not modified
if (xinsResult instanceof NotModifiedResult) {
_notModified.recordCall(start, duration);
// Call succeeded
} else if (xinsResult.getErrorCode() == null) {
_successful.recordCall(start, duration);
// Call failed
} else {
_unsuccessful.recordCall(start, duration);
String errorCode = xinsResult.getErrorCode();
Statistic errorCodeStat = _errorCodeStatistics.get(errorCode);
if (errorCodeStat == null) {
errorCodeStat = new Statistic();
}
errorCodeStat.recordCall(start, duration);
_errorCodeStatistics.put(errorCode, errorCodeStat);
}
}
/**
* Resets the statistics for this function.
*/
final synchronized void resetStatistics() {
_successful.reset();
_unsuccessful.reset();
_notModified.reset();
_errorCodeStatistics.clear();
}
/**
* Get the successful statistic as an {@link org.xins.common.xml.Element}.
*
* @return
* the successful element, cannot be <code>null</code>
*/
public synchronized Element getSuccessfulElement() {
return _successful.getElement("successful", null);
}
/**
* Indicates whether not modified has been returned by the function.
*
* @return
* <code>true</code> is not modified has been returned at least once, <code>false</code> otherwise
*/
public synchronized boolean hasNotModified() {
return _notModified._calls > 0;
}
/**
* Get the not modified statistic as an {@link org.xins.common.xml.Element}.
*
* @return
* the not modified element, cannot be <code>null</code>
*/
public synchronized Element getNotModifiedElement() {
return _notModified.getElement("not-modified", null);
}
/**
* Get the unsuccessful statistics as an array of {@link org.xins.common.xml.Element}.
*
* @param detailed
* If <code>true</code>, the unsuccessful results will be returned
* per error code. Otherwise only one unsuccessful containing all
* unsuccessful result will be returned.
*
* @return
* the successful element, cannot be empty.
*/
public synchronized Element[] getUnsuccessfulElement(boolean detailed) {
if (!detailed || _errorCodeStatistics.isEmpty()) {
Element[] result = new Element[1];
result[0] = _unsuccessful.getElement("unsuccessful", null);
return result;
} else {
Element[] result = new Element[_errorCodeStatistics.size()];
int i = 0;
for (String nextErrorCode : _errorCodeStatistics.keySet()) {
Statistic nextStat = _errorCodeStatistics.get(nextErrorCode);
result[i] = nextStat.getElement("unsuccessful", nextErrorCode);
i++;
}
return result;
}
}
/**
* Group of statistics data.
*
* <p>The implementation of this class is thread-safe.
*
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
*
* @since XINS 1.1.0
*/
private static final class Statistic {
/**
* The number of calls executed up until now. Initially <code>0L</code>.
*/
private int _calls;
/**
* The start time of the most recent call. Initially <code>0L</code>.
*/
private long _lastStart;
/**
* The duration of the most recent call. Initially <code>0L</code>.
*/
private long _lastDuration;
/**
* The context identifier of the most recent call. Initially empty.
*/
private String _lastContextId;
/**
* The total duration of all calls up until now. Initially
* <code>0L</code>.
*/
private long _duration;
/**
* The minimum time a call took. Initially set to
* {@link Long#MAX_VALUE}.
*/
private long _min = Long.MAX_VALUE;
/**
* The start time of the call that took the shortest. Initially
* <code>0L</code>.
*/
private long _minStart;
/**
* The context identifier of the call that took the shortest. Initially empty.
*/
private String _minContextId;
/**
* The duration of the call that took the longest. Initially
* <code>0L</code>.
*/
private long _max;
/**
* The start time of the call that took the longest. Initially
* <code>0L</code>.
*/
private long _maxStart;
/**
* The context identifier of the call that took the longest. Initially empty.
*/
private String _maxContextId;
/**
* Constructs a new <code>Statistic</code> object.
*/
private Statistic() {
_min = Long.MAX_VALUE;
}
/**
* Records a call.
*
* @param start
* the start time, in milliseconds since the UNIX Epoch, not
* <code>null</code>.
*
* @param duration
* duration of the call, in milliseconds since the
* <a href="http://en.wikipedia.org/wiki/Unix_Epoch">UNIX Epoch</a>.
*/
public synchronized void recordCall(long start, long duration) {
_lastStart = start;
_lastDuration = duration;
_calls++;
_duration += duration;
_min = _min > duration ? duration : _min;
_max = _max < duration ? duration : _max;
_minStart = (_min == duration) ? start : _minStart;
_maxStart = (_max == duration) ? start : _maxStart;
_lastContextId = NDC.peek();
if (_min == duration) {
_minContextId = _lastContextId;
}
if (_max == duration) {
_maxContextId = _lastContextId;
}
}
/**
* Get this statistic as an {@link Element}.
*
* @param successful
* true if the result is successful, false otherwise.
* @param errorCode
* the errorCode of the unsuccessful result, if you want it also
* specified in the returned element.
*
* @return
* the statistic, cannot be <code>null</code>
*/
public synchronized Element getElement(String name, String errorCode) {
String average;
String min;
String minStart;
String max;
String maxStart;
String lastStart;
String lastDuration;
if (_calls == 0) {
average = NOT_AVAILABLE;
min = NOT_AVAILABLE;
minStart = NOT_AVAILABLE;
max = NOT_AVAILABLE;
maxStart = NOT_AVAILABLE;
lastStart = NOT_AVAILABLE;
lastDuration = NOT_AVAILABLE;
} else if (_duration == 0) {
average = "0";
min = String.valueOf(_min);
minStart = DateConverter.toDateString(TIME_ZONE, _minStart);
max = String.valueOf(_max);
maxStart = DateConverter.toDateString(TIME_ZONE, _maxStart);
lastStart = DateConverter.toDateString(TIME_ZONE, _lastStart);
lastDuration = String.valueOf(_lastDuration);
} else {
average = String.valueOf(_duration / _calls);
min = String.valueOf(_min);
minStart = DateConverter.toDateString(TIME_ZONE, _minStart);
max = String.valueOf(_max);
maxStart = DateConverter.toDateString(TIME_ZONE, _maxStart);
lastStart = DateConverter.toDateString(TIME_ZONE, _lastStart);
lastDuration = String.valueOf(_lastDuration);
}
Element element = ElementFormatter.createMainElement(name);
Document doc = element.getOwnerDocument();
element.setAttribute("count", String.valueOf(_calls));
element.setAttribute("average", average);
if (errorCode != null) {
element.setAttribute("errorcode", errorCode);
}
Element minElem = doc.createElement("min");
minElem.setAttribute("start", minStart);
minElem.setAttribute("duration", min);
minElem.setAttribute("contextId", _minContextId);
element.appendChild(minElem);
Element maxElem = doc.createElement("max");
maxElem.setAttribute("start", maxStart);
maxElem.setAttribute("duration", max);
maxElem.setAttribute("contextId", _maxContextId);
element.appendChild(maxElem);
Element lastElem = doc.createElement("last");
lastElem.setAttribute("start", lastStart);
lastElem.setAttribute("duration", lastDuration);
lastElem.setAttribute("contextId", _lastContextId);
element.appendChild(lastElem);
return element;
}
/**
* Resets this statistic.
*/
public synchronized void reset() {
_calls = 0;
_lastStart = 0L;
_lastDuration = 0L;
_duration = 0L;
_min = Long.MAX_VALUE;
_minStart = 0L;
_max = 0L;
_maxStart = 0L;
}
}
}