package org.googlecode.perftrace.perf4j;
import org.googlecode.perftrace.PerfTrace;
import org.googlecode.perftrace.util.StringUtils;
/**
*
* A LoggingStopWatch prevents the need to explicitly send the StopWatch string
* to a Logger when stopping. Instead, all of the stop() and lap() methods
* themselves are responsible for persisting the StopWatch:
*
* <pre>
* LoggingStopWatch stopWatch = new LoggingStopWatch();
* ...some code
* stopWatch.stop("codeBlock1"); //calling stop writes the StopWatch string to std err.
* </pre>
*
* This class just writes all StopWatch messages to the standard error stream,
* but subclasses will use Loggers from various logging frameworks to persist
* the StopWatch.
*
*
*/
@SuppressWarnings("serial")
public class LoggingStopWatch extends StopWatch {
/**
* This threshold determines if a log call will be made. Only elapsed times
* greater than this amount will be logged.
*/
private long timeThreshold = 0L;
/**
* Set this to true if both normal and slow suffixes should be appended to
* every StopWatch tag.
*/
private boolean normalAndSlowSuffixesEnabled = false;
/**
* If normalAndSlowSuffixesEnabled == true then this suffix will be appended
* to the tag for elapsedTimes < timeThreshold. If
* normalAndSlowSuffixesEnabled == true and timeThreshold == 0 then this
* suffix will ALWAYS be appended to the tag.
*/
private String normalSuffix = "normal";
/**
* If normalAndSlowSuffixesEnabled == true then this suffix will be appended
* to the tag for elapsedTimes >= timeThreshold. If
* normalAndSlowSuffixesEnabled == true and timeThreshold == 0 then this
* suffix will NEVER be appended to the tag.
*/
private String slowSuffix = "slow";
private String successSuffix = "success";
private String failureSuffix = "failure";
private boolean successOrFailureFlag = true;
private String logRetString;
private boolean isRootMethod = false;
private transient PerfTrace perftrace;
// --- Constructors ---
/**
* Creates a LoggingStopWatch with a blank tag, no message and started at
* the instant of creation.
*/
public LoggingStopWatch(PerfTrace perftrace) {
this(false, perftrace);
}
public LoggingStopWatch(boolean isRootMethod, PerfTrace perftrace) {
super();
this.isRootMethod = isRootMethod;
this.perftrace = perftrace;
perftrace.addPerfWatch(this);
}
/**
* Creates a LoggingStopWatch with the specified tag, no message and started
* at the instant of creation.
*
* @param tag
* The tag name for this timing call. Tags are used to group
* timing logs, thus each block of code being timed should have a
* unique tag. Note that tags can take a hierarchical format
* using dot notation.
*/
public LoggingStopWatch(String tag, PerfTrace perftrace) {
this(tag, false, perftrace);
}
public LoggingStopWatch(String tag, boolean isRootMethod,
PerfTrace perftrace) {
super(tag);
this.isRootMethod = isRootMethod;
this.perftrace = perftrace;
perftrace.addPerfWatch(this);
}
/**
* Creates a LoggingStopWatch with the specified tag and message, started an
* the instant of creation.
*
* @param tag
* The tag name for this timing call. Tags are used to group
* timing logs, thus each block of code being timed should have a
* unique tag. Note that tags can take a hierarchical format
* using dot notation.
* @param message
* Additional text to be printed with the logging statement of
* this LoggingStopWatch.
*/
public LoggingStopWatch(String tag, String message, boolean isRootMethod,
PerfTrace perftrace) {
super(tag, message);
this.isRootMethod = isRootMethod;
this.perftrace = perftrace;
perftrace.addPerfWatch(this);
}
/**
* Creates a LoggingStopWatch with a specified start and elapsed time, tag,
* and message. This constructor should normally not be called by third
* party code; it is intended to allow for deserialization of StopWatch
* logs.
*
* @param startTime
* The start time in milliseconds
* @param elapsedTime
* The elapsed time in milliseconds
* @param tag
* The tag used to group timing logs of the same code block
* @param message
* Additional message text
*/
public LoggingStopWatch(long startTime, long elapsedTime, String tag,
String message, boolean isRootMethod, PerfTrace perftrace) {
super(startTime, elapsedTime, tag, message);
this.isRootMethod = isRootMethod;
this.perftrace = perftrace;
perftrace.addPerfWatch(this);
}
// --- Bean Properties ---
/**
* Gets a threshold level, in milliseconds, below which logging calls will
* not be made. Defaults to 0, meaning that the log method is always called
* on stop or lap regardless of the elapsed time.
*
* @return The time threshold in milliseconds.
*/
public long getTimeThreshold() {
return timeThreshold;
}
/**
* Sets a threshold level, in milliseconds, below which logging calls will
* not be made. You can set this to a high positive value if you only want
* logging to occur for abnormally slow execution times. Note, though, that
* you may wish to leave the threshold at 0 and attach a
* {@link org.perf4j.log4j.JmxAttributeStatisticsAppender} in the logging
* configuration to be notified when times are outside acceptable
* thresholds.
*
* @param timeThreshold
* The minimum elapsed time, in milliseconds, below which log
* calls will not be made.
* @return this instance, for use with method chaining if desired
* @see org.perf4j.log4j.JmxAttributeStatisticsAppender#getNotificationThresholds()
*/
public LoggingStopWatch setTimeThreshold(long timeThreshold) {
this.timeThreshold = timeThreshold;
return this;
}
/**
* Determines whether or not to append normalSuffix or slowSuffix to every
* tag logged by this stopwatch.
*
* @return whether or not to append normalSuffix or slowSuffix to every tag
* logged by this stopwatch.
*/
public boolean isNormalAndSlowSuffixesEnabled() {
return normalAndSlowSuffixesEnabled;
}
/**
* Sets whether to append normalSuffix and slowSuffix when timeThreshold
* > 0 AND elapsedTime >= timeThreshold
*
* @param normalAndSlowSuffixesEnabled
* true enables logging extra suffixes to normal and slow events;
* false (default) suppresses the suffixes
*/
public LoggingStopWatch setNormalAndSlowSuffixesEnabled(
boolean normalAndSlowSuffixesEnabled) {
this.normalAndSlowSuffixesEnabled = normalAndSlowSuffixesEnabled;
return this;
}
/**
* The suffix to append to the tag if normalAndSlowSuffixesEnabled=true and
* elapsedTime < timeThreshold and timeThreshold > 0. Default is
* ".normal".
*
* @return the suffix to append if normalAndSlowSuffixesEnabled=true and the
* event was normal and under the threshold
*/
public String getNormalSuffix() {
return normalSuffix;
}
/**
* Sets the suffix to use when normalAndSlowSuffixesEnabled=true and
* timeThreshold > 0 and elapsedTime < timeThreshold. Setting this to
* "" is equivalent to setting to null.
*
* @param normalSuffix
* the suffix to append if normalAndSlowSuffixesEnabled and the
* elapsedtime is under the threshold
*/
public LoggingStopWatch setNormalSuffix(String normalSuffix) {
if (normalSuffix == null || "".equals(normalSuffix)) {
throw new IllegalArgumentException(
"normalSuffix cannot be blank. param=" + normalSuffix);
}
this.normalSuffix = normalSuffix;
return this;
}
/**
* The suffix to append to the tag if normalAndSlowSuffixesEnabled=true and
* elapsedTime >= timeThreshold and timeThreshold > 0. Default is
* ".slow"
*
* @return the suffix to append if normalAndSlowSuffixesEnabled=true and the
* event was slow and over the threshold.
*/
public String getSlowSuffix() {
return slowSuffix;
}
/**
* Sets the suffix to use when normalAndSlowSuffixesEnabled=true and
* timeThreshold > 0 and elapsedTime >= timeThreshold. Setting this to
* "" is equivalent to setting to null.
*
* @param slowSuffix
* the suffix to append if normalAndSlowSuffixesEnabled and the
* elapsedtime is under the threshold
*/
public LoggingStopWatch setSlowSuffix(String slowSuffix) {
if (slowSuffix == null || "".equals(slowSuffix)) {
throw new IllegalArgumentException(
"slowSuffix cannot be blank. param=" + slowSuffix);
}
this.slowSuffix = slowSuffix;
return this;
}
public PerfTrace getPerftrace() {
return perftrace;
}
public void setPerftrace(PerfTrace perftrace) {
this.perftrace = perftrace;
}
public String getAdditionSeg() {
String slowsuffix = isNormalAndSlowSuffixesEnabled()
&& (getTimeThreshold() != 0)
&& (getElapsedTime() >= getTimeThreshold()) ? getSlowSuffix()
: getNormalSuffix();
String failureOrSuccess = isSuccessOrFailureFlag() ? getSuccessSuffix()
: getFailureSuffix();
return String.format("[%s %s]", slowsuffix, failureOrSuccess);
}
// Just overridden to make use of covariant return types
public LoggingStopWatch setTag(String tag) {
super.setTag(tag);
return this;
}
// Just overridden to make use of covariant return types
public LoggingStopWatch setMessage(String message) {
super.setMessage(message);
return this;
}
public String getLogRetString() {
return logRetString;
}
public void setLogRetString(String logRetString) {
this.logRetString = logRetString;
}
public String getSuccessSuffix() {
return successSuffix;
}
public void setSuccessSuffix(String successSuffix) {
this.successSuffix = successSuffix;
}
public String getFailureSuffix() {
return failureSuffix;
}
public void setFailureSuffix(String failureSuffix) {
this.failureSuffix = failureSuffix;
}
public boolean isSuccessOrFailureFlag() {
return successOrFailureFlag;
}
public void setSuccessOrFailureFlag(boolean successOrFailureFlag) {
this.successOrFailureFlag = successOrFailureFlag;
}
public boolean isRootMethod() {
return isRootMethod;
}
public void setRootMethod(boolean isRootMethod) {
this.isRootMethod = isRootMethod;
}
// --- Stop/Lap/Helper Methods ---
/**
* This stop method is overridden to perform the logging itself instead of
* needing to make a separate call to persist the timing information.
*
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String stop() {
String retVal = StringUtils.join(new Object[] { super.stop(),
getAdditionSeg() }, LOG_SEG);
setLogRetString(retVal);
doLogInternal(retVal, null);
getPerftrace().watchStop(this);
return retVal;
}
/**
* In cases where a code block terminated by throwing an exception, you may
* wish to have the exception logged in addition to the time it took to
* execute the block, in which case this method will write out the
* exception's stack trace in addition to the StopWatch timing method.
*
* @param exception
* The exception that was thrown by the timed code block
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String stop(Throwable exception) {
if (exception != null) {
setSuccessOrFailureFlag(false);
setMessage(getMessage()
+ String.format("" + "[ExceptionMsg:%s]", exception
.getMessage()));
}
return stop();
}
/**
* Identical to {@link #stop(String)}, but also allows you to specify an
* exception to be logged.
*
* @param tag
* The grouping tag for this StopWatch
* @param exception
* The exception that was thrown by the timed code block
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String stop(String tag, Throwable exception) {
setTag(tag);
return stop(exception);
}
/**
* Identical to {@link #stop(String, String)}, but also allows you to
* specify an exception to be logged.
*
* @param tag
* The grouping tag for this StopWatch
* @param message
* A descriptive message about the timed block
* @param exception
* The exception that was thrown by the timed code block
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String stop(String tag, String message, Throwable exception) {
setTag(tag);
setMessage(message);
return stop(exception);
}
/**
* Identical to {@link #lap(String)}, but also allows you to specify an
* exception to be logged.
*
* @param tag
* The grouping tag for the PREVIOUS code block that was timed.
* @param exception
* The exception that was thrown by the timed code block.
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String lap(String tag, Throwable exception) {
String retVal = stop(tag, exception);
start();
return retVal;
}
/**
* Identical to {@link #lap(String, String)}, but also allows you to specify
* an exception to be logged.
*
* @param tag
* The grouping tag for the PREVIOUS code block that was timed.
* @param message
* A descriptive message about the timed block
* @param exception
* The exception that was thrown by the timed code block
* @return this.toString(), however, this should not be passed to a logger
* as it will have already been logged.
*/
public String lap(String tag, String message, Throwable exception) {
String retVal = stop(tag, message, exception);
start();
return retVal;
}
/**
* Determines whether or not logging is currently enabled for normal log
* messages for this StopWatch. This implementation always returns true, but
* subclasses should override this method if logging can be disabled. For
* example, a StopWatch that uses log4j Loggers will return false if the
* Logger is not currently enabled for the Level at which the log method is
* called.
*
* @return true if calls to one of the stop() or lap() methods that do NOT
* take an exception will result in the StopWatch being written to a
* persisting log.
*/
public boolean isLogging() {
return true;
}
// --- Template Methods ---
/**
* This log method can be overridden by subclasses in order to persist the
* StopWatch, for example by using a log4j Logger. The default
* implementation here just writes the StopWatch to the standard error
* stream.
*
* @param stopWatchAsString
* The serialized StopWatch string
* @param exception
* An exception, if any, that was also passed to the stop() or
* lap() methods - may be null.
*/
protected void log(String stopWatchAsString, Throwable exception) {
System.err.println(stopWatchAsString);
if (exception != null) {
exception.printStackTrace();
}
}
public boolean isOverTimeThreshold() {
long elapsedTime = getElapsedTime(); // to allow for subclasses to
// override this value
long timeThreshold = getTimeThreshold(); // to allow for subclasses to
// override this value
if (timeThreshold == 0 || elapsedTime >= timeThreshold)
return true;
return false;
}
// --- Object Methods ---
public LoggingStopWatch clone() {
return (LoggingStopWatch) super.clone();
}
// --- Private Methods ---
// Helper method only calls log if elapsed time is greater than the time
// threshold
private void doLogInternal(String stopWatchAsString, Throwable exception) {
// if normalAndSlowSuffixesEnabled then always log with the suffixes
// added
// getTag() should take care of appending the correct tag, and should
// already be part of stopWatchAsString
// Otherwise we default to the backward-compatible behavior: namely:
// in most cases timeThreshold will be 0, so just short circuit out as
// fast as possible
// long elapsedTime = getElapsedTime(); // to allow for subclasses to
// override this value
// long timeThreshold = getTimeThreshold(); // to allow for subclasses
// to
// override this value
if (isOverTimeThreshold()) {
log(stopWatchAsString, exception);
}
}
}